Web: wire AppLayout into routing + stub section pages (step 3/5)

- App.tsx: every authenticated route now nests under one
  RequireAuth + AppLayout parent route (react-router nested
  routes/<Outlet />) instead of each page wrapping its own guard.
- New routes/pages: /recettes, /liste-de-courses, /foyer, each a thin
  wrapper around a shared ComingSoonPage ("cette section arrive
  bientôt.") — no backend behind them yet, matches the "pages stub
  dédiées" choice discussed in chat.
- locales/fr/translation.json: nav labels + stub copy.

HomePage still shows its own greeting/logout card here (unchanged,
now nested inside AppLayout's <Outlet /> — briefly duplicating the
sidebar's greeting/logout) — replaced by the actual planning view in
the next commit.
This commit is contained in:
Nicolas 2026-08-16 21:04:51 +02:00
parent f4ab482b52
commit c07f8ad82e
7 changed files with 90 additions and 7 deletions

View file

@ -1,27 +1,37 @@
import { Navigate, Route, Routes } from "react-router-dom"; import { Navigate, Route, Routes } from "react-router-dom";
import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated"; import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated";
import { RequireAuth } from "./features/auth/RequireAuth"; import { RequireAuth } from "./features/auth/RequireAuth";
import { AppLayout } from "./layouts/AppLayout";
import { HomePage } from "./pages/HomePage"; import { HomePage } from "./pages/HomePage";
import { HouseholdPage } from "./pages/HouseholdPage";
import { LoginPage } from "./pages/LoginPage"; import { LoginPage } from "./pages/LoginPage";
import { RecipesPage } from "./pages/RecipesPage";
import { ShoppingListPage } from "./pages/ShoppingListPage";
import { SignupPage } from "./pages/SignupPage"; import { SignupPage } from "./pages/SignupPage";
/** /**
* Top-level route table. `/` requires an authenticated session (see * Top-level route table. Every authenticated section is nested under one
* {@link RequireAuth}); `/login` and `/signup` redirect an already-logged-in * `RequireAuth` + `AppLayout` parent route (sidebar chrome + `/auth`
* visitor to `/` instead (see {@link RedirectIfAuthenticated}). Anything * guard applied once, not per-page see {@link AppLayout}); `/login` and
* else falls back to `/`, which itself redirects to `/login` if needed. * `/signup` redirect an already-logged-in visitor to `/` instead (see
* {@link RedirectIfAuthenticated}). Anything else falls back to `/`, which
* itself redirects to `/login` if needed.
*/ */
export function App() { export function App() {
return ( return (
<Routes> <Routes>
<Route <Route
path="/"
element={ element={
<RequireAuth> <RequireAuth>
<HomePage /> <AppLayout />
</RequireAuth> </RequireAuth>
} }
/> >
<Route path="/" element={<HomePage />} />
<Route path="/recettes" element={<RecipesPage />} />
<Route path="/liste-de-courses" element={<ShoppingListPage />} />
<Route path="/foyer" element={<HouseholdPage />} />
</Route>
<Route <Route
path="/login" path="/login"
element={ element={

View file

@ -42,5 +42,17 @@
"home": { "home": {
"greeting": "Bonjour {{firstName}} {{lastName}} 👋", "greeting": "Bonjour {{firstName}} {{lastName}} 👋",
"logout": "Se déconnecter" "logout": "Se déconnecter"
},
"recipes": {
"title": "Recettes",
"comingSoon": "Cette section arrive bientôt."
},
"shoppingList": {
"title": "Liste de courses",
"comingSoon": "Cette section arrive bientôt."
},
"household": {
"title": "Foyer & profil",
"comingSoon": "Cette section arrive bientôt."
} }
} }

View file

@ -0,0 +1,12 @@
// =============================================================================
// Styles for ComingSoonPage shared by every stub section page.
// =============================================================================
.coming-soon-page {
max-width: 40rem;
p {
color: var(--color-text-muted);
font-size: var(--font-size-md);
}
}

View file

@ -0,0 +1,23 @@
import "./ComingSoonPage.scss";
interface ComingSoonPageProps {
title: string;
description: string;
}
/**
* Placeholder rendered by every section that has a route/sidebar entry but
* no real feature behind it yet (Recettes, Liste de courses, Foyer &
* profil see `RecipesPage.tsx` etc.). One shared component instead of
* three near-identical markup blocks; each page still gets its own file
* (and its own copy, via i18n) so building out a real feature later means
* rewriting one dedicated file, not splitting a generic route.
*/
export function ComingSoonPage({ title, description }: ComingSoonPageProps) {
return (
<div className="coming-soon-page">
<h1>{title}</h1>
<p>{description}</p>
</div>
);
}

View file

@ -0,0 +1,8 @@
import { useTranslation } from "react-i18next";
import { ComingSoonPage } from "./ComingSoonPage";
/** Household & profile section — routed at `/foyer`. No backend yet beyond auth, stub for now. */
export function HouseholdPage() {
const { t } = useTranslation();
return <ComingSoonPage title={t("household.title")} description={t("household.comingSoon")} />;
}

View file

@ -0,0 +1,8 @@
import { useTranslation } from "react-i18next";
import { ComingSoonPage } from "./ComingSoonPage";
/** Recipes section — routed at `/recettes`. No backend yet (see README's "Planning" section), stub for now. */
export function RecipesPage() {
const { t } = useTranslation();
return <ComingSoonPage title={t("recipes.title")} description={t("recipes.comingSoon")} />;
}

View file

@ -0,0 +1,10 @@
import { useTranslation } from "react-i18next";
import { ComingSoonPage } from "./ComingSoonPage";
/** Shopping list section — routed at `/liste-de-courses`. No backend yet, stub for now. */
export function ShoppingListPage() {
const { t } = useTranslation();
return (
<ComingSoonPage title={t("shoppingList.title")} description={t("shoppingList.comingSoon")} />
);
}