From 7abe030bb97de6a3abd25558f402653ca2ec2f6c Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 17 Aug 2026 10:45:58 +0200 Subject: [PATCH] =?UTF-8?q?Web:=20pages=20Compte/Pr=C3=A9f=C3=A9rences/Foy?= =?UTF-8?q?er,=20client=20API=20et=20d=C3=A9placement=20des=20menus=20(ste?= =?UTF-8?q?p=206/8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pages/settings/AccountSettingsPage: identité + suppression de compte (confirmation en deux temps, mot de passe requis) - pages/settings/PreferencesPage: régime + allergies/intolérances, sorti de HouseholdPage (attribut du profil, pas du foyer) - pages/settings/HouseholdSettingsPage: sans foyer → créer/rejoindre ; avec foyer → renommer (hot-save), code d'invitation, membres, retirer un membre / supprimer le foyer (admin) ou le quitter - HouseholdPage.tsx/.scss supprimés (contenu réparti ci-dessus) - apiClient: createHouse/joinHouse/leaveHouse/deleteHouse/ removeHouseMember/deleteAccount - AuthContext: deleteAccount() - i18n: namespaces account/preferences réorganisés, household réduit au foyer, common.saving/saved factorisées --- apps/web/src/App.tsx | 42 +- apps/web/src/api/client.ts | 32 +- apps/web/src/features/auth/AuthContext.tsx | 13 +- .../src/features/profile/AllergySelect.tsx | 4 +- apps/web/src/features/profile/DietSelect.tsx | 12 +- .../src/features/profile/HouseNameField.tsx | 2 +- .../src/features/profile/profile-forms.scss | 9 +- apps/web/src/locales/fr/translation.json | 84 +++- apps/web/src/pages/HouseholdPage.scss | 46 --- apps/web/src/pages/HouseholdPage.tsx | 239 ----------- .../pages/settings/AccountSettingsPage.tsx | 106 +++++ .../pages/settings/HouseholdSettingsPage.tsx | 386 ++++++++++++++++++ .../src/pages/settings/PreferencesPage.tsx | 185 +++++++++ .../src/pages/settings/settings-pages.scss | 151 +++++++ 14 files changed, 984 insertions(+), 327 deletions(-) delete mode 100644 apps/web/src/pages/HouseholdPage.scss delete mode 100644 apps/web/src/pages/HouseholdPage.tsx create mode 100644 apps/web/src/pages/settings/AccountSettingsPage.tsx create mode 100644 apps/web/src/pages/settings/HouseholdSettingsPage.tsx create mode 100644 apps/web/src/pages/settings/PreferencesPage.tsx create mode 100644 apps/web/src/pages/settings/settings-pages.scss diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 94cc69c..ec6d75f 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -3,7 +3,6 @@ import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated import { RequireAuth } from "./features/auth/RequireAuth"; import { AppLayout } from "./layouts/AppLayout"; import { HomePage } from "./pages/HomePage"; -import { HouseholdPage } from "./pages/HouseholdPage"; import { LoginPage } from "./pages/LoginPage"; import { RecipesPage } from "./pages/RecipesPage"; import { ShoppingListPage } from "./pages/ShoppingListPage"; @@ -11,6 +10,9 @@ 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"; /** * Top-level route table. Every authenticated section is nested under one @@ -20,11 +22,18 @@ import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdP * {@link RedirectIfAuthenticated}). Anything else falls back to `/`, which * itself redirects to `/login` if needed. * - * `/onboarding/*` (household/regime/allergens) 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`). + * `/parametres/*` (compte/préférences/foyer) 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 ( @@ -39,16 +48,11 @@ export function App() { } /> } /> } /> - } /> + } /> + } /> + } /> + } /> - - - - } - /> } /> + + + + } + /> { + return this.request("/auth/me", { method: "DELETE", body: JSON.stringify({ password }) }); + } + /** Fetches the current user's household's planning for today, or `null` if there isn't one yet. */ public getCurrentPlanning(): Promise { return this.request("/planning/current"); @@ -113,7 +118,7 @@ export class ApiClient { return this.request("/reference/allergies"); } - /** Fetches the current user's household. */ + /** Fetches the current user's household (with its member list), or `null` if they don't have one yet. */ public getCurrentHouse(): Promise { return this.request("/house/current"); } @@ -123,6 +128,31 @@ export class ApiClient { return this.request("/house/current", { method: "PATCH", body: JSON.stringify({ name }) }); } + /** Creates a new household, with the caller as its admin — rejects with `ALREADY_HAS_HOUSE` if they already belong to one. */ + public createHouse(name: string): Promise { + return this.request("/house", { method: "POST", body: JSON.stringify({ name }) }); + } + + /** Joins an existing household by invite code — rejects with `ALREADY_HAS_HOUSE`/`INVITE_CODE_NOT_FOUND`. */ + public joinHouse(inviteCode: string): Promise { + return this.request("/house/join", { method: "POST", body: JSON.stringify({ inviteCode }) }); + } + + /** Removes the current user from their household — hands off adminship or deletes the household if they were its last member (see the API's `house.service.ts`). */ + public leaveHouse(): Promise { + return this.request("/house/leave", { method: "POST" }); + } + + /** Deletes the current user's household outright — every member loses it. Admin-only. */ + public deleteHouse(): Promise { + return this.request("/house/current", { method: "DELETE" }); + } + + /** Removes one specific member from the current user's household. Admin-only. */ + public removeHouseMember(memberId: number): Promise { + return this.request(`/house/members/${memberId}`, { method: "DELETE" }); + } + /** Sets (or clears, with `null`) the current user's dietary regime. */ public updateDiet(dietId: number | null): Promise { return this.request("/profile/diet", { method: "PATCH", body: JSON.stringify({ dietId }) }); diff --git a/apps/web/src/features/auth/AuthContext.tsx b/apps/web/src/features/auth/AuthContext.tsx index cca04ac..1a8b58a 100644 --- a/apps/web/src/features/auth/AuthContext.tsx +++ b/apps/web/src/features/auth/AuthContext.tsx @@ -14,11 +14,13 @@ interface AuthContextValue { login: (input: LoginInput) => Promise; /** Ends the session and clears `user`. */ logout: () => Promise; + /** Permanently deletes the current account and clears `user`. Throws `ApiError` (e.g. wrong password) on failure. */ + deleteAccount: (password: string) => Promise; /** * Re-fetches the current profile and updates `user`. Needed after * anything that changes profile fields `user` carries (e.g. `dietId`) * outside of `signup`/`login` — `PATCH /profile/diet` (see - * `HouseholdPage.tsx`) updates the database directly via `apiClient`, + * `PreferencesPage.tsx`) updates the database directly via `apiClient`, * which doesn't touch this context on its own. */ refreshUser: () => Promise; @@ -59,12 +61,19 @@ export function AuthProvider({ children }: { children: ReactNode }) { setUser(null); }, []); + const deleteAccount = useCallback(async (password: string) => { + await apiClient.deleteAccount(password); + setUser(null); + }, []); + const refreshUser = useCallback(async () => { setUser(await apiClient.me()); }, []); return ( - + {children} ); diff --git a/apps/web/src/features/profile/AllergySelect.tsx b/apps/web/src/features/profile/AllergySelect.tsx index d271ac3..0141dae 100644 --- a/apps/web/src/features/profile/AllergySelect.tsx +++ b/apps/web/src/features/profile/AllergySelect.tsx @@ -12,8 +12,8 @@ interface AllergySelectProps { * Multi-select (checkbox grid, not a native ` onChange(e.target.value === "" ? null : Number(e.target.value))} > - + {diets.map((diet) => (