All checks were successful
Ses sections n'ont pas de sens sur le web (pas de « version installée », la vérif de mise à jour y est un no-op) et son seul réglage web-pertinent — la case « toujours rester sur le site web » — vit mieux dans le flux qu'il pilote. - `AppLayout.tsx` : `SETTINGS_ITEMS` gagne un `nativeOnly`, filtré une fois au chargement du module (`settingsNavItems`) via `Capacitor.isNativePlatform()` — l'entrée disparaît du menu Paramètres (sidebar + feuille « Plus »). - `App.tsx` : la route `/parametres/application` redirige vers `/parametres/compte` sur le web, comme le précédent `/foyer`, pour qu'un bookmark ne tombe pas sur une page orpheline. - `AppSettingsPage.tsx` : devient natif-only — la branche `webHint` et la section « rester sur le site web » sont retirées. - La case « toujours rester sur le site web » déménage sur `/telecharger` (`DownloadPage`) : page publique qu'un visiteur non connecté sur navigateur téléphone peut atteindre, et c'est exactement le flux que la préférence concerne. `localStorage` sur l'origine web publique, là où `attemptOpenNativeApp` la lit. i18n `appSettings.redirect.*` → `download.stayOnWeb.*` ; `appSettings.version.webHint` supprimée. Tests : `sidebar.cy.ts` / `app-settings-page.cy.ts` — l'entrée et la page sont absentes sur le web, la route redirige. `download-page.cy.ts` — la case « rester sur le site web » par défaut décochée, persistée au reload. Local OK (sidebar 5/5, app-settings 2/2, download 3/3). Closes #107 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
206 lines
9.4 KiB
TypeScript
206 lines
9.4 KiB
TypeScript
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 { 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 { 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 `<Routes>`, 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 (
|
|
<>
|
|
<UpdateBanner />
|
|
<Routes>
|
|
<Route
|
|
element={
|
|
<RequireAuth>
|
|
<AppLayout />
|
|
</RequireAuth>
|
|
}
|
|
>
|
|
<Route path="/" element={<PlanningPage />} />
|
|
{/* 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). */}
|
|
<Route path="/recettes" element={<RecipesPage />} />
|
|
<Route path="/recettes/nouvelle" element={<RecipeFormPage />} />
|
|
<Route path="/recettes/importer/:sourceKey/:externalId" element={<ImportRecipePage />} />
|
|
<Route path="/recettes/sources/:sourceKey/:externalId" element={<RecipesPage />} />
|
|
<Route path="/recettes/:id" element={<RecipesPage />} />
|
|
<Route path="/recettes/:id/modifier" element={<RecipeFormPage />} />
|
|
<Route path="/liste-de-courses" element={<ShoppingListPage />} />
|
|
<Route path="/cuisiner" element={<CookingSessionPage />} />
|
|
<Route path="/parametres/compte" element={<AccountSettingsPage />} />
|
|
<Route path="/parametres/preferences" element={<PreferencesPage />} />
|
|
<Route path="/parametres/foyer" element={<HouseholdSettingsPage />} />
|
|
<Route path="/parametres/preferences-utilisateur" element={<UserPreferencesPage />} />
|
|
{/* 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). */}
|
|
<Route
|
|
path="/parametres/application"
|
|
element={
|
|
Capacitor.isNativePlatform() ? (
|
|
<AppSettingsPage />
|
|
) : (
|
|
<Navigate to="/parametres/compte" replace />
|
|
)
|
|
}
|
|
/>
|
|
<Route path="/parametres/credits" element={<CreditsPage />} />
|
|
<Route path="/foyer" element={<Navigate to="/parametres/foyer" replace />} />
|
|
</Route>
|
|
<Route
|
|
path="/onboarding/regime"
|
|
element={
|
|
<RequireAuth>
|
|
<OnboardingDietPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/onboarding/foyer"
|
|
element={
|
|
<RequireAuth>
|
|
<OnboardingHouseholdPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/onboarding/sources"
|
|
element={
|
|
<RequireAuth>
|
|
<OnboardingSourcesPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/onboarding/allergenes"
|
|
element={
|
|
<RequireAuth>
|
|
<OnboardingAllergensPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/login"
|
|
element={
|
|
<RedirectIfAuthenticated>
|
|
<LoginPage />
|
|
</RedirectIfAuthenticated>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/signup"
|
|
element={
|
|
<RedirectIfAuthenticated>
|
|
<SignupPage />
|
|
</RedirectIfAuthenticated>
|
|
}
|
|
/>
|
|
<Route path="/telecharger" element={<DownloadPage />} />
|
|
<Route
|
|
element={
|
|
<AdminAuthProvider>
|
|
<Outlet />
|
|
</AdminAuthProvider>
|
|
}
|
|
>
|
|
<Route path="/admin/login" element={<AdminLoginPage />} />
|
|
<Route
|
|
path="/admin"
|
|
element={
|
|
<RequireAdmin>
|
|
<AdminLayout />
|
|
</RequireAdmin>
|
|
}
|
|
>
|
|
<Route index element={<AdminDashboardPage />} />
|
|
<Route path="monitoring" element={<AdminMonitoringPage />} />
|
|
<Route path="corrections" element={<AdminCorrectionsPage />} />
|
|
<Route path="catalogue" element={<AdminCatalogPage />} />
|
|
<Route path="didacticiels" element={<AdminToursPage />} />
|
|
</Route>
|
|
</Route>
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</>
|
|
);
|
|
}
|