Files
GNX-WEB/frontEnd/components/shared/layout/CookieConsent.tsx
Iliyan Angelov 136f75a859 update
2025-11-24 08:18:18 +02:00

392 lines
14 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useCookieConsent, CookiePreferences } from './CookieConsentContext';
// Cookie type definitions
interface CookieType {
id: keyof CookiePreferences;
name: string;
description: string;
required: boolean;
icon: string;
}
const cookieTypes: CookieType[] = [
{
id: 'necessary',
name: 'Necessary Cookies',
description: 'Essential cookies required for the website to function properly. These cannot be disabled.',
required: true,
icon: 'fas fa-shield-alt',
},
{
id: 'functional',
name: 'Functional Cookies',
description: 'These cookies enable enhanced functionality and personalization, such as remembering your preferences.',
required: false,
icon: 'fas fa-cogs',
},
];
// Main Cookie Consent Banner Component
export const CookieConsentBanner: React.FC = () => {
const { state, config, acceptAll, acceptNecessary, showSettings } = useCookieConsent();
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (state.showBanner && !state.showSettings) {
// Small delay to ensure smooth animation
const timer = setTimeout(() => setIsVisible(true), 100);
return () => clearTimeout(timer);
} else {
setIsVisible(false);
}
}, [state.showBanner, state.showSettings]);
// Hide banner when settings modal is open
if (!state.showBanner || !isVisible || state.showSettings) return null;
return (
<AnimatePresence>
{/* Fullscreen overlay to center the banner */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
style={{
position: 'fixed',
inset: 0,
zIndex: 10000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(17, 24, 39, 0.45)',
backdropFilter: 'blur(4px)',
}}
>
{/* Centered enterprise-style card */}
<motion.div
initial={{ opacity: 0, scale: 0.96, y: 8 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.98, y: 8 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
className="cookie-consent-banner"
style={{
width: 'min(680px, 92vw)',
background: '#0b1220',
color: '#e5e7eb',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 16,
boxShadow: '0 25px 70px rgba(0,0,0,0.45)',
padding: 24,
}}
>
<div className="cookie-consent-banner__container" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div className="cookie-consent-banner__content" style={{ display: 'flex', gap: 16 }}>
<div
className="cookie-consent-banner__icon"
style={{
width: 48,
height: 48,
borderRadius: 12,
display: 'grid',
placeItems: 'center',
background: 'linear-gradient(135deg, rgba(199, 213, 236, 0.39), rgba(147,197,253,0.08))',
color: '#93c5fd',
flex: '0 0 auto',
}}
>
<i className="fas fa-cookie-bite"></i>
</div>
<div className="cookie-consent-banner__text" style={{ display: 'grid', gap: 6 }}>
<h3 style={{ margin: 0, fontSize: 20, fontWeight: 700 }}>Cookie Preferences</h3>
<p style={{ margin: 0, lineHeight: 1.6, color: '#ffffff' }}>
We use only essential functional cookies to ensure our website works properly. We do not collect
personal data or use tracking cookies. Your privacy is important to us.
</p>
{config.showPrivacyNotice && (
<div className="cookie-consent-banner__links" style={{ marginTop: 6 }}>
<a
href={config.privacyPolicyUrl}
target="_blank"
rel="noopener noreferrer"
style={{ color: '#93c5fd', textDecoration: 'underline' }}
>
Privacy Policy
</a>
</div>
)}
</div>
</div>
<div
className="cookie-consent-banner__actions"
style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 8 }}
>
<button
type="button"
className="cookie-consent-banner__btn cookie-consent-banner__btn--secondary"
onClick={showSettings}
style={{
padding: '10px 14px',
borderRadius: 10,
border: '1px solid rgba(255,255,255,0.12)',
background: 'transparent',
color: '#e5e7eb',
fontWeight: 600,
}}
>
<i className="fas fa-cog" style={{ marginRight: 8 }}></i>
Settings
</button>
<button
type="button"
className="cookie-consent-banner__btn cookie-consent-banner__btn--primary"
onClick={acceptNecessary}
style={{
padding: '10px 14px',
borderRadius: 10,
border: '1px solid rgba(59,130,246,0.35)',
background: 'linear-gradient(135deg, rgba(59,130,246,0.25), rgba(37,99,235,0.35))',
color: '#ffffff',
fontWeight: 700,
}}
>
<i className="fas fa-check" style={{ marginRight: 8 }}></i>
Accept Functional Only
</button>
</div>
</div>
</motion.div>
</motion.div>
</AnimatePresence>
);
};
// Cookie Settings Modal Component
export const CookieSettingsModal: React.FC = () => {
const { state, config, hideSettings, acceptSelected, updatePreferences, withdrawConsent, exportConsentData } = useCookieConsent();
const [tempPreferences, setTempPreferences] = useState<CookiePreferences>(state.preferences);
useEffect(() => {
setTempPreferences(state.preferences);
}, [state.preferences]);
const handlePreferenceChange = (type: keyof CookiePreferences, value: boolean) => {
if (type === 'necessary') return; // Cannot change necessary cookies
setTempPreferences(prev => ({
...prev,
[type]: value,
}));
};
const handleSavePreferences = () => {
acceptSelected(tempPreferences);
};
const handleAcceptAll = () => {
const allAccepted: CookiePreferences = {
necessary: true,
functional: true,
};
acceptSelected(allAccepted);
};
if (!state.showSettings) return null;
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="cookie-settings-overlay"
onClick={hideSettings}
style={{
zIndex: 10001, // Higher than banner overlay (10000)
}}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
transition={{ duration: 0.2 }}
className="cookie-settings-modal"
onClick={(e) => e.stopPropagation()}
>
<div className="cookie-settings-modal__header">
<div className="cookie-settings-modal__header-content">
<h2>Cookie Preferences</h2>
<p className="cookie-settings-modal__version">v{config.version}</p>
</div>
<button
type="button"
className="cookie-settings-modal__close"
onClick={hideSettings}
aria-label="Close settings"
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="cookie-settings-modal__content">
<div className="cookie-settings-modal__description">
<p>
We respect your privacy and only use essential functional cookies.
You can choose which types of cookies to allow below.
</p>
</div>
<div className="cookie-settings-modal__types">
{cookieTypes.map((cookieType) => (
<div
key={cookieType.id}
className={`cookie-settings-modal__type ${
cookieType.required ? 'cookie-settings-modal__type--required' : ''
}`}
>
<div className="cookie-settings-modal__type-header">
<div className="cookie-settings-modal__type-info">
<i className={cookieType.icon}></i>
<div>
<h4>{cookieType.name}</h4>
<p>{cookieType.description}</p>
</div>
</div>
<div className="cookie-settings-modal__type-toggle">
<label className="cookie-toggle">
<input
type="checkbox"
checked={tempPreferences[cookieType.id]}
onChange={(e) => handlePreferenceChange(cookieType.id, e.target.checked)}
disabled={cookieType.required}
/>
<span className="cookie-toggle__slider"></span>
</label>
</div>
</div>
</div>
))}
</div>
<div className="cookie-settings-modal__privacy">
<h4>Privacy Information</h4>
<ul>
<li>We do not collect personal data without your explicit consent</li>
<li>Functional cookies are used only to maintain website functionality</li>
<li>We do not use tracking, analytics, or marketing cookies</li>
<li>You can change your preferences at any time</li>
<li>Data retention period: {config.retentionPeriod} days</li>
<li>Contact: <a href={`mailto:${config.dataControllerEmail}`}>{config.dataControllerEmail}</a></li>
</ul>
</div>
{config.enableDetailedSettings && (
<div className="cookie-settings-modal__enterprise">
<h4>Enterprise Features</h4>
<div className="cookie-settings-modal__enterprise-actions">
<button
type="button"
className="cookie-settings-modal__btn cookie-settings-modal__btn--outline"
onClick={() => {
const data = exportConsentData();
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `cookie-consent-data-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}}
>
<i className="fas fa-download"></i>
Export Data
</button>
<button
type="button"
className="cookie-settings-modal__btn cookie-settings-modal__btn--danger"
onClick={() => {
if (confirm('Are you sure you want to withdraw your consent? This will reset all preferences.')) {
withdrawConsent();
}
}}
>
<i className="fas fa-user-times"></i>
Withdraw Consent
</button>
</div>
</div>
)}
</div>
<div className="cookie-settings-modal__footer">
<button
type="button"
className="cookie-settings-modal__btn cookie-settings-modal__btn--secondary"
onClick={hideSettings}
>
Cancel
</button>
<button
type="button"
className="cookie-settings-modal__btn cookie-settings-modal__btn--primary"
onClick={handleSavePreferences}
>
Save Preferences
</button>
</div>
</motion.div>
</motion.div>
</AnimatePresence>
);
};
// Main Cookie Consent Component that combines both banner and modal
export const CookieConsent: React.FC = () => {
return (
<>
<CookieConsentBanner />
<CookieSettingsModal />
</>
);
};
// Cookie Preferences Display Component (for footer or privacy page)
export const CookiePreferencesDisplay: React.FC = () => {
const { state, showSettings, resetConsent } = useCookieConsent();
return (
<div className="cookie-preferences-display">
<h3>Cookie Preferences</h3>
<div className="cookie-preferences-display__status">
<p>
<strong>Status:</strong> {state.hasConsented ? 'Consent Given' : 'No Consent'}
</p>
<p>
<strong>Functional Cookies:</strong> {state.preferences.functional ? 'Enabled' : 'Disabled'}
</p>
</div>
<div className="cookie-preferences-display__actions">
<button
type="button"
className="cookie-preferences-display__btn"
onClick={showSettings}
>
Update Preferences
</button>
<button
type="button"
className="cookie-preferences-display__btn cookie-preferences-display__btn--reset"
onClick={resetConsent}
>
Reset Consent
</button>
</div>
</div>
);
};