docs(admin): add README documentation for admin and auth features
- add comprehensive README for admin feature covering structure, API, registry, and extension points - add comprehensive README for auth feature covering structure, API, and usage examples
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
# 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.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
│ └── 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 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)`
|
||||
|
||||
Construit les sections de navigation pour la sidebar à partir du registre. Marque l'entrée active selon `pathname`.
|
||||
|
||||
```js
|
||||
const sections = buildNavigationSections('/admin/users');
|
||||
// [{ 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 (
|
||||
<StatCard
|
||||
title="Commandes"
|
||||
value={loading ? '-' : String(data?.total ?? 0)}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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? })`**
|
||||
|
||||
| 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`) |
|
||||
|
||||
---
|
||||
|
||||
### 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,
|
||||
});
|
||||
```
|
||||
|
||||
**`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? })`**
|
||||
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
### 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/<slug>`. `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' });
|
||||
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
|
||||
```
|
||||
Reference in New Issue
Block a user