From 50d68fc522eef809cf9bb1e7c7ab240b81e97e45 Mon Sep 17 00:00:00 2001 From: luciano Date: Wed, 29 Apr 2026 15:48:32 +0200 Subject: [PATCH] feat(web): privacy-first cookie banner - New CookieBanner client component anchored at the bottom of the locale layout, dismissed via Button click and persisted via localStorage key ketopath:cookie-notice-ack (no cookie set for the dismissal itself) - Italian copy: only essential cookies for authentication/session, no analytics or third-party trackers (consistent with the ADR 0001 stance and CLAUDE.md "Privacy first" non-negotiable) - Layout body now uses bg-background / text-foreground tokens so dark mode toggling will work the day we add it Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/messages/it.json | 5 +++ apps/web/src/app/[locale]/layout.tsx | 4 ++- apps/web/src/components/cookie-banner.tsx | 42 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/cookie-banner.tsx diff --git a/apps/web/messages/it.json b/apps/web/messages/it.json index 65dd103..6337d6d 100644 --- a/apps/web/messages/it.json +++ b/apps/web/messages/it.json @@ -9,6 +9,11 @@ "signOut": "Esci", "completeProfile": "Completa il tuo profilo" }, + "CookieBanner": { + "aria": "Informativa cookie", + "message": "Usiamo solo cookie essenziali per l'autenticazione e la sessione. Niente analytics o tracker di terze parti.", + "dismiss": "Ho capito" + }, "Metadata": { "title": "KetoPath — Chetogenica e digiuno intermittente personalizzati", "description": "Web app italiana per la gestione strutturata di chetogenica e digiuno intermittente, con piani adattivi, tracking e lista della spesa automatica." diff --git a/apps/web/src/app/[locale]/layout.tsx b/apps/web/src/app/[locale]/layout.tsx index b053649..757913c 100644 --- a/apps/web/src/app/[locale]/layout.tsx +++ b/apps/web/src/app/[locale]/layout.tsx @@ -5,6 +5,7 @@ import { NextIntlClientProvider } from 'next-intl'; import { getMessages, getTranslations, setRequestLocale } from 'next-intl/server'; import type { ReactNode } from 'react'; +import { CookieBanner } from '@/components/cookie-banner'; import { locales, type Locale } from '@/i18n'; import '@/styles/globals.css'; @@ -48,9 +49,10 @@ export default async function LocaleLayout({ return ( - + {children} + diff --git a/apps/web/src/components/cookie-banner.tsx b/apps/web/src/components/cookie-banner.tsx new file mode 100644 index 0000000..3535ff6 --- /dev/null +++ b/apps/web/src/components/cookie-banner.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useEffect, useState } from 'react'; + +import { Button } from '@/components/ui/button'; + +const STORAGE_KEY = 'ketopath:cookie-notice-ack'; + +export function CookieBanner() { + const t = useTranslations('CookieBanner'); + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (typeof window === 'undefined') return; + if (window.localStorage.getItem(STORAGE_KEY) !== 'true') { + setVisible(true); + } + }, []); + + function dismiss() { + window.localStorage.setItem(STORAGE_KEY, 'true'); + setVisible(false); + } + + if (!visible) return null; + + return ( +
+
+

{t('message')}

+ +
+
+ ); +}