Start integrating the onboarding flow with backend 4
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { OnboardingWizard, OnboardingStep } from '../../../components/domain/onboarding/OnboardingWizard';
|
||||
import { onboardingApiService } from '../../../services/api/onboarding.service';
|
||||
import { useOnboarding } from '../../../hooks/business/useOnboarding';
|
||||
import { useAuthUser, useIsAuthenticated } from '../../../stores/auth.store';
|
||||
import { LoadingSpinner } from '../../../components/shared/LoadingSpinner';
|
||||
|
||||
@@ -18,162 +18,138 @@ const OnboardingPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const user = useAuthUser();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [globalData, setGlobalData] = useState<any>({});
|
||||
|
||||
// Use the onboarding business hook
|
||||
const {
|
||||
currentStep,
|
||||
steps,
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
isInitialized,
|
||||
onboardingStatus,
|
||||
nextStep,
|
||||
previousStep,
|
||||
goToStep,
|
||||
updateStepData,
|
||||
validateCurrentStep,
|
||||
createTenant,
|
||||
processSalesFile,
|
||||
generateInventorySuggestions,
|
||||
createInventoryFromSuggestions,
|
||||
getBusinessModelGuide,
|
||||
downloadTemplate,
|
||||
completeOnboarding,
|
||||
clearError,
|
||||
reset
|
||||
} = useOnboarding();
|
||||
|
||||
// Define the 8 onboarding steps (simplified by merging data upload + analysis)
|
||||
const steps: OnboardingStep[] = [
|
||||
{
|
||||
id: 'setup',
|
||||
title: '🏢 Setup',
|
||||
description: 'Configuración básica de tu panadería y creación del tenant',
|
||||
component: BakerySetupStep,
|
||||
isRequired: true,
|
||||
validation: (data) => {
|
||||
if (!data.bakery?.name) return 'El nombre de la panadería es requerido';
|
||||
if (!data.bakery?.business_model) return 'El modelo de negocio es requerido';
|
||||
if (!data.bakery?.address) return 'La dirección es requerida';
|
||||
if (!data.bakery?.city) return 'La ciudad es requerida';
|
||||
if (!data.bakery?.postal_code) return 'El código postal es requerido';
|
||||
if (!data.bakery?.phone) return 'El teléfono es requerido';
|
||||
// Tenant creation will happen automatically when validation passes
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'data-processing',
|
||||
title: '📊 Historial de Ventas',
|
||||
description: 'Sube tus datos de ventas para obtener insights personalizados',
|
||||
component: DataProcessingStep,
|
||||
isRequired: true,
|
||||
validation: (data) => {
|
||||
if (!data.files?.salesData) return 'Debes cargar el archivo de datos de ventas';
|
||||
if (data.processingStage !== 'completed') return 'El procesamiento debe completarse antes de continuar';
|
||||
if (!data.processingResults?.is_valid) return 'Los datos deben ser válidos para continuar';
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'review',
|
||||
title: '📋 Revisión',
|
||||
description: 'Revisión de productos detectados por IA y resultados',
|
||||
component: ReviewStep,
|
||||
isRequired: true,
|
||||
validation: (data) => {
|
||||
if (!data.reviewCompleted) return 'Debes revisar y aprobar los productos detectados';
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'inventory',
|
||||
title: '⚙️ Inventario',
|
||||
description: 'Configuración de inventario (stock, fechas de vencimiento)',
|
||||
component: InventorySetupStep,
|
||||
isRequired: true,
|
||||
validation: (data) => {
|
||||
if (!data.inventoryConfigured) return 'Debes configurar el inventario básico';
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'suppliers',
|
||||
title: '🏪 Proveedores',
|
||||
description: 'Configuración de proveedores y asociaciones',
|
||||
component: SuppliersStep,
|
||||
isRequired: false,
|
||||
validation: () => null // Optional step
|
||||
},
|
||||
{
|
||||
id: 'ml-training',
|
||||
title: '🎯 Inteligencia',
|
||||
description: 'Creación de tu asistente inteligente personalizado',
|
||||
component: MLTrainingStep,
|
||||
isRequired: true,
|
||||
validation: (data) => {
|
||||
if (data.trainingStatus !== 'completed') return 'El entrenamiento del modelo debe completarse';
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'completion',
|
||||
title: '🎉 Listo',
|
||||
description: 'Finalización y preparación para usar la plataforma',
|
||||
component: CompletionStep,
|
||||
isRequired: true,
|
||||
validation: () => null
|
||||
// Map steps to components
|
||||
const stepComponents: { [key: string]: React.ComponentType<any> } = {
|
||||
'setup': BakerySetupStep,
|
||||
'data-processing': DataProcessingStep,
|
||||
'review': ReviewStep,
|
||||
'inventory': InventorySetupStep,
|
||||
'suppliers': SuppliersStep,
|
||||
'ml-training': MLTrainingStep,
|
||||
'completion': CompletionStep
|
||||
};
|
||||
|
||||
// Convert hook steps to OnboardingWizard format
|
||||
const wizardSteps: OnboardingStep[] = steps.map(step => ({
|
||||
id: step.id,
|
||||
title: step.title,
|
||||
description: step.description,
|
||||
component: stepComponents[step.id],
|
||||
isRequired: step.isRequired,
|
||||
validation: step.validation
|
||||
}));
|
||||
|
||||
const handleStepChange = (stepIndex: number, stepData: any) => {
|
||||
const stepId = steps[stepIndex]?.id;
|
||||
if (stepId) {
|
||||
updateStepData(stepId, stepData);
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
return nextStep();
|
||||
};
|
||||
|
||||
const handlePrevious = () => {
|
||||
return previousStep();
|
||||
};
|
||||
|
||||
const handleComplete = async (allData: any) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Mark onboarding as complete in the backend
|
||||
if (user?.tenant_id) {
|
||||
await onboardingApiService.completeOnboarding(user.tenant_id, {
|
||||
completedAt: new Date().toISOString(),
|
||||
data: allData
|
||||
});
|
||||
}
|
||||
|
||||
// Navigate to dashboard
|
||||
navigate('/app/dashboard', {
|
||||
state: {
|
||||
message: '¡Felicidades! Tu panadería ha sido configurada exitosamente.',
|
||||
type: 'success'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error completing onboarding:', error);
|
||||
// Still navigate to dashboard but show warning
|
||||
navigate('/app/dashboard', {
|
||||
state: {
|
||||
message: 'Configuración completada. Algunos ajustes finales pueden estar pendientes.',
|
||||
type: 'warning'
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
const success = await completeOnboarding();
|
||||
if (success) {
|
||||
// Navigation is handled inside completeOnboarding
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleExit = () => {
|
||||
const confirmExit = window.confirm(
|
||||
'¿Estás seguro de que quieres salir del proceso de configuración? Tu progreso se guardará automáticamente.'
|
||||
);
|
||||
|
||||
if (confirmExit) {
|
||||
navigate('/app/dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
// Redirect to login if not authenticated
|
||||
// Redirect if user is not authenticated
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
navigate('/login', {
|
||||
state: {
|
||||
message: 'Debes iniciar sesión para acceder al onboarding.',
|
||||
returnUrl: '/app/onboarding'
|
||||
}
|
||||
});
|
||||
if (isInitialized && !isAuthenticated) {
|
||||
navigate('/auth/login');
|
||||
}
|
||||
}, [isAuthenticated, navigate]);
|
||||
}, [isAuthenticated, isInitialized, navigate]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner overlay text="Completando configuración..." />;
|
||||
// Clear error when user navigates away
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (error) {
|
||||
clearError();
|
||||
}
|
||||
};
|
||||
}, [error, clearError]);
|
||||
|
||||
// Show loading while initializing
|
||||
if (!isInitialized || isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<LoadingSpinner size="lg" message="Inicializando onboarding..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Don't render if not authenticated (will redirect)
|
||||
if (!isAuthenticated || !user) {
|
||||
return <LoadingSpinner overlay text="Verificando autenticación..." />;
|
||||
// Show error state
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h2 className="text-xl font-semibold text-red-600 mb-4">Error en Onboarding</h2>
|
||||
<p className="text-gray-600 mb-4">{error}</p>
|
||||
<div className="space-x-4">
|
||||
<button
|
||||
onClick={clearError}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||
>
|
||||
Reintentar
|
||||
</button>
|
||||
<button
|
||||
onClick={reset}
|
||||
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
|
||||
>
|
||||
Reiniciar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--bg-primary)]">
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<OnboardingWizard
|
||||
steps={steps}
|
||||
steps={wizardSteps}
|
||||
currentStep={currentStep}
|
||||
data={data}
|
||||
onStepChange={handleStepChange}
|
||||
onNext={handleNext}
|
||||
onPrevious={handlePrevious}
|
||||
onComplete={handleComplete}
|
||||
onExit={handleExit}
|
||||
className="py-8"
|
||||
onGoToStep={goToStep}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user