// src/pages/onboarding.tsx import React, { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; import { CheckIcon, ArrowRightIcon, ArrowLeftIcon, CloudArrowUpIcon, BuildingStorefrontIcon, UserCircleIcon, CpuChipIcon } from '@heroicons/react/24/outline'; import { SalesUploader } from '../components/data/SalesUploader'; import { TrainingProgressCard } from '../components/training/TrainingProgressCard'; import { useAuth } from '../contexts/AuthContext'; import { authService, RegisterData } from '../api/services/authService'; import { dataApi, TrainingRequest, TrainingTask } from '../api/services/api'; // Assuming dataApi and types are in api/services/api.ts import { NotificationToast } from '../components/common/NotificationToast'; // Assuming this exists import { Product, defaultProducts } from '../components/common/ProductSelector'; // Assuming defaultProducts are here // Define the shape of the form data interface OnboardingFormData { // Step 1: User Registration full_name: string; email: string; password: string; confirm_password: string; // Step 2: Bakery Information bakery_name: string; address: string; city: string; postal_code: string; has_nearby_schools: boolean; has_nearby_offices: boolean; selected_products: Product[]; // New: For product selection // Step 3: Sales History File salesFile: File | null; // Step 4: Model Training status trainingStatus: 'pending' | 'running' | 'completed' | 'failed'; trainingTaskId: string | null; } const OnboardingPage: React.FC = () => { const router = useRouter(); const { user, register, login } = useAuth(); // Use login from AuthContext to set user state const [currentStep, setCurrentStep] = useState(1); const [completedSteps, setCompletedSteps] = useState([]); const [loading, setLoading] = useState(false); const [notification, setNotification] = useState<{ id: string; type: 'success' | 'error' | 'warning' | 'info'; title: string; message: string; } | null>(null); const [formData, setFormData] = useState({ full_name: '', email: '', password: '', confirm_password: '', bakery_name: '', address: '', city: 'Madrid', // Default to Madrid postal_code: '', has_nearby_schools: false, has_nearby_offices: false, selected_products: defaultProducts.slice(0, 3), // Default selected products salesFile: null, trainingStatus: 'pending', trainingTaskId: null, }); const [errors, setErrors] = useState>({}); useEffect(() => { // If user is already authenticated and on onboarding, redirect to dashboard if (user && currentStep === 1) { router.push('/dashboard'); } }, [user, router, currentStep]); const showNotification = useCallback((type: 'success' | 'error' | 'warning' | 'info', title: string, message: string) => { setNotification({ id: Date.now().toString(), type, title, message }); setTimeout(() => setNotification(null), 5000); }, []); const handleNext = async () => { setErrors({}); // Clear previous errors let newErrors: Partial = {}; // Validate current step before proceeding if (currentStep === 1) { if (!formData.full_name) newErrors.full_name = 'Nombre completo es requerido.'; if (!formData.email || !/\S+@\S+\.\S+/.test(formData.email)) newErrors.email = 'Email inválido.'; if (!formData.password || formData.password.length < 6) newErrors.password = 'La contraseña debe tener al menos 6 caracteres.'; if (formData.password !== formData.confirm_password) newErrors.confirm_password = 'Las contraseñas no coinciden.'; if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } setLoading(true); try { const registerData: RegisterData = { full_name: formData.full_name, email: formData.email, password: formData.password, // tenant_name will be derived from bakery_name later or provided by backend }; // Call register from AuthContext to handle token storage and user state await register(registerData); showNotification('success', 'Registro exitoso', 'Tu cuenta ha sido creada.'); } catch (err: any) { newErrors.email = err.message || 'Error al registrar usuario.'; showNotification('error', 'Error de registro', newErrors.email); setLoading(false); setErrors(newErrors); return; } finally { setLoading(false); } } else if (currentStep === 2) { if (!formData.bakery_name) newErrors.bakery_name = 'Nombre de la panadería es requerido.'; if (!formData.address) newErrors.address = 'Dirección es requerida.'; if (!formData.city) newErrors.city = 'Ciudad es requerida.'; if (!formData.postal_code) newErrors.postal_code = 'Código postal es requerido.'; if (formData.selected_products.length === 0) newErrors.selected_products = 'Debes seleccionar al menos un producto.'; if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } setLoading(true); try { // Assume an API call to update user/tenant profile with bakery info // This is a placeholder; actual implementation depends on your backend API for onboarding/user updates await authService.updateProfile({ tenant_name: formData.bakery_name, // Assuming tenant_name can be updated via profile }); showNotification('success', 'Datos de panadería', 'Información guardada correctamente.'); } catch (err: any) { showNotification('error', 'Error al guardar', 'No se pudo guardar la información de la panadería.'); setErrors({ bakery_name: 'Error al guardar la información.' }); setLoading(false); return; } finally { setLoading(false); } } else if (currentStep === 3) { if (!formData.salesFile) { newErrors.salesFile = 'Por favor, sube tu historial de ventas.'; setErrors(newErrors); return; } // Sales file will be uploaded by the SalesUploader component directly } setCompletedSteps((prev) => [...new Set([...prev, currentStep])]); setCurrentStep((prev) => prev + 1); }; const handleBack = () => { setCurrentStep((prev) => prev - 1); setErrors({}); // Clear errors when going back }; const handleSalesFileUpload = useCallback(async (file: File) => { setLoading(true); setErrors({}); try { // Assuming dataApi.uploadSalesHistory exists for uploading files const response = await dataApi.uploadSalesHistory(file, { products: formData.selected_products.map(p => p.id) }); setFormData((prev) => ({ ...prev, salesFile: file })); showNotification('success', 'Archivo subido', 'Historial de ventas cargado exitosamente.'); // After successful upload, immediately trigger training const trainingRequest: TrainingRequest = { force_retrain: true, // Example: force a new training products: formData.selected_products.map(p => p.id), }; const trainingTask: TrainingTask = await dataApi.startTraining(trainingRequest); setFormData((prev) => ({ ...prev, trainingStatus: trainingTask.status, trainingTaskId: trainingTask.job_id, })); showNotification('info', 'Entrenamiento iniciado', 'Tu modelo de predicción se está entrenando.'); setCompletedSteps((prev) => [...new Set([...prev, currentStep])]); // Mark step 3 as complete setCurrentStep(4); // Move to the training progress step } catch (err: any) { const errorMessage = err.message || 'Error al subir el archivo o iniciar el entrenamiento.'; setErrors({ salesFile: errorMessage }); showNotification('error', 'Error', errorMessage); } finally { setLoading(false); } }, [formData.selected_products, showNotification, currentStep]); const handleTrainingComplete = useCallback(() => { setFormData((prev) => ({ ...prev, trainingStatus: 'completed' })); showNotification('success', 'Entrenamiento completado', 'Tu modelo ha sido entrenado y está listo.'); setCompletedSteps((prev) => [...new Set([...prev, 4])]); // Mark step 4 as complete setCurrentStep(5); // Move to final step }, [showNotification]); const handleSubmitFinal = () => { router.push('/dashboard'); }; const steps = [ { id: 1, name: 'Información Personal', icon: UserCircleIcon, content: (
setFormData({ ...formData, full_name: e.target.value })} required /> {errors.full_name &&

{errors.full_name}

}
setFormData({ ...formData, email: e.target.value })} required /> {errors.email &&

{errors.email}

}
setFormData({ ...formData, password: e.target.value })} required /> {errors.password &&

{errors.password}

}
setFormData({ ...formData, confirm_password: e.target.value })} required /> {errors.confirm_password &&

{errors.confirm_password}

}
), }, { id: 2, name: 'Detalles de la Panadería', icon: BuildingStorefrontIcon, content: (
setFormData({ ...formData, bakery_name: e.target.value })} required /> {errors.bakery_name &&

{errors.bakery_name}

}
setFormData({ ...formData, address: e.target.value })} required /> {errors.address &&

{errors.address}

}
setFormData({ ...formData, city: e.target.value })} required /> {errors.city &&

{errors.city}

}
setFormData({ ...formData, postal_code: e.target.value })} required /> {errors.postal_code &&

{errors.postal_code}

}
setFormData({ ...formData, has_nearby_schools: e.target.checked })} className="h-4 w-4 text-pania-blue border-gray-300 rounded focus:ring-pania-blue" />
setFormData({ ...formData, has_nearby_offices: e.target.checked })} className="h-4 w-4 text-pania-blue border-gray-300 rounded focus:ring-pania-blue" />
{/* Product Selector */}

Selecciona los productos para los que deseas predicciones.

{/* Simple checkbox-based product selection for demo, replace with ProductSelector */}
{defaultProducts.map(product => (
p.id === product.id)} onChange={(e) => { if (e.target.checked) { setFormData(prev => ({ ...prev, selected_products: [...prev.selected_products, product] })); } else { setFormData(prev => ({ ...prev, selected_products: prev.selected_products.filter(p => p.id !== product.id) })); } }} className="h-4 w-4 text-pania-blue border-gray-300 rounded focus:ring-pania-blue" />
))}
{errors.selected_products &&

{errors.selected_products}

}
), }, { id: 3, name: 'Subir Historial de Ventas', icon: CloudArrowUpIcon, content: (

Casi estamos listos para predecir tus ventas.

Por favor, sube tu historial de ventas en formato CSV o Excel. Cuantos más datos, ¡más precisas serán las predicciones!

{formData.salesFile && (

Archivo seleccionado: {formData.salesFile.name}

)} {errors.salesFile &&

{errors.salesFile}

}
), }, { id: 4, name: 'Entrenamiento del Modelo', icon: CpuChipIcon, // Using CpuChipIcon for training content: (

Estamos entrenando tu modelo de predicción.

Esto puede tomar unos minutos, por favor, no cierres esta página.

{formData.trainingTaskId ? ( ) : (

Esperando el inicio del entrenamiento...

)}
), }, { id: 5, name: '¡Listo!', icon: CheckIcon, content: (

¡Tu modelo de IA está listo!

Ahora puedes ir al Dashboard para ver tus predicciones y comenzar a optimizar tu negocio.

), }, ]; const currentStepData = steps[currentStep - 1]; return (
Onboarding - PanIA {notification && (
setNotification(null)} />
)}

PanIA

Configuración Inicial

{/* Step Indicator */} {/* Step Content */}

Paso {currentStep}: {currentStepData.name}

{currentStepData.content}
{/* Navigation Buttons */}
{currentStep > 1 && currentStep < 5 && ( )} {currentStep < 5 && ( )} {currentStep === 5 && ( )}
); }; export default OnboardingPage;