414 lines
11 KiB
TypeScript
414 lines
11 KiB
TypeScript
'use client';
|
|
|
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
|
|
|
// Types for cookie consent
|
|
export interface CookiePreferences {
|
|
necessary: boolean;
|
|
functional: boolean;
|
|
}
|
|
|
|
export interface ConsentAuditLog {
|
|
timestamp: string;
|
|
action: 'consent_given' | 'consent_updated' | 'consent_withdrawn' | 'settings_opened';
|
|
preferences: CookiePreferences;
|
|
userAgent: string;
|
|
ipAddress?: string;
|
|
sessionId: string;
|
|
}
|
|
|
|
export interface CookieConsentConfig {
|
|
version: string;
|
|
companyName: string;
|
|
privacyPolicyUrl: string;
|
|
cookiePolicyUrl: string;
|
|
dataControllerEmail: string;
|
|
retentionPeriod: number; // days
|
|
enableAuditLog: boolean;
|
|
enableDetailedSettings: boolean;
|
|
showPrivacyNotice: boolean;
|
|
}
|
|
|
|
export interface CookieConsentState {
|
|
hasConsented: boolean;
|
|
preferences: CookiePreferences;
|
|
showBanner: boolean;
|
|
showSettings: boolean;
|
|
consentDate?: string;
|
|
lastUpdated?: string;
|
|
auditLog: ConsentAuditLog[];
|
|
}
|
|
|
|
export interface CookieConsentContextType {
|
|
state: CookieConsentState;
|
|
config: CookieConsentConfig;
|
|
acceptAll: () => void;
|
|
acceptNecessary: () => void;
|
|
acceptSelected: (preferences: Partial<CookiePreferences>) => void;
|
|
showSettings: () => void;
|
|
hideSettings: () => void;
|
|
updatePreferences: (preferences: Partial<CookiePreferences>) => void;
|
|
resetConsent: () => void;
|
|
withdrawConsent: () => void;
|
|
exportConsentData: () => string;
|
|
getConsentSummary: () => any;
|
|
}
|
|
|
|
// Default cookie preferences
|
|
const defaultPreferences: CookiePreferences = {
|
|
necessary: true, // Always true, cannot be disabled
|
|
functional: false,
|
|
};
|
|
|
|
// Enterprise configuration
|
|
const defaultConfig: CookieConsentConfig = {
|
|
version: '2.0',
|
|
companyName: 'Your Company Name',
|
|
privacyPolicyUrl: '/policy?type=privacy',
|
|
cookiePolicyUrl: '/policy?type=privacy',
|
|
dataControllerEmail: 'privacy@yourcompany.com',
|
|
retentionPeriod: 365, // 1 year
|
|
enableAuditLog: true,
|
|
enableDetailedSettings: true,
|
|
showPrivacyNotice: true,
|
|
};
|
|
|
|
// Default state
|
|
const defaultState: CookieConsentState = {
|
|
hasConsented: false,
|
|
preferences: defaultPreferences,
|
|
showBanner: false,
|
|
showSettings: false,
|
|
auditLog: [],
|
|
};
|
|
|
|
// Create context
|
|
const CookieConsentContext = createContext<CookieConsentContextType | undefined>(undefined);
|
|
|
|
// Storage keys
|
|
const CONSENT_STORAGE_KEY = 'cookie-consent-preferences';
|
|
const CONSENT_VERSION = '2.0';
|
|
|
|
// Utility functions
|
|
const generateSessionId = (): string => {
|
|
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
};
|
|
|
|
const createAuditLogEntry = (
|
|
action: ConsentAuditLog['action'],
|
|
preferences: CookiePreferences
|
|
): ConsentAuditLog => {
|
|
return {
|
|
timestamp: new Date().toISOString(),
|
|
action,
|
|
preferences,
|
|
userAgent: typeof window !== 'undefined' ? window.navigator.userAgent : '',
|
|
sessionId: generateSessionId(),
|
|
};
|
|
};
|
|
|
|
// Provider component
|
|
export const CookieConsentProvider: React.FC<{
|
|
children: ReactNode;
|
|
config?: Partial<CookieConsentConfig>;
|
|
}> = ({ children, config: customConfig }) => {
|
|
const [state, setState] = useState<CookieConsentState>(defaultState);
|
|
const config = { ...defaultConfig, ...customConfig };
|
|
|
|
// Load saved preferences on mount
|
|
useEffect(() => {
|
|
const loadSavedPreferences = () => {
|
|
try {
|
|
const saved = localStorage.getItem(CONSENT_STORAGE_KEY);
|
|
if (saved) {
|
|
const parsed = JSON.parse(saved);
|
|
// Check if saved version matches current version
|
|
if (parsed.version === CONSENT_VERSION) {
|
|
setState({
|
|
hasConsented: true,
|
|
preferences: parsed.preferences,
|
|
showBanner: false,
|
|
showSettings: false,
|
|
consentDate: parsed.consentDate,
|
|
lastUpdated: parsed.lastUpdated,
|
|
auditLog: parsed.auditLog || [],
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
}
|
|
|
|
// Show banner if no valid consent found
|
|
setState(prev => ({ ...prev, showBanner: true }));
|
|
};
|
|
|
|
loadSavedPreferences();
|
|
}, []);
|
|
|
|
// Save preferences to localStorage with audit logging
|
|
const savePreferences = (preferences: CookiePreferences, action: ConsentAuditLog['action'] = 'consent_given') => {
|
|
try {
|
|
const auditEntry = config.enableAuditLog ? createAuditLogEntry(action, preferences) : null;
|
|
|
|
const data = {
|
|
version: CONSENT_VERSION,
|
|
preferences,
|
|
timestamp: new Date().toISOString(),
|
|
consentDate: state.consentDate || new Date().toISOString(),
|
|
lastUpdated: new Date().toISOString(),
|
|
auditLog: auditEntry ? [...state.auditLog, auditEntry] : state.auditLog,
|
|
};
|
|
|
|
localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(data));
|
|
|
|
// Update state with audit log
|
|
if (auditEntry) {
|
|
setState(prev => ({
|
|
...prev,
|
|
auditLog: [...prev.auditLog, auditEntry],
|
|
}));
|
|
}
|
|
} catch (error) {
|
|
}
|
|
};
|
|
|
|
// Accept all cookies
|
|
const acceptAll = () => {
|
|
const allAccepted: CookiePreferences = {
|
|
necessary: true,
|
|
functional: true,
|
|
};
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
hasConsented: true,
|
|
preferences: allAccepted,
|
|
showBanner: false,
|
|
showSettings: false,
|
|
consentDate: prev.consentDate || new Date().toISOString(),
|
|
lastUpdated: new Date().toISOString(),
|
|
}));
|
|
|
|
savePreferences(allAccepted, 'consent_given');
|
|
};
|
|
|
|
// Accept only necessary cookies
|
|
const acceptNecessary = () => {
|
|
const necessaryOnly: CookiePreferences = {
|
|
necessary: true,
|
|
functional: false,
|
|
};
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
hasConsented: true,
|
|
preferences: necessaryOnly,
|
|
showBanner: false,
|
|
showSettings: false,
|
|
consentDate: prev.consentDate || new Date().toISOString(),
|
|
lastUpdated: new Date().toISOString(),
|
|
}));
|
|
|
|
savePreferences(necessaryOnly, 'consent_given');
|
|
};
|
|
|
|
// Accept selected cookies
|
|
const acceptSelected = (preferences: Partial<CookiePreferences>) => {
|
|
const newPreferences: CookiePreferences = {
|
|
necessary: true, // Always true
|
|
functional: preferences.functional ?? false,
|
|
};
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
hasConsented: true,
|
|
preferences: newPreferences,
|
|
showBanner: false,
|
|
showSettings: false,
|
|
consentDate: prev.consentDate || new Date().toISOString(),
|
|
lastUpdated: new Date().toISOString(),
|
|
}));
|
|
|
|
savePreferences(newPreferences, 'consent_updated');
|
|
};
|
|
|
|
// Show settings modal
|
|
const showSettings = () => {
|
|
setState(prev => ({ ...prev, showSettings: true }));
|
|
|
|
// Log settings opened
|
|
if (config.enableAuditLog) {
|
|
const auditEntry = createAuditLogEntry('settings_opened', state.preferences);
|
|
setState(prev => ({
|
|
...prev,
|
|
auditLog: [...prev.auditLog, auditEntry],
|
|
}));
|
|
}
|
|
};
|
|
|
|
// Hide settings modal
|
|
const hideSettings = () => {
|
|
setState(prev => ({ ...prev, showSettings: false }));
|
|
};
|
|
|
|
// Update preferences (for settings modal)
|
|
const updatePreferences = (preferences: Partial<CookiePreferences>) => {
|
|
setState(prev => ({
|
|
...prev,
|
|
preferences: {
|
|
...prev.preferences,
|
|
...preferences,
|
|
necessary: true, // Always keep necessary as true
|
|
},
|
|
}));
|
|
};
|
|
|
|
// Reset consent (for testing or user request)
|
|
const resetConsent = () => {
|
|
try {
|
|
localStorage.removeItem(CONSENT_STORAGE_KEY);
|
|
} catch (error) {
|
|
}
|
|
|
|
setState({
|
|
hasConsented: false,
|
|
preferences: defaultPreferences,
|
|
showBanner: true,
|
|
showSettings: false,
|
|
auditLog: [],
|
|
});
|
|
};
|
|
|
|
// Withdraw consent (GDPR compliance)
|
|
const withdrawConsent = () => {
|
|
try {
|
|
localStorage.removeItem(CONSENT_STORAGE_KEY);
|
|
} catch (error) {
|
|
}
|
|
|
|
const auditEntry = config.enableAuditLog ? createAuditLogEntry('consent_withdrawn', defaultPreferences) : null;
|
|
|
|
setState({
|
|
hasConsented: false,
|
|
preferences: defaultPreferences,
|
|
showBanner: true,
|
|
showSettings: false,
|
|
auditLog: auditEntry ? [...state.auditLog, auditEntry] : state.auditLog,
|
|
});
|
|
};
|
|
|
|
// Export consent data (GDPR compliance)
|
|
const exportConsentData = (): string => {
|
|
const exportData = {
|
|
consentData: {
|
|
hasConsented: state.hasConsented,
|
|
preferences: state.preferences,
|
|
consentDate: state.consentDate,
|
|
lastUpdated: state.lastUpdated,
|
|
auditLog: state.auditLog,
|
|
},
|
|
config: {
|
|
version: config.version,
|
|
companyName: config.companyName,
|
|
retentionPeriod: config.retentionPeriod,
|
|
},
|
|
exportDate: new Date().toISOString(),
|
|
};
|
|
|
|
return JSON.stringify(exportData, null, 2);
|
|
};
|
|
|
|
// Get consent summary
|
|
const getConsentSummary = () => {
|
|
return {
|
|
hasConsented: state.hasConsented,
|
|
preferences: state.preferences,
|
|
consentDate: state.consentDate,
|
|
lastUpdated: state.lastUpdated,
|
|
auditLogCount: state.auditLog.length,
|
|
config: {
|
|
version: config.version,
|
|
companyName: config.companyName,
|
|
retentionPeriod: config.retentionPeriod,
|
|
},
|
|
};
|
|
};
|
|
|
|
const contextValue: CookieConsentContextType = {
|
|
state,
|
|
config,
|
|
acceptAll,
|
|
acceptNecessary,
|
|
acceptSelected,
|
|
showSettings,
|
|
hideSettings,
|
|
updatePreferences,
|
|
resetConsent,
|
|
withdrawConsent,
|
|
exportConsentData,
|
|
getConsentSummary,
|
|
};
|
|
|
|
return (
|
|
<CookieConsentContext.Provider value={contextValue}>
|
|
{children}
|
|
</CookieConsentContext.Provider>
|
|
);
|
|
};
|
|
|
|
// Hook to use cookie consent context
|
|
export const useCookieConsent = (): CookieConsentContextType => {
|
|
const context = useContext(CookieConsentContext);
|
|
if (context === undefined) {
|
|
throw new Error('useCookieConsent must be used within a CookieConsentProvider');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
// Hook to check if specific cookie type is allowed
|
|
export const useCookiePermission = (type: keyof CookiePreferences): boolean => {
|
|
const { state } = useCookieConsent();
|
|
return state.preferences[type];
|
|
};
|
|
|
|
// Hook for functional features (only runs if functional cookies are allowed)
|
|
export const useFunctional = () => {
|
|
const { state } = useCookieConsent();
|
|
const isEnabled = state.preferences.functional && state.hasConsented;
|
|
|
|
const saveUserPreference = (key: string, value: any) => {
|
|
if (isEnabled && typeof window !== 'undefined') {
|
|
try {
|
|
localStorage.setItem(`user-preference-${key}`, JSON.stringify(value));
|
|
} catch (error) {
|
|
}
|
|
}
|
|
};
|
|
|
|
const loadUserPreference = (key: string, defaultValue?: any) => {
|
|
if (isEnabled && typeof window !== 'undefined') {
|
|
try {
|
|
const saved = localStorage.getItem(`user-preference-${key}`);
|
|
return saved ? JSON.parse(saved) : defaultValue;
|
|
} catch (error) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
return defaultValue;
|
|
};
|
|
|
|
const rememberUserAction = (action: string, data?: any) => {
|
|
if (isEnabled) {
|
|
// Implement your user action tracking logic here
|
|
}
|
|
};
|
|
|
|
return {
|
|
saveUserPreference,
|
|
loadUserPreference,
|
|
rememberUserAction,
|
|
isEnabled,
|
|
};
|
|
};
|