229 lines
6.7 KiB
TypeScript
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 { getAuthToken } 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 !!getAuthToken();
|
|
}, []);
|
|
|
|
// 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;
|
|
}
|