sync: Auto-sync from Mikes-MacBook-Air.local at 2026-03-09 08:14:13
Synced files: - Session logs updated - Latest context and credentials - Command/directive updates Machine: Mikes-MacBook-Air.local Timestamp: 2026-03-09 08:14:13 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
160
projects/msp-tools/quote-wizard/frontend/src/hooks/useWizard.ts
Normal file
160
projects/msp-tools/quote-wizard/frontend/src/hooks/useWizard.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import type { WizardStep } from '@/types/quote';
|
||||
|
||||
/**
|
||||
* Wizard steps configuration for the 7-step MSP Quote Wizard
|
||||
*/
|
||||
const WIZARD_STEPS: Omit<WizardStep, 'isComplete' | 'isActive'>[] = [
|
||||
{
|
||||
id: 'company',
|
||||
title: 'Company Profile',
|
||||
description: 'Tell us about your business',
|
||||
},
|
||||
{
|
||||
id: 'gps',
|
||||
title: 'GPS Monitoring',
|
||||
description: 'Select your monitoring tier',
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
title: 'Support Plan',
|
||||
description: 'Choose your support level',
|
||||
},
|
||||
{
|
||||
id: 'voip',
|
||||
title: 'VoIP Phone System',
|
||||
description: 'Business phone options',
|
||||
},
|
||||
{
|
||||
id: 'web-email',
|
||||
title: 'Web & Email',
|
||||
description: 'Hosting and email services',
|
||||
},
|
||||
{
|
||||
id: 'summary',
|
||||
title: 'Review Quote',
|
||||
description: 'Review your selections',
|
||||
},
|
||||
{
|
||||
id: 'contact',
|
||||
title: 'Get Your Quote',
|
||||
description: 'Submit your information',
|
||||
},
|
||||
];
|
||||
|
||||
export interface UseWizardReturn {
|
||||
currentStep: number;
|
||||
steps: WizardStep[];
|
||||
totalSteps: number;
|
||||
isFirstStep: boolean;
|
||||
isLastStep: boolean;
|
||||
goToStep: (step: number) => void;
|
||||
nextStep: () => void;
|
||||
prevStep: () => void;
|
||||
markStepComplete: (stepIndex: number) => void;
|
||||
markStepIncomplete: (stepIndex: number) => void;
|
||||
resetWizard: () => void;
|
||||
progress: number;
|
||||
canProceed: boolean;
|
||||
setCanProceed: (canProceed: boolean) => void;
|
||||
currentStepId: string;
|
||||
getStepByIndex: (index: number) => WizardStep | undefined;
|
||||
}
|
||||
|
||||
export function useWizard(): UseWizardReturn {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [completedSteps, setCompletedSteps] = useState<Set<number>>(new Set());
|
||||
const [canProceed, setCanProceed] = useState(true);
|
||||
|
||||
const totalSteps = WIZARD_STEPS.length;
|
||||
const isFirstStep = currentStep === 0;
|
||||
const isLastStep = currentStep === totalSteps - 1;
|
||||
|
||||
const steps: WizardStep[] = useMemo(() => {
|
||||
return WIZARD_STEPS.map((step, index) => ({
|
||||
...step,
|
||||
isComplete: completedSteps.has(index),
|
||||
isActive: index === currentStep,
|
||||
}));
|
||||
}, [currentStep, completedSteps]);
|
||||
|
||||
const currentStepId = useMemo(() => {
|
||||
return WIZARD_STEPS[currentStep]?.id || '';
|
||||
}, [currentStep]);
|
||||
|
||||
const progress = useMemo(() => {
|
||||
// Progress based on current step position (0 to 100)
|
||||
return Math.round((currentStep / (totalSteps - 1)) * 100);
|
||||
}, [currentStep, totalSteps]);
|
||||
|
||||
const goToStep = useCallback(
|
||||
(step: number) => {
|
||||
if (step >= 0 && step < totalSteps) {
|
||||
// Allow going back to any previous step
|
||||
// Only allow going forward to completed steps or the next step
|
||||
if (step <= currentStep || completedSteps.has(step - 1) || step === currentStep + 1) {
|
||||
setCurrentStep(step);
|
||||
}
|
||||
}
|
||||
},
|
||||
[totalSteps, currentStep, completedSteps]
|
||||
);
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
if (!isLastStep && canProceed) {
|
||||
// Mark current step as complete when moving forward
|
||||
setCompletedSteps((prev) => new Set(prev).add(currentStep));
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
}
|
||||
}, [currentStep, isLastStep, canProceed]);
|
||||
|
||||
const prevStep = useCallback(() => {
|
||||
if (!isFirstStep) {
|
||||
setCurrentStep((prev) => prev - 1);
|
||||
}
|
||||
}, [isFirstStep]);
|
||||
|
||||
const markStepComplete = useCallback((stepIndex: number) => {
|
||||
setCompletedSteps((prev) => new Set(prev).add(stepIndex));
|
||||
}, []);
|
||||
|
||||
const markStepIncomplete = useCallback((stepIndex: number) => {
|
||||
setCompletedSteps((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(stepIndex);
|
||||
return newSet;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resetWizard = useCallback(() => {
|
||||
setCurrentStep(0);
|
||||
setCompletedSteps(new Set());
|
||||
setCanProceed(true);
|
||||
}, []);
|
||||
|
||||
const getStepByIndex = useCallback(
|
||||
(index: number): WizardStep | undefined => {
|
||||
return steps[index];
|
||||
},
|
||||
[steps]
|
||||
);
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
steps,
|
||||
totalSteps,
|
||||
isFirstStep,
|
||||
isLastStep,
|
||||
goToStep,
|
||||
nextStep,
|
||||
prevStep,
|
||||
markStepComplete,
|
||||
markStepIncomplete,
|
||||
resetWizard,
|
||||
progress,
|
||||
canProceed,
|
||||
setCanProceed,
|
||||
currentStepId,
|
||||
getStepByIndex,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user