537543bf91
Swaps the hand-rolled CookieBanner.tsx/useConsent.ts/TrackingScripts.tsx for kiprotect/klaro — brings a real per-service consent list and bundled German UI translations that a custom implementation would have had to build from scratch (per user feedback that a proper CMP is worth it over a purely custom binary accept/reject banner). klaroConfig.ts builds Klaro's config dynamically from the existing tracking-codes backend collection (one Klaro "service" per row, grouped by consentCategory as its purpose). loadTrackingCode.ts is the actual script-injection side effect, wired in via each service's `callback(consent)` — same GA4/Facebook-Pixel/GTM/custom loader logic TrackingScripts.tsx had, just triggered imperatively instead of declaratively rendered. Brand color applied via Klaro's CSS custom property overrides (styling.green1 etc.), not custom SCSS. No @types/klaro package exists — types/klaro.d.ts declares only the small surface actually used. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
"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;
|
|
}
|