import React, { createContext, useContext, useEffect, useState } from 'react'; export interface CookieConsent { essential: boolean; // Always true preferences: boolean; // Theme, UI preferences analytics: boolean; // Measurement / analytics } interface CookieContextType { consent: CookieConsent; updateConsent: (newConsent: CookieConsent) => void; acceptAll: () => void; rejectAll: () => void; hasInteracted: boolean; // True if user has made a choice showBanner: boolean; closeBanner: () => void; } const CONSENT_COOKIE_NAME = 'xeewy_cookie_consent'; const CONSENT_COOKIE_MAX_AGE_DAYS = 180; const defaultConsent: CookieConsent = { essential: true, preferences: false, analytics: false, }; const getCookie = (name: string): string | null => { const prefix = `${name}=`; const parts = document.cookie.split(';').map((part) => part.trim()); const found = parts.find((part) => part.startsWith(prefix)); if (!found) return null; return found.slice(prefix.length); }; const setCookie = (name: string, value: string, days: number) => { const maxAge = days * 24 * 60 * 60; const securePart = window.location.protocol === 'https:' ? '; Secure' : ''; document.cookie = `${name}=${value}; Max-Age=${maxAge}; Path=/; SameSite=Lax${securePart}`; }; const removeCookie = (name: string) => { const securePart = window.location.protocol === 'https:' ? '; Secure' : ''; document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax${securePart}`; }; const CookieContext = createContext(undefined); export const CookieProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [consent, setConsent] = useState(defaultConsent); const [hasInteracted, setHasInteracted] = useState(false); const [showBanner, setShowBanner] = useState(false); const signConsent = async (rawConsent: CookieConsent) => { const response = await fetch('/api/cookies/sign-consent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ consent: { essential: true, preferences: Boolean(rawConsent.preferences), analytics: Boolean(rawConsent.analytics) } }) }); const payload = (await response.json().catch(() => null)) as { token?: string; message?: string } | null; if (!response.ok || !payload?.token) { throw new Error(payload?.message || 'Unable to sign consent.'); } return payload.token; }; const verifyConsentToken = async (token: string) => { const response = await fetch('/api/cookies/verify-consent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) }); const payload = (await response.json().catch(() => null)) as { consent?: Partial; message?: string; } | null; if (!response.ok || !payload?.consent) { return null; } return { ...defaultConsent, ...payload.consent, essential: true } as CookieConsent; }; useEffect(() => { const savedConsent = getCookie(CONSENT_COOKIE_NAME); const bootstrapConsent = async () => { if (!savedConsent) { setShowBanner(true); return; } const verified = await verifyConsentToken(savedConsent); if (!verified) { removeCookie(CONSENT_COOKIE_NAME); setConsent(defaultConsent); setHasInteracted(false); setShowBanner(true); return; } setConsent(verified); setHasInteracted(true); setShowBanner(false); }; void bootstrapConsent(); }, []); const saveConsent = async (newConsent: CookieConsent) => { try { const token = await signConsent(newConsent); setCookie(CONSENT_COOKIE_NAME, token, CONSENT_COOKIE_MAX_AGE_DAYS); setConsent(newConsent); setHasInteracted(true); setShowBanner(false); } catch { // Keep banner visible if signing fails setShowBanner(true); } }; const updateConsent = (newConsent: CookieConsent) => { void saveConsent({ ...newConsent, essential: true }); }; const acceptAll = () => { void saveConsent({ essential: true, preferences: true, analytics: true, }); }; const rejectAll = () => { void saveConsent({ essential: true, preferences: false, analytics: false, }); }; const closeBanner = () => { setShowBanner(false); }; return ( {children} ); }; export const useCookie = () => { const context = useContext(CookieContext); if (!context) { throw new Error('useCookie must be used within a CookieProvider'); } return context; };