diff --git a/admin-compliance/components/sdk/CookieBannerOverlay.tsx b/admin-compliance/components/sdk/CookieBannerOverlay.tsx index b3ad43a..860c803 100644 --- a/admin-compliance/components/sdk/CookieBannerOverlay.tsx +++ b/admin-compliance/components/sdk/CookieBannerOverlay.tsx @@ -7,14 +7,11 @@ import { } from './cookie-banner-vendors' /** - * CookieBannerOverlay — Cookie consent banner with Drittland-Schutz. + * CookieBannerOverlay — DSGVO/CNIL-konformer Cookie-Banner mit "Nur EU/EWR" Toggle. * - * Unique feature: Users can accept a category (e.g. Marketing) but block - * all vendors that transfer data to non-EU countries. This means: - * - Marketing = ON, Drittland-Schutz = ON → LinkedIn (EU) loads, Facebook (USA) does NOT - * - The consent state records both category consent AND blocked vendors - * - * No other CMP offers per-vendor country-based blocking within accepted categories. + * Alle 4 Kategorien sind auf der ersten Ebene sichtbar (DSK OH Telemedien 2022). + * Vendor-Details aufklappbar per Kategorie. EWR-Toggle blockiert Non-EU-Anbieter + * auch bei aktivierter Kategorie — einzigartiges CMP-Feature. */ const STORAGE_KEY = 'bp-sdk-cookie-consent' @@ -29,24 +26,26 @@ interface ConsentState { timestamp: string } - +function getStoredConsent(): ConsentState | null { + if (typeof window === 'undefined') return null + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return null + return JSON.parse(raw) + } catch { + return null + } +} export function CookieBannerOverlay() { const [isOpen, setIsOpen] = useState(false) - const [showSettings, setShowSettings] = useState(false) const [consent, setConsent] = useState({ - necessary: true, - statistics: false, - marketing: false, - functional: false, - ewrOnly: false, - blockedVendors: [], - timestamp: '', + necessary: true, statistics: false, marketing: false, functional: false, + ewrOnly: false, blockedVendors: [], timestamp: '', }) const nonEWRCount = useMemo(() => countNonEWRVendors(), []) - // Compute which vendors are actually blocked const blockedVendors = useMemo(() => { if (!consent.ewrOnly) return [] const blocked: string[] = [] @@ -54,9 +53,7 @@ export function CookieBannerOverlay() { const catEnabled = key === 'necessary' || consent[key as keyof ConsentState] if (!catEnabled) continue for (const v of cat.vendors) { - if (isOutsideEWR(v.country)) { - blocked.push(v.name) - } + if (isOutsideEWR(v.country)) blocked.push(v.name) } } return blocked @@ -64,24 +61,17 @@ export function CookieBannerOverlay() { useEffect(() => { const stored = getStoredConsent() - if (!stored) { - setIsOpen(true) - } else { - setConsent(stored) - } + if (!stored) setIsOpen(true) + else setConsent(stored) }, []) useEffect(() => { - const handler = () => { - setIsOpen(true) - setShowSettings(true) - } + const handler = () => setIsOpen(true) window.addEventListener('openCookieBanner', handler) return () => window.removeEventListener('openCookieBanner', handler) }, []) const saveConsent = useCallback((state: ConsentState) => { - // Compute blocked vendors before saving const blocked: string[] = [] if (state.ewrOnly) { for (const [key, cat] of Object.entries(CATEGORY_VENDORS)) { @@ -96,149 +86,86 @@ export function CookieBannerOverlay() { localStorage.setItem(STORAGE_KEY, JSON.stringify(withMeta)) setConsent(withMeta) setIsOpen(false) - setShowSettings(false) window.dispatchEvent(new CustomEvent('sdkCookieConsentUpdated', { detail: withMeta })) }, []) - const handleAcceptAll = () => { - saveConsent({ ...consent, necessary: true, statistics: true, marketing: true, functional: true }) - } - - const handleRejectAll = () => { - saveConsent({ ...consent, necessary: true, statistics: false, marketing: false, functional: false }) - } - - const handleSaveSettings = () => { - saveConsent(consent) - } - if (!isOpen) return null return ( <> -
{}} - /> +
-
+
- {/* Header */} -
+ + {/* Header with EWR toggle */} +
-
-

- - - - Cookie-Einstellungen -

-

- Wir verwenden Cookies und aehnliche Technologien, um Ihnen die bestmoegliche Erfahrung zu bieten. +

+

Cookie-Einstellungen

+

+ Waehlen Sie, welche Cookie-Kategorien Sie zulassen moechten.

- - {/* EWR-Only Toggle — always visible in header */} setConsent(prev => ({ ...prev, ewrOnly: !prev.ewrOnly }))} blockedCount={blockedVendors.length} + nonEWRCount={nonEWRCount} />
+
- {/* Blocked vendors pills */} - {consent.ewrOnly && blockedVendors.length > 0 && ( -
- {blockedVendors.map(name => ( - - - - - {name} - - ))} + {/* Categories — always visible (CNIL/DSK compliant) */} +
+ {Object.entries(CATEGORY_VENDORS).map(([key, cat]) => ( + key !== 'necessary' && setConsent(prev => ({ ...prev, [key]: v }))} + /> + ))} +
+ + {/* Buttons — two equal-weight options */} +
+
+ + +
+
+ + - )} - -
- {/* Settings */} - {showSettings && ( -
- {/* Category Sections */} -
- {Object.entries(CATEGORY_VENDORS).map(([key, cat]) => ( - key !== 'necessary' && setConsent(prev => ({ ...prev, [key]: v }))} - /> - ))} -
-
- )} - - {/* Buttons */} -
- {!showSettings ? ( - <> - - - - - ) : ( - <> - - - - - )} -
@@ -273,55 +200,84 @@ export function CookieBannerFAB() { } -function CategorySection({ - categoryKey, - label, - description, - vendors, - checked, - disabled, - ewrOnly, - onChange, -}: { - categoryKey: string - label: string - description: string - vendors: VendorInfo[] - checked: boolean - disabled?: boolean - ewrOnly: boolean - onChange: (v: boolean) => void +// ─── EWR Toggle with Info Button ────────────────────────── + +function EWRToggle({ checked, onChange, blockedCount, nonEWRCount }: { + checked: boolean; onChange: () => void; blockedCount: number; nonEWRCount: number +}) { + const [showInfo, setShowInfo] = useState(false) + + return ( +
+
+ + + Nur EU/EWR + + +
+ {checked && blockedCount > 0 && ( + {blockedCount} blockiert + )} + {showInfo && ( +
+
Nur EU/EWR-Anbieter
+

+ Erlaubt nur Anbieter mit Sitz im EWR (EU + Island, Liechtenstein, Norwegen) oder + der Schweiz. {nonEWRCount} Anbieter ausserhalb werden blockiert — auch bei + aktivierter Cookie-Kategorie. +

+ +
+ )} +
+ ) +} + + +// ─── Category Section with Vendor Table ─────────────────── + +function CategorySection({ label, description, vendors, checked, disabled, ewrOnly, onChange }: { + label: string; description: string; vendors: VendorInfo[]; checked: boolean + disabled?: boolean; ewrOnly: boolean; onChange: (v: boolean) => void }) { const [expanded, setExpanded] = useState(false) - - const euVendors = vendors.filter(v => isEWR(v.country)) const nonEuVendors = vendors.filter(v => isOutsideEWR(v.country)) const blockedCount = ewrOnly && checked ? nonEuVendors.length : 0 const activeCount = checked ? vendors.length - blockedCount : 0 return (
-
-