import { useState } from "react"; import { useTranslation } from "react-i18next"; import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom"; import { BottomSheet } from "../components/ui/BottomSheet"; import { UpdateBanner } from "../features/app-update/UpdateBanner"; import { useAuth } from "../features/auth/AuthContext"; import { useIsMobileViewport } from "../lib/useIsMobileViewport"; import "./AppLayout.scss"; import { AccountIcon, ChevronLeftIcon, CreditsIcon, DietPreferencesIcon, HouseholdIcon, MoreIcon, PlanningIcon, RecipesIcon, SettingsIcon, ShoppingListIcon, UserPreferencesIcon, } from "./nav-icons"; /** `localStorage` key persisting {@link AppLayout}'s collapsed/expanded sidebar state across reloads. Desktop-only — the mobile bottom nav has nothing to collapse. */ const SIDEBAR_COLLAPSED_KEY = "batchcooking:sidebarCollapsed"; /** * One entry in the app's main nav. `key` maps to `layout.nav.` in * `locales/fr/translation.json` — adding a section is one array entry plus * one locale key, no other file to touch. Shared verbatim between the * desktop sidebar and the mobile bottom tab bar (see {@link MobileBottomNav}) * — same items, same routing, just a different container. */ const NAV_ITEMS = [ { to: "/", key: "planning", Icon: PlanningIcon, tourTarget: "nav-planning" }, { to: "/recettes", key: "recipes", Icon: RecipesIcon, tourTarget: "nav-recipes" }, { to: "/liste-de-courses", key: "shoppingList", Icon: ShoppingListIcon, tourTarget: "nav-shopping-list", }, ] as const; /** * One entry in the "Paramètres" nav — revealed by {@link SettingsMenu} on * desktop, listed inside {@link MobileMoreSheet} on mobile. `key` maps to * `layout.settings.nav.`. */ const SETTINGS_ITEMS = [ { to: "/parametres/compte", key: "account", Icon: AccountIcon }, { to: "/parametres/preferences", key: "preferences", Icon: DietPreferencesIcon }, { to: "/parametres/foyer", key: "household", Icon: HouseholdIcon }, { to: "/parametres/preferences-utilisateur", key: "userPreferences", Icon: UserPreferencesIcon }, { to: "/parametres/credits", key: "credits", Icon: CreditsIcon }, ] as const; /** * Shell for every authenticated page: on desktop/tablet, a sidebar (brand, * section nav, a collapsible "Paramètres" nav, and the account menu at the * bottom); on a phone, a bottom tab bar instead (see {@link MobileBottomNav}) * — a genuinely different component tree, not a CSS-only reflow of the same * one. Either way, a main content area renders the matched child route via * ``. * * Which shell renders is decided once, here, via * {@link useIsMobileViewport} — the mobile/tablet UI chantier's own audit * found this app had *zero* precedent for branching actual markup by * viewport before this component; every other consumer of that hook (the * planning popovers, in a later ticket of the same chantier) should follow * this same "check once at the shell/container level" pattern rather than * re-checking deep inside a leaf component. * * The desktop sidebar itself can still be collapsed to an icon-only rail * (see `isCollapsed`/`SIDEBAR_COLLAPSED_KEY`) — every nav item keeps its * icon and gains a `title` tooltip, its label just hides via CSS * (`.app-sidebar.collapsed .label`, see AppLayout.scss), so collapsing is a * single class toggle on the `