feat(analytics): GA4 with GDPR Consent Mode v2

Google Analytics integration, off by default and GDPR-compliant for EU:

- src/lib/analytics/gtag.ts: typed event helpers + consent control. Every
  function is a safe no-op when NEXT_PUBLIC_GA_ID is unset.
- GoogleAnalytics.tsx: loads gtag.js with Consent Mode v2, all storage
  defaulting to "denied". anonymize_ip on, send_page_view off.
- ConsentBanner.tsx: on-brand cookie banner, localized to all 5 locales,
  persists choice for one year, flips analytics_storage to granted on accept.
- PageViewTracker.tsx: fires page_view on App Router client navigation
  (inside Suspense for useSearchParams).
- Key conversion events wired: ai_consultation_submitted (primary funnel
  goal) and ai_chat_opened.
- Consent strings added to messages/{en,it,vec,es,de}.json.

Build plumbing:
- NEXT_PUBLIC_GA_ID inlined at build time via Dockerfile ARG +
  docker-compose build.args (NEXT_PUBLIC_* must exist during next build,
  not just runtime).
- Nginx CSP extended to allow googletagmanager.com + google-analytics.com.
- env template documents NEXT_PUBLIC_GA_ID (empty = analytics disabled).

Verified: production build inlines the Measurement ID into the client
bundle; site builds cleanly both with and without the ID set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 06:53:04 -05:00
parent 3a94e7c003
commit 1ee8288c7e
17 changed files with 981 additions and 2 deletions
+6
View File
@@ -46,6 +46,12 @@ RUN DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" npx prisma gene
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
# NEXT_PUBLIC_* vars are inlined into the client bundle at BUILD time, so the
# GA Measurement ID must be present here (not just at runtime). Passed from
# docker-compose build.args -> .env. Empty by default = analytics disabled.
ARG NEXT_PUBLIC_GA_ID=""
ENV NEXT_PUBLIC_GA_ID=$NEXT_PUBLIC_GA_ID
RUN npm run build
# ── Stage 4: Production runner ──