Files
einfach-produktiv/app/widerruf/page.tsx
T
Marco 7c26bf7b7c Fix cart-badge false pulse, blog hero-card misattribution, legal breadcrumb color
- Cart badge pulsed on every page load/reload, not just real cart
  changes — useCartCount's SSR snapshot is always 0, so hydration alone
  satisfied the "count increased" check. Skip the very first effect run.
- WishlistButton: touch-device smaller/subtler sizing now applies
  regardless of revealOnHover (e.g. the homepage spotlight heart), not
  just the shop grid's hover-reveal mode.
- /blog: the big featured-style hero card was applied to whatever post
  sorted first post-filter, even when that post isn't actually the
  `featured` one (a category filter can exclude the real featured post
  entirely). Now gated on the post's own `featured` flag.
- Legal pages' "Startseite" breadcrumb link now matches the same
  text-brand/hover:underline treatment already used elsewhere on these
  pages (e.g. Impressum's Anbieter email link), instead of staying muted
  until hover.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
2026-07-31 22:46:24 +00:00

133 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import Link from "next/link";
import Image from "next/image";
import { draftMode } from "next/headers";
import { Reveal } from "../components/Reveal";
import { Footer } from "../components/Footer";
import { TrustRow } from "../components/TrustRow";
import { RichText, extractHeadings } from "../components/RichText";
import { LiveRichText } from "../components/LiveRichText";
import { SectionTOC, MobileSectionTOC } from "../components/SectionTOC";
import { getLegalPage } from "../lib/payload";
import { formatMonthYear } from "../lib/format";
export const metadata: Metadata = {
title: "Widerrufsrecht",
description: "Widerrufsbelehrung und Muster-Widerrufsformular für einfach produktiv.",
alternates: { canonical: "/widerruf" },
};
export default async function WiderrufPage() {
const { isEnabled: isPreview } = await draftMode();
const page = await getLegalPage("widerruf", { draft: isPreview });
const headings = page ? extractHeadings(page.content) : [];
return (
<>
<main className="flex flex-col flex-1 bg-bg-base">
<Reveal className="flex flex-col gap-3 items-start pb-6 pt-10 px-[var(--layout-padding-x)] w-full">
<p className="flex items-center gap-2 text-body-sm text-text-muted">
<Link href="/" className="text-brand hover:underline">Startseite</Link>
<span></span>
<span className="text-text-primary">Widerruf</span>
</p>
<p
className="font-semibold text-h-feature text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Widerrufsrecht
</p>
{page && <p className="text-body text-text-muted">Stand: {formatMonthYear(page.updatedAt)}</p>}
</Reveal>
{/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
comment. Outside the sidebar's `hidden lg:flex` wrapper below
(that wrapper's `hidden` would hide this too otherwise). */}
<div className="lg:hidden px-[var(--layout-padding-x)] pb-4 w-full">
<MobileSectionTOC sections={headings} />
</div>
<div className="flex flex-col lg:flex-row gap-8 lg:gap-12 items-start pb-10 pt-2 px-[var(--layout-padding-x)] w-full">
<div className="hidden lg:flex flex-col gap-6 w-[22.5rem] shrink-0 lg:sticky lg:top-32 lg:self-start">
<SectionTOC sections={headings} />
{/* Static, not part of the CMS content — same reasoning as
the Impressum/Datenschutz/AGB pages' own callout cards. */}
<div className="bg-bg-muted flex flex-col gap-3 items-start p-6 rounded-md w-full">
<Image alt="" src="/icon-trust-leaf.png" width={28} height={28} className="size-7 object-contain" />
<p
className="font-semibold text-body text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Nachhaltig handeln
</p>
<p className="text-body-sm text-text-muted">
Wir setzen auf digitale Produkte und vermeiden unnötige Ressourcenverschwendung.
</p>
<p className="text-body-sm text-text-muted">Für eine bessere Zukunft für uns alle.</p>
<div className="h-[0.125rem] w-8 bg-brand" />
</div>
</div>
<div className="w-full lg:flex-1 min-w-0 flex flex-col gap-8">
{page ? (
isPreview ? <LiveRichText initialContent={page.content} /> : <RichText content={page.content} />
) : (
<p className="text-body text-text-muted">Inhalte werden gerade aktualisiert.</p>
)}
{/* Generated live from company-settings (/api/muster-widerrufsformular)
instead of the CMS attachment field — see that route's own
comment on why: the static uploaded PDF's "An:" address
silently went stale whenever an admin updated the
Impressum's Anbieterdaten without also re-exporting this
file by hand. */}
<a
href="/api/muster-widerrufsformular"
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-5 bg-bg-muted hover:bg-bg-white border border-border rounded-md p-6 transition-colors"
>
<div className="flex size-12 shrink-0 items-center justify-center rounded-sm bg-bg-white border border-border text-brand">
<svg viewBox="0 0 24 24" className="size-6" fill="none" aria-hidden="true">
<path
d="M6 2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path d="M15 2v5h5" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
<path
d="M8.5 17.5v-5h1.4c.94 0 1.6.62 1.6 1.5s-.66 1.5-1.6 1.5H8.5m5.2 2v-5h1.2c1.16 0 1.9.9 1.9 2.5s-.74 2.5-1.9 2.5h-1.2Z"
stroke="currentColor"
strokeWidth="1.3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
<p className="font-semibold text-body text-text-primary">
Muster-Widerrufsformular (PDF)
</p>
<p className="text-body-sm text-text-muted">In neuem Tab öffnen</p>
</div>
<svg
viewBox="0 0 24 24"
className="size-5 shrink-0 text-text-muted group-hover:text-brand group-hover:translate-x-0.5 transition-all"
fill="none"
aria-hidden="true"
>
<path d="M5 12h14m0 0-6-6m6 6-6 6" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</div>
</div>
<TrustRow />
</main>
<Footer />
</>
);
}