# 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                    buildNavigationSections, registre (Next.js-free)
├── protect.js                  gardes d'accès
├── navigation.js               buildNavigationSections, buildBottomNavItems, getNavItemBasePath, findActiveNavContext
├── 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
// Gardes RSC — chemin dédié (next/navigation + next/headers, non compatible turbopackIgnore)
import { protectAdmin, isAdmin } from '@zen/core/features/admin/protect';

// Registre et navigation — compatible modules externes
import { 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 (
    <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?, 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, basePath?, 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 (cible du clic dans la sidebar) |
| `basePath` | `string` | Préfixe de routes considérées « sous » cette entrée — utilisé pour souligner l'item actif et construire le fil d'Ariane sur les sous-routes (`/edit/:id`, `/new`...). Auto-déduit : si `href` finit par `/list`, on retire le `/list` ; sinon `basePath = href`. À spécifier explicitement uniquement quand l'auto-déduction ne suffit pas. |
| `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. |

#### Item actif et fil d'Ariane

L'item « actif » dans la sidebar est celui dont le `basePath` matche le préfixe **le plus long** du pathname courant. Sur `/admin/posts/blogue/edit/1`, l'item « Posts » (basePath `/admin/posts/blogue`) est souligné ; sur `/admin/posts/blogue/taxonomies/categories`, c'est l'item « Catégories » qui gagne car son basePath est plus spécifique.

Le fil d'Ariane se construit automatiquement à partir de cet item :

- `[icon] > <section.title> > <item.label>` — si la section contient plusieurs items
- `[icon] > <item.label>` — si la section ne contient que cet item et qu'ils portent le même nom (ex : section "Utilisateurs" + item "Utilisateurs")
- Suivi de `> Nouveau` ou `> Modification` quand l'URL contient `/new` ou `/edit/:id` après le basePath.

Pour personnaliser les labels d'action, enregistrer une page avec un slug `<root>:new` ou `<root>:edit` :

```js
registerPage({ slug: 'posts:edit', breadcrumbLabel: "Modifier l'article" });
registerPage({ slug: 'posts:new',  breadcrumbLabel: 'Nouvel article' });
```

(`<root>` = premier segment d'URL après `/admin/`, ex : `posts` pour `/admin/posts/blogue/edit/1`.)

---

### 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', 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
```
