import { lazy, Suspense } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; import { AuthProvider, useAuth } from "./lib/auth"; import type { User } from "../shared/types"; import { AppShell } from "./components/AppShell"; import { AdminShell } from "./components/AdminShell"; import { EmployeesPage } from "./pages/EmployeesPage"; import { ServicesPage } from "./pages/ServicesPage"; import { ClientsPage } from "./pages/ClientsPage"; import { TicketsPage } from "./pages/TicketsPage"; import { ClientDetailPage } from "./pages/ClientDetailPage"; import { CashPage } from "./pages/CashPage"; import { NotificationsPage } from "./pages/NotificationsPage"; import { SettingsPage } from "./pages/SettingsPage"; import { MyPerformancePage } from "./pages/MyPerformancePage"; import { AdminOverviewPage } from "./pages/admin/AdminOverviewPage"; import { AdminBusinessesPage } from "./pages/admin/AdminBusinessesPage"; import { AdminBusinessDetailPage } from "./pages/admin/AdminBusinessDetailPage"; import BookingPage from "./pages/public/BookingPage"; // Las dos páginas públicas van aparte del bundle principal por dos razones que // apuntan en direcciones opuestas, y por eso importan las dos: quien ya tiene // sesión nunca las ve y no debe descargarlas, y quien llega a la landing no debe // descargar FullCalendar ni recharts para leer una página de venta. // // El login tiene que ser lazy junto con la landing, no solo la landing: es el único // consumidor eager de framer-motion, y con él en el chunk de entrada Rollup mete // `lib/motion` ahí también. Eso obliga al chunk de la landing a importar el de // entrada, que a su vez arrastra `charts` y `calendar`. Medido: 39 kB gzip de // motion en cada carga del panel, más recharts y FullCalendar en la landing. const LandingPage = lazy(() => import("./pages/LandingPage")); const LoginPage = lazy(() => import("./pages/LoginPage").then((m) => ({ default: m.LoginPage })) ); // Las dos únicas páginas que importan librerías pesadas: recharts (~111 KB gzip) y // FullCalendar (~76 KB gzip). Eager, viven en el chunk de entrada, que se descarga // en TODA visita — incluida la landing, que no grafica ni agenda nada. Su frontera // de Suspense está en el de AppShell. const DashboardPage = lazy(() => import("./pages/DashboardPage").then((m) => ({ default: m.DashboardPage })) ); const CalendarPage = lazy(() => import("./pages/CalendarPage").then((m) => ({ default: m.CalendarPage })) ); /** Destino del usuario según su rol. Única definición de la regla. */ export function homePathFor(user: User): string { if (user.role === "admin") return "/admin"; return user.role === "owner" ? "/dashboard" : "/calendar"; } function Splash() { return (
Cargando AgendaMax…
); } function AppRoutes() { const { user, loading } = useAuth(); if (loading) return ; const home = user ? homePathFor(user) : "/login"; const isAdmin = user?.role === "admin"; return ( {/* ---- Públicas ---- */} ) : ( }> ) } /> ) : ( }> ) } /> {/* Sin sesión, cualquier otra ruta manda al login. */} {!user && } />} {/* ---- Admin de plataforma ---- */} {user && isAdmin && ( }> } /> } /> } /> } /> )} {/* ---- Negocio ---- */} {user && !isAdmin && ( }> {user.role === "owner" ? ( } /> ) : ( } /> )} } /> } /> } /> } /> {user.role === "owner" && } />} {user.role === "owner" && } />} {user.role === "owner" && } />} {user.role === "owner" && } />} {user.role === "owner" && } />} {user.role === "owner" && } />} {/* La autorización real vive en los middlewares del servidor; esto es UX. */} } /> } /> )} ); } export default function App() { return ( {/* Reservas públicas: fuera del AuthProvider a propósito, no asumas usuario. */} } /> } /> ); }