Files
bakery-ia/frontend/src/App.tsx

363 lines
11 KiB
TypeScript
Raw Normal View History

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;