import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react'; import type { CheckoutPackage, CheckoutStepId } from './types'; interface CheckoutState { currentStep: CheckoutStepId; selectedPackage: CheckoutPackage | null; packageOptions: CheckoutPackage[]; authUser: unknown; isAuthenticated: boolean; paymentIntent: string | null; loading: boolean; error: string | null; paymentCompleted: boolean; checkoutSessionId: string | null; } interface CheckoutWizardContextType { state: CheckoutState; selectedPackage: CheckoutPackage | null; packageOptions: CheckoutPackage[]; currentStep: CheckoutStepId; isAuthenticated: boolean; authUser: unknown; paddleConfig?: { environment?: string | null; client_token?: string | null; } | null; paymentCompleted: boolean; checkoutSessionId: string | null; selectPackage: (pkg: CheckoutPackage) => void; setSelectedPackage: (pkg: CheckoutPackage) => void; setAuthUser: (user: unknown) => void; nextStep: () => void; prevStep: () => void; previousStep: () => void; goToStep: (step: CheckoutStepId) => void; cancelCheckout: () => void; updatePaymentIntent: (clientSecret: string | null) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; resetPaymentState: () => void; setPaymentCompleted: (completed: boolean) => void; setCheckoutSessionId: (sessionId: string | null) => void; clearCheckoutSessionId: () => void; } const CheckoutWizardContext = createContext(null); const initialState: CheckoutState = { currentStep: 'package', selectedPackage: null, packageOptions: [], authUser: null, isAuthenticated: false, paymentIntent: null, loading: false, error: null, paymentCompleted: false, checkoutSessionId: null, }; type CheckoutAction = | { type: 'SELECT_PACKAGE'; payload: CheckoutPackage } | { type: 'SET_AUTH_USER'; payload: unknown } | { type: 'NEXT_STEP' } | { type: 'PREV_STEP' } | { type: 'GO_TO_STEP'; payload: CheckoutStepId } | { type: 'UPDATE_PAYMENT_INTENT'; payload: string | null } | { type: 'SET_LOADING'; payload: boolean } | { type: 'SET_ERROR'; payload: string | null } | { type: 'SET_PAYMENT_COMPLETED'; payload: boolean } | { type: 'SET_CHECKOUT_SESSION_ID'; payload: string | null }; function checkoutReducer(state: CheckoutState, action: CheckoutAction): CheckoutState { switch (action.type) { case 'SELECT_PACKAGE': return { ...state, selectedPackage: action.payload, paymentCompleted: false }; case 'SET_AUTH_USER': return { ...state, authUser: action.payload, isAuthenticated: Boolean(action.payload) }; case 'NEXT_STEP': { const steps: CheckoutStepId[] = ['package', 'auth', 'payment', 'confirmation']; const currentIndex = steps.indexOf(state.currentStep); if (currentIndex < steps.length - 1) { return { ...state, currentStep: steps[currentIndex + 1] }; } return state; } case 'PREV_STEP': { const prevSteps: CheckoutStepId[] = ['package', 'auth', 'payment', 'confirmation']; const prevIndex = prevSteps.indexOf(state.currentStep); if (prevIndex > 0) { return { ...state, currentStep: prevSteps[prevIndex - 1] }; } return state; } case 'GO_TO_STEP': return { ...state, currentStep: action.payload }; case 'UPDATE_PAYMENT_INTENT': return { ...state, paymentIntent: action.payload }; case 'SET_LOADING': return { ...state, loading: action.payload }; case 'SET_ERROR': return { ...state, error: action.payload }; case 'SET_PAYMENT_COMPLETED': return { ...state, paymentCompleted: action.payload }; case 'SET_CHECKOUT_SESSION_ID': return { ...state, checkoutSessionId: action.payload }; default: return state; } } interface CheckoutWizardProviderProps { children: React.ReactNode; initialPackage?: CheckoutPackage; packageOptions?: CheckoutPackage[]; initialStep?: CheckoutStepId; initialAuthUser?: unknown; initialIsAuthenticated?: boolean; paddle?: { environment?: string | null; client_token?: string | null; } | null; } export function CheckoutWizardProvider({ children, initialPackage, packageOptions, initialStep, initialAuthUser, initialIsAuthenticated, paddle, }: CheckoutWizardProviderProps) { const customInitialState: CheckoutState = { ...initialState, currentStep: initialStep || 'package', selectedPackage: initialPackage || null, packageOptions: packageOptions || [], authUser: initialAuthUser || null, isAuthenticated: initialIsAuthenticated || Boolean(initialAuthUser), }; const checkoutSessionStorageKey = 'checkout-session-id'; const [state, dispatch] = useReducer(checkoutReducer, customInitialState); // Load state from localStorage on mount useEffect(() => { const savedState = localStorage.getItem('checkout-wizard-state'); if (savedState) { try { const parsed = JSON.parse(savedState); const hasValidPackage = parsed.selectedPackage && typeof parsed.selectedPackage.paddle_price_id === 'string' && parsed.selectedPackage.paddle_price_id !== ''; if (hasValidPackage && initialPackage && parsed.selectedPackage.id === initialPackage.id && parsed.currentStep !== 'confirmation') { // Restore state selectively if (parsed.selectedPackage) dispatch({ type: 'SELECT_PACKAGE', payload: parsed.selectedPackage }); if (parsed.currentStep) dispatch({ type: 'GO_TO_STEP', payload: parsed.currentStep }); } else { localStorage.removeItem('checkout-wizard-state'); } } catch (error) { console.error('Failed to restore checkout state:', error); } } const storedSession = localStorage.getItem(checkoutSessionStorageKey); if (storedSession) { dispatch({ type: 'SET_CHECKOUT_SESSION_ID', payload: storedSession }); } }, [initialPackage]); // Save state to localStorage whenever it changes useEffect(() => { localStorage.setItem('checkout-wizard-state', JSON.stringify({ selectedPackage: state.selectedPackage, currentStep: state.currentStep, })); }, [state.selectedPackage, state.currentStep]); // Clear localStorage when confirmation step is reached useEffect(() => { if (state.currentStep === 'confirmation') { localStorage.removeItem('checkout-wizard-state'); } }, [state.currentStep]); useEffect(() => { if (state.checkoutSessionId) { localStorage.setItem(checkoutSessionStorageKey, state.checkoutSessionId); } else { localStorage.removeItem(checkoutSessionStorageKey); } }, [state.checkoutSessionId]); const selectPackage = useCallback((pkg: CheckoutPackage) => { dispatch({ type: 'SELECT_PACKAGE', payload: pkg }); }, []); const setAuthUser = useCallback((user: unknown) => { dispatch({ type: 'SET_AUTH_USER', payload: user }); }, []); const nextStep = useCallback(() => { dispatch({ type: 'NEXT_STEP' }); }, []); const prevStep = useCallback(() => { dispatch({ type: 'PREV_STEP' }); }, []); const goToStep = useCallback((step: CheckoutStepId) => { dispatch({ type: 'GO_TO_STEP', payload: step }); }, []); const updatePaymentIntent = useCallback((clientSecret: string | null) => { dispatch({ type: 'UPDATE_PAYMENT_INTENT', payload: clientSecret }); }, []); const setLoading = useCallback((loading: boolean) => { dispatch({ type: 'SET_LOADING', payload: loading }); }, []); const setError = useCallback((error: string | null) => { dispatch({ type: 'SET_ERROR', payload: error }); }, []); const setSelectedPackage = useCallback((pkg: CheckoutPackage) => { dispatch({ type: 'SELECT_PACKAGE', payload: pkg }); }, []); const resetPaymentState = useCallback(() => { dispatch({ type: 'UPDATE_PAYMENT_INTENT', payload: null }); dispatch({ type: 'SET_LOADING', payload: false }); dispatch({ type: 'SET_ERROR', payload: null }); dispatch({ type: 'SET_PAYMENT_COMPLETED', payload: false }); dispatch({ type: 'SET_CHECKOUT_SESSION_ID', payload: null }); }, []); const setPaymentCompleted = useCallback((completed: boolean) => { dispatch({ type: 'SET_PAYMENT_COMPLETED', payload: completed }); }, []); const setCheckoutSessionId = useCallback((sessionId: string | null) => { dispatch({ type: 'SET_CHECKOUT_SESSION_ID', payload: sessionId }); }, []); const clearCheckoutSessionId = useCallback(() => { dispatch({ type: 'SET_CHECKOUT_SESSION_ID', payload: null }); }, []); const cancelCheckout = useCallback(() => { // Track abandoned checkout (fire and forget) if (state.authUser || state.selectedPackage) { fetch('/checkout/track-abandoned', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, body: JSON.stringify({ package_id: state.selectedPackage?.id, step: state.currentStep, email: state.authUser?.email, }), }).catch(error => { console.error('Failed to track abandoned checkout:', error); }); } // State aus localStorage entfernen localStorage.removeItem('checkout-wizard-state'); localStorage.removeItem(checkoutSessionStorageKey); // Zur Package-Übersicht zurückleiten window.location.href = '/packages'; }, [state, checkoutSessionStorageKey]); const value: CheckoutWizardContextType = { state, selectedPackage: state.selectedPackage, packageOptions: state.packageOptions, currentStep: state.currentStep, isAuthenticated: state.isAuthenticated, authUser: state.authUser, paddleConfig: paddle ?? null, paymentCompleted: state.paymentCompleted, checkoutSessionId: state.checkoutSessionId, selectPackage, setSelectedPackage, setAuthUser, nextStep, prevStep, previousStep: prevStep, goToStep, cancelCheckout, updatePaymentIntent, setLoading, setError, resetPaymentState, setPaymentCompleted, setCheckoutSessionId, clearCheckoutSessionId, }; return ( {children} ); } export function useCheckoutWizard(): CheckoutWizardContextType { const context = useContext(CheckoutWizardContext); if (!context) { throw new Error('useCheckoutWizard must be used within a CheckoutWizardProvider'); } return context; }