CHANGES:
1. **Make suppliers-setup unconditional:**
- Removed isConditional: true and condition
- Suppliers step now ALWAYS appears in onboarding flow
- No longer depends on stockEntryCompleted flag
2. **Make ml-training unconditional:**
- Removed isConditional: true and condition
- ML training step now ALWAYS appears in onboarding flow
- No longer depends on aiAnalysisComplete flag
3. **Completely rewrote CompletionStep content:**
- Changed title: "¡Felicidades! Tu Sistema Está Listo"
- Shows what user HAS ACCOMPLISHED during onboarding:
✓ Información de Panadería
✓ Inventario con IA
✓ Proveedores Agregados
✓ Recetas Configuradas
✓ Calidad Establecida
✓ Equipo Invitado
✓ Modelo IA Entrenado
- REMOVED misleading "One More Thing" section that asked users
to configure things they JUST configured
- Changed next steps to celebrate accomplishments and guide to dashboard
- Updated buttons: "Ir al Panel de Control →" (single clear CTA)
FIXES:
- User frustration: suppliers and ML training steps were hidden
- User confusion: completion message didn't make sense - asking to
configure suppliers, inventory, recipes after they just did it
ONBOARDING FLOW NOW:
1. bakery-type-selection
2. setup
3. smart-inventory-setup
4. product-categorization
5. initial-stock-entry
6. suppliers-setup ✓ ALWAYS SHOWS
7. recipes-setup (conditional on bakery type)
8. production-processes (conditional on bakery type)
9. quality-setup
10. team-setup
11. ml-training ✓ ALWAYS SHOWS
12. setup-review
13. completion (celebrates accomplishments!)
Files Modified:
- frontend/src/components/domain/onboarding/UnifiedOnboardingWizard.tsx
- frontend/src/components/domain/onboarding/steps/CompletionStep.tsx
534 lines
20 KiB
TypeScript
534 lines
20 KiB
TypeScript
import React, { useState, useEffect, useMemo } from 'react';
|
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Button } from '../../ui/Button';
|
|
import { Card, CardHeader, CardBody } from '../../ui/Card';
|
|
import { useAuth } from '../../../contexts/AuthContext';
|
|
import { useUserProgress, useMarkStepCompleted } from '../../../api/hooks/onboarding';
|
|
import { useTenantActions } from '../../../stores/tenant.store';
|
|
import { useTenantInitializer } from '../../../stores/useTenantInitializer';
|
|
import { WizardProvider, useWizardContext, BakeryType, DataSource } from './context';
|
|
import {
|
|
BakeryTypeSelectionStep,
|
|
RegisterTenantStep,
|
|
UploadSalesDataStep,
|
|
ProductCategorizationStep,
|
|
InitialStockEntryStep,
|
|
ProductionProcessesStep,
|
|
MLTrainingStep,
|
|
CompletionStep
|
|
} from './steps';
|
|
// Import setup wizard steps
|
|
import {
|
|
SuppliersSetupStep,
|
|
RecipesSetupStep,
|
|
QualitySetupStep,
|
|
TeamSetupStep,
|
|
ReviewSetupStep,
|
|
} from '../setup-wizard/steps';
|
|
import { Building2 } from 'lucide-react';
|
|
|
|
interface StepConfig {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
component: React.ComponentType<any>;
|
|
isConditional?: boolean;
|
|
condition?: (context: any) => boolean;
|
|
}
|
|
|
|
interface StepProps {
|
|
onNext?: () => void;
|
|
onPrevious?: () => void;
|
|
onComplete?: (data?: any) => void;
|
|
onUpdate?: (data?: any) => void;
|
|
isFirstStep?: boolean;
|
|
isLastStep?: boolean;
|
|
initialData?: any;
|
|
}
|
|
|
|
const OnboardingWizardContent: React.FC = () => {
|
|
const { t } = useTranslation();
|
|
const [searchParams] = useSearchParams();
|
|
const navigate = useNavigate();
|
|
const { user } = useAuth();
|
|
const wizardContext = useWizardContext();
|
|
|
|
// All possible steps with conditional visibility
|
|
// All step IDs match backend ONBOARDING_STEPS exactly
|
|
const ALL_STEPS: StepConfig[] = [
|
|
// Phase 1: Discovery
|
|
{
|
|
id: 'bakery-type-selection',
|
|
title: t('onboarding:steps.bakery_type.title', 'Tipo de Panadería'),
|
|
description: t('onboarding:steps.bakery_type.description', 'Selecciona tu tipo de negocio'),
|
|
component: BakeryTypeSelectionStep,
|
|
},
|
|
// Phase 2: Core Setup
|
|
{
|
|
id: 'setup',
|
|
title: t('onboarding:steps.setup.title', 'Registrar Panadería'),
|
|
description: t('onboarding:steps.setup.description', 'Información básica'),
|
|
component: RegisterTenantStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.state.bakeryType !== null,
|
|
},
|
|
// Phase 2a: AI-Assisted Path (ONLY PATH NOW)
|
|
{
|
|
id: 'smart-inventory-setup',
|
|
title: t('onboarding:steps.smart_inventory.title', 'Subir Datos de Ventas'),
|
|
description: t('onboarding:steps.smart_inventory.description', 'Configuración con IA'),
|
|
component: UploadSalesDataStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.tenantId !== null,
|
|
},
|
|
{
|
|
id: 'product-categorization',
|
|
title: t('onboarding:steps.categorization.title', 'Categorizar Productos'),
|
|
description: t('onboarding:steps.categorization.description', 'Clasifica ingredientes vs productos'),
|
|
component: ProductCategorizationStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.state.aiAnalysisComplete,
|
|
},
|
|
{
|
|
id: 'initial-stock-entry',
|
|
title: t('onboarding:steps.stock.title', 'Niveles de Stock'),
|
|
description: t('onboarding:steps.stock.description', 'Cantidades iniciales'),
|
|
component: InitialStockEntryStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.state.categorizationCompleted,
|
|
},
|
|
{
|
|
id: 'suppliers-setup',
|
|
title: t('onboarding:steps.suppliers.title', 'Proveedores'),
|
|
description: t('onboarding:steps.suppliers.description', 'Configura tus proveedores'),
|
|
component: SuppliersSetupStep,
|
|
// Always show - no conditional
|
|
},
|
|
{
|
|
id: 'recipes-setup',
|
|
title: t('onboarding:steps.recipes.title', 'Recetas'),
|
|
description: t('onboarding:steps.recipes.description', 'Recetas de producción'),
|
|
component: RecipesSetupStep,
|
|
isConditional: true,
|
|
condition: (ctx) =>
|
|
ctx.state.bakeryType === 'production' || ctx.state.bakeryType === 'mixed',
|
|
},
|
|
{
|
|
id: 'production-processes',
|
|
title: t('onboarding:steps.processes.title', 'Procesos'),
|
|
description: t('onboarding:steps.processes.description', 'Procesos de terminado'),
|
|
component: ProductionProcessesStep,
|
|
isConditional: true,
|
|
condition: (ctx) =>
|
|
ctx.state.bakeryType === 'retail' || ctx.state.bakeryType === 'mixed',
|
|
},
|
|
// Phase 3: Advanced Features (Optional)
|
|
{
|
|
id: 'quality-setup',
|
|
title: t('onboarding:steps.quality.title', 'Calidad'),
|
|
description: t('onboarding:steps.quality.description', 'Estándares de calidad'),
|
|
component: QualitySetupStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.tenantId !== null,
|
|
},
|
|
{
|
|
id: 'team-setup',
|
|
title: t('onboarding:steps.team.title', 'Equipo'),
|
|
description: t('onboarding:steps.team.description', 'Miembros del equipo'),
|
|
component: TeamSetupStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.tenantId !== null,
|
|
},
|
|
// Phase 4: ML & Finalization
|
|
{
|
|
id: 'ml-training',
|
|
title: t('onboarding:steps.ml_training.title', 'Entrenamiento IA'),
|
|
description: t('onboarding:steps.ml_training.description', 'Modelo personalizado'),
|
|
component: MLTrainingStep,
|
|
// Always show - no conditional
|
|
},
|
|
{
|
|
id: 'setup-review',
|
|
title: t('onboarding:steps.review.title', 'Revisión'),
|
|
description: t('onboarding:steps.review.description', 'Confirma tu configuración'),
|
|
component: ReviewSetupStep,
|
|
isConditional: true,
|
|
condition: (ctx) => ctx.tenantId !== null,
|
|
},
|
|
{
|
|
id: 'completion',
|
|
title: t('onboarding:steps.completion.title', 'Completado'),
|
|
description: t('onboarding:steps.completion.description', '¡Todo listo!'),
|
|
component: CompletionStep,
|
|
},
|
|
];
|
|
|
|
// Filter visible steps based on wizard context
|
|
// useMemo ensures VISIBLE_STEPS recalculates when wizard context state changes
|
|
// This fixes the bug where conditional steps (suppliers, ml-training) weren't showing
|
|
const VISIBLE_STEPS = useMemo(() => {
|
|
const visibleSteps = ALL_STEPS.filter(step => {
|
|
if (!step.isConditional) return true;
|
|
if (!step.condition) return true;
|
|
return step.condition(wizardContext);
|
|
});
|
|
|
|
console.log('🔄 VISIBLE_STEPS recalculated:', visibleSteps.map(s => s.id));
|
|
console.log('📊 Wizard state:', {
|
|
stockEntryCompleted: wizardContext.state.stockEntryCompleted,
|
|
aiAnalysisComplete: wizardContext.state.aiAnalysisComplete,
|
|
categorizationCompleted: wizardContext.state.categorizationCompleted,
|
|
});
|
|
|
|
return visibleSteps;
|
|
}, [wizardContext.state, wizardContext.tenantId]);
|
|
|
|
const isNewTenant = searchParams.get('new') === 'true';
|
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
const [isInitialized, setIsInitialized] = useState(isNewTenant);
|
|
const [canContinue, setCanContinue] = useState(true); // Track if current step allows continuation
|
|
|
|
useTenantInitializer();
|
|
|
|
const { data: userProgress, isLoading: isLoadingProgress, error: progressError } = useUserProgress(
|
|
user?.id || '',
|
|
{ enabled: !!user?.id }
|
|
);
|
|
|
|
const markStepCompleted = useMarkStepCompleted();
|
|
const { setCurrentTenant } = useTenantActions();
|
|
const [autoCompletionAttempted, setAutoCompletionAttempted] = React.useState(false);
|
|
|
|
// Auto-complete user_registered step
|
|
useEffect(() => {
|
|
if (userProgress && user?.id && !autoCompletionAttempted && !markStepCompleted.isPending) {
|
|
const userRegisteredStep = userProgress.steps.find(s => s.step_name === 'user_registered');
|
|
|
|
if (!userRegisteredStep?.completed) {
|
|
console.log('🔄 Auto-completing user_registered step for new user...');
|
|
setAutoCompletionAttempted(true);
|
|
|
|
const existingData = userRegisteredStep?.data || {};
|
|
|
|
markStepCompleted.mutate({
|
|
userId: user.id,
|
|
stepName: 'user_registered',
|
|
data: {
|
|
...existingData,
|
|
auto_completed: true,
|
|
completed_at: new Date().toISOString(),
|
|
source: 'onboarding_wizard_auto_completion'
|
|
}
|
|
}, {
|
|
onSuccess: () => console.log('✅ user_registered step auto-completed successfully'),
|
|
onError: (error) => {
|
|
console.error('❌ Failed to auto-complete user_registered step:', error);
|
|
setAutoCompletionAttempted(false);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}, [userProgress, user?.id, autoCompletionAttempted, markStepCompleted.isPending]);
|
|
|
|
// Initialize step index based on backend progress
|
|
useEffect(() => {
|
|
if (isNewTenant) return;
|
|
|
|
if (userProgress && !isInitialized) {
|
|
console.log('🔄 Initializing onboarding progress:', userProgress);
|
|
|
|
const userRegisteredStep = userProgress.steps.find(s => s.step_name === 'user_registered');
|
|
if (!userRegisteredStep?.completed) {
|
|
console.log('⏳ Waiting for user_registered step to be auto-completed...');
|
|
return;
|
|
}
|
|
|
|
let stepIndex = 0;
|
|
|
|
if (isNewTenant) {
|
|
console.log('🆕 New tenant creation - starting from first step');
|
|
stepIndex = 0;
|
|
} else {
|
|
const currentStepFromBackend = userProgress.current_step;
|
|
stepIndex = VISIBLE_STEPS.findIndex(step => step.id === currentStepFromBackend);
|
|
|
|
console.log(`🎯 Backend current step: "${currentStepFromBackend}", found at index: ${stepIndex}`);
|
|
|
|
if (stepIndex === -1) {
|
|
for (let i = 0; i < VISIBLE_STEPS.length; i++) {
|
|
const step = VISIBLE_STEPS[i];
|
|
const stepProgress = userProgress.steps.find(s => s.step_name === step.id);
|
|
|
|
if (!stepProgress?.completed) {
|
|
stepIndex = i;
|
|
console.log(`📍 Found first incomplete step: "${step.id}" at index ${i}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (stepIndex === -1) {
|
|
stepIndex = VISIBLE_STEPS.length - 1;
|
|
console.log('✅ All steps completed, going to last step');
|
|
}
|
|
}
|
|
|
|
const firstIncompleteStepIndex = VISIBLE_STEPS.findIndex(step => {
|
|
const stepProgress = userProgress.steps.find(s => s.step_name === step.id);
|
|
return !stepProgress?.completed;
|
|
});
|
|
|
|
if (firstIncompleteStepIndex !== -1 && stepIndex > firstIncompleteStepIndex) {
|
|
console.log(`🚫 User trying to skip ahead. Redirecting to first incomplete step at index ${firstIncompleteStepIndex}`);
|
|
stepIndex = firstIncompleteStepIndex;
|
|
}
|
|
}
|
|
|
|
console.log(`🎯 Final step index: ${stepIndex} ("${VISIBLE_STEPS[stepIndex]?.id}")`);
|
|
|
|
if (stepIndex !== currentStepIndex) {
|
|
setCurrentStepIndex(stepIndex);
|
|
}
|
|
setIsInitialized(true);
|
|
}
|
|
}, [userProgress, isInitialized, currentStepIndex, isNewTenant, VISIBLE_STEPS]);
|
|
|
|
const currentStep = VISIBLE_STEPS[currentStepIndex];
|
|
|
|
const handleStepComplete = async (data?: any) => {
|
|
if (!user?.id) {
|
|
console.error('User ID not available');
|
|
return;
|
|
}
|
|
|
|
if (markStepCompleted.isPending) {
|
|
console.warn(`⚠️ Step completion already in progress for "${currentStep.id}", skipping duplicate call`);
|
|
return;
|
|
}
|
|
|
|
console.log(`🎯 Completing step: "${currentStep.id}" with data:`, data);
|
|
|
|
try {
|
|
// Update wizard context based on step
|
|
if (currentStep.id === 'bakery-type-selection' && data?.bakeryType) {
|
|
wizardContext.updateBakeryType(data.bakeryType as BakeryType);
|
|
}
|
|
if (currentStep.id === 'data-source-choice' && data?.dataSource) {
|
|
wizardContext.updateDataSource(data.dataSource as DataSource);
|
|
}
|
|
if (currentStep.id === 'smart-inventory-setup' && data?.aiSuggestions) {
|
|
wizardContext.updateAISuggestions(data.aiSuggestions);
|
|
wizardContext.setAIAnalysisComplete(true);
|
|
}
|
|
if (currentStep.id === 'product-categorization' && data?.categorizedProducts) {
|
|
wizardContext.updateCategorizedProducts(data.categorizedProducts);
|
|
wizardContext.markStepComplete('categorizationCompleted');
|
|
}
|
|
if (currentStep.id === 'initial-stock-entry' && data?.productsWithStock) {
|
|
wizardContext.updateProductsWithStock(data.productsWithStock);
|
|
wizardContext.markStepComplete('stockEntryCompleted');
|
|
}
|
|
if (currentStep.id === 'inventory-setup') {
|
|
wizardContext.markStepComplete('inventoryCompleted');
|
|
}
|
|
if (currentStep.id === 'setup' && data?.tenant) {
|
|
setCurrentTenant(data.tenant);
|
|
}
|
|
|
|
// Mark step as completed in backend
|
|
console.log(`📤 Sending API request to complete step: "${currentStep.id}"`);
|
|
await markStepCompleted.mutateAsync({
|
|
userId: user.id,
|
|
stepName: currentStep.id,
|
|
data
|
|
});
|
|
|
|
console.log(`✅ Successfully completed step: "${currentStep.id}"`);
|
|
|
|
// Special handling for smart-inventory-setup
|
|
if (currentStep.id === 'smart-inventory-setup' && data?.shouldAutoCompleteSuppliers) {
|
|
try {
|
|
console.log('🔄 Auto-completing suppliers-setup step...');
|
|
await markStepCompleted.mutateAsync({
|
|
userId: user.id,
|
|
stepName: 'suppliers-setup',
|
|
data: {
|
|
auto_completed: true,
|
|
completed_at: new Date().toISOString(),
|
|
source: 'inventory_creation_auto_completion',
|
|
}
|
|
});
|
|
console.log('✅ Suppliers-setup step auto-completed successfully');
|
|
} catch (supplierError) {
|
|
console.warn('⚠️ Could not auto-complete suppliers-setup step:', supplierError);
|
|
}
|
|
}
|
|
|
|
if (currentStep.id === 'completion') {
|
|
wizardContext.resetWizard();
|
|
navigate(isNewTenant ? '/app/dashboard' : '/app');
|
|
} else {
|
|
if (currentStepIndex < VISIBLE_STEPS.length - 1) {
|
|
setCurrentStepIndex(currentStepIndex + 1);
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
console.error(`❌ Error completing step "${currentStep.id}":`, error);
|
|
const errorMessage = error?.response?.data?.detail || error?.message || 'Unknown error';
|
|
alert(`${t('onboarding:errors.step_failed', 'Error al completar paso')} "${currentStep.title}": ${errorMessage}`);
|
|
}
|
|
};
|
|
|
|
const handleStepUpdate = (data?: any) => {
|
|
// Handle canContinue state updates from setup wizard steps
|
|
if (data?.canContinue !== undefined) {
|
|
setCanContinue(data.canContinue);
|
|
}
|
|
|
|
// Handle intermediate updates without marking step complete
|
|
if (currentStep.id === 'bakery-type-selection' && data?.bakeryType) {
|
|
wizardContext.updateBakeryType(data.bakeryType as BakeryType);
|
|
}
|
|
if (currentStep.id === 'data-source-choice' && data?.dataSource) {
|
|
wizardContext.updateDataSource(data.dataSource as DataSource);
|
|
}
|
|
if (currentStep.id === 'product-categorization' && data?.categorizedProducts) {
|
|
wizardContext.updateCategorizedProducts(data.categorizedProducts);
|
|
}
|
|
if (currentStep.id === 'initial-stock-entry' && data?.productsWithStock) {
|
|
wizardContext.updateProductsWithStock(data.productsWithStock);
|
|
}
|
|
};
|
|
|
|
// Show loading state
|
|
if (!isNewTenant && (isLoadingProgress || !isInitialized)) {
|
|
return (
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6">
|
|
<Card padding="lg" shadow="lg">
|
|
<CardBody>
|
|
<div className="flex items-center justify-center space-x-3">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
|
|
<p className="text-[var(--text-secondary)] text-sm sm:text-base">{t('common:loading', 'Cargando tu progreso...')}</p>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Show error state
|
|
if (!isNewTenant && progressError) {
|
|
return (
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6">
|
|
<Card padding="lg" shadow="lg">
|
|
<CardBody>
|
|
<div className="text-center space-y-4">
|
|
<div className="w-14 h-14 sm:w-16 sm:h-16 mx-auto bg-[var(--color-error)]/10 rounded-full flex items-center justify-center">
|
|
<svg className="w-7 h-7 sm:w-8 sm:h-8 text-[var(--color-error)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h2 className="text-base sm:text-lg font-semibold text-[var(--text-primary)] mb-2">
|
|
{t('onboarding:errors.network_error', 'Error al cargar progreso')}
|
|
</h2>
|
|
<p className="text-sm sm:text-base text-[var(--text-secondary)] mb-4 px-2">
|
|
{t('onboarding:errors.try_again', 'No pudimos cargar tu progreso. Puedes continuar desde el inicio.')}
|
|
</p>
|
|
<Button onClick={() => setIsInitialized(true)} variant="primary" size="lg">
|
|
{t('onboarding:wizard.navigation.next', 'Continuar')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const StepComponent = currentStep.component;
|
|
const progressPercentage = isNewTenant
|
|
? ((currentStepIndex + 1) / VISIBLE_STEPS.length) * 100
|
|
: userProgress?.completion_percentage || ((currentStepIndex + 1) / VISIBLE_STEPS.length) * 100;
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 space-y-4 sm:space-y-6 pb-6">
|
|
{/* Progress Header */}
|
|
<Card shadow="sm" padding="lg">
|
|
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4 space-y-2 sm:space-y-0">
|
|
<div className="text-center sm:text-left">
|
|
<h1 className="text-xl sm:text-2xl font-bold text-[var(--text-primary)]">
|
|
{isNewTenant ? t('onboarding:wizard.title_new', 'Nueva Panadería') : t('onboarding:wizard.title', 'Configuración Inicial')}
|
|
</h1>
|
|
<p className="text-[var(--text-secondary)] text-xs sm:text-sm mt-1">
|
|
{t('onboarding:wizard.subtitle', 'Configura tu sistema paso a paso')}
|
|
</p>
|
|
</div>
|
|
<div className="text-center sm:text-right">
|
|
<div className="text-sm text-[var(--text-secondary)]">
|
|
{t('onboarding:wizard.progress.step_of', 'Paso {{current}} de {{total}}', {
|
|
current: currentStepIndex + 1,
|
|
total: VISIBLE_STEPS.length
|
|
})}
|
|
</div>
|
|
<div className="text-xs text-[var(--text-tertiary)]">
|
|
{Math.round(progressPercentage)}% {t('onboarding:wizard.progress.completed', 'completado')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="w-full bg-[var(--bg-secondary)] rounded-full h-2 sm:h-3">
|
|
<div
|
|
className="bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary)]/80 h-2 sm:h-3 rounded-full transition-all duration-500 ease-out"
|
|
style={{ width: `${progressPercentage}%` }}
|
|
/>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Step Content */}
|
|
<Card shadow="lg" padding="none">
|
|
<CardHeader padding="lg" divider>
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center">
|
|
<div className="w-5 h-5 sm:w-6 sm:h-6 bg-[var(--color-primary)] rounded-full flex items-center justify-center text-white text-xs font-bold">
|
|
{currentStepIndex + 1}
|
|
</div>
|
|
</div>
|
|
<div className="flex-1">
|
|
<h2 className="text-lg sm:text-xl font-semibold text-[var(--text-primary)]">
|
|
{currentStep.title}
|
|
</h2>
|
|
<p className="text-[var(--text-secondary)] text-xs sm:text-sm">
|
|
{currentStep.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardBody padding="lg">
|
|
<StepComponent
|
|
onNext={() => {}}
|
|
onPrevious={() => {}}
|
|
onComplete={handleStepComplete}
|
|
onUpdate={handleStepUpdate}
|
|
isFirstStep={currentStepIndex === 0}
|
|
isLastStep={currentStepIndex === VISIBLE_STEPS.length - 1}
|
|
canContinue={canContinue}
|
|
/>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const UnifiedOnboardingWizard: React.FC = () => {
|
|
return (
|
|
<WizardProvider>
|
|
<OnboardingWizardContent />
|
|
</WizardProvider>
|
|
);
|
|
};
|
|
|
|
export default UnifiedOnboardingWizard;
|