import { Capacitor } from "@capacitor/core"; import { Navigate, Outlet, Route, Routes } from "react-router-dom"; import { AdminAuthProvider } from "./features/admin/AdminAuthContext"; import { RequireAdmin } from "./features/admin/RequireAdmin"; import { UpdateBanner } from "./features/app-update/UpdateBanner"; import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated"; import { RequireAuth } from "./features/auth/RequireAuth"; import { AdminLayout } from "./layouts/AdminLayout"; import { AppLayout } from "./layouts/AppLayout"; import { CatalogPage as AdminCatalogPage } from "./pages/admin/catalog/CatalogPage"; import { CorrectionsPage as AdminCorrectionsPage } from "./pages/admin/corrections/CorrectionsPage"; import { DashboardPage as AdminDashboardPage } from "./pages/admin/dashboard/DashboardPage"; import { AdminLoginPage } from "./pages/admin/login/AdminLoginPage"; import { MessagesPage as AdminMessagesPage } from "./pages/admin/messages/MessagesPage"; import { MonitoringPage as AdminMonitoringPage } from "./pages/admin/monitoring/MonitoringPage"; import { ToursPage as AdminToursPage } from "./pages/admin/tours/ToursPage"; import { LoginPage } from "./pages/auth/LoginPage"; import { SignupPage } from "./pages/auth/SignupPage"; import { CookingSessionPage } from "./pages/cooking-session/CookingSessionPage"; import { DownloadPage } from "./pages/download/DownloadPage"; import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensPage"; import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage"; import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage"; import { OnboardingSourcesPage } from "./pages/onboarding/OnboardingSourcesPage"; import { PlanningPage } from "./pages/planning/PlanningPage"; import { ImportRecipePage } from "./pages/recipes/ImportRecipePage"; import { RecipeFormPage } from "./pages/recipes/RecipeFormPage"; import { RecipesPage } from "./pages/recipes/RecipesPage"; import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage"; import { AppSettingsPage } from "./pages/settings/AppSettingsPage"; import { ContactPage } from "./pages/settings/ContactPage"; import { CreditsPage } from "./pages/settings/CreditsPage"; import { HouseholdSettingsPage } from "./pages/settings/HouseholdSettingsPage"; import { PreferencesPage } from "./pages/settings/PreferencesPage"; import { UserPreferencesPage } from "./pages/settings/UserPreferencesPage"; import { ShoppingListPage } from "./pages/shopping-list/ShoppingListPage"; /** * Top-level route table. Every authenticated section is nested under one * `RequireAuth` + `AppLayout` parent route (sidebar chrome + `/auth` * guard applied once, not per-page — see {@link AppLayout}); `/login` and * `/signup` redirect an already-logged-in visitor to `/` instead (see * {@link RedirectIfAuthenticated}). Anything else falls back to `/`, which * itself redirects to `/login` if needed. * * `/parametres/*` (compte/préférences alimentaires/foyer/préférences * utilisateur/application/crédits) are the settings pages, reachable from * the sidebar's bottom "Paramètres" menu and the account menu (see * `AppLayout`) — nested under `AppLayout` like every other authenticated * section. `/foyer` is the old, pre-split combined page's path; it now * just redirects to `/parametres/foyer` so an existing bookmark/link keeps * working. * * `/onboarding/*` (regime/foyer/sources/allergens, in that order) is also * `RequireAuth`-gated — reached right after signup, once a session already * exists — but deliberately its own top-level route group, *not* nested * under `AppLayout`: a focused, distraction-free wizard with no sidebar, * same full-page-card language as `/login`/`/signup` (see `onboarding.scss`). * `/onboarding/sources` is conditional — only reached when the `foyer` step * created/joined a household (see `OnboardingHouseholdPage`'s `goToNextStep`); * skipped otherwise, straight to `/onboarding/allergenes`. * * `/telecharger` is the mobile-app-redirect chantier's fallback landing spot * (see `lib/mobile-app-redirect.ts`'s own doc comment) — public like * `/login`/`/signup`, but deliberately *not* wrapped in * `RedirectIfAuthenticated`: a signed-in visitor still has a legitimate * reason to land here (logged in on the web, app just not installed on * this particular device yet). * * `/admin/*` is the internal admin surface (metrics, monitoring, correction * triage, off-catalog ingredient review). It's its own top-level group, * wrapped in {@link AdminAuthProvider} so the `GET /admin/api/auth/me` session * probe only runs under `/admin` — the admin session (cookie `admin_session`, * `ADMIN_JWT_SECRET`) is entirely separate from the end-user one, so this is * never nested under `RequireAuth`. `/admin/login` is the only * unauthenticated admin route; everything else sits under `RequireAdmin` + * `AdminLayout`. * * {@link UpdateBanner} is mounted here, outside ``, rather than in * `AppLayout` — the native app-update check (issue #66) must run on every * app start, session or not, and `AppLayout` only mounts once a visitor is * past `RequireAuth`. It's a `position: fixed` strip and renders nothing on * the web, so mounting it app-wide costs the routed pages no layout. */ export function App() { return ( <> } > } /> {/* Same component for both — a master-detail layout, not a navigation to a separate page: the tab bar + table stay mounted, only RecipesPage's detail panel changes with `:id` (see RecipesPage.tsx). */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Native app only (issue #107): on the web its sections are inert (no "installed version", the update check is a no-op) or have moved to `/telecharger` (the stay-on-web toggle), and the nav entry is hidden — so a stray bookmark redirects rather than landing on an orphaned page (same pattern as `/foyer` below). */} ) : ( ) } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } > } /> } > } /> } /> } /> } /> } /> } /> } /> ); }