import React from 'react'; import { fetchOnboardingStatus, trackOnboarding } from '../api'; import { useAuth } from '../auth/context'; export type OnboardingProgress = { welcomeSeen: boolean; packageSelected: boolean; eventCreated: boolean; lastStep?: string | null; adminAppOpenedAt?: string | null; selectedPackage?: { id: number; name: string; priceText?: string | null; isSubscription?: boolean; } | null; inviteCreated: boolean; brandingConfigured: boolean; }; type OnboardingUpdate = Partial & { serverStep?: string; meta?: Record; }; type OnboardingContextValue = { progress: OnboardingProgress; setProgress: (updater: (prev: OnboardingProgress) => OnboardingProgress) => void; markStep: (step: OnboardingUpdate) => void; reset: () => void; }; const DEFAULT_PROGRESS: OnboardingProgress = { welcomeSeen: false, packageSelected: false, eventCreated: false, lastStep: null, adminAppOpenedAt: null, selectedPackage: null, inviteCreated: false, brandingConfigured: false, }; const STORAGE_KEY = 'tenant-admin:onboarding-progress'; const OnboardingProgressContext = React.createContext(undefined); function readStoredProgress(): OnboardingProgress { if (typeof window === 'undefined') { return DEFAULT_PROGRESS; } try { const raw = window.localStorage.getItem(STORAGE_KEY); if (!raw) { return DEFAULT_PROGRESS; } const parsed = JSON.parse(raw) as Partial; return { ...DEFAULT_PROGRESS, ...parsed, }; } catch (error) { console.warn('[OnboardingProgress] Failed to parse stored value', error); return DEFAULT_PROGRESS; } } function writeStoredProgress(progress: OnboardingProgress) { if (typeof window === 'undefined') { return; } try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(progress)); } catch (error) { console.warn('[OnboardingProgress] Failed to persist value', error); } } export function OnboardingProgressProvider({ children }: { children: React.ReactNode }) { const [progress, setProgressState] = React.useState(() => readStoredProgress()); const [synced, setSynced] = React.useState(false); const { status } = useAuth(); React.useEffect(() => { if (status !== 'authenticated') { if (synced) { setSynced(false); } return; } if (synced) { return; } let cancelled = false; fetchOnboardingStatus().then((status) => { if (cancelled) { return; } if (!status) { setSynced(true); return; } setProgressState((prev) => { const next: OnboardingProgress = { ...prev, adminAppOpenedAt: status.steps.admin_app_opened_at ?? prev.adminAppOpenedAt ?? null, eventCreated: Boolean(status.steps.event_created ?? prev.eventCreated), packageSelected: Boolean(status.steps.selected_packages ?? prev.packageSelected), inviteCreated: Boolean(status.steps.invite_created ?? prev.inviteCreated), brandingConfigured: Boolean(status.steps.branding_completed ?? prev.brandingConfigured), }; writeStoredProgress(next); return next; }); if (!status.steps.admin_app_opened_at) { const timestamp = new Date().toISOString(); trackOnboarding('admin_app_opened').catch(() => {}); setProgressState((prev) => { const next = { ...prev, adminAppOpenedAt: timestamp }; writeStoredProgress(next); return next; }); } setSynced(true); }).catch(() => { if (!cancelled) { setSynced(true); } }); return () => { cancelled = true; }; }, [status, synced]); const setProgress = React.useCallback((updater: (prev: OnboardingProgress) => OnboardingProgress) => { setProgressState((prev) => { const next = updater(prev); writeStoredProgress(next); return next; }); }, []); const markStep = React.useCallback((step: OnboardingUpdate) => { const { serverStep, meta, ...rest } = step; setProgress((prev) => { const derived: Partial = {}; switch (serverStep) { case 'package_selected': derived.packageSelected = true; break; case 'event_created': derived.eventCreated = true; break; case 'invite_created': derived.inviteCreated = true; break; case 'branding_configured': derived.brandingConfigured = true; break; default: break; } const next: OnboardingProgress = { ...prev, ...rest, ...derived, lastStep: typeof rest.lastStep === 'undefined' ? prev.lastStep : rest.lastStep, }; if (serverStep === 'admin_app_opened' && !next.adminAppOpenedAt) { next.adminAppOpenedAt = new Date().toISOString(); } return next; }); if (serverStep) { trackOnboarding(serverStep, meta).catch(() => {}); } }, [setProgress]); const reset = React.useCallback(() => { setProgress(() => DEFAULT_PROGRESS); }, [setProgress]); const value = React.useMemo(() => ({ progress, setProgress, markStep, reset, }), [progress, setProgress, markStep, reset]); return ( {children} ); } export function useOnboardingProgress() { const context = React.useContext(OnboardingProgressContext); if (!context) { throw new Error('useOnboardingProgress must be used within OnboardingProgressProvider'); } return context; }