From 098ba79c8f701990b02cc2eaa748adc7f911223e Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 26 Aug 2026 20:41:21 +0000 Subject: [PATCH] Frontend support for the Hero block; H1 moves out of the hardcoded wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app/[slug]/page.tsx and LivePageContent.tsx no longer render a hardcoded H1 from page.title — that's now the Hero block's job (see HeroBlock.ts's own comment on why). Breadcrumb stays, still built from page.title. Also backfilled /lebensuhr's already-migrated Pages document with a Hero block (order 0) carrying its real headline — without it the page would have lost its H1 entirely once the hardcoded wrapper was removed. --- app/[slug]/page.tsx | 6 ------ app/components/LivePageContent.tsx | 11 +++-------- app/components/PageBlocks.tsx | 25 +++++++++++++++++++++++++ app/lib/payload.ts | 11 +++++++++++ 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index ec66659..c01c976 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -76,12 +76,6 @@ export default async function DynamicPage({ {page.title}

-

- {page.title} -

diff --git a/app/components/LivePageContent.tsx b/app/components/LivePageContent.tsx index fb436fc..7f5cbf8 100644 --- a/app/components/LivePageContent.tsx +++ b/app/components/LivePageContent.tsx @@ -8,8 +8,9 @@ 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 +// Live-previewable subset of a Pages document: the breadcrumb and the +// layout blocks (the H1 now lives in the first block, usually a "hero" — +// see HeroBlock.ts) — 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 @@ -30,12 +31,6 @@ export function LivePageContent({ initialPage }: { initialPage: Page }) { {page.title}

-

- {page.title} -

diff --git a/app/components/PageBlocks.tsx b/app/components/PageBlocks.tsx index 0df01c2..48dcf77 100644 --- a/app/components/PageBlocks.tsx +++ b/app/components/PageBlocks.tsx @@ -36,6 +36,31 @@ export async function PageBlocks({ blocks }: { blocks: PageBlock[] }) { // makes for its own author-bio/"Weiterlesen" chrome). export function renderPageBlockSync(block: PageBlock): React.ReactNode { switch (block.blockType) { + // Same "font-playfair clamp headline" treatment every hand-built page + // (/lebensuhr, /3x3-system, /ueber-mich) used for its own H1 before + // this block existed — see HeroBlock.ts's own comment on why the + // headline moved out of Pages.title and into here. + case "hero": + return ( +
+

+ {block.headline} +

+ {block.subline &&

{block.subline}

} + {block.ctaLabel && block.ctaHref && ( + + {block.ctaLabel} + + )} +
+ ); + case "richTextSection": return ; diff --git a/app/lib/payload.ts b/app/lib/payload.ts index ff6f1e8..56fda65 100644 --- a/app/lib/payload.ts +++ b/app/lib/payload.ts @@ -1293,6 +1293,7 @@ export async function getTrackingCodes(): Promise { // full page section, not an inline prose element. export type PageBlock = + | { blockType: "hero"; id: string; headline: string; subline: string | null; ctaLabel: string | null; ctaHref: string | null } | { blockType: "richTextSection"; id: string; content: unknown } | { blockType: "stepRow"; id: string; items: { id: string; icon: string; title: string; subtitle: string | null; description: string }[] } | { blockType: "quote"; id: string; text: string; label: string | null } @@ -1317,6 +1318,7 @@ export type Page = { type PayloadPageImageField = { url?: string | null } | number | null; type PayloadPageBlock = + | { blockType: "hero"; id: string; headline: string; subline?: string | null; ctaLabel?: string | null; ctaHref?: string | null } | { blockType: "richTextSection"; id: string; content: unknown } | { blockType: "stepRow"; id: string; items: { id: string; icon: string; title: string; subtitle?: string | null; description: string }[] } | { blockType: "quote"; id: string; text: string; label?: string | null } @@ -1358,6 +1360,15 @@ function mapPayloadPageBlock(block: PayloadPageBlock): PageBlock { id: block.id, items: block.items.map((item) => ({ ...item, subtitle: item.subtitle ?? null })), }; + case "hero": + return { + blockType: "hero", + id: block.id, + headline: block.headline, + subline: block.subline ?? null, + ctaLabel: block.ctaLabel ?? null, + ctaHref: block.ctaHref ?? null, + }; default: return block; }