"use client"; import { createContext, type ReactNode, useCallback, useContext, useMemo, } from "react"; import { useLocation, useNavigate } from "react-router-dom"; // ============================================================================= // TYPES // ============================================================================= export interface OnboardingStepConfig { /** URL path segment for this step (e.g., 'business-function') */ path: string; /** Display name for progress indicator */ name: string; /** * The component to render for this step. * Use the `useOnboarding()` hook inside the component to access navigation helpers. */ component: React.ComponentType; } export interface OnboardingStepProps { /** Navigate to next step */ goNext: () => void; /** Navigate to previous step */ goBack: () => void; /** Navigate to a specific step by path */ goTo: (path: string) => void; /** Current step index (0-based) */ currentStep: number; /** Total number of steps */ totalSteps: number; /** Whether this is the first step */ isFirst: boolean; /** Whether this is the last step */ isLast: boolean; /** Current step path */ currentPath: string; } export interface OnboardingContextValue extends OnboardingStepProps { /** All step configurations */ steps: OnboardingStepConfig[]; /** Base path for the wizard */ basePath: string; } export interface OnboardingWizardConfig { /** Base path for the onboarding wizard (e.g., '/onboarding') */ basePath?: string; /** Step configurations */ steps: OnboardingStepConfig[]; /** Callback when wizard completes (navigates past last step) */ onComplete?: () => void; /** Callback when user exits/cancels */ onExit?: () => void; } // ============================================================================= // CONTEXT // ============================================================================= const OnboardingContext = createContext(null); export function useOnboarding(): OnboardingContextValue { const context = useContext(OnboardingContext); if (!context) { throw new Error("useOnboarding must be used within an OnboardingProvider"); } return context; } export function useOnboardingOptional(): OnboardingContextValue | null { return useContext(OnboardingContext); } // ============================================================================= // PROVIDER // ============================================================================= interface OnboardingProviderProps { children: ReactNode; config: OnboardingWizardConfig; } export function OnboardingProvider({ children, config, }: OnboardingProviderProps) { const { steps, basePath = "", onComplete } = config; const navigate = useNavigate(); const location = useLocation(); // Determine current step from URL const currentPath = location.pathname.replace(/^\/+/, "").replace(/\/+$/, ""); const currentStep = useMemo(() => { const index = steps.findIndex((step) => step.path === currentPath); return index >= 0 ? index : 0; }, [steps, currentPath]); const totalSteps = steps.length; const isFirst = currentStep === 0; const isLast = currentStep === totalSteps - 1; const goTo = useCallback( (path: string) => { navigate(`/${path}`); }, [navigate], ); const goNext = useCallback(() => { if (isLast) { onComplete?.(); } else { const nextStep = steps[currentStep + 1]; if (nextStep) { goTo(nextStep.path); } } }, [currentStep, isLast, steps, goTo, onComplete]); const goBack = useCallback(() => { if (!isFirst) { const prevStep = steps[currentStep - 1]; if (prevStep) { goTo(prevStep.path); } } }, [currentStep, isFirst, steps, goTo]); const value = useMemo( () => ({ steps, basePath, currentStep, totalSteps, isFirst, isLast, currentPath, goNext, goBack, goTo, }), [ steps, basePath, currentStep, totalSteps, isFirst, isLast, currentPath, goNext, goBack, goTo, ], ); return ( {children} ); }