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>
118 lines
4.2 KiB
TypeScript
118 lines
4.2 KiB
TypeScript
import type { TrackingCode } from "./payload";
|
|
|
|
// Klaro's own config shape (kiprotect/klaro, see node_modules/klaro's
|
|
// dist/config.js for the fully-annotated reference) — only the fields we
|
|
// actually set are typed here, not Klaro's full surface.
|
|
export type KlaroConfig = {
|
|
version: number;
|
|
elementID: string;
|
|
styling: Record<string, string | string[]>;
|
|
storageMethod: "cookie";
|
|
cookieName: string;
|
|
cookieExpiresAfterDays: number;
|
|
default: boolean;
|
|
mustConsent: boolean;
|
|
acceptAll: boolean;
|
|
hideDeclineAll: boolean;
|
|
noticeAsModal: boolean;
|
|
translations: Record<string, Record<string, unknown>>;
|
|
services: {
|
|
name: string;
|
|
title: string;
|
|
purposes: string[];
|
|
required: boolean;
|
|
default: boolean;
|
|
onlyOnce: boolean;
|
|
callback: (consent: boolean) => void;
|
|
}[];
|
|
};
|
|
|
|
export function serviceName(code: TrackingCode): string {
|
|
return `tracking-code-${code.id}`;
|
|
}
|
|
|
|
function providerTitle(code: TrackingCode): string {
|
|
switch (code.provider) {
|
|
case "google-analytics":
|
|
return "Google Analytics";
|
|
case "facebook-pixel":
|
|
return "Facebook Pixel";
|
|
case "google-tag-manager":
|
|
return "Google Tag Manager";
|
|
default:
|
|
return "Sonstiges Tracking-Skript";
|
|
}
|
|
}
|
|
|
|
// `onAccept` is threaded in rather than importing loadTrackingCode.ts
|
|
// directly here — this module only builds a plain config object (no DOM
|
|
// access), kept separate from the actual script-injection side effect so
|
|
// it stays trivially testable/reusable if the loading mechanism ever
|
|
// changes.
|
|
export function buildKlaroConfig(codes: TrackingCode[], onAccept: (code: TrackingCode) => void): KlaroConfig {
|
|
const serviceTranslations: Record<string, { title: string }> = {};
|
|
for (const code of codes) serviceTranslations[serviceName(code)] = { title: providerTitle(code) };
|
|
|
|
return {
|
|
version: 1,
|
|
elementID: "klaro",
|
|
// 'light' (not 'dark'), 'bottom' (matches the previous custom
|
|
// CookieBanner.tsx's own position), 'wide' (roomier than Klaro's
|
|
// narrow default notice, closer to the old banner's proportions).
|
|
// Extra keys beyond `theme` override individual CSS custom properties
|
|
// (Klaro's injectStyles() applies any non-'theme' `styling` key as a
|
|
// `--<key>` var) — green1 is what colors the "Alle akzeptieren"
|
|
// button (.cm-btn-success), so this is how the accept button gets the
|
|
// site's actual brand color instead of Klaro's default green.
|
|
styling: {
|
|
theme: ["light", "bottom", "wide"],
|
|
green1: "#f6a701", // --color-brand
|
|
"button-text-color": "#1a1a18", // --color-text-primary — dark text reads better on the brand orange than Klaro's default white
|
|
"border-radius": "6px",
|
|
"font-family": "inherit",
|
|
},
|
|
storageMethod: "cookie",
|
|
cookieName: "klaro-consent",
|
|
cookieExpiresAfterDays: 180,
|
|
default: false,
|
|
mustConsent: false,
|
|
acceptAll: true,
|
|
hideDeclineAll: false,
|
|
noticeAsModal: false,
|
|
translations: {
|
|
de: {
|
|
consentModal: {
|
|
title: "Cookie-Einstellungen",
|
|
description: "Hier kannst du einsehen und anpassen, welche Cookies/Skripte diese Seite verwendet.",
|
|
},
|
|
consentNotice: {
|
|
description: "Wir nutzen Cookies, um diese Seite zu betreiben und ihre Nutzung zu verstehen.",
|
|
learnMore: "Einstellungen",
|
|
},
|
|
purposes: {
|
|
necessary: "Notwendig",
|
|
analytics: "Analyse",
|
|
marketing: "Marketing",
|
|
},
|
|
...serviceTranslations,
|
|
},
|
|
},
|
|
services: codes.map((code) => ({
|
|
name: serviceName(code),
|
|
title: providerTitle(code),
|
|
purposes: [code.consentCategory],
|
|
required: code.consentCategory === "necessary",
|
|
default: code.consentCategory === "necessary",
|
|
// A restock/page-reload shouldn't re-fire the loader for a service
|
|
// the visitor already accepted in an earlier session within this
|
|
// same page load — Klaro still calls `callback` once per accepted
|
|
// service per page load either way, `onlyOnce` just governs repeat
|
|
// toggling within one session.
|
|
onlyOnce: true,
|
|
callback: (consent: boolean) => {
|
|
if (consent) onAccept(code);
|
|
},
|
|
})),
|
|
};
|
|
}
|