batchCooking/apps/web/src/App.tsx
Nicolas acab18ac4a feat(recipes): catalogue v2 - visibilité, favoris, régimes et catalogue d'ingrédients exhaustif
Recipe catalog v2:
- Recipe gagne visibility (PERSONAL/HOUSE/PUBLIC), authorId, authorHouseId
- Favoris par utilisateur (RecipeFavorite), régimes associés (RecipeDiet)
- Aliments "pas aimés" par utilisateur (UserProfileDislikedIngredient),
  distinct des allergies médicales
- API: GET /recipes?tab=favoris|perso|foyer|publique avec contrôle d'accès
  complet, POST/DELETE /recipes/:id/favorite, édition/suppression réservées
  à l'auteur (403 NOT_RECIPE_AUTHOR), GET/PATCH /profile/disliked-ingredients
- Frontend: vue maître-détail (onglets + tableau + panneau détail),
  formulaire enrichi (visibilité, régimes), section préférences pour les
  aliments pas aimés

Catalogue d'ingrédients de référence:
- Extension du seed de 39 à ~430 ingrédients (viandes, poissons/fruits de
  mer, légumes, fruits, féculents, condiments/sauces, épices/herbes, pains
  à sandwich, cuisines italienne/asiatique/mexicaine/maghrébine, liquides
  et boissons de cuisine, bouillons/fonds)
- Chaque ingrédient lié à ses allergènes UE (IngredientAllergy) — les 14
  allergènes réglementaires restent tous couverts
- Seeding optimisé en requêtes groupées (createMany/diff ciblé) plutôt
  qu'un upsert par ligne, pour garder resetDatabase() rapide en test

Tests: 102 tests Mocha + 32 scénarios BDD, tous verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 09:29:11 +02:00

109 lines
4.4 KiB
TypeScript

import { Navigate, Route, Routes } from "react-router-dom";
import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated";
import { RequireAuth } from "./features/auth/RequireAuth";
import { AppLayout } from "./layouts/AppLayout";
import { LoginPage } from "./pages/LoginPage";
import { PlanningPage } from "./pages/PlanningPage";
import { RecipeFormPage } from "./pages/RecipeFormPage";
import { RecipesPage } from "./pages/RecipesPage";
import { ShoppingListPage } from "./pages/ShoppingListPage";
import { SignupPage } from "./pages/SignupPage";
import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensPage";
import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage";
import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage";
import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage";
import { HouseholdSettingsPage } from "./pages/settings/HouseholdSettingsPage";
import { PreferencesPage } from "./pages/settings/PreferencesPage";
import { UserPreferencesPage } from "./pages/settings/UserPreferencesPage";
/**
* 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) 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/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`).
*/
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/:id" element={<RecipesPage />} />
<Route path="/recettes/:id/modifier" element={<RecipeFormPage />} />
<Route path="/liste-de-courses" element={<ShoppingListPage />} />
<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="/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/allergenes"
element={
<RequireAuth>
<OnboardingAllergensPage />
</RequireAuth>
}
/>
<Route
path="/login"
element={
<RedirectIfAuthenticated>
<LoginPage />
</RedirectIfAuthenticated>
}
/>
<Route
path="/signup"
element={
<RedirectIfAuthenticated>
<SignupPage />
</RedirectIfAuthenticated>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
}