Add /impressum and /datenschutz pages backed by Payload

- lib/payload.ts: getLegalPage(type) alongside the existing
  getBlogPosts/getProducts fetchers
- New RichText component: small dependency-free Lexical JSON → JSX
  renderer for Payload's richText fields (headings get stable
  "section-N" ids for anchor/TOC linking)
- New SectionTOC: generalized from VersandTOC into a reusable
  scroll-spy sidebar driven by any {id, title}[] — Datenschutz needs one
  built from CMS-authored headings, not a hardcoded array. VersandTOC is
  now a thin wrapper around it. Sticky positioning moved from the nav
  itself to each page's sidebar wrapper, so a TOC and an extra card
  (Impressum/Datenschutz both have one) scroll together as one unit
  instead of the card drifting away independently
- TOC clicks set the active item immediately and hold it for ~1s over
  the scroll-spy observer, covering both "target already fully visible,
  nothing to scroll" and "an unrelated section flickers through the
  intersection band mid-scroll"
- Both pages follow the same breadcrumb+H1+TOC-sidebar+content layout
  established for /versand
This commit is contained in:
Marco
2026-07-19 17:38:02 +00:00
parent 8c397c5dcb
commit 1b434fe23e
7 changed files with 429 additions and 47 deletions
+35
View File
@@ -112,3 +112,38 @@ export async function getProductBySlug(slug: string): Promise<Product | null> {
const products = await getProducts();
return products.find((p) => p.id === slug) ?? null;
}
export type LegalPageType = "impressum" | "datenschutz" | "agb" | "widerruf";
export type LegalPage = {
type: LegalPageType;
title: string;
content: unknown;
};
type PayloadLegalPage = {
type: LegalPageType;
title: string;
content: unknown;
};
export async function getLegalPage(type: LegalPageType): Promise<LegalPage | null> {
const params = new URLSearchParams({
"where[tenant.slug][equals]": TENANT_SLUG,
"where[type][equals]": type,
limit: "1",
});
const res = await fetch(`${PAYLOAD_URL}/api/legal-pages?${params}`, {
next: { revalidate: 60 },
});
if (!res.ok) {
console.error(`getLegalPage: Payload returned ${res.status} ${res.statusText}`);
return null;
}
const data: { docs?: PayloadLegalPage[] } = await res.json();
const doc = data.docs?.[0];
if (!doc) return null;
return { type: doc.type, title: doc.title, content: doc.content };
}