107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import type { Block, TextBlockProps, ButtonBlockProps, ImageBlockProps, SectionBlockProps } from "@/features/editor/state/editorStore";
|
|
|
|
// 에디터 크롬 없이 실제 랜딩 페이지처럼 블록들을 렌더링하는 컴포넌트
|
|
interface PublicPageRendererProps {
|
|
blocks: Block[];
|
|
}
|
|
|
|
export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
|
const sectionBlocks = blocks.filter((b) => b.type === "section");
|
|
const rootBlocks = blocks.filter((b) => !b.sectionId && b.type !== "section");
|
|
|
|
const renderBlock = (block: Block) => {
|
|
if (block.type === "text") {
|
|
const props = block.props as TextBlockProps;
|
|
|
|
const alignClass =
|
|
props.align === "center" ? "text-center" : props.align === "right" ? "text-right" : "text-left";
|
|
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`}>
|
|
{props.text}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
if (block.type === "button") {
|
|
const props = block.props as ButtonBlockProps;
|
|
|
|
return (
|
|
<a
|
|
key={block.id}
|
|
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}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
if (block.type === "image") {
|
|
const props = block.props as ImageBlockProps;
|
|
|
|
return (
|
|
<div key={block.id} className="w-full flex justify-center">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img src={props.src || ""} alt={props.alt} className="max-w-full h-auto object-contain" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const renderSection = (section: Block) => {
|
|
const props = section.props as SectionBlockProps;
|
|
|
|
const bgClass =
|
|
props.background === "muted" ? "bg-slate-900" : props.background === "primary" ? "bg-sky-900" : "bg-slate-950";
|
|
|
|
const pyClass = props.paddingY === "sm" ? "py-8" : props.paddingY === "lg" ? "py-20" : "py-12";
|
|
|
|
const columns = props.columns && props.columns.length > 0 ? props.columns : [{ id: `${section.id}_col`, span: 12 }];
|
|
|
|
return (
|
|
<section key={section.id} className={`${bgClass} ${pyClass}`}>
|
|
<div className="mx-auto max-w-5xl px-4">
|
|
<div className="flex gap-8">
|
|
{columns.map((col) => {
|
|
const basis = `${(col.span / 12) * 100}%`;
|
|
const columnBlocks = blocks.filter((b) => b.sectionId === section.id && b.columnId === col.id);
|
|
|
|
return (
|
|
<div key={col.id} className="flex flex-col gap-4" style={{ flexBasis: basis }}>
|
|
{columnBlocks.map((b) => (
|
|
<div key={b.id}>{renderBlock(b)}</div>
|
|
))}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="flex-1 flex flex-col bg-slate-950 text-slate-50">
|
|
{/* 루트 텍스트/버튼/이미지 블록들이 있다면 페이지 상단에 노출 */}
|
|
{rootBlocks.length > 0 && (
|
|
<section className="bg-slate-950 py-12">
|
|
<div className="mx-auto max-w-3xl px-4 flex flex-col gap-4">
|
|
{rootBlocks.map((b) => (
|
|
<div key={b.id}>{renderBlock(b)}</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* 섹션 블록들 */}
|
|
{sectionBlocks.map((section) => renderSection(section))}
|
|
</div>
|
|
);
|
|
}
|