feat(pages): add reusable page block editor and renderer baseline
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import { parsePageBlocks } from "@cms/content"
|
||||
import Image from "next/image"
|
||||
|
||||
type PageEntity = {
|
||||
title: string
|
||||
status: string
|
||||
@@ -10,6 +13,20 @@ type PublicPageViewProps = {
|
||||
}
|
||||
|
||||
export function PublicPageView({ page }: PublicPageViewProps) {
|
||||
const blocks = (() => {
|
||||
try {
|
||||
return parsePageBlocks(page.content)
|
||||
} catch {
|
||||
return [
|
||||
{
|
||||
id: "fallback-rich-text",
|
||||
type: "rich_text" as const,
|
||||
body: page.content,
|
||||
},
|
||||
]
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<article className="mx-auto flex w-full max-w-4xl flex-col gap-6 px-6 py-16">
|
||||
<header className="space-y-3">
|
||||
@@ -18,8 +35,105 @@ export function PublicPageView({ page }: PublicPageViewProps) {
|
||||
{page.summary ? <p className="text-neutral-600">{page.summary}</p> : null}
|
||||
</header>
|
||||
|
||||
<section className="prose prose-neutral max-w-none whitespace-pre-wrap rounded-xl border border-neutral-200 bg-white p-6 text-neutral-800">
|
||||
{page.content}
|
||||
<section className="space-y-4 rounded-xl border border-neutral-200 bg-white p-6 text-neutral-800">
|
||||
{blocks.map((block) => {
|
||||
if (block.type === "hero") {
|
||||
return (
|
||||
<section key={block.id} className="space-y-2 rounded border border-neutral-200 p-4">
|
||||
<h2 className="text-2xl font-semibold">{block.heading}</h2>
|
||||
{block.subheading ? <p className="text-neutral-600">{block.subheading}</p> : null}
|
||||
{block.ctaLabel && block.ctaHref ? (
|
||||
<a
|
||||
href={block.ctaHref}
|
||||
className="inline-flex rounded bg-neutral-900 px-3 py-1.5 text-sm text-white"
|
||||
>
|
||||
{block.ctaLabel}
|
||||
</a>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
if (block.type === "rich_text") {
|
||||
return (
|
||||
<section
|
||||
key={block.id}
|
||||
className="prose prose-neutral max-w-none whitespace-pre-wrap"
|
||||
>
|
||||
{block.body}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
if (block.type === "gallery") {
|
||||
return (
|
||||
<section key={block.id} className="space-y-3">
|
||||
{block.title ? <h3 className="text-lg font-medium">{block.title}</h3> : null}
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{block.imageIds.length === 0 ? (
|
||||
<p className="text-sm text-neutral-500">No media linked yet.</p>
|
||||
) : (
|
||||
block.imageIds.map((imageId) => (
|
||||
<Image
|
||||
key={imageId}
|
||||
src={`/api/media/file/${imageId}`}
|
||||
alt=""
|
||||
width={1200}
|
||||
height={800}
|
||||
className="h-48 w-full rounded border border-neutral-200 object-cover"
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
if (block.type === "cta") {
|
||||
return (
|
||||
<a
|
||||
key={block.id}
|
||||
href={block.href}
|
||||
className={`inline-flex rounded px-3 py-2 text-sm ${
|
||||
block.variant === "secondary"
|
||||
? "border border-neutral-300 text-neutral-800"
|
||||
: "bg-neutral-900 text-white"
|
||||
}`}
|
||||
>
|
||||
{block.label}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
if (block.type === "form") {
|
||||
return (
|
||||
<section key={block.id} className="space-y-2 rounded border border-neutral-200 p-4">
|
||||
<h3 className="text-lg font-medium">{block.title || "Form block"}</h3>
|
||||
<p className="text-sm text-neutral-600">
|
||||
{block.description || "Form integration pending."}
|
||||
</p>
|
||||
<p className="text-xs text-neutral-500">formKey: {block.formKey}</p>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section key={block.id} className="space-y-2 rounded border border-neutral-200 p-4">
|
||||
{block.title ? <h3 className="text-lg font-medium">{block.title}</h3> : null}
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
{block.cards.map((card) => (
|
||||
<article key={card.id} className="rounded border border-neutral-200 p-3">
|
||||
<h4 className="font-medium">{card.name}</h4>
|
||||
{card.price ? <p className="text-sm text-neutral-700">{card.price}</p> : null}
|
||||
{card.description ? (
|
||||
<p className="text-sm text-neutral-600">{card.description}</p>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
</article>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user