Link Impressum's seller identity to company-settings, not hand-typed prose

"Angaben zum Anbieter"/"Umsatzsteuer"/"Handelsregister"/"Geschäftsführung"/
"Verantwortlich für den Inhalt" used to be hand-typed into the Impressum's
richText content (seed-legal-pages.ts) with no connection to the same
seller data the invoice PDFs and every email footer already pull from
company-settings — an admin updating one had no reason to remember the
other existed, and the old text was already stale in one concrete way:
it never showed Handelsregister/Geschäftsführung at all even though
company-settings has modeled both since the legal-form work shipped.

Now rendered by a new AnbieterAngaben component, straight from
getCompanySettings(), positioned above the CMS richText (which keeps
only genuinely editorial content: Kontakt, Haftung für Inhalte, Haftung
für Links, Urheberrecht). Same "structural/brand elements in code, only
pull the actual numbers/copy that need single-sourcing from data"
pattern this page's own Nachhaltigkeit sidebar card already used.

RichText.tsx's headingId() is now exported so the new block's headings
get the exact same id-assignment logic as CMS-driven ones, keeping the
SectionTOC sidebar's ids in sync with both sources.

/impressum moves from static to dynamic rendering (it now fetches live
company-settings data, cache: "no-store") — an acceptable tradeoff for
a legally-required page to never show stale seller info.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-23 11:10:02 +00:00
parent 039b8ba28d
commit 21c4e9f007
3 changed files with 113 additions and 5 deletions
@@ -0,0 +1,92 @@
import type { ReactNode } from "react";
import { headingId } from "../../components/RichText";
import type { CompanySettings } from "../../lib/payload";
import type { TOCSection } from "../../components/SectionTOC";
// Renders "Angaben zum Anbieter"/"Umsatzsteuer"/(conditionally)
// "Handelsregister"/"Geschäftsführung"/"Verantwortlich für den Inhalt"
// straight from company-settings, matching RichText.tsx's own heading/
// paragraph classes so it reads as one continuous page with the CMS
// content below it, not a bolted-on block. This used to be hand-typed
// prose baked into the Impressum's richText (seed-legal-pages.ts on the
// Payload side) — duplicated, and silently out of date the moment an
// admin changed company-settings without also remembering to re-edit the
// Impressum text by hand. Single-sourced here instead, same "structural/
// brand elements in code, only pull the actual numbers/copy that need
// single-sourcing from data" pattern this page's own Nachhaltigkeit card
// already uses (see page.tsx's comment on that).
//
// Also closes a real compliance gap the old hand-typed text had: it never
// showed Handelsregister/Geschäftsführung at all, even though
// company-settings already models both (§37a HGB/§35a GmbHG) — those
// fields just weren't wired into the Impressum. A sole proprietorship
// (this shop's current legalForm) has neither, so neither section shows
// today, but the moment that changes in company-settings, the Impressum
// picks it up automatically instead of needing a second manual edit.
export function anbieterAngabenHeadings(seller: CompanySettings | null): TOCSection[] {
if (!seller) return [];
const sections = ["Angaben zum Anbieter", "Umsatzsteuer"];
if (seller.registerCourt && seller.registerNumber) sections.push("Handelsregister");
if (seller.managingDirector) sections.push("Geschäftsführung");
sections.push("Verantwortlich für den Inhalt");
return sections.map((title) => ({ id: headingId(title), title }));
}
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: ReactNode }) {
return <p className="text-body text-text-body">{children}</p>;
}
export function AnbieterAngaben({ seller }: { seller: CompanySettings }) {
return (
<div className="flex flex-col gap-4 w-full">
<Heading>Angaben zum Anbieter</Heading>
<P>{seller.sellerName}</P>
<P>{seller.sellerStreet}</P>
<P>
{seller.sellerZip} {seller.sellerCity}
</P>
<P>{seller.sellerCountry}</P>
<P>E-Mail: {seller.sellerEmail}</P>
<Heading>Umsatzsteuer</Heading>
<P>Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:</P>
<P>{seller.vatId}</P>
{seller.registerCourt && seller.registerNumber && (
<>
<Heading>Handelsregister</Heading>
<P>{seller.registerCourt}</P>
<P>{seller.registerNumber}</P>
</>
)}
{seller.managingDirector && (
<>
<Heading>Geschäftsführung</Heading>
<P>{seller.managingDirector}</P>
</>
)}
<Heading>Verantwortlich für den Inhalt</Heading>
<P>{seller.managingDirector || seller.sellerName}</P>
<P>{seller.sellerStreet}</P>
<P>
{seller.sellerZip} {seller.sellerCity}
</P>
<P>{seller.sellerCountry}</P>
</div>
);
}