Skip to content

feat(privacy): consent-gated cookie banner (no tracking until accepted)#12

Merged
Manuel10i merged 1 commit into
mainfrom
feat/cookie-consent
Jul 5, 2026
Merged

feat(privacy): consent-gated cookie banner (no tracking until accepted)#12
Manuel10i merged 1 commit into
mainfrom
feat/cookie-consent

Conversation

@Manuel10i

Copy link
Copy Markdown
Owner

Was

Ein Cookie-Consent-Banner auf jeder Seite (Content-Seiten und Karte), plus korrektes Consent-Gating: bis akzeptiert wird nichts getrackt, danach werden vorher aufgelaufene Events nachgeliefert und ab dann live gefeuert.

Verhalten (genau wie gewünscht)

  • Vor Entscheidung: Google Analytics wird gar nicht geladen (kein Script, keine Cookies, keine Requests). trackEvent-Aufrufe wandern in eine In-Memory-Queue — nichts verlässt den Browser.
  • Accept: GA wird geladen; sobald gtag bereit ist, wird die Queue geleert (Nachlieferung), und alle weiteren Events feuern live.
  • Decline: Queue wird verworfen, es wird nie etwas gesendet.
  • Persistenz: Wahl in localStorage; wiederkehrende Besucher sehen den Banner nicht erneut, akzeptierte laden GA sofort.

Umsetzung

  • lib/analytics.ts — consent-aware Layer (Queue, Zustand unknown|granted|denied, Flush, Persistenz). trackEvent-Signatur unverändert, alle bestehenden Aufrufer (LanguageSwitcher, ContactForm, MapDisclaimer) profitieren automatisch.
  • components/CookieConsent.tsx — Banner + consent-gated GoogleAnalytics-Loader. Nutzt useSyncExternalStore, damit der Server weder Banner noch Analytics rendert (kein Flash, keine Hydration-Mismatches). Pollt gtag-Readiness und flusht dann die Queue.
  • Root-Layout rendert den Banner statt GA bedingungslos zu laden.
  • Consent-Texte für en/de/es/fr ergänzt.

Verifikation

  • Unit-Tests (6, neu): kein Senden vor Consent → Nachlieferung in Reihenfolge bei Accept → Live-Feuern danach; Decline sendet nie; „granted aber gtag noch nicht da" → Flush bei markGtagReady; Wiederherstellung aus localStorage; Persistenz; Subscribe/Unsubscribe.
  • SSR geprüft (Build mit Test-GA-ID): /, /destination/gothenburg und /map enthalten kein googletagmanager vor Consent.
  • tsc ✓, eslint ✓ (0 errors), next build ✓. 38 vorbestehende React.act-Testfehler unverändert, keine neuen.

Hinweis: Einen Live-Klick-Durchlauf im Browser konnte ich in dieser Umgebung nicht ausführen (kein Browser-Tool verfügbar); die Consent-Logik ist aber durch Unit-Tests abgedeckt und das GA-Gating per SSR-Check bestätigt.

Generated with Claude Code

Add a cookie consent banner shown on every page (content pages and the
map) and stop loading Google Analytics until the visitor accepts.

- lib/analytics: consent-aware layer. trackEvent holds events in an
  in-memory queue while consent is unknown (nothing leaves the browser);
  on accept the queue is flushed and later events fire live; on decline
  the queue is dropped and nothing is ever sent. Choice persisted in
  localStorage and restored for returning visitors.
- CookieConsent: banner + consent-gated GoogleAnalytics loader. GA is only
  mounted after accept, so no tracking cookies/requests occur beforehand;
  once gtag is ready the queued events drain. Uses useSyncExternalStore so
  the server renders neither banner nor analytics.
- Root layout renders the banner instead of GA unconditionally.
- consent strings added for en/de/es/fr.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Manuel10i Manuel10i merged commit 2e7110a into main Jul 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant