feat: add GDPR cookie consent banner and upgrade-to-Pro link
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 3m8s

- Cookie consent banner with accept all / essential only buttons
- Uses existing i18n messages (en/fr) and localStorage for persistence
- Animated with Framer Motion, respects dark/light theme
- Free tier users see "Passer Pro →" link next to their badge in sidebar

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 20:01:36 +02:00
parent 0eccb531f4
commit 7aa0c5a892
3 changed files with 82 additions and 8 deletions

View File

@@ -0,0 +1,65 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
import { motion, AnimatePresence } from "framer-motion";
import { Button } from "@/components/ui/button";
const STORAGE_KEY = "cookie-consent";
export function CookieConsent() {
const t = useTranslations("cookieConsent");
const [visible, setVisible] = useState(false);
useEffect(() => {
setVisible(!localStorage.getItem(STORAGE_KEY));
}, []);
function acceptAll() {
localStorage.setItem(STORAGE_KEY, "all");
setVisible(false);
}
function essentialOnly() {
localStorage.setItem(STORAGE_KEY, "essential");
setVisible(false);
}
return (
<AnimatePresence>
{visible && (
<motion.div
initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 100, opacity: 0 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="fixed bottom-0 inset-x-0 z-50 p-4"
>
<div className="mx-auto max-w-3xl rounded-t-2xl border border-border bg-background/95 backdrop-blur-md shadow-2xl p-6">
<div className="flex items-start gap-3">
<span className="text-2xl shrink-0" aria-hidden="true">
🍪
</span>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-foreground mb-1">
{t("title")}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
{t("description")}
</p>
<div className="flex flex-wrap items-center gap-3 mt-4">
<Button variant="outline" size="sm" onClick={essentialOnly}>
{t("essentialOnly")}
</Button>
<Button size="sm" onClick={acceptAll}>
{t("acceptAll")}
</Button>
</div>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
);
}