에디터 인라인/속성 패널 리팩터링 및 폼 전송 기본 기능 추가
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import type { Block, TextBlockProps, ButtonBlockProps, ImageBlockProps, SectionBlockProps } from "@/features/editor/state/editorStore";
|
||||
import { useState } from "react";
|
||||
import type {
|
||||
Block,
|
||||
TextBlockProps,
|
||||
ButtonBlockProps,
|
||||
ImageBlockProps,
|
||||
SectionBlockProps,
|
||||
FormBlockProps,
|
||||
} from "@/features/editor/state/editorStore";
|
||||
|
||||
// 에디터 크롬 없이 실제 랜딩 페이지처럼 블록들을 렌더링하는 컴포넌트
|
||||
interface PublicPageRendererProps {
|
||||
@@ -11,6 +19,9 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
const sectionBlocks = blocks.filter((b) => b.type === "section");
|
||||
const rootBlocks = blocks.filter((b) => !b.sectionId && b.type !== "section");
|
||||
|
||||
const [formStatus, setFormStatus] = useState<"idle" | "submitting" | "success" | "error">("idle");
|
||||
const [formMessage, setFormMessage] = useState<string>("");
|
||||
|
||||
const renderBlock = (block: Block) => {
|
||||
if (block.type === "text") {
|
||||
const props = block.props as TextBlockProps;
|
||||
@@ -20,7 +31,10 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
const sizeClass = props.size === "sm" ? "text-sm" : props.size === "lg" ? "text-2xl" : "text-base";
|
||||
|
||||
return (
|
||||
<p key={block.id} className={`${alignClass} ${sizeClass} text-slate-50 leading-relaxed`}>
|
||||
<p
|
||||
key={block.id}
|
||||
className={`${alignClass} ${sizeClass} text-slate-50 leading-relaxed whitespace-pre-wrap`}
|
||||
>
|
||||
{props.text}
|
||||
</p>
|
||||
);
|
||||
@@ -35,11 +49,98 @@ export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
href={props.href}
|
||||
className="inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-500 transition-colors"
|
||||
>
|
||||
{props.label}
|
||||
<span className="whitespace-pre-wrap">{props.label}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (block.type === "form") {
|
||||
const props = block.props as FormBlockProps;
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const form = e.currentTarget;
|
||||
const data = new FormData(form);
|
||||
|
||||
try {
|
||||
setFormStatus("submitting");
|
||||
setFormMessage("");
|
||||
|
||||
const res = await fetch("/api/forms/submit", {
|
||||
method: "POST",
|
||||
body: data,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setFormStatus("success");
|
||||
setFormMessage(props.successMessage ?? "성공적으로 전송되었습니다.");
|
||||
form.reset();
|
||||
} else {
|
||||
setFormStatus("error");
|
||||
setFormMessage(props.errorMessage ?? "전송 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[PublicPageRenderer] form submit error", error);
|
||||
setFormStatus("error");
|
||||
setFormMessage(props.errorMessage ?? "전송 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form key={block.id} className="space-y-3" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-1 text-xs text-slate-200">
|
||||
<label className="flex flex-col gap-1">
|
||||
<span>이름</span>
|
||||
<input
|
||||
className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-xs outline-none focus:border-sky-500"
|
||||
name="name"
|
||||
placeholder="이름을 입력하세요"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col gap-1">
|
||||
<span>이메일</span>
|
||||
<input
|
||||
className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-xs outline-none focus:border-sky-500"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col gap-1">
|
||||
<span>메시지</span>
|
||||
<textarea
|
||||
className="w-full min-h-[100px] rounded border border-slate-700 bg-slate-900 px-3 py-2 text-xs outline-none focus:border-sky-500"
|
||||
name="message"
|
||||
placeholder="전달할 내용을 입력하세요"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={formStatus === "submitting"}
|
||||
className="inline-flex items-center justify-center rounded border border-emerald-500 bg-emerald-600 px-4 py-2 text-xs font-medium text-white hover:bg-emerald-500 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
>
|
||||
{formStatus === "submitting" ? "전송 중..." : "폼 전송"}
|
||||
</button>
|
||||
|
||||
{formStatus !== "idle" && formMessage ? (
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
formStatus === "success" ? "text-emerald-400" : "text-rose-400"
|
||||
}`}
|
||||
>
|
||||
{formMessage}
|
||||
</p>
|
||||
) : null}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
if (block.type === "image") {
|
||||
const props = block.props as ImageBlockProps;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user