"use client"; import { useEffect, useState } 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[] }) { // Kept in state (not just called once) so the persistent reopen button // below can call Klaro.show() itself, any time after the initial // accept/reject decision — a visitor must always be able to revisit // their choice, not just on first load. const [klaroModule, setKlaroModule] = useState(null); 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); setKlaroModule(Klaro); }); 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. }, []); if (!klaroModule) return null; return ( ); }