158 lines
5.5 KiB
TypeScript
158 lines
5.5 KiB
TypeScript
import { parsePageBlocks } from "@cms/content"
|
|
import Image from "next/image"
|
|
|
|
type PageEntity = {
|
|
title: string
|
|
status: string
|
|
summary: string | null
|
|
content: string
|
|
}
|
|
|
|
type PublicPageViewProps = {
|
|
page: PageEntity
|
|
}
|
|
|
|
function resolveFormLink(formKey: string): { href: string; label: string } {
|
|
const normalized = formKey.trim().toLowerCase()
|
|
|
|
if (normalized === "commission" || normalized === "commissions") {
|
|
return { href: "/commissions", label: "Open commission form" }
|
|
}
|
|
|
|
return { href: `/#form-${normalized || "contact"}`, label: "Open contact form" }
|
|
}
|
|
|
|
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">
|
|
<p className="text-sm uppercase tracking-[0.2em] text-neutral-500">{page.status}</p>
|
|
<h1 className="text-4xl font-semibold tracking-tight">{page.title}</h1>
|
|
{page.summary ? <p className="text-neutral-600">{page.summary}</p> : null}
|
|
</header>
|
|
|
|
<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") {
|
|
const formLink = resolveFormLink(block.formKey)
|
|
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>
|
|
<a
|
|
href={formLink.href}
|
|
className="inline-flex rounded border border-neutral-300 px-3 py-1.5 text-sm"
|
|
>
|
|
{formLink.label}
|
|
</a>
|
|
</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>
|
|
)
|
|
}
|