From c116a9f52510f6f9000a219eacc792a6ab12db9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Wed, 26 Aug 2026 15:13:40 +0200 Subject: [PATCH] feat(web): add CookieBanner component --- apps/frontend/src/components/CookieBanner.css | 49 ++++++++++++++ apps/frontend/src/components/CookieBanner.jsx | 67 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 apps/frontend/src/components/CookieBanner.css create mode 100644 apps/frontend/src/components/CookieBanner.jsx diff --git a/apps/frontend/src/components/CookieBanner.css b/apps/frontend/src/components/CookieBanner.css new file mode 100644 index 0000000..b6d682c --- /dev/null +++ b/apps/frontend/src/components/CookieBanner.css @@ -0,0 +1,49 @@ +.cookie-banner-overlay { + position: fixed; + inset: 0; + background: rgba(28, 25, 23, 0.5); + display: flex; + align-items: flex-end; + justify-content: center; + z-index: 2000; + animation: fadeIn 0.2s ease; +} +.cookie-banner { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg) var(--radius-lg) 0 0; + padding: 1.5rem 1.25rem 1.25rem; + width: 100%; + max-width: 480px; + max-height: 85vh; + overflow-y: auto; + box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.15); + animation: slideUp 0.25s ease; +} +.cookie-banner-title { font-size: 1.1rem; font-weight: 700; color: var(--text-main); margin: 0 0 0.5rem; } +.cookie-banner-desc { font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; margin: 0 0 1rem; } +.cookie-categories { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem; } +.cookie-category { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.75rem; background: var(--surface-muted); border-radius: var(--radius); } +.cookie-category-info { display: flex; flex-direction: column; gap: 0.15rem; flex: 1; } +.cookie-category-name { font-size: 0.9rem; font-weight: 600; color: var(--text-main); } +.cookie-category-desc { font-size: 0.78rem; color: var(--text-muted); line-height: 1.4; } +.cookie-toggle { background: none; border: none; cursor: pointer; padding: 0; } +.cookie-toggle-track { display: block; width: 44px; height: 24px; background: var(--border); border-radius: 12px; position: relative; transition: background 0.2s; } +.cookie-toggle--on .cookie-toggle-track { background: var(--primary); } +.cookie-toggle-thumb { display: block; width: 20px; height: 20px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: transform 0.2s; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); } +.cookie-toggle--on .cookie-toggle-thumb { transform: translateX(20px); } +.cookie-toggle--locked .cookie-toggle-track { background: var(--primary); opacity: 0.6; cursor: not-allowed; } +.cookie-banner-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; } +.cookie-btn { flex: 1; min-width: 0; padding: 0.6rem 1rem; border-radius: 999px; border: none; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: opacity 0.15s; white-space: nowrap; } +.cookie-btn:hover { opacity: 0.85; } +.cookie-btn--primary { background: var(--primary); color: var(--on-primary); } +.cookie-btn--secondary { background: var(--surface-muted); color: var(--text-main); border: 1px solid var(--border); } +.cookie-btn--tertiary { background: transparent; color: var(--primary); border: 1px solid var(--primary); } +.cookie-more-info { display: block; width: 100%; margin-top: 0.75rem; padding: 0; background: none; border: none; color: var(--text-muted); font-size: 0.8rem; cursor: pointer; text-align: center; text-decoration: underline; } +.cookie-more-info:hover { color: var(--text-main); } +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } +@media (min-width: 769px) { + .cookie-banner-overlay { align-items: center; padding: 1rem; } + .cookie-banner { border-radius: var(--radius-lg); max-width: 420px; } +} diff --git a/apps/frontend/src/components/CookieBanner.jsx b/apps/frontend/src/components/CookieBanner.jsx new file mode 100644 index 0000000..41d1d27 --- /dev/null +++ b/apps/frontend/src/components/CookieBanner.jsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { useTranslation } from '../i18n'; +import './CookieBanner.css'; + +function CookieBanner({ onConsent, onPrivacyClick }) { + const { t } = useTranslation(); + const [categories, setCategories] = useState({ + analytics: false, + preferences: false, + health_data: false, + }); + + function toggleCategory(cat) { + setCategories(prev => ({ ...prev, [cat]: !prev[cat] })); + } + + function handleAcceptAll() { + onConsent({ essential: true, analytics: true, preferences: true, health_data: true }); + } + + function handleRejectOptional() { + onConsent({ essential: true, analytics: false, preferences: false, health_data: false }); + } + + function handleSave() { + onConsent({ essential: true, ...categories }); + } + + return ( +
+
+

{t('cookie_banner.title')}

+

{t('cookie_banner.description')}

+
+
+
+ {t('cookie_banner.category.essential')} + {t('cookie_banner.category.essential_desc')} +
+
+ +
+
+ {['analytics', 'preferences', 'health_data'].map(cat => ( +
+
+ {t(`cookie_banner.category.${cat}`)} + {t(`cookie_banner.category.${cat}_desc`)} +
+ +
+ ))} +
+
+ + + +
+ +
+
+ ); +} + +export default CookieBanner;