"use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Loader } from "lucide-react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { useRouter } from "next/navigation" import { useSession } from "next-auth/react" import { Button } from "@/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { createTemplateAction } from "../service" import { TEMPLATE_CATEGORY_OPTIONS } from "../validations" // Validation Schema (수정됨) const createTemplateSchema = z.object({ name: z.string().min(1, "템플릿 이름은 필수입니다").max(100, "템플릿 이름은 100자 이하여야 합니다"), slug: z.string() .min(1, "Slug는 필수입니다") .max(50, "Slug는 50자 이하여야 합니다") .regex(/^[a-z0-9-]+$/, "Slug는 소문자, 숫자, 하이픈만 사용 가능합니다"), description: z.string().max(500, "설명은 500자 이하여야 합니다").optional(), category: z.string().optional(), // 빈 문자열이나 undefined 모두 허용 }) type CreateTemplateSchema = z.infer interface CreateTemplateSheetProps extends React.ComponentPropsWithRef { } export function CreateTemplateSheet({ ...props }: CreateTemplateSheetProps) { const [isCreatePending, startCreateTransition] = React.useTransition() const router = useRouter() const { data: session } = useSession(); const form = useForm({ resolver: zodResolver(createTemplateSchema), defaultValues: { name: "", slug: "", description: "", category: undefined, // 기본값을 undefined로 설정 }, }) // 이름 입력 시 자동으로 slug 생성 React.useEffect(() => { const watchedName = form.watch("name") if (watchedName && !form.formState.dirtyFields.slug) { const autoSlug = watchedName .toLowerCase() .replace(/[^a-z0-9\s-]/g, '') // 특수문자 제거 .replace(/\s+/g, '-') // 공백을 하이픈으로 .replace(/-+/g, '-') // 연속 하이픈 제거 .trim() .slice(0, 50) // 최대 50자 form.setValue("slug", autoSlug, { shouldValidate: false }) } }, [form]) // 기본 템플릿 내용 생성 const getDefaultContent = (category: string, name: string) => { const templates = { 'welcome-email': ` 환영합니다!

안녕하세요, {{userName}}님!

${name}에 오신 것을 환영합니다.

{{message}}

`, 'password-reset': ` 비밀번호 재설정

비밀번호 재설정

안녕하세요, {{userName}}님.

비밀번호 재설정을 위해 아래 링크를 클릭해주세요.

비밀번호 재설정
`, 'notification': ` 알림

알림

안녕하세요, {{userName}}님.

{{message}}

`, } return templates[category as keyof typeof templates] || ` ${name}

${name}

안녕하세요, {{userName}}님.

{{message}}

` } // 기본 변수 생성 const getDefaultVariables = (category: string) => { const variableTemplates = { 'welcome-email': [ { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' }, { variableName: 'email', variableType: 'string', isRequired: true, description: '사용자 이메일' }, { variableName: 'message', variableType: 'string', isRequired: false, description: '환영 메시지' }, ], 'password-reset': [ { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' }, { variableName: 'expiryTime', variableType: 'string', isRequired: true, description: '링크 유효 시간' }, { variableName: 'resetLink', variableType: 'string', isRequired: true, description: '재설정 URL' }, { variableName: 'supportEmail', variableType: 'string', isRequired: true, description: 'eVCP 서포터 이메일' }, ], 'notification': [ { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' }, { variableName: 'email', variableType: 'string', isRequired: true, description: '사용자 이메일' }, { variableName: 'message', variableType: 'string', isRequired: true, description: '알림 메시지' }, ], } return variableTemplates[category as keyof typeof variableTemplates] || [ { variableName: 'userName', variableType: 'string', isRequired: true, description: '사용자 이름' }, { variableName: 'message', variableType: 'string', isRequired: false, description: '메시지 내용' }, ] } const getDefaultSubject = (category: string, name: string) => { const subjectTemplates = { 'welcome-email': '{{siteName}}에 오신 것을 환영합니다, {{userName}}님!', 'password-reset': '{{userName}}님의 비밀번호 재설정 요청', 'notification': '[{{notificationType}}] {{title}}', 'invoice': '{{companyName}} 인보이스 #{{invoiceNumber}}', 'marketing': '{{title}} - {{siteName}}', 'system': '[시스템] {{title}}' } return subjectTemplates[category as keyof typeof subjectTemplates] || `${name} - {{siteName}}` } function onSubmit(input: CreateTemplateSchema) { startCreateTransition(async () => { if (!session?.user?.id) { toast.error("로그인이 필요합니다") return } const defaultContent = getDefaultContent(input.category || '', input.name) const defaultVariables = getDefaultVariables(input.category || '') const defaultSubject = getDefaultSubject(input.category || '', input.name) const { error, data } = await createTemplateAction({ name: input.name, slug: input.slug, subject: defaultSubject, content: defaultContent, description: input.description, category: input.category || undefined, // 빈 문자열 대신 undefined 전달 sampleData: { userName: '홍길동', email: 'user@example.com', message: '샘플 메시지입니다.', }, createdBy: Number(session.user.id), variables: defaultVariables, }) if (error) { toast.error(error) return } form.reset() props.onOpenChange?.(false) toast.success("템플릿이 생성되었습니다") // 생성된 템플릿의 세부 페이지로 이동 if (data?.slug) { router.push(`/evcp/email-template/${data.slug}`) } else { window.location.reload() } }) } return ( 새 템플릿 생성 새로운 이메일 템플릿을 생성합니다. 기본 구조가 자동으로 생성되며, 생성 후 세부 내용을 편집할 수 있습니다.
( 템플릿 이름 )} /> ( Slug URL에 사용될 고유 식별자입니다. 소문자, 숫자, 하이픈만 사용 가능합니다. )} /> ( 카테고리 카테고리에 따라 기본 템플릿과 변수가 자동으로 생성됩니다. )} /> ( 설명 (선택사항)