-
-
Notifications
You must be signed in to change notification settings - Fork 4
feat(ui): add GDPR-compliant cookie consent banner with i18n support #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState, useEffect } from 'react'; | ||
| import { X } from 'lucide-react'; | ||
| import { Link } from '@/i18n/routing'; | ||
| import { Button } from '@/components/ui/button'; | ||
| import { useTranslations } from 'next-intl'; | ||
|
|
||
| export function CookieBanner() { | ||
| const t = useTranslations('CookieBanner'); | ||
| const [isVisible, setIsVisible] = useState(false); | ||
| const [isMounted, setIsMounted] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| setIsMounted(true); | ||
| const consent = localStorage.getItem('cookie-consent'); | ||
| if (!consent) { | ||
| const timer = setTimeout(() => setIsVisible(true), 500); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, []); | ||
|
|
||
| const handleAccept = () => { | ||
| try { | ||
| localStorage.setItem('cookie-consent', 'accepted'); | ||
| } catch (error) { | ||
| console.error('Failed to save cookie consent:', error); | ||
| } | ||
| setIsVisible(false); | ||
| }; | ||
|
|
||
| const handleDecline = () => { | ||
| try { | ||
| localStorage.setItem('cookie-consent', 'declined'); | ||
| } catch (error) { | ||
| console.error('Failed to save cookie consent:', error); | ||
| } | ||
| setIsVisible(false); | ||
| }; | ||
|
|
||
| if (!isMounted || !isVisible) return null; | ||
|
|
||
| return ( | ||
| <div className="fixed bottom-0 left-0 right-0 z-[100] p-4 md:p-6 animate-in slide-in-from-bottom-full fade-in duration-700"> | ||
| <div className="mx-auto max-w-4xl rounded-2xl border border-gray-200 bg-white/90 p-5 shadow-2xl backdrop-blur-md dark:border-gray-800 dark:bg-gray-900/90 md:flex md:items-center md:justify-between md:gap-6"> | ||
| <div className="mb-4 md:mb-0 md:flex-1"> | ||
| <h3 className="mb-1 text-lg font-semibold text-gray-900 dark:text-white"> | ||
| {t('title')} | ||
| </h3> | ||
| <p className="text-sm leading-relaxed text-gray-600 dark:text-gray-300"> | ||
| {t('description')}{' '} | ||
| <Link | ||
| href="/privacy-policy" | ||
| className="font-medium text-blue-600 underline decoration-blue-600/30 underline-offset-4 transition-colors hover:text-blue-700 hover:decoration-blue-700 dark:text-blue-500 dark:hover:text-blue-400" | ||
| > | ||
| {t('policyLink')} | ||
| </Link> | ||
| . | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="flex flex-col gap-3 sm:flex-row sm:items-center"> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleDecline} | ||
| className="w-full sm:w-auto" | ||
| > | ||
| {t('decline')} | ||
| </Button> | ||
| <Button | ||
| variant="primary" | ||
| size="sm" | ||
| onClick={handleAccept} | ||
| className="w-full sm:w-auto shadow-lg shadow-blue-500/20" | ||
| > | ||
| {t('accept')} | ||
| </Button> | ||
| </div> | ||
|
|
||
| <button | ||
| onClick={handleDecline} | ||
| className="absolute right-2 top-2 rounded-full p-1 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300 md:hidden" | ||
| aria-label={t('decline')} | ||
| > | ||
| <X size={20} /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
Comment on lines
+9
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing: Consent revocation mechanism. Once a user makes a choice, there's no way for them to change their mind or revoke consent. GDPR requires that users can withdraw consent as easily as they can give it. Consider adding a mechanism (e.g., a footer link or settings page) where users can clear their consent and see the banner again. Would you like me to suggest an implementation for a consent revocation mechanism, such as a "Cookie Settings" link in the footer that clears the localStorage value? |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 98
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 98
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 776
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 1442
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 20569
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 6455
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 3506
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 399
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 8649
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 3440
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
Repository: DevLoversTeam/devlovers.net
Length of output: 326
Remove or repurpose the CookieBanner—currently it stores consent with no tracking system to enforce it against.
The component saves user consent preferences to localStorage, but the codebase has no analytics or tracking systems integrated. The @vercel/analytics package is installed but unused. Users are prompted to accept/decline cookies, but this preference has no effect since there are no cookies or trackers to control.
Either remove the banner if no analytics are planned, or implement a complete consent-driven tracking solution where:
As-is, the banner creates a compliance-adjacent appearance without actual protection, which is worse than no banner.
🤖 Prompt for AI Agents