import React, { useEffect, useState, useCallback } from 'react'; import { Box, Stepper, Step, StepLabel, Button, Typography, Paper, LinearProgress, Fade, Slide, useTheme, useMediaQuery, IconButton, Tooltip, Container } from '@mui/material'; import { ArrowBack, ArrowForward, CheckCircle, HelpOutline, Close } from '@mui/icons-material'; import UserBadge from '../shared/UserBadge'; import { startOnboarding, getCurrentStep, setCurrentStep, getProgress } from '../../api/onboarding'; import { apiClient } from '../../api/client'; import ApiKeyStep from './ApiKeyStep'; import WebsiteStep from './WebsiteStep'; import CompetitorAnalysisStep from './CompetitorAnalysisStep'; import PersonalizationStep from './PersonalizationStep'; import IntegrationsStep from './IntegrationsStep'; import FinalStep from './FinalStep'; const steps = [ { label: 'API Keys', description: 'Connect your AI services', icon: '🔑' }, { label: 'Website', description: 'Set up your website', icon: '🌐' }, { label: 'Research', description: 'Discover competitors', icon: '🔍' }, { label: 'Personalization', description: 'Customize your experience', icon: '⚙️' }, { label: 'Integrations', description: 'Connect additional services', icon: '🔗' }, { label: 'Finish', description: 'Complete setup', icon: '✅' } ]; interface WizardProps { onComplete?: () => void; } interface StepHeaderContent { title: string; description: string; } const Wizard: React.FC = ({ onComplete }) => { const [activeStep, setActiveStep] = useState(0); const [loading, setLoading] = useState(true); const [progress, setProgressState] = useState(0); const [direction, setDirection] = useState<'left' | 'right'>('right'); const [showHelp, setShowHelp] = useState(false); const [showProgressMessage, setShowProgressMessage] = useState(false); const [progressMessage, setProgressMessage] = useState(''); // sessionId removed - backend uses Clerk user ID from auth token const [stepData, setStepData] = useState(null); const [stepHeaderContent, setStepHeaderContent] = useState({ title: steps[0].label, description: steps[0].description }); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); useEffect(() => { console.log('Wizard: Component mounted'); const init = async () => { try { setLoading(true); console.log('Wizard: Starting initialization...'); // Check if we already have init data from App (cached in sessionStorage) const cachedInit = sessionStorage.getItem('onboarding_init'); if (cachedInit) { console.log('Wizard: Using cached init data from batch endpoint'); const data = JSON.parse(cachedInit); // Extract data from batch response const { user, onboarding, session } = data; // Set state from cached data - NO API CALLS NEEDED! setActiveStep(onboarding.current_step - 1); setProgressState(onboarding.completion_percentage); // Note: Session managed by Clerk auth, no need to track separately console.log('Wizard: Initialized from cache:', { step: onboarding.current_step, progress: onboarding.completion_percentage, userId: session.session_id // Clerk user ID from backend }); setLoading(false); return; // ← Skip redundant API calls! } // Fallback: If no cached data (shouldn't happen), make batch call console.log('Wizard: No cached data, making batch init call'); const response = await apiClient.get('/api/onboarding/init'); const { user, onboarding, session } = response.data; // Cache for future use sessionStorage.setItem('onboarding_init', JSON.stringify(response.data)); // Set state from API response setActiveStep(onboarding.current_step - 1); setProgressState(onboarding.completion_percentage); // Note: Session managed by Clerk auth, no need to track separately console.log('Wizard: Initialized from API:', { step: onboarding.current_step, progress: onboarding.completion_percentage, userId: session.session_id // Clerk user ID from backend }); } catch (error) { console.error('Error initializing onboarding:', error); } finally { setLoading(false); } }; init(); }, []); const handleNext = async (rawStepData?: any) => { if (rawStepData && typeof rawStepData === 'object') { if (typeof rawStepData.preventDefault === 'function') { rawStepData.preventDefault(); } if (typeof rawStepData.stopPropagation === 'function') { rawStepData.stopPropagation(); } } const currentStepData = rawStepData && typeof rawStepData === 'object' && 'nativeEvent' in rawStepData ? undefined : rawStepData; // Store step data in state if (currentStepData) { setStepData(currentStepData); } console.log('Wizard: handleNext called with stepData:', currentStepData); console.log('Wizard: Current activeStep:', activeStep); console.log('Wizard: Steps length:', steps.length); setDirection('right'); const nextStep = activeStep + 1; console.log('Wizard: Next step will be:', nextStep); // Show progress message const newProgress = ((nextStep + 1) / steps.length) * 100; setProgressMessage(`Your data is saved, moving to the next step. Progress is ${Math.round(newProgress)}%`); setShowProgressMessage(true); // Hide message after 3 seconds setTimeout(() => { setShowProgressMessage(false); }, 3000); // Complete the current step (activeStep + 1 because steps are 1-indexed) const currentStepNumber = activeStep + 1; const stepWasCompleted = currentStepData && typeof currentStepData === 'object' && (currentStepData.website || currentStepData.businessData); if (!stepWasCompleted) { console.warn('Wizard: No serialized step data supplied; skipping backend completion for step', currentStepNumber); } else { console.log('Wizard: Completing current step:', currentStepNumber, 'with data:', currentStepData); try { await setCurrentStep(currentStepNumber, currentStepData); } catch (error) { console.error('Wizard: Failed to complete step with backend. Aborting progression.', error); setShowProgressMessage(false); setProgressMessage(''); setLoading(false); return; } console.log('Wizard: Checking backend step after completion...'); const stepResponse = await getCurrentStep(); console.log('Wizard: Backend says current step should be:', stepResponse.step); } setActiveStep(nextStep); console.log('Wizard: Setting activeStep to:', nextStep); // Update progress setProgressState(newProgress); // If this is the final step, call onComplete if (nextStep === steps.length - 1) { console.log('Wizard: This is the final step, calling onComplete'); onComplete?.(); } else { console.log('Wizard: Not the final step, continuing to next step'); } }; const handleBack = async () => { setDirection('left'); const prevStep = activeStep - 1; setActiveStep(prevStep); // Do not complete a step when navigating back; just update UI state // Backend step progression should only occur on forward completion with valid data // Update progress const newProgress = ((prevStep + 1) / steps.length) * 100; setProgressState(newProgress); }; const handleStepClick = (stepIndex: number) => { if (stepIndex <= activeStep) { setDirection(stepIndex > activeStep ? 'right' : 'left'); setActiveStep(stepIndex); // Do not complete a step on arbitrary step navigation; only adjust UI } }; const updateHeaderContent = useCallback((content: StepHeaderContent) => { setStepHeaderContent(content); }, []); const handleComplete = async () => { console.log('Wizard: handleComplete called - completing onboarding'); try { // Call onComplete to notify parent component onComplete?.(); } catch (error) { console.error('Error completing onboarding:', error); } }; const renderStepContent = (step: number) => { console.log('Wizard: renderStepContent called with step:', step, 'stepData:', stepData); const stepComponents = [ , , , , , ]; return ( {stepComponents[step]} ); }; if (loading) { return ( Setting up your workspace... ); } return ( {/* Header with Stepper */} {/* Progress Message */} {showProgressMessage && ( {progressMessage} )} {/* Top Row - Title and Actions */} {stepHeaderContent.title} setShowHelp(!showHelp)} sx={{ color: 'white', bgcolor: 'rgba(255, 255, 255, 0.1)', backdropFilter: 'blur(10px)', '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.2)', } }} > {/* Progress Bar */} Setup Progress {Math.round(progress)}% Complete {/* Stepper in Header */} {steps.map((step, index) => ( handleStepClick(index)} sx={{ cursor: index <= activeStep ? 'pointer' : 'default', '& .MuiStepLabel-iconContainer': { background: index <= activeStep ? 'rgba(255, 255, 255, 0.2)' : 'rgba(255, 255, 255, 0.1)', borderRadius: '50%', width: 40, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', color: index <= activeStep ? 'white' : 'rgba(255, 255, 255, 0.6)', fontSize: '1.2rem', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', boxShadow: index <= activeStep ? '0 4px 12px rgba(255, 255, 255, 0.2)' : 'none', '&:hover': { transform: index <= activeStep ? 'scale(1.05)' : 'none', boxShadow: index <= activeStep ? '0 6px 16px rgba(255, 255, 255, 0.3)' : 'none', } }, }} > {step.icon} {step.label} ))} {/* Content */} {renderStepContent(activeStep)} {/* Navigation */} Step {activeStep + 1} of {steps.length} {activeStep === steps.length - 1 && ( )} ); }; export default Wizard;