Ship tracking-codes Phase 2: cookie consent banner + gated script injection
CookieBanner.tsx (equally-weighted Akzeptieren/Ablehnen, TTDSG) +
useConsent() cookie hook + TrackingScripts.tsx render active
tracking-codes rows only once their consentCategory is actually
accepted ('necessary' always renders). Wired into layout.tsx via the
new getTrackingCodes() fetcher. This is what makes the Phase 1
backend collection (tracking-codes) actually usable end to end.
Also reworks NotifyMeForm back to always-visible input+button (better
UX than a collapse-to-reveal step) — the resulting taller CTA is now
reserved on every card via NotifyMeFormReservedSpace, an invisible
twin rendered behind the real button, so an in-stock card's row
height matches an out-of-stock sibling's without the grid's
row-stretch pushing buttons out of alignment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useConsent } from "../lib/useConsent";
|
||||
|
||||
/**
|
||||
* Bottom banner, shown whenever useConsent()'s cookie hasn't recorded a
|
||||
* decision yet — the hard prerequisite for TrackingScripts.tsx to ever
|
||||
* render an analytics/marketing script (see that file's own comment).
|
||||
* Two equally-sized buttons, not a prominent "Akzeptieren" next to a
|
||||
* de-emphasized reject link — TTDSG requires an equally easy way to
|
||||
* decline, not just technically present a way to.
|
||||
*/
|
||||
export function CookieBanner() {
|
||||
const { consent, loaded, setConsent } = useConsent();
|
||||
const visible = loaded && consent === null;
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{visible && (
|
||||
<motion.div
|
||||
initial={{ y: 80, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 80, opacity: 0 }}
|
||||
transition={{ duration: 0.25, ease: "easeOut" }}
|
||||
role="dialog"
|
||||
aria-label="Cookie-Einstellungen"
|
||||
className="fixed inset-x-0 bottom-0 z-50 border-t border-border bg-bg-base px-4 py-5 shadow-[0_-4px_24px_rgba(0,0,0,0.06)] sm:px-6"
|
||||
>
|
||||
<div className="mx-auto flex max-w-5xl flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p className="text-body-sm text-text-primary">
|
||||
Wir nutzen Cookies, um diese Seite zu betreiben und ihre Nutzung zu verstehen. Mehr dazu in unserer{" "}
|
||||
<Link href="/datenschutz" className="underline hover:text-brand">
|
||||
Datenschutzerklärung
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<div className="flex shrink-0 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConsent({ analytics: false })}
|
||||
className="flex-1 rounded-sm border border-border px-5 py-2.5 text-body-sm font-semibold text-text-primary transition-colors hover:border-brand sm:flex-none"
|
||||
>
|
||||
Ablehnen
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConsent({ analytics: true })}
|
||||
className="flex-1 rounded-sm bg-brand px-5 py-2.5 text-body-sm font-bold text-text-primary transition-colors hover:bg-brand-hover sm:flex-none"
|
||||
>
|
||||
Akzeptieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user