From 2da125eca2e7e0c8cf9f278a3d9c594bdd7c9195 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 06:47:10 +0000 Subject: [PATCH] =?UTF-8?q?Espace=20adh=C3=A9rent=20:=20plus=20de=20barre?= =?UTF-8?q?=20lat=C3=A9rale=20hors=20staff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un adhérent voyait deux fois le même menu : la barre latérale ne portait chez lui que la section « Côté adhérent » — Informations, Mon profil, Mes promotions — et EspaceHeader rend déjà ces trois entrées en onglets sous le bandeau. Un quart de l'écran pour une redite. BackendShell branche donc sur isStaff : hors staff, il ne reste qu'un bandeau de marque, aligné sur la colonne de contenu, et la page prend toute la largeur. Le staff garde la barre, qui est chez lui la navigation principale et le seul chemin vers son propre espace. Le piège était sur téléphone : sous 1024px les actions du compte vivaient dans le tiroir, pas dans l'entête. Sans tiroir, la déconnexion serait devenue inatteignable — .backend-header--plain rétablit leur affichage à toutes les largeurs, et un test le verrouille avec l'absence d'aside et de bouton de tiroir dans la variante. Vérifié au navigateur en 1440 et 390 px, adhérent et administrateur : aucune barre ni bouton de tiroir côté adhérent, actions présentes aux deux largeurs, aucun débordement horizontal ; les dix-sept entrées de l'administrateur et son tiroir sont intacts. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014QsLjRAnuLwivqxCbM4WeP --- CLAUDE.md | 11 +++++ src/app/backend/BackendShell.tsx | 39 ++++++++++++++++- src/app/globals.css | 75 ++++++++++++++++++++++++++++++++ tests/backend-nav.test.ts | 38 ++++++++++++++++ 4 files changed, 162 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index fe92971..6f0ef31 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -268,6 +268,17 @@ même raison. droit à l'image) et `/backend/espace/promotions` (dépôt et suivi des promos, les offres **en ligne** en tête). `EspaceHeader` porte le bandeau et les onglets ; toute action qui touche l'espace revalide les deux chemins. +- **Pas de barre latérale hors staff** : `BackendShell` branche sur + `isStaff(user.role)`. Chez un adhérent la barre ne porterait que la section + « Côté adhérent », c'est-à-dire les trois entrées déjà rendues en onglets par + `EspaceHeader` — deux menus pour la même chose. Il ne reste qu'un bandeau de + marque, aligné sur la colonne de contenu (`max-width` 1040). Conséquence à ne + pas perdre de vue : sous 1024px les actions du compte vivaient **dans le + tiroir** ; sans tiroir, `.backend-header--plain` rétablit leur affichage dans + l'entête, sinon la déconnexion deviendrait inatteignable sur téléphone. + `tests/backend-nav.test.ts` verrouille les deux points. Le staff garde la + barre : elle y est la navigation principale, et « Côté adhérent » son seul + chemin vers son propre espace. - La catégorie d'une promotion est un **type de produit ou de service** (`src/lib/promo-categories.ts`, groupes pour ``), pas le métier de l'adhérent ; `defaultPromoCategory(slug)` pré-sélectionne depuis le métier. diff --git a/src/app/backend/BackendShell.tsx b/src/app/backend/BackendShell.tsx index a27a5e4..bd2984a 100644 --- a/src/app/backend/BackendShell.tsx +++ b/src/app/backend/BackendShell.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useState, type ReactNode } from "react"; import { BackendIcon, type BackendIconName } from "@/components/BackendIcons"; -import { can, ROLE_LABELS } from "@/lib/rbac"; +import { can, isStaff, ROLE_LABELS } from "@/lib/rbac"; import type { AppRole } from "@/types/next-auth"; import { doSignOut } from "./actions"; @@ -192,6 +192,43 @@ export function BackendShell({ ); + /** + * Un adhérent n'a pas de barre latérale : elle ne porterait que la section + * « Côté adhérent », c'est-à-dire les trois entrées déjà présentes en onglets + * sous le bandeau de l'espace. Deux menus pour la même chose, dont un qui + * mange un quart de l'écran. Il ne reste donc qu'un bandeau de marque, et le + * contenu prend toute la largeur — recentré pour ne pas s'étirer. + * + * Le staff la garde : chez lui elle est la navigation principale, et + * « Côté adhérent » y est le seul chemin vers son propre espace. + */ + if (!isStaff(user.role)) { + return ( +
+
+ + + {/* eslint-disable-next-line @next/next/no-img-element */} + Plein R + + + Plein R + Espace adhérent + + +
+ {siteLink} + {signOut} +
+
+ + {/* Le bandeau de l'espace porte déjà le nom et les onglets : répéter + ici le titre de la page ferait un troisième étage d'en-tête. */} +
{children}
+
+ ); + } + return (
{menuOpen && ( diff --git a/src/app/globals.css b/src/app/globals.css index 76d7fea..124846c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -850,6 +850,81 @@ img { min-width: 0; } +/* ---- Backend sans barre latérale (espace adhérent) ---- + Un adhérent n'a que les trois onglets de son espace : une barre latérale + les répéterait. `.backend` étant une rangée flex, la variante repasse en + bloc pour que l'entête et le contenu s'empilent simplement. */ +.backend--plain { + display: block; +} +/* L'entête s'aligne sur la colonne de contenu : sans cela le logo collerait + au bord de l'écran pendant que le contenu reste centré. */ +.backend-header--plain { + padding: 14px max(24px, calc((100% - 1040px) / 2)); +} +.backend-brand { + display: flex; + align-items: center; + gap: 11px; + margin-right: auto; + min-width: 0; + text-decoration: none; +} +.backend-brand__mark { + display: inline-flex; + flex-shrink: 0; + padding: 5px; + border-radius: 11px; + background: #fff; + border: 1px solid #e6dcc6; +} +.backend-brand__text { + display: flex; + flex-direction: column; + line-height: 1.15; + min-width: 0; +} +.backend-brand__name { + font-size: 17px; + font-weight: 800; + color: var(--ink-strong); +} +.backend-brand__sub { + font-size: 11.5px; + color: var(--muted-3); +} +/* Sans barre latérale, il n'y a plus de tiroir où loger les actions sur + téléphone : elles restent dans l'entête à toutes les largeurs. */ +.backend-header--plain .backend-header__actions { + display: flex; +} +.backend-content--plain { + width: 100%; + max-width: 1040px; + margin: 0 auto; +} + +@media (max-width: 1024px) { + .backend-header--plain { + padding: 11px 16px; + } + .backend-content--plain { + max-width: none; + } +} + +/* Le nom de marque s'efface avant les actions : le logo suffit à situer la + page, deux boutons tronqués ne servent à rien. */ +@media (max-width: 560px) { + .backend-brand__text { + display: none; + } + .backend-header--plain .backend-action { + padding: 8px 11px; + font-size: 13px; + } +} + ::-webkit-scrollbar { width: 10px; height: 10px; diff --git a/tests/backend-nav.test.ts b/tests/backend-nav.test.ts index 5cb9b15..c3b5184 100644 --- a/tests/backend-nav.test.ts +++ b/tests/backend-nav.test.ts @@ -42,3 +42,41 @@ test("chaque icône déclarée dans le type est dessinée", () => { assert.ok(drawn.has(name), `l'icône « ${name} » est déclarée mais pas dessinée`); } }); + +/** + * Un adhérent n'a pas de barre latérale : elle ne porterait que la section + * « Côté adhérent », déjà rendue en onglets par `EspaceHeader`. Le test tient + * les deux bouts — la coquille doit brancher sur le rôle, et sa variante sans + * barre ne doit rendre ni `aside`, ni bouton de tiroir. + */ +const memberStart = shell.indexOf("if (!isStaff(user.role))"); +// La tranche s'arrête à la coquille du staff, sinon elle l'embarquerait et +// le test verrait son