Backend : - `createRecipe` refactorisé en fine enveloppe autour d'un nouvel helper interne `createRecipeInternal`, paramétré par une source d'import optionnelle ; nouvelle fonction exportée `createImportedRecipe` qui réutilise toute la validation ingrédients/unités/diets et le matching des tech steps, sans dupliquer cette logique. - La locale de l'adaptateur source est propagée jusqu'au chargement des `TechStepMapping`, pour que le texte anglais (TheMealDB, etc.) soit matché contre le bon jeu de règles au lieu du défaut français. - Nouvel endpoint `POST /sources/:sourceKey/import/:externalId` — valide le payload via `createRecipeSchema` (même schéma qu'une création manuelle) et persiste une vraie `Recipe` liée à la source (`sourceId`/`externalId`). - Nouveau code d'erreur `RECIPE_ALREADY_IMPORTED` (4022) quand l'item a déjà été importé pour ce foyer. Frontend : - `ImportRecipePage` (nouvelle page, `/recettes/importer/:sourceKey/:externalId`) — pré-remplit le formulaire depuis `previewSourceItem`, en miroir de `RecipeFormPage` (mêmes sous-composants : `IngredientRow`, `IngredientPicker`, `StepListEditor`, `DietTagSelect`). Ajoute une section dédiée aux lignes d'ingrédients non résolues automatiquement : l'utilisateur choisit un ingrédient réel via l'`IngredientPicker` existant ou retire la ligne — aucune recette invalide n'est jamais soumise, le bouton d'import reste désactivé tant qu'il en reste. - `SourceItemPreviewPanel` gagne un lien « Importer cette recette » vers cet écran. Tests : - Mocha (`apps/api/test/sources.test.ts`) : 6 nouveaux tests sur `POST /sources/:sourceKey/import/:externalId` (payload valide, ingrédient/unité inconnus, déjà importé, deux foyers distincts, locale de la source respectée pour les tech steps). 282 tests passent au total, aucune régression. - Cypress : nouveau scénario Gherkin bout-en-bout dans `recipe-sources.feature` (parcourir → prévisualiser → importer → résoudre un ingrédient non reconnu → confirmer → atterrir sur la recette sauvegardée). Steps d'édition d'ingrédients/étapes génériques déplacés de `recipe-form.ts` vers `cypress/support/step_definitions/common.steps.ts`, réutilisables par ce nouveau scénario. Suite : étape 4 (ajouter au planning déclenche l'import si nécessaire). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
125 lines
5.2 KiB
TypeScript
125 lines
5.2 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 { ImportRecipePage } from "./pages/ImportRecipePage";
|
|
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 { OnboardingSourcesPage } from "./pages/onboarding/OnboardingSourcesPage";
|
|
import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage";
|
|
import { CreditsPage } from "./pages/settings/CreditsPage";
|
|
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/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`.
|
|
*/
|
|
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/: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="/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="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
);
|
|
}
|