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