Files
landing-builder/lib/schema/page.ts
T

119 lines
3.2 KiB
TypeScript

import { z } from 'zod'
const hexColor = z
.string()
.regex(/^#([0-9a-fA-F]{3}){1,2}$/i, 'invalid hex color')
const url = z.string().url()
const sectionHeroSchema = z.object({
type: z.literal('hero'),
props: z.object({
heading: z.string().min(1),
subheading: z.string().optional().default(''),
imageUrl: url,
}),
})
const sectionFaqSchema = z.object({
type: z.literal('faq'),
props: z.object({
items: z.array(z.object({ q: z.string().min(1), a: z.string().min(1) })).min(1),
}),
})
const sectionCtaSchema = z.object({
type: z.literal('cta'),
props: z.object({ text: z.string().min(1), href: z.string().optional().default('#') }),
})
const sectionFeaturesSchema = z.object({
type: z.literal('features'),
props: z.object({ items: z.array(z.string().min(1)).min(1) }),
})
const sectionTestimonialsSchema = z.object({
type: z.literal('testimonials'),
props: z.object({ items: z.array(z.object({ author: z.string().min(1), quote: z.string().min(1) })).min(1) }),
})
// 확장: hero, faq, cta, features, testimonials 허용
const sectionSchema = z.union([
sectionHeroSchema,
sectionFaqSchema,
sectionCtaSchema,
sectionFeaturesSchema,
sectionTestimonialsSchema,
])
const baseField = {
name: z.string().min(1),
label: z.string().min(1),
required: z.boolean().optional().default(false),
pattern: z.string().optional(),
maxLength: z.number().int().positive().optional(),
}
const textField = z.object({ type: z.literal('text'), ...baseField })
const emailField = z.object({ type: z.literal('email'), ...baseField })
const telField = z.object({ type: z.literal('tel'), ...baseField })
const textareaField = z.object({ type: z.literal('textarea'), ...baseField })
const dateField = z.object({ type: z.literal('date'), ...baseField })
const checkboxField = z.object({ type: z.literal('checkbox'), ...baseField })
const radioField = z.object({
type: z.literal('radio'),
...baseField,
options: z.array(z.string().min(1)).min(1),
})
const selectField = z.object({
type: z.literal('select'),
...baseField,
options: z.array(z.string().min(1)).min(1),
})
// 파일 업로드는 제외
export const formFieldSchema = z.union([
textField,
emailField,
telField,
textareaField,
dateField,
checkboxField,
radioField,
selectField,
])
export const pageSchema = z.object({
title: z.string().min(1),
locale: z.string().min(2),
theme: z.object({
primaryColor: hexColor,
fontFamily: z.string().min(1),
}),
sections: z.array(sectionSchema),
form: z.object({
actionUrl: z.string().optional().default(''),
fields: z.array(formFieldSchema).min(0),
spamProtection: z.object({
honeypotFieldName: z.string().min(1).default('_hp'),
minSubmitSeconds: z.number().min(0).default(2),
}),
}),
analytics: z
.object({
ga4MeasurementId: z.string().optional().default(''),
metaPixelId: z.string().optional().default(''),
customHeadHtml: z.string().optional().default(''),
})
.optional()
.default({ ga4MeasurementId: '', metaPixelId: '', customHeadHtml: '' }),
seo: z.object({
title: z.string().min(1),
description: z.string().min(1),
ogImage: url.optional(),
}),
})
export type Page = z.infer<typeof pageSchema>
export type FormField = z.infer<typeof formFieldSchema>