- Wired the checkout wizard for Google “comfort login”: added Socialite controller + dependency, new Google env
hooks in config/services.php/.env.example, and updated wizard steps/controllers to store session payloads, attach packages, and surface localized success/error states. - Retooled payment handling for both Stripe and PayPal, adding richer status management in CheckoutController/ PayPalController, fallback flows in the wizard’s PaymentStep.tsx, and fresh feature tests for intent creation, webhooks, and the wizard CTA. - Introduced a consent-aware Matomo analytics stack: new consent context, cookie-banner UI, useAnalytics/ useCtaExperiment hooks, and MatomoTracker component, then instrumented marketing pages (Home, Packages, Checkout) with localized copy and experiment tracking. - Polished package presentation across marketing UIs by centralizing formatting in PresentsPackages, surfacing localized description tables/placeholders, tuning badges/layouts, and syncing guest/marketing translations. - Expanded docs & reference material (docs/prp/*, TODOs, public gallery overview) and added a Playwright smoke test for the hero CTA while reconciling outstanding checklist items.
This commit is contained in:
185
resources/js/contexts/consent.tsx
Normal file
185
resources/js/contexts/consent.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
const CONSENT_STORAGE_KEY = 'fotospiel.consent';
|
||||
const CONSENT_VERSION = '2025-10-17-1';
|
||||
|
||||
export type ConsentCategory = 'functional' | 'analytics';
|
||||
|
||||
export type ConsentPreferences = Record<ConsentCategory, boolean>;
|
||||
|
||||
interface StoredConsent {
|
||||
version: string;
|
||||
preferences: ConsentPreferences;
|
||||
decisionMade: boolean;
|
||||
updatedAt: string | null;
|
||||
}
|
||||
|
||||
const defaultPreferences: ConsentPreferences = {
|
||||
functional: true,
|
||||
analytics: false,
|
||||
};
|
||||
|
||||
const defaultState: StoredConsent = {
|
||||
version: CONSENT_VERSION,
|
||||
preferences: { ...defaultPreferences },
|
||||
decisionMade: false,
|
||||
updatedAt: null,
|
||||
};
|
||||
|
||||
interface ConsentContextValue {
|
||||
preferences: ConsentPreferences;
|
||||
decisionMade: boolean;
|
||||
showBanner: boolean;
|
||||
acceptAll: () => void;
|
||||
rejectAll: () => void;
|
||||
savePreferences: (preferences: Partial<ConsentPreferences>) => void;
|
||||
hasConsent: (category: ConsentCategory) => boolean;
|
||||
openPreferences: () => void;
|
||||
closePreferences: () => void;
|
||||
isPreferencesOpen: boolean;
|
||||
}
|
||||
|
||||
const ConsentContext = createContext<ConsentContextValue | undefined>(undefined);
|
||||
|
||||
function normalizeState(state: StoredConsent | null): StoredConsent {
|
||||
if (!state || state.version !== CONSENT_VERSION) {
|
||||
return { ...defaultState };
|
||||
}
|
||||
|
||||
return {
|
||||
version: CONSENT_VERSION,
|
||||
decisionMade: state.decisionMade ?? false,
|
||||
updatedAt: state.updatedAt ?? null,
|
||||
preferences: {
|
||||
...defaultPreferences,
|
||||
...state.preferences,
|
||||
functional: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getInitialState(): StoredConsent {
|
||||
if (typeof window === 'undefined') {
|
||||
return { ...defaultState };
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = window.localStorage.getItem(CONSENT_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return { ...defaultState };
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw) as StoredConsent;
|
||||
return normalizeState(parsed);
|
||||
} catch {
|
||||
return { ...defaultState };
|
||||
}
|
||||
}
|
||||
|
||||
export const ConsentProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [state, setState] = useState<StoredConsent>(() => getInitialState());
|
||||
const [isPreferencesOpen, setPreferencesOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(state));
|
||||
}, [state]);
|
||||
|
||||
const acceptAll = useCallback(() => {
|
||||
setState({
|
||||
version: CONSENT_VERSION,
|
||||
preferences: { functional: true, analytics: true },
|
||||
decisionMade: true,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
setPreferencesOpen(false);
|
||||
}, []);
|
||||
|
||||
const rejectAll = useCallback(() => {
|
||||
setState({
|
||||
version: CONSENT_VERSION,
|
||||
preferences: { functional: true, analytics: false },
|
||||
decisionMade: true,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
setPreferencesOpen(false);
|
||||
}, []);
|
||||
|
||||
const savePreferences = useCallback((preferences: Partial<ConsentPreferences>) => {
|
||||
setState((prev) => ({
|
||||
version: CONSENT_VERSION,
|
||||
preferences: {
|
||||
...defaultPreferences,
|
||||
...prev.preferences,
|
||||
...preferences,
|
||||
functional: true,
|
||||
},
|
||||
decisionMade: true,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}));
|
||||
setPreferencesOpen(false);
|
||||
}, []);
|
||||
|
||||
const hasConsent = useCallback(
|
||||
(category: ConsentCategory) => {
|
||||
return Boolean(state.preferences?.[category]);
|
||||
},
|
||||
[state.preferences],
|
||||
);
|
||||
|
||||
const openPreferences = useCallback(() => {
|
||||
setPreferencesOpen(true);
|
||||
}, []);
|
||||
|
||||
const closePreferences = useCallback(() => {
|
||||
setPreferencesOpen(false);
|
||||
}, []);
|
||||
|
||||
const value = useMemo<ConsentContextValue>(
|
||||
() => ({
|
||||
preferences: state.preferences,
|
||||
decisionMade: state.decisionMade,
|
||||
showBanner: !state.decisionMade,
|
||||
acceptAll,
|
||||
rejectAll,
|
||||
savePreferences,
|
||||
hasConsent,
|
||||
openPreferences,
|
||||
closePreferences,
|
||||
isPreferencesOpen,
|
||||
}),
|
||||
[
|
||||
state.preferences,
|
||||
state.decisionMade,
|
||||
acceptAll,
|
||||
rejectAll,
|
||||
savePreferences,
|
||||
hasConsent,
|
||||
openPreferences,
|
||||
closePreferences,
|
||||
isPreferencesOpen,
|
||||
],
|
||||
);
|
||||
|
||||
return <ConsentContext.Provider value={value}>{children}</ConsentContext.Provider>;
|
||||
};
|
||||
|
||||
export const useConsent = (): ConsentContextValue => {
|
||||
const context = useContext(ConsentContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useConsent must be used within a ConsentProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user