Add the page-builder frontend: /[slug] catch-all + block renderer
Pages/getPageBySlug/mapPayloadPage in payload.ts follow the exact getPostBySlug/mapPayloadPost pattern. PageBlocks.tsx renders a Pages doc's `layout` field, reusing existing components (RichText, StepArrow, STEP_ICONS, TestimonialsGrid) rather than reinventing per- block styling — matches what /lebensuhr, /3x3-system, and /7-tage-klarheits-check already hand-built. LivePageContent.tsx mirrors LivePostContent.tsx's live-preview pattern, using a synchronous subset of the block renderer (testimonialsRef needs an async fetch a client component can't perform inline, so it's skipped in preview only — same "editable subset" scope-cut LivePostContent.tsx already makes). buildWebPageSchema in structuredData.ts is the generic JSON-LD fallback for content types with no bespoke schema (Article/ Product don't fit a page-builder page). Verified end-to-end: inserted a real Pages test document (SQL, since no admin auth available here) covering richTextSection/quote/ctaCard, confirmed /[slug] renders it correctly, confirmed notFound() after deleting it, then cleaned up the test row.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useLivePreview } from "@payloadcms/live-preview-react";
|
||||
import { Reveal } from "./Reveal";
|
||||
import { renderPageBlockSync } from "./PageBlocks";
|
||||
import { mapPayloadPage, type PayloadPage, type Page } from "../lib/payload";
|
||||
|
||||
const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://payload.mk360.de";
|
||||
|
||||
// Live-previewable subset of a Pages document: breadcrumb/H1 and the
|
||||
// layout blocks — same "editable subset only" scope as LivePostContent.tsx
|
||||
// (see that file's own comment). `testimonialsRef` blocks are skipped here
|
||||
// (see renderPageBlockSync's own comment) since they need an async fetch a
|
||||
// client component can't perform inline; editing that block still shows up
|
||||
// once the page is saved and reloaded outside the preview iframe.
|
||||
export function LivePageContent({ initialPage }: { initialPage: Page }) {
|
||||
const { data } = useLivePreview<PayloadPage>({
|
||||
initialData: initialPage as unknown as PayloadPage,
|
||||
serverURL: PAYLOAD_URL,
|
||||
depth: 2,
|
||||
});
|
||||
const page = data?.slug ? mapPayloadPage(data) : initialPage;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Reveal className="flex flex-col gap-4 items-start pt-10 pb-8 px-[var(--layout-padding-x)] w-full max-w-[48rem] mx-auto">
|
||||
<p className="flex items-center gap-2 text-body-sm text-text-muted">
|
||||
<Link href="/" className="hover:text-brand transition-colors">Startseite</Link>
|
||||
<span>›</span>
|
||||
<span className="text-text-primary">{page.title}</span>
|
||||
</p>
|
||||
<p
|
||||
className="font-semibold text-[clamp(2.25rem,1.393rem+1.786vw,3rem)] text-text-primary leading-[1.15]"
|
||||
style={{ fontFamily: "var(--font-playfair)" }}
|
||||
>
|
||||
{page.title}
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={0.1} className="w-full max-w-[48rem] mx-auto px-[var(--layout-padding-x)] pb-14 flex flex-col gap-5">
|
||||
{page.layout.map(renderPageBlockSync)}
|
||||
</Reveal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user