2025-08-03 19:49:57 +02:00
|
|
|
import React, { useState, useEffect } from 'react';
|
2025-08-03 19:23:20 +02:00
|
|
|
import { Toaster } from 'react-hot-toast';
|
2025-08-11 07:01:08 +02:00
|
|
|
import toast from 'react-hot-toast';
|
2025-08-03 19:23:20 +02:00
|
|
|
|
|
|
|
|
// Components
|
|
|
|
|
import LoadingSpinner from './components/ui/LoadingSpinner';
|
|
|
|
|
import ErrorBoundary from './components/ErrorBoundary';
|
|
|
|
|
import LandingPage from './pages/landing/LandingPage';
|
|
|
|
|
import LoginPage from './pages/auth/LoginPage';
|
|
|
|
|
import RegisterPage from './pages/auth/RegisterPage';
|
|
|
|
|
import OnboardingPage from './pages/onboarding/OnboardingPage';
|
|
|
|
|
import DashboardPage from './pages/dashboard/DashboardPage';
|
2025-08-08 19:21:23 +02:00
|
|
|
import ProductionPage from './pages/production/ProductionPage';
|
2025-08-03 19:23:20 +02:00
|
|
|
import ForecastPage from './pages/forecast/ForecastPage';
|
|
|
|
|
import OrdersPage from './pages/orders/OrdersPage';
|
2025-08-15 17:53:59 +02:00
|
|
|
import InventoryPage from './pages/inventory/InventoryPage';
|
|
|
|
|
import SalesPage from './pages/sales/SalesPage';
|
2025-08-03 19:23:20 +02:00
|
|
|
import SettingsPage from './pages/settings/SettingsPage';
|
|
|
|
|
import Layout from './components/layout/Layout';
|
|
|
|
|
|
|
|
|
|
// Store and types
|
|
|
|
|
import { store } from './store';
|
|
|
|
|
import { Provider } from 'react-redux';
|
|
|
|
|
|
2025-08-11 07:01:08 +02:00
|
|
|
// Onboarding utilities
|
|
|
|
|
import { OnboardingRouter, type NextAction, type RoutingDecision } from './utils/onboardingRouter';
|
|
|
|
|
|
2025-08-03 19:23:20 +02:00
|
|
|
// i18n
|
|
|
|
|
import './i18n';
|
|
|
|
|
|
|
|
|
|
// Global styles
|
|
|
|
|
import './styles/globals.css';
|
|
|
|
|
|
2025-08-15 17:53:59 +02:00
|
|
|
type CurrentPage = 'landing' | 'login' | 'register' | 'onboarding' | 'dashboard' | 'reports' | 'orders' | 'production' | 'inventory' | 'sales' | 'settings';
|
2025-08-03 19:23:20 +02:00
|
|
|
|
|
|
|
|
interface User {
|
|
|
|
|
id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
fullName: string;
|
|
|
|
|
role: string;
|
|
|
|
|
isOnboardingComplete: boolean;
|
2025-08-11 07:01:08 +02:00
|
|
|
tenant_id?: string;
|
2025-08-03 19:23:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface AppState {
|
|
|
|
|
isAuthenticated: boolean;
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
user: User | null;
|
|
|
|
|
currentPage: CurrentPage;
|
2025-08-11 07:01:08 +02:00
|
|
|
routingDecision: RoutingDecision | null;
|
2025-08-03 19:23:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const LoadingFallback = () => (
|
|
|
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<LoadingSpinner size="lg" />
|
|
|
|
|
<p className="mt-4 text-gray-600">Cargando PanIA...</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const App: React.FC = () => {
|
|
|
|
|
const [appState, setAppState] = useState<AppState>({
|
|
|
|
|
isAuthenticated: false,
|
|
|
|
|
isLoading: true,
|
|
|
|
|
user: null,
|
2025-08-11 07:01:08 +02:00
|
|
|
currentPage: 'landing',
|
|
|
|
|
routingDecision: null
|
2025-08-03 19:23:20 +02:00
|
|
|
});
|
|
|
|
|
|
2025-08-11 07:01:08 +02:00
|
|
|
// Helper function to map NextAction to CurrentPage
|
|
|
|
|
const mapActionToPage = (action: NextAction): CurrentPage => {
|
|
|
|
|
const actionPageMap: Record<NextAction, CurrentPage> = {
|
|
|
|
|
'register': 'register',
|
|
|
|
|
'login': 'login',
|
|
|
|
|
'onboarding_bakery': 'onboarding',
|
|
|
|
|
'onboarding_data': 'onboarding',
|
|
|
|
|
'onboarding_training': 'onboarding',
|
|
|
|
|
'dashboard': 'dashboard',
|
|
|
|
|
'landing': 'landing'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return actionPageMap[action] || 'landing';
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-03 19:23:20 +02:00
|
|
|
// Initialize app and check authentication
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const initializeApp = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// Check for stored auth token
|
|
|
|
|
const token = localStorage.getItem('auth_token');
|
|
|
|
|
const userData = localStorage.getItem('user_data');
|
|
|
|
|
|
|
|
|
|
if (token && userData) {
|
|
|
|
|
const user = JSON.parse(userData);
|
2025-08-11 07:01:08 +02:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Use enhanced onboarding router to determine next action
|
|
|
|
|
const routingDecision = await OnboardingRouter.getNextActionForUser();
|
|
|
|
|
const nextPage = mapActionToPage(routingDecision.nextAction);
|
|
|
|
|
|
|
|
|
|
setAppState({
|
|
|
|
|
isAuthenticated: true,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
user,
|
|
|
|
|
currentPage: nextPage,
|
|
|
|
|
routingDecision
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Show welcome message with progress
|
|
|
|
|
if (routingDecision.message && routingDecision.completionPercentage > 0) {
|
|
|
|
|
toast.success(`Welcome back! ${routingDecision.message} (${Math.round(routingDecision.completionPercentage)}% complete)`);
|
|
|
|
|
}
|
|
|
|
|
} catch (onboardingError) {
|
|
|
|
|
// Fallback to legacy logic if onboarding API fails
|
|
|
|
|
console.warn('Onboarding API failed, using legacy logic:', onboardingError);
|
|
|
|
|
setAppState({
|
|
|
|
|
isAuthenticated: true,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
user,
|
|
|
|
|
currentPage: user.isOnboardingComplete ? 'dashboard' : 'onboarding',
|
|
|
|
|
routingDecision: null
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-03 19:23:20 +02:00
|
|
|
} else {
|
2025-08-11 07:01:08 +02:00
|
|
|
// Unauthenticated user
|
|
|
|
|
const routingDecision = OnboardingRouter.getNextActionForGuest();
|
2025-08-03 19:23:20 +02:00
|
|
|
setAppState(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
isLoading: false,
|
2025-08-11 07:01:08 +02:00
|
|
|
currentPage: 'landing',
|
|
|
|
|
routingDecision
|
2025-08-03 19:23:20 +02:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('App initialization error:', error);
|
|
|
|
|
setAppState(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
isLoading: false,
|
2025-08-11 07:01:08 +02:00
|
|
|
currentPage: 'landing',
|
|
|
|
|
routingDecision: null
|
2025-08-03 19:23:20 +02:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
initializeApp();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-08-11 07:01:08 +02:00
|
|
|
const handleLogin = async (user: User, token: string) => {
|
2025-08-03 19:23:20 +02:00
|
|
|
localStorage.setItem('auth_token', token);
|
|
|
|
|
localStorage.setItem('user_data', JSON.stringify(user));
|
|
|
|
|
|
2025-08-11 07:01:08 +02:00
|
|
|
try {
|
|
|
|
|
// Mark user registration as complete
|
|
|
|
|
await OnboardingRouter.completeStep('user_registered', {
|
|
|
|
|
user_id: user.id,
|
|
|
|
|
email: user.email,
|
|
|
|
|
login_type: 'existing_user'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Determine next action based on current progress
|
|
|
|
|
const routingDecision = await OnboardingRouter.getNextActionForUser();
|
|
|
|
|
const nextPage = mapActionToPage(routingDecision.nextAction);
|
|
|
|
|
|
|
|
|
|
setAppState({
|
|
|
|
|
isAuthenticated: true,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
user,
|
|
|
|
|
currentPage: nextPage,
|
|
|
|
|
routingDecision
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Show progress message
|
|
|
|
|
if (routingDecision.message) {
|
|
|
|
|
toast.success(routingDecision.message);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Enhanced login routing failed, using fallback:', error);
|
|
|
|
|
// Fallback to legacy logic
|
|
|
|
|
setAppState({
|
|
|
|
|
isAuthenticated: true,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
user,
|
|
|
|
|
currentPage: user.isOnboardingComplete ? 'dashboard' : 'onboarding',
|
|
|
|
|
routingDecision: null
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-03 19:23:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleLogout = () => {
|
|
|
|
|
localStorage.removeItem('auth_token');
|
|
|
|
|
localStorage.removeItem('user_data');
|
|
|
|
|
|
|
|
|
|
setAppState({
|
|
|
|
|
isAuthenticated: false,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
user: null,
|
2025-08-12 18:17:30 +02:00
|
|
|
currentPage: 'landing', // 👈 Return to landing page after logout
|
|
|
|
|
routingDecision: null
|
2025-08-03 19:23:20 +02:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-11 07:01:08 +02:00
|
|
|
const handleOnboardingComplete = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// Mark all onboarding steps as complete
|
|
|
|
|
await OnboardingRouter.completeStep('dashboard_accessible', {
|
|
|
|
|
completion_time: new Date().toISOString(),
|
|
|
|
|
user_id: appState.user?.id
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const updatedUser = { ...appState.user!, isOnboardingComplete: true };
|
|
|
|
|
localStorage.setItem('user_data', JSON.stringify(updatedUser));
|
|
|
|
|
|
|
|
|
|
setAppState(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
user: updatedUser,
|
|
|
|
|
currentPage: 'dashboard',
|
|
|
|
|
routingDecision: {
|
|
|
|
|
nextAction: 'dashboard',
|
|
|
|
|
currentStep: 'dashboard_accessible',
|
|
|
|
|
completionPercentage: 100,
|
|
|
|
|
message: 'Welcome to your PanIA dashboard!'
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
toast.success('¡Configuración completada! Bienvenido a tu dashboard de PanIA 🎉');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Enhanced onboarding completion failed, using fallback:', error);
|
|
|
|
|
// Fallback logic
|
|
|
|
|
const updatedUser = { ...appState.user!, isOnboardingComplete: true };
|
|
|
|
|
localStorage.setItem('user_data', JSON.stringify(updatedUser));
|
|
|
|
|
|
|
|
|
|
setAppState(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
user: updatedUser,
|
|
|
|
|
currentPage: 'dashboard'
|
|
|
|
|
}));
|
|
|
|
|
}
|
2025-08-03 19:23:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const navigateTo = (page: CurrentPage) => {
|
|
|
|
|
setAppState(prev => ({ ...prev, currentPage: page }));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (appState.isLoading) {
|
|
|
|
|
return <LoadingFallback />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const renderCurrentPage = () => {
|
|
|
|
|
// Public pages (non-authenticated)
|
|
|
|
|
if (!appState.isAuthenticated) {
|
|
|
|
|
switch (appState.currentPage) {
|
|
|
|
|
case 'login':
|
|
|
|
|
return (
|
|
|
|
|
<LoginPage
|
|
|
|
|
onLogin={handleLogin}
|
|
|
|
|
onNavigateToRegister={() => navigateTo('register')}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
case 'register':
|
|
|
|
|
return (
|
|
|
|
|
<RegisterPage
|
|
|
|
|
onLogin={handleLogin}
|
|
|
|
|
onNavigateToLogin={() => navigateTo('login')}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
default:
|
|
|
|
|
return (
|
|
|
|
|
<LandingPage
|
|
|
|
|
onNavigateToLogin={() => navigateTo('login')}
|
|
|
|
|
onNavigateToRegister={() => navigateTo('register')}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Authenticated pages
|
|
|
|
|
if (!appState.user?.isOnboardingComplete && appState.currentPage !== 'settings') {
|
|
|
|
|
return (
|
|
|
|
|
<OnboardingPage
|
|
|
|
|
user={appState.user!}
|
|
|
|
|
onComplete={handleOnboardingComplete}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Main app pages with layout
|
|
|
|
|
const pageComponent = () => {
|
|
|
|
|
switch (appState.currentPage) {
|
2025-08-08 19:21:23 +02:00
|
|
|
case 'reports':
|
2025-08-03 19:23:20 +02:00
|
|
|
return <ForecastPage />;
|
|
|
|
|
case 'orders':
|
|
|
|
|
return <OrdersPage />;
|
2025-08-08 19:21:23 +02:00
|
|
|
case 'production':
|
|
|
|
|
return <ProductionPage />;
|
2025-08-15 17:53:59 +02:00
|
|
|
case 'inventory':
|
|
|
|
|
return <InventoryPage />;
|
|
|
|
|
case 'sales':
|
|
|
|
|
return <SalesPage />;
|
2025-08-03 19:23:20 +02:00
|
|
|
case 'settings':
|
|
|
|
|
return <SettingsPage user={appState.user!} onLogout={handleLogout} />;
|
|
|
|
|
default:
|
2025-08-08 19:21:23 +02:00
|
|
|
return <DashboardPage
|
|
|
|
|
onNavigateToOrders={() => navigateTo('orders')}
|
|
|
|
|
onNavigateToReports={() => navigateTo('reports')}
|
|
|
|
|
onNavigateToProduction={() => navigateTo('production')}
|
2025-08-15 17:53:59 +02:00
|
|
|
onNavigateToInventory={() => navigateTo('inventory')}
|
|
|
|
|
onNavigateToSales={() => navigateTo('sales')}
|
2025-08-08 19:21:23 +02:00
|
|
|
/>;
|
2025-08-03 19:23:20 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Layout
|
|
|
|
|
user={appState.user!}
|
|
|
|
|
currentPage={appState.currentPage}
|
|
|
|
|
onNavigate={navigateTo}
|
|
|
|
|
onLogout={handleLogout}
|
|
|
|
|
>
|
|
|
|
|
{pageComponent()}
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Provider store={store}>
|
|
|
|
|
<ErrorBoundary>
|
|
|
|
|
<div className="App min-h-screen bg-gray-50">
|
|
|
|
|
{renderCurrentPage()}
|
|
|
|
|
|
|
|
|
|
{/* Global Toast Notifications */}
|
|
|
|
|
<Toaster
|
|
|
|
|
position="top-right"
|
|
|
|
|
toastOptions={{
|
|
|
|
|
duration: 4000,
|
|
|
|
|
style: {
|
|
|
|
|
background: '#fff',
|
|
|
|
|
color: '#333',
|
|
|
|
|
boxShadow: '0 4px 25px -5px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
borderRadius: '12px',
|
|
|
|
|
padding: '16px',
|
|
|
|
|
},
|
|
|
|
|
success: {
|
|
|
|
|
iconTheme: {
|
|
|
|
|
primary: '#22c55e',
|
|
|
|
|
secondary: '#fff',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
error: {
|
|
|
|
|
iconTheme: {
|
|
|
|
|
primary: '#ef4444',
|
|
|
|
|
secondary: '#fff',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</ErrorBoundary>
|
|
|
|
|
</Provider>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default App;
|