Add mobile swipe gallery, shared ProductCard, dynamic Datenschutz address, GSC verification

- ProductGallery: touch swipe (left/right) now navigates slides on mobile,
  previously click-only.
- New shared ProductCard component used by ProductGrid/RelatedProducts/
  MerklisteGrid — product title is now the card's link (replaces the
  separate "Mehr erfahren" line), consistent aspect ratio across all
  three grids, more compact cards.
- Datenschutz: name/address/email now come from company-settings via a
  new VerantwortlicherBlock, same single-source pattern as Impressum's
  AnbieterAngaben — no more hand-typed address to keep in sync.
- layout.tsx: render googleSearchConsoleVerification via Next's native
  verification.google metadata field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-08-02 05:38:55 +00:00
parent 23f8efcc2b
commit 9617478b0d
10 changed files with 283 additions and 265 deletions
+12 -4
View File
@@ -7,8 +7,9 @@ import { Footer } from "../components/Footer";
import { RichText, extractHeadings } from "../components/RichText";
import { LiveRichText } from "../components/LiveRichText";
import { SectionTOC, MobileSectionTOC } from "../components/SectionTOC";
import { getLegalPage } from "../lib/payload";
import { getLegalPage, getCompanySettings } from "../lib/payload";
import { formatMonthYear } from "../lib/format";
import { VerantwortlicherBlock, verantwortlicherHeadings } from "./components/VerantwortlicherBlock";
export const metadata: Metadata = {
title: "Datenschutzerklärung",
@@ -18,8 +19,11 @@ export const metadata: Metadata = {
export default async function DatenschutzPage() {
const { isEnabled: isPreview } = await draftMode();
const page = await getLegalPage("datenschutz", { draft: isPreview });
const headings = page ? extractHeadings(page.content) : [];
const [page, seller] = await Promise.all([getLegalPage("datenschutz", { draft: isPreview }), getCompanySettings()]);
// "1. Verantwortlicher" headings first — that block renders above the
// CMS content below (same reasoning as Impressum's own headings
// composition, see AnbieterAngaben.tsx).
const headings = [...verantwortlicherHeadings(), ...(page ? extractHeadings(page.content) : [])];
return (
<>
@@ -69,7 +73,11 @@ export default async function DatenschutzPage() {
</div>
</div>
<div className="w-full lg:flex-1 min-w-0">
<div className="w-full lg:flex-1 min-w-0 flex flex-col gap-8">
{/* Name/Adresse/E-Mail kommen direkt aus company-settings, nicht
aus der CMS-Richtext unten — single-sourced, gleiche
Begründung wie Impressum's AnbieterAngaben.tsx. */}
{seller && <VerantwortlicherBlock seller={seller} />}
{page ? (
isPreview ? <LiveRichText initialContent={page.content} /> : <RichText content={page.content} />
) : (