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
@@ -0,0 +1,54 @@
import { headingId } from "../../components/RichText";
import type { CompanySettings } from "../../lib/payload";
import type { TOCSection } from "../../components/SectionTOC";
// Renders "1. Verantwortlicher" straight from company-settings, same
// single-source-of-truth pattern as Impressum's AnbieterAngaben.tsx — this
// used to be hand-typed name/address/email baked into the Datenschutz
// richText (seed-datenschutz.ts on the Payload side), silently out of
// date the moment company-settings changed without a matching manual
// edit here too. The heading itself stays numbered "1." (headingId's own
// `^(\d+)\.` match turns that into "section-1", exactly the id this
// section's TOC entry already had before the RichText stopped rendering
// it — no anchor breakage).
export function verantwortlicherHeadings(): TOCSection[] {
return [{ id: headingId("1. Verantwortlicher"), title: "1. Verantwortlicher" }];
}
function Heading({ children }: { children: string }) {
return (
<h2
id={headingId(children)}
className="font-semibold text-h-small text-text-primary mt-2 scroll-mt-32 first:mt-0"
style={{ fontFamily: "var(--font-lora)" }}
>
{children}
<span className="block h-[0.125rem] w-8 bg-brand mt-2" aria-hidden />
</h2>
);
}
function P({ children }: { children: React.ReactNode }) {
return <p className="text-body text-text-body">{children}</p>;
}
export function VerantwortlicherBlock({ seller }: { seller: CompanySettings }) {
return (
<div className="flex flex-col gap-4 w-full">
<Heading>1. Verantwortlicher</Heading>
<P>Verantwortlich für die Datenverarbeitung auf dieser Website ist:</P>
<div className="flex flex-col gap-1">
<P>{seller.managingDirector || seller.sellerName}</P>
<P>{seller.sellerStreet}</P>
<P>
{seller.sellerZip} {seller.sellerCity}
</P>
<P>{seller.sellerCountry}</P>
<P>
E-Mail: <a href={`mailto:${seller.sellerEmail}`} className="text-brand hover:underline">{seller.sellerEmail}</a>
</P>
</div>
<P>Ein gesetzlich vorgeschriebener Datenschutzbeauftragter ist für unser Unternehmen aufgrund seiner Größe derzeit nicht erforderlich.</P>
</div>
);
}