xpeditis2.0/apps/frontend/src/lib/context/cookie-context.tsx
2026-06-12 11:33:37 +02:00

229 lines
6.7 KiB
TypeScript

/**
* Cookie Consent Context
*
* Provides cookie consent state and methods to the application
* Syncs with backend for authenticated users
*/
'use client';
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
import {
getConsentPreferences,
updateConsentPreferences,
type CookiePreferences,
} from '../api/gdpr';
import { hasSession } from '../api/client';
const STORAGE_KEY = 'cookieConsent';
const STORAGE_DATE_KEY = 'cookieConsentDate';
interface CookieContextType {
preferences: CookiePreferences;
showBanner: boolean;
showSettings: boolean;
isLoading: boolean;
setShowBanner: (show: boolean) => void;
setShowSettings: (show: boolean) => void;
acceptAll: () => Promise<void>;
acceptEssentialOnly: () => Promise<void>;
savePreferences: (prefs: CookiePreferences) => Promise<void>;
openPreferences: () => void;
}
const defaultPreferences: CookiePreferences = {
essential: true,
functional: false,
analytics: false,
marketing: false,
};
const CookieContext = createContext<CookieContextType | undefined>(undefined);
export function CookieProvider({ children }: { children: React.ReactNode }) {
const [preferences, setPreferences] = useState<CookiePreferences>(defaultPreferences);
const [showBanner, setShowBanner] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [hasInitialized, setHasInitialized] = useState(false);
// Check if user is authenticated
const isAuthenticated = useCallback(() => {
return hasSession();
}, []);
// Load preferences from localStorage
const loadFromLocalStorage = useCallback((): CookiePreferences | null => {
if (typeof window === 'undefined') return null;
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
return JSON.parse(stored);
} catch {
return null;
}
}
return null;
}, []);
// Save preferences to localStorage
const saveToLocalStorage = useCallback((prefs: CookiePreferences) => {
if (typeof window === 'undefined') return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
localStorage.setItem(STORAGE_DATE_KEY, new Date().toISOString());
}, []);
// Apply preferences (gtag, etc.)
const applyPreferences = useCallback((prefs: CookiePreferences) => {
if (typeof window === 'undefined') return;
// Google Analytics consent
const gtag = (window as any).gtag;
if (gtag) {
gtag('consent', 'update', {
analytics_storage: prefs.analytics ? 'granted' : 'denied',
ad_storage: prefs.marketing ? 'granted' : 'denied',
functionality_storage: prefs.functional ? 'granted' : 'denied',
});
}
// Sentry (if analytics enabled)
const Sentry = (window as any).Sentry;
if (Sentry) {
if (prefs.analytics) {
Sentry.init && Sentry.getCurrentHub && Sentry.getCurrentHub().getClient()?.getOptions();
}
}
}, []);
// Initialize preferences
useEffect(() => {
const initializePreferences = async () => {
setIsLoading(true);
// First, check localStorage
const localPrefs = loadFromLocalStorage();
if (localPrefs) {
setPreferences(localPrefs);
applyPreferences(localPrefs);
setShowBanner(false);
} else {
// No local consent - show banner
setShowBanner(true);
}
// If authenticated, try to sync with backend
if (isAuthenticated() && localPrefs) {
try {
const backendPrefs = await getConsentPreferences();
if (backendPrefs) {
// Backend has preferences - use them
const prefs: CookiePreferences = {
essential: backendPrefs.essential,
functional: backendPrefs.functional,
analytics: backendPrefs.analytics,
marketing: backendPrefs.marketing,
};
setPreferences(prefs);
saveToLocalStorage(prefs);
applyPreferences(prefs);
} else if (localPrefs) {
// No backend preferences but we have local - sync to backend
try {
await updateConsentPreferences({
...localPrefs,
userAgent: navigator.userAgent,
});
} catch (syncError) {
console.warn('Failed to sync consent to backend:', syncError);
}
}
} catch (error) {
console.warn('Failed to fetch consent from backend:', error);
// Continue with local preferences
}
}
setIsLoading(false);
setHasInitialized(true);
};
initializePreferences();
}, [isAuthenticated, loadFromLocalStorage, saveToLocalStorage, applyPreferences]);
// Save preferences (to localStorage and optionally backend)
const savePreferences = useCallback(
async (prefs: CookiePreferences) => {
// Ensure essential is always true
const safePrefs = { ...prefs, essential: true };
setPreferences(safePrefs);
saveToLocalStorage(safePrefs);
applyPreferences(safePrefs);
setShowBanner(false);
setShowSettings(false);
// Sync to backend if authenticated
if (isAuthenticated()) {
try {
await updateConsentPreferences({
...safePrefs,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
});
} catch (error) {
console.warn('Failed to sync consent to backend:', error);
// Local save succeeded, backend sync failed - that's okay
}
}
},
[isAuthenticated, saveToLocalStorage, applyPreferences]
);
const acceptAll = useCallback(async () => {
await savePreferences({
essential: true,
functional: true,
analytics: true,
marketing: true,
});
}, [savePreferences]);
const acceptEssentialOnly = useCallback(async () => {
await savePreferences({
essential: true,
functional: false,
analytics: false,
marketing: false,
});
}, [savePreferences]);
const openPreferences = useCallback(() => {
setShowBanner(true);
setShowSettings(true);
}, []);
const value: CookieContextType = {
preferences,
showBanner,
showSettings,
isLoading,
setShowBanner,
setShowSettings,
acceptAll,
acceptEssentialOnly,
savePreferences,
openPreferences,
};
return <CookieContext.Provider value={value}>{children}</CookieContext.Provider>;
}
export function useCookieConsent() {
const context = useContext(CookieContext);
if (context === undefined) {
throw new Error('useCookieConsent must be used within a CookieProvider');
}
return context;
}