import React, { useState, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { PublicLayout } from '../../components/layout'; import { Button } from '../../components/ui'; import { getDemoAccounts, createDemoSession, DemoAccount, demoSessionAPI } from '../../api/services/demo'; import { apiClient } from '../../api/client'; import { Check, Clock, Shield, Play, Zap, ArrowRight, Store, Factory, Loader2 } from 'lucide-react'; import { markTourAsStartPending } from '../../features/demo-onboarding'; const POLL_INTERVAL_MS = 1500; // Poll every 1.5 seconds export const DemoPage: React.FC = () => { const { t } = useTranslation(); const [demoAccounts, setDemoAccounts] = useState([]); const [loading, setLoading] = useState(true); const [creatingSession, setCreatingSession] = useState(false); const [error, setError] = useState(null); const [progressPercentage, setProgressPercentage] = useState(0); const [estimatedTime, setEstimatedTime] = useState(5); useEffect(() => { const fetchDemoAccounts = async () => { try { const accounts = await getDemoAccounts(); setDemoAccounts(accounts); } catch (err) { setError(t('demo:errors.loading_accounts', 'Error al cargar las cuentas demo')); console.error('Error fetching demo accounts:', err); } finally { setLoading(false); } }; fetchDemoAccounts(); }, []); const pollStatus = useCallback(async (sessionId: string) => { try { const statusData = await demoSessionAPI.getSessionStatus(sessionId); // Calculate progress - ALWAYS update, even if no progress data yet if (statusData.progress && Object.keys(statusData.progress).length > 0) { const services = Object.values(statusData.progress); const totalServices = services.length; if (totalServices > 0) { const completedServices = services.filter( (s) => s.status === 'completed' || s.status === 'failed' ).length; const percentage = Math.round((completedServices / totalServices) * 100); setProgressPercentage(percentage); // Estimate remaining time const remainingServices = totalServices - completedServices; setEstimatedTime(Math.max(remainingServices * 2, 1)); } else { // No services yet, show minimal progress setProgressPercentage(5); } } else { // No progress data yet, show initial state setProgressPercentage(10); } // Check if ready to redirect // CRITICAL: Wait for inventory, recipes, AND suppliers to complete // to prevent dashboard from showing SetupWizardBlocker const progress = statusData.progress || {}; const inventoryReady = progress.inventory?.status === 'completed'; const recipesReady = progress.recipes?.status === 'completed'; const suppliersReady = progress.suppliers?.status === 'completed'; const criticalServicesReady = inventoryReady && recipesReady && suppliersReady; // Additionally verify that we have minimum required data to bypass SetupWizardBlocker // The SetupWizardBlocker requires: 3+ ingredients, 1+ suppliers, 1+ recipes // Ensure progress data exists for all required services const hasInventoryProgress = !!progress.inventory; const hasSuppliersProgress = !!progress.suppliers; const hasRecipesProgress = !!progress.recipes; // Extract counts with defensive checks const ingredientsCount = hasInventoryProgress ? (progress.inventory.details?.ingredients || 0) : 0; const suppliersCount = hasSuppliersProgress ? (progress.suppliers.details?.suppliers || 0) : 0; const recipesCount = hasRecipesProgress ? (progress.recipes.details?.recipes || 0) : 0; // Verify we have the minimum required counts const hasMinimumIngredients = (typeof ingredientsCount === 'number' && ingredientsCount >= 3); const hasMinimumSuppliers = (typeof suppliersCount === 'number' && suppliersCount >= 1); const hasMinimumRecipes = (typeof recipesCount === 'number' && recipesCount >= 1); // Ensure all required services have completed AND we have minimum data const hasMinimumRequiredData = hasInventoryProgress && hasSuppliersProgress && hasRecipesProgress && hasMinimumIngredients && hasMinimumSuppliers && hasMinimumRecipes; const shouldRedirect = (statusData.status === 'ready' && hasMinimumRequiredData) || // Ready status AND minimum required data (criticalServicesReady && hasMinimumRequiredData); // Critical services done + minimum required data if (shouldRedirect) { // Show 100% before redirect setProgressPercentage(100); // Small delay for smooth transition setTimeout(() => { window.location.href = `/app/dashboard?session=${sessionId}`; }, 300); return true; // Stop polling } return false; // Continue polling } catch (err) { console.error('Error polling session status:', err); return false; } }, []); const handleStartDemo = async (accountType: string) => { setCreatingSession(true); setError(null); setProgressPercentage(0); setEstimatedTime(6); try { const session = await createDemoSession({ demo_account_type: accountType as 'individual_bakery' | 'central_baker', }); console.log('✅ Demo session created:', session); // Store session ID in API client apiClient.setDemoSessionId(session.session_id); // Set the virtual tenant ID in API client apiClient.setTenantId(session.virtual_tenant_id); console.log('✅ Set API client tenant ID:', session.virtual_tenant_id); // Store session info in localStorage for UI localStorage.setItem('demo_mode', 'true'); localStorage.setItem('demo_session_id', session.session_id); localStorage.setItem('demo_account_type', accountType); localStorage.setItem('demo_expires_at', session.expires_at); localStorage.setItem('demo_tenant_id', session.virtual_tenant_id); // Start polling IMMEDIATELY in parallel with other setup const pollInterval = setInterval(async () => { const shouldStop = await pollStatus(session.session_id); if (shouldStop) { clearInterval(pollInterval); } }, POLL_INTERVAL_MS); // Initialize tenant store and other setup in parallel (non-blocking) Promise.all([ import('../../stores/tenant.store').then(({ useTenantStore }) => { const demoTenant = { id: session.virtual_tenant_id, name: session.demo_config?.name || `Demo ${accountType}`, business_type: accountType === 'individual_bakery' ? 'bakery' : 'central_baker', business_model: accountType, address: session.demo_config?.address || 'Demo Address', city: session.demo_config?.city || 'Madrid', postal_code: '28001', phone: null, is_active: true, subscription_tier: 'demo', ml_model_trained: false, last_training_date: null, owner_id: 'demo-user', created_at: new Date().toISOString(), }; useTenantStore.getState().setCurrentTenant(demoTenant); console.log('✅ Initialized tenant store with demo tenant:', demoTenant); }), // Mark tour to start automatically Promise.resolve(markTourAsStartPending()), ]).catch(err => console.error('Error initializing tenant store:', err)); // Initial poll (don't wait for tenant store) const shouldStop = await pollStatus(session.session_id); if (shouldStop) { clearInterval(pollInterval); } } catch (err: any) { setError(err?.message || t('demo:errors.creating_session', 'Error al crear sesión demo')); console.error('Error creating demo session:', err); setCreatingSession(false); } }; const getAccountIcon = (accountType: string) => { return accountType === 'individual_bakery' ? Store : Factory; }; if (loading) { return (

{t('demo:loading.initial', 'Cargando cuentas demo...')}

); } return ( {/* Hero Section */}
{t('demo:hero.badge', 'Demo Interactiva')}

{t('demo:hero.title', 'Prueba El Panadero Digital')} {t('demo:hero.subtitle', 'sin compromiso')}

{t('demo:hero.description', 'Elige el tipo de panadería que se ajuste a tu negocio')}

{t('demo:hero.benefits.no_credit_card', 'Sin tarjeta de crédito')}
{t('demo:hero.benefits.access_time', '30 minutos de acceso')}
{t('demo:hero.benefits.real_data', 'Datos reales en español')}
{error && (
{error}
)} {/* Demo Account Cards */}
{demoAccounts.map((account) => { const Icon = getAccountIcon(account.account_type); return (
{/* Gradient overlay */}
{/* Header */}

{account.account_type === 'individual_bakery' ? t('demo:accounts.individual_bakery.title', 'Panadería Individual con Producción local') : t('demo:accounts.central_baker.title', 'Panadería Franquiciada con Obrador Central')}

{account.account_type === 'individual_bakery' ? t('demo:accounts.individual_bakery.subtitle', account.business_model) : t('demo:accounts.central_baker.subtitle', 'Punto de Venta + Obrador Central')}

{t('demo:accounts.demo_badge', 'DEMO')}
{/* Description */}

{account.description}

{/* Key Characteristics */}

{account.account_type === 'individual_bakery' ? t('demo:accounts.individual_bakery.characteristics.title', 'Características del negocio') : t('demo:accounts.central_baker.characteristics.title', 'Características del negocio')}

{account.account_type === 'individual_bakery' ? ( <>
{t('demo:accounts.individual_bakery.characteristics.employees', 'Empleados')}: {t('demo:accounts.individual_bakery.characteristics.employees_value', '~8')}
{t('demo:accounts.individual_bakery.characteristics.shifts', 'Turnos')}: {t('demo:accounts.individual_bakery.characteristics.shifts_value', '1/día')}
{t('demo:accounts.individual_bakery.characteristics.sales', 'Ventas')}: {t('demo:accounts.individual_bakery.characteristics.sales_value', 'Directas')}
{t('demo:accounts.individual_bakery.characteristics.products', 'Productos')}: {t('demo:accounts.individual_bakery.characteristics.products_value', 'Local')}
) : ( <>
{t('demo:accounts.central_baker.characteristics.employees', 'Empleados')}: {t('demo:accounts.central_baker.characteristics.employees_value', '~5-6')}
{t('demo:accounts.central_baker.characteristics.shifts', 'Turnos')}: {t('demo:accounts.central_baker.characteristics.shifts_value', '2/día')}
{t('demo:accounts.central_baker.characteristics.model', 'Modelo')}: {t('demo:accounts.central_baker.characteristics.model_value', 'Franquicia')}
{t('demo:accounts.central_baker.characteristics.products', 'Productos')}: {t('demo:accounts.central_baker.characteristics.products_value', 'De obrador')}
)}
{/* Features */} {account.features && account.features.length > 0 && (

{t('demo:accounts.features_title', 'Funcionalidades incluidas:')}

{account.features.map((feature, idx) => (
{feature}
))}
)} {/* CTA Button */}
); })}
{/* Footer CTA */}

{t('demo:footer.have_account', '¿Ya tienes una cuenta?')}

{t('demo:footer.login_link', 'Inicia sesión aquí')}
{/* Loading Modal Overlay */} {creatingSession && (
{/* Animated loader */}
{Math.min(progressPercentage, 100)}%

{progressPercentage >= 100 ? t('demo:loading.ready_title', '¡Listo! Redirigiendo...') : t('demo:loading.preparing_title', 'Preparando tu Demo')}

{progressPercentage >= 100 ? t('demo:loading.ready_description', 'Tu entorno está listo. Accediendo al dashboard...') : t('demo:loading.preparing_description', 'Configurando tu entorno personalizado con datos de muestra...')}

{/* Progress bar */}
{/* Estimated time - Only show if not complete */} {progressPercentage < 100 && (
{t('demo:loading.estimated_time', 'Tiempo estimado: ~{{seconds}}s', { seconds: estimatedTime })}
)} {/* Tips while loading */} {progressPercentage < 100 && (

{t('demo:loading.tip', '💡 Tip: La demo incluye datos reales de panaderías españolas para que puedas explorar todas las funcionalidades')}

)} {/* Error message if any */} {error && (
{error}
)}
)} ); }; export default DemoPage;