← back to Watches
src/components/CookieConsent.jsx
340 lines
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
/**
* GDPR-Compliant Cookie Consent Banner
* Implements EU cookie law requirements with granular consent options
*/
const CookieConsent = () => {
const [showBanner, setShowBanner] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [preferences, setPreferences] = useState({
necessary: true, // Always true - required for functionality
analytics: false,
marketing: false,
preferences: false
});
useEffect(() => {
// Check if user has already given consent
const consentCookie = getCookie('cookieConsent');
if (!consentCookie) {
setShowBanner(true);
} else {
// Load saved preferences
try {
const savedPreferences = JSON.parse(consentCookie);
setPreferences(savedPreferences);
} catch (e) {
setShowBanner(true);
}
}
}, []);
const getCookie = (name) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
};
const saveConsent = async (prefs) => {
try {
// Save to backend
const response = await fetch('/api/gdpr/consent', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
preferences: prefs
})
});
if (response.ok) {
// Save to cookie
const consentData = JSON.stringify(prefs);
document.cookie = `cookieConsent=${consentData}; path=/; max-age=31536000; SameSite=Strict`;
setShowBanner(false);
setShowSettings(false);
}
} catch (error) {
console.error('Error saving consent:', error);
}
};
const handleAcceptAll = () => {
const allAccepted = {
necessary: true,
analytics: true,
marketing: true,
preferences: true
};
setPreferences(allAccepted);
saveConsent(allAccepted);
};
const handleRejectAll = () => {
const minimal = {
necessary: true,
analytics: false,
marketing: false,
preferences: false
};
setPreferences(minimal);
saveConsent(minimal);
};
const handleSavePreferences = () => {
saveConsent(preferences);
};
const togglePreference = (key) => {
if (key === 'necessary') return; // Cannot disable necessary cookies
setPreferences(prev => ({
...prev,
[key]: !prev[key]
}));
};
return (
<AnimatePresence>
{showBanner && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.5 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black z-40"
onClick={() => setShowBanner(false)}
/>
{/* Cookie Banner */}
<motion.div
initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 100, opacity: 0 }}
transition={{ type: 'spring', damping: 25 }}
className="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-gray-800 shadow-2xl border-t-4 border-omega-red"
>
{!showSettings ? (
// Main Banner
<div className="container mx-auto px-4 py-6 max-w-6xl">
<div className="flex flex-col md:flex-row items-start md:items-center gap-4">
{/* Cookie Icon */}
<div className="text-6xl">🍪</div>
{/* Content */}
<div className="flex-1">
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-2">
We Value Your Privacy
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
We use cookies to enhance your browsing experience, analyze site traffic,
and personalize content. By clicking "Accept All", you consent to our use
of cookies. You can customize your preferences or decline non-essential cookies.
</p>
<a
href="/privacy-policy.html"
target="_blank"
rel="noopener noreferrer"
className="text-omega-red hover:underline text-sm mt-1 inline-block"
>
Read our Privacy Policy →
</a>
</div>
{/* Actions */}
<div className="flex flex-col sm:flex-row gap-2 w-full md:w-auto">
<button
onClick={() => setShowSettings(true)}
className="px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors font-semibold text-sm"
>
Customize
</button>
<button
onClick={handleRejectAll}
className="px-6 py-3 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors font-semibold text-sm"
>
Reject All
</button>
<button
onClick={handleAcceptAll}
className="px-6 py-3 bg-omega-red text-white rounded-lg hover:bg-red-700 transition-colors font-semibold text-sm"
>
Accept All
</button>
</div>
</div>
</div>
) : (
// Settings Panel
<div className="container mx-auto px-4 py-6 max-w-4xl">
<div className="mb-6">
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
Cookie Preferences
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Choose which cookies you want to accept. Essential cookies cannot be disabled
as they are required for the site to function.
</p>
</div>
<div className="space-y-4 mb-6">
{/* Necessary Cookies */}
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border-2 border-green-500">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<span className="text-2xl">✓</span>
<div>
<h4 className="font-bold text-gray-900 dark:text-white">
Essential Cookies
</h4>
<span className="text-xs text-green-600 dark:text-green-400 font-semibold">
Always Active
</span>
</div>
</div>
<div className="w-12 h-6 bg-green-500 rounded-full flex items-center justify-end px-1">
<div className="w-4 h-4 bg-white rounded-full"></div>
</div>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300">
These cookies are necessary for the website to function and cannot be disabled.
They include session management, security tokens, and basic functionality.
</p>
</div>
{/* Analytics Cookies */}
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<span className="text-2xl">📊</span>
<div>
<h4 className="font-bold text-gray-900 dark:text-white">
Analytics Cookies
</h4>
<span className="text-xs text-gray-500 dark:text-gray-400">
Optional
</span>
</div>
</div>
<button
onClick={() => togglePreference('analytics')}
className={`w-12 h-6 rounded-full transition-colors ${
preferences.analytics ? 'bg-omega-red' : 'bg-gray-300 dark:bg-gray-600'
} flex items-center ${preferences.analytics ? 'justify-end' : 'justify-start'} px-1`}
>
<div className="w-4 h-4 bg-white rounded-full"></div>
</button>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300">
Help us understand how visitors use our website through page views,
popular watches, and user journey analytics.
</p>
</div>
{/* Preference Cookies */}
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<span className="text-2xl">⚙️</span>
<div>
<h4 className="font-bold text-gray-900 dark:text-white">
Preference Cookies
</h4>
<span className="text-xs text-gray-500 dark:text-gray-400">
Optional
</span>
</div>
</div>
<button
onClick={() => togglePreference('preferences')}
className={`w-12 h-6 rounded-full transition-colors ${
preferences.preferences ? 'bg-omega-red' : 'bg-gray-300 dark:bg-gray-600'
} flex items-center ${preferences.preferences ? 'justify-end' : 'justify-start'} px-1`}
>
<div className="w-4 h-4 bg-white rounded-full"></div>
</button>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300">
Remember your settings like dark mode, preferred view, and saved filters
for a personalized experience.
</p>
</div>
{/* Marketing Cookies */}
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<span className="text-2xl">🎯</span>
<div>
<h4 className="font-bold text-gray-900 dark:text-white">
Marketing Cookies
</h4>
<span className="text-xs text-gray-500 dark:text-gray-400">
Optional
</span>
</div>
</div>
<button
onClick={() => togglePreference('marketing')}
className={`w-12 h-6 rounded-full transition-colors ${
preferences.marketing ? 'bg-omega-red' : 'bg-gray-300 dark:bg-gray-600'
} flex items-center ${preferences.marketing ? 'justify-end' : 'justify-start'} px-1`}
>
<div className="w-4 h-4 bg-white rounded-full"></div>
</button>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300">
Currently not used. Reserved for future marketing and advertising purposes
if implemented.
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-2 justify-end">
<button
onClick={() => setShowSettings(false)}
className="px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors font-semibold"
>
Back
</button>
<button
onClick={handleRejectAll}
className="px-6 py-3 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors font-semibold"
>
Reject All
</button>
<button
onClick={handleSavePreferences}
className="px-6 py-3 bg-omega-red text-white rounded-lg hover:bg-red-700 transition-colors font-semibold"
>
Save Preferences
</button>
</div>
<div className="mt-4 text-center">
<a
href="/privacy-policy.html"
target="_blank"
rel="noopener noreferrer"
className="text-omega-red hover:underline text-sm"
>
Read full Privacy Policy →
</a>
</div>
</div>
)}
</motion.div>
</>
)}
</AnimatePresence>
);
};
export default CookieConsent;