"use client"; import { useEffect } from "react"; import "klaro/dist/klaro.css"; import type { TrackingCode } from "../lib/payload"; import { buildKlaroConfig } from "../lib/klaroConfig"; import { loadTrackingCode } from "../lib/loadTrackingCode"; // Replaced a hand-rolled CookieBanner.tsx + useConsent.ts + TrackingScripts.tsx // with kiprotect/klaro (open source, self-hosted, npm install klaro) — // the custom banner only ever covered the accept/reject UI itself; Klaro // additionally brings a real per-service consent list, bundled German UI // translations, and (via `cookies`, not used here yet) cookie-deletion on // withdrawal — all things a hand-rolled version would have had to build // from scratch. See project memory for the fuller reasoning. // // Dynamically imported inside an effect (client-only, after mount) rather // than a static top-level import — Klaro touches `window`/`document` at // module-eval time in places, which isn't SSR-safe. The CSS import above // stays static (Next.js requires CSS imports to be static, not inside a // dynamic import()), paired with the "-no-css" JS build so the stylesheet // isn't loaded twice. export function KlaroConsentManager({ codes }: { codes: TrackingCode[] }) { useEffect(() => { // Nothing to ask consent for — don't even load/render Klaro. A cookie // banner with zero services to list would just be visual noise. if (codes.length === 0) return; let cancelled = false; import("klaro/dist/klaro-no-css").then((Klaro) => { if (cancelled) return; const config = buildKlaroConfig(codes, loadTrackingCode); Klaro.setup(config); }); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps -- `codes` comes // from a server-fetched, 60s-ISR-cached layout prop; it's stable for // the lifetime of this component in practice, and Klaro.setup() isn't // meant to be called more than once per page load anyway. }, []); return null; }