# Admin Ce répertoire fournit l'interface d'administration complète : layout, navigation, tableau de bord, gestion des utilisateurs et des rôles. Il expose un **registre runtime** pour permettre aux projets consommateurs d'ajouter des widgets, des entrées de navigation et des pages sans modifier le core. > Le pattern `zen.extensions.js` documenté ici reste valide pour les extensions in-projet (extensions ad hoc spécifiques à une app). Pour distribuer une extension réutilisable comme un package npm, consulter [docs/MODULES.md](../../../docs/MODULES.md) — la même API d'enregistrement s'utilise mais le module est auto-découvert via les `dependencies` du projet consommateur. --- ## Structure ``` src/features/admin/ ├── index.js protectAdmin, isAdmin, buildNavigationSections, registre ├── protect.js gardes d'accès ├── navigation.js buildNavigationSections, buildBottomNavItems ├── registry.js registre runtime d'extensions ├── AdminLayout.server.js layout RSC de l'admin ├── AdminPage.server.js page RSC racine (protège + collecte les données widgets) ├── AdminPage.client.js shell client ├── components/ │ ├── index.js re-export │ ├── AdminHeader.js │ ├── AdminShell.js │ ├── AdminSidebar.js │ ├── AdminTop.js │ ├── RoleEditModal.client.js │ ├── ThemeToggle.js │ ├── UserCreateModal.client.js │ └── UserEditModal.client.js ├── devkit/ │ ├── ComponentsPage.client.js │ ├── DevkitPage.client.js │ └── IconsPage.client.js ├── pages/ │ ├── ConfirmEmailChangePage.client.js │ ├── DashboardPage.client.js │ ├── ProfilePage.client.js │ ├── RolesPage.client.js │ ├── SettingsPage.client.js │ └── UsersPage.client.js └── widgets/ ├── index.client.js auto-registration des widgets core (côté client) ├── index.server.js auto-registration des widgets core (côté serveur) ├── users.client.js widget Utilisateurs (composant) └── users.server.js widget Utilisateurs (fetcher) ``` --- ## Import ```js import { protectAdmin, isAdmin, buildNavigationSections } from '@zen/core/features/admin'; import { registerWidget, registerWidgetFetcher, registerNavItem, registerNavSection, registerPage, } from '@zen/core/features/admin'; ``` --- ## Pages intégrées | Route | Page | |-------|------| | `/admin/dashboard` | Tableau de bord avec widgets | | `/admin/users` | Liste, création et gestion des utilisateurs | | `/admin/roles` | Gestion des rôles et permissions | | `/admin/settings` | Paramètres de l'application | | `/admin/profile` | Profil de l'utilisateur connecté | | `/admin/confirm-email-change` | Confirmation de changement d'email | --- ## API ### `protectAdmin(options?)` Garde serveur. Redirige si l'utilisateur n'est pas connecté ou n'a pas la permission `ADMIN_ACCESS`. Retourne la session courante. ```js const session = await protectAdmin(); // session.user est disponible ``` | Option | Type | Défaut | Description | |--------|------|--------|-------------| | `redirectTo` | `string` | `'/auth/login'` | Redirection si non authentifié | | `forbiddenRedirect` | `string` | `'/'` | Redirection si non autorisé | --- ### `isAdmin()` Vérifie si l'utilisateur courant a la permission `ADMIN_ACCESS`. Retourne `boolean`. ```js const admin = await isAdmin(); if (!admin) return null; ``` --- ### `buildNavigationSections(pathname, userPermissions?)` Construit les sections de navigation pour la sidebar à partir du registre. Marque l'entrée active selon `pathname`. Les items dont le champ `permission` n'est pas présent dans `userPermissions` sont automatiquement exclus ; si tous les items d'une section sont exclus, la section disparaît également. ```js const permissions = await getUserPermissions(session.user.id); const sections = buildNavigationSections('/admin/users', permissions); // [{ id, title, icon, items: [{ name, href, icon, current }] }] ``` --- ## Registre d'extensions Le registre permet d'ajouter des widgets, des entrées de navigation et des pages sans toucher au core. Les enregistrements se font via des imports à effet de bord dans le layout racine du projet consommateur. ### Ajouter un widget Un widget est composé de deux parties : un fetcher serveur qui collecte les données, et un composant client qui les affiche. ```js // app/admin/orders/ordersWidget.server.js import { registerWidgetFetcher } from '@zen/core/features/admin'; import { countOrders } from './orders.server.js'; registerWidgetFetcher('orders', async () => ({ total: await countOrders(), })); ``` ```js // app/admin/orders/ordersWidget.client.js 'use client'; import { registerWidget } from '@zen/core/features/admin'; import { StatCard } from '@zen/core/shared/components'; function OrdersWidget({ data, loading }) { return ( ); } registerWidget({ id: 'orders', Component: OrdersWidget, order: 20 }); ``` Le composant reçoit `data` (retour du fetcher) et `loading` (booléen). Si le fetcher échoue, `data` est `null` et `loading` reste `false`. **`registerWidgetFetcher(id, fetcher)`** | Paramètre | Type | Description | |-----------|------|-------------| | `id` | `string` | Identifiant unique du widget | | `fetcher` | `async () => object` | Fonction serveur qui retourne les données | **`registerWidget({ id, Component, order?, permission? })`** | Paramètre | Type | Description | |-----------|------|-------------| | `id` | `string` | Identifiant unique (doit correspondre au fetcher) | | `Component` | `ReactComponent` | Composant client affiché dans le tableau de bord | | `order` | `number` | Position dans la grille (défaut : `0`) | | `permission` | `string` | Clé de permission requise pour voir ce widget (ex. `'users.view'`). Le widget est masqué si l'utilisateur ne possède pas cette permission. | --- ### Ajouter une entrée de navigation ```js import { registerNavSection, registerNavItem } from '@zen/core/features/admin'; registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 }); registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce', order: 10, permission: 'orders.view', // optionnel — masqué si l'utilisateur n'a pas cette permission }); ``` **`registerNavSection({ id, title, icon, order? })`** | Paramètre | Type | Description | |-----------|------|-------------| | `id` | `string` | Identifiant unique de la section | | `title` | `string` | Titre affiché dans la sidebar | | `icon` | `string` | Nom d'icône Hugeicons | | `order` | `number` | Ordre d'affichage (défaut : `0`) | **`registerNavItem({ id, label, icon, href, sectionId?, order?, position?, permission? })`** | Paramètre | Type | Description | |-----------|------|-------------| | `id` | `string` | Identifiant unique de l'entrée | | `label` | `string` | Texte affiché | | `icon` | `string` | Nom d'icône Hugeicons | | `href` | `string` | URL de destination | | `sectionId` | `string` | Section parente (défaut : `'main'`) | | `order` | `number` | Ordre d'affichage (défaut : `0`) | | `position` | `string` | `'bottom'` pour épingler en bas de la sidebar | | `permission` | `string` | Clé de permission requise pour voir cette entrée (ex. `'orders.view'`). L'entrée est masquée si l'utilisateur ne possède pas cette permission. | --- ### Ajouter une page ```js import { registerPage } from '@zen/core/features/admin'; import OrdersPage from './OrdersPage.js'; registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes', }); ``` La page est rendue sous `/admin/`. `AdminPage.client.js` résout le composant à partir du slug dans les paramètres de route. **`registerPage({ slug, Component, title?, breadcrumbLabel? })`** | Paramètre | Type | Description | |-----------|------|-------------| | `slug` | `string` | Segment d'URL sous `/admin/` | | `Component` | `ReactComponent` | Composant client rendu pour cette route | | `title` | `string` | Titre de la page (optionnel) | | `breadcrumbLabel` | `string` | Label du fil d'Ariane (optionnel, défaut : `title`) | --- ## Câbler les extensions dans le projet consommateur Regrouper tous les enregistrements dans un fichier de point d'entrée unique, puis l'importer une seule fois depuis le layout racine. ```js // app/zen.extensions.js import './admin/orders/ordersWidget.server.js'; import './admin/orders/ordersWidget.client.js'; import { registerNavSection, registerNavItem, registerPage } from '@zen/core/features/admin'; import OrdersPage from './admin/orders/OrdersPage.js'; registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 }); registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce', permission: 'orders.view' }); registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes' }); ``` ```js // app/layout.js import './zen.extensions'; // les side effects enregistrent tout ``` --- ## DevKit Le DevKit est une section de l'admin réservée au développement. Il expose une galerie de composants et un catalogue d'icônes. Il s'active via la variable d'environnement `ZEN_DEVKIT_ENABLED=true` et n'est jamais rendu en production. | Route | Contenu | |-------|---------| | `/admin/devkit/components` | Galerie des composants partagés | | `/admin/devkit/icons` | Catalogue d'icônes Hugeicons | --- ## Ajouter un widget core Les widgets intégrés au core suivent le même pattern que les widgets consommateurs, avec une étape supplémentaire : déclarer les fichiers dans les index d'auto-registration. ```js // src/features/admin/widgets/myWidget.server.js import { registerWidgetFetcher } from '../registry.js'; registerWidgetFetcher('myWidget', async () => ({ ... })); // src/features/admin/widgets/index.server.js import './myWidget.server.js'; // ajouter cette ligne ``` ```js // src/features/admin/widgets/myWidget.client.js 'use client'; import { registerWidget } from '../registry.js'; // ... registerWidget({ id: 'myWidget', Component: MyWidget, order: 20 }); // src/features/admin/widgets/index.client.js import './myWidget.client.js'; // ajouter cette ligne ```