Some checks failed
Nouvelle route /parametres/application + entrée dans SETTINGS_ITEMS (desktop et sheet mobile, un seul tableau partagé). Thème et "rejouer un tutoriel" restent inchangés dans /parametres/preferences-utilisateur (décision explicite du chantier). Le contenu affiché diffère réellement selon la plateforme, pas par simple choix cosmétique — chaque moitié de la page est inerte sur l'autre plateforme, pas juste hors-sujet : - Version installée + "Vérifier les mises à jour" (natif seulement) : useAppUpdateCheck (déjà utilisé par la bannière silencieuse existante) gagne un checkNow() qui relance la même comparaison sans attendre le throttle de 6h, réutilisant la logique existante plutôt que de la dupliquer (fetchLatestVersionResult extraite en fonction partagée). Nouveau hook useInstalledVersion (App.getInfo() natif, __APP_VERSION__ web). - "Toujours rester sur le site web" (web seulement, tout appareil) : pilote STAY_ON_WEB_PREFERENCE_KEY (déjà exporté par mobile-app-redirect.ts, ticket #84). Volontairement masqué côté natif : le WebView Capacitor tourne sur sa propre origine https://localhost, un localStorage totalement séparé de celui du vrai site web que lit attemptOpenNativeApp — modifier la préférence depuis l'app ne changerait donc jamais rien pour un visiteur mobile réel. Documenté explicitement dans le commentaire de la page pour ne pas réintroduire ce contrôle-fantôme plus tard. Couverture Cypress : nouveau app-settings-page.cy.ts (contenu web, persistance du toggle après reload) ; sidebar.cy.ts étendu pour couvrir le nouveau lien. Le déclenchement natif réel (App.getInfo(), l'appel réseau de checkNow) n'a pas de point d'entrée testable depuis un run Cypress web — même posture déjà acceptée pour useAppUpdateCheck/ UpdateBanner, la logique de comparaison pure reste couverte par version-compare.cy.tsx (inchangé). Suite complète : biome check, tsc -b --force, build, 146/146 e2e, 71/71 composants — tous verts. Closes #86 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
181 lines
8.1 KiB
TypeScript
181 lines
8.1 KiB
TypeScript
import { Navigate, Outlet, Route, Routes } from "react-router-dom";
|
|
import { AdminAuthProvider } from "./features/admin/AdminAuthContext";
|
|
import { RequireAdmin } from "./features/admin/RequireAdmin";
|
|
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`.
|
|
*/
|
|
export function App() {
|
|
return (
|
|
<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 />} />
|
|
<Route path="/parametres/application" element={<AppSettingsPage />} />
|
|
<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>
|
|
);
|
|
}
|