From 3131df2b71fae26a4ffa3e80aae8763486b3dde9 Mon Sep 17 00:00:00 2001 From: Hyko Date: Tue, 14 Apr 2026 19:26:48 -0400 Subject: [PATCH] refactor: remove module system integration from admin and CLI Removes all module-related logic from the admin dashboard, CLI database initialization, and AdminPages component: - Drop `initModules` call from `db init` CLI command and simplify the completion message to only reflect core feature tables - Remove `getModuleDashboardStats` and module page routing from admin stats actions and update usage documentation accordingly - Simplify `AdminPagesClient` to remove module page loading, lazy components, and module-specific props (`moduleStats`, `modulePageInfo`, `routeInfo`, `enabledModules`) --- PROJECT.md | 387 ++++++++++++++++++ src/cli/database.js | 13 +- src/features/admin/actions/statsActions.js | 30 +- src/features/admin/components/AdminPages.js | 79 +--- .../admin/components/pages/DashboardPage.js | 34 +- .../admin/components/pages/UserEditPage.js | 31 +- src/features/admin/navigation.server.js | 18 +- src/features/admin/page.js | 37 -- src/features/provider/ZenProvider.js | 25 +- 9 files changed, 415 insertions(+), 239 deletions(-) create mode 100644 PROJECT.md diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..c4728b8 --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,387 @@ +# ZEN — Plan du projet + +> Un CMS Next.js construit sur l'essentiel, rien de plus, rien de moins. + +ZEN est un système de gestion de contenu (CMS) pour Next.js. Il s'installe automatiquement dans n'importe quel projet Next.js via : + +```bash +npx @zen/start +``` + +Le package principal est `@zen/core`. Il fournit toute l'infrastructure nécessaire pour gérer un site : authentification, base de données, stockage, courriels, paiements, PDF, tâches planifiées, notifications. Chaque fonctionnalité est indépendante — les cores ne se connaissent pas entre eux, mais le reste du CMS s'appuie sur chacun d'eux. + +--- + +## Principes directeurs + +**Core purity** — Chaque core contient uniquement du code propre à son domaine. Aucune logique métier, aucune dépendance vers un autre core. + +**Minimal by default** — Pas de fonctionnalité superflue. Si ce n'est pas nécessaire, ça n'existe pas. + +**Sécuritaire par défaut** — Requêtes paramétrées, protection CSRF, limitation de débit, validation en entrée, erreurs opaques vers le client. + +**Performant** — Connexions en pool, cache HTTP, génération différée des services. + +--- + +## Structure du projet + +``` +src/ + core/ # Infrastructure fondamentale — la base de tout + features/ # Fonctionnalités du CMS utilisant les cores + shared/ # Utilitaires, composants et styles partagés +``` + +--- + +## `src/core` — Les piliers du CMS + +Chaque core est une brique indépendante. Il n'existe qu'une seule façon de faire chaque chose dans ZEN : passer par le core concerné. L'ensemble du CMS, des features aux modules, repose sur ces cores. + +--- + +### API + +**`@zen/core/api`** + +L'API est le point d'entrée unique de toutes les requêtes HTTP du CMS. Que ce soit depuis l'Admin, le front-end du site ou un module tiers, tout passe par là. Il n'existe aucune autre route API dans ZEN. + +Toutes les requêtes arrivent via le catch-all Next.js `app/zen/api/[...path]/route.js`. Le router les dispatche vers le bon handler selon le chemin et la méthode HTTP. + +**Ce qu'il fait :** +- Routage dynamique avec paramètres nommés (`:id`) et wildcards (`/**`) +- Protection CSRF automatique sur toutes les requêtes mutantes (POST, PUT, PATCH, DELETE) +- Limitation de débit par IP avec des préréglages par action (login, register, api) +- Injection de session dans le contexte de chaque handler +- Trois niveaux d'accès : `public`, `user` (session requise), `admin` (rôle admin requis) +- Enregistrement dynamique des routes par les features +- Réponses standardisées via `apiSuccess()` et `apiError()` + +**Règle absolue :** Toute route API du CMS doit être enregistrée dans ce router. Jamais de `route.js` parallèle. + +--- + +### Database + +**`@zen/core/database`** + +La couche d'accès à la base de données PostgreSQL. Toute l'application communique avec la base de données uniquement via ce core — jamais directement. + +**Ce qu'il fait :** +- Pool de connexions PostgreSQL (max 20 clients, reconnexion automatique) +- Fonctions de requête de bas niveau : `query()`, `queryOne()`, `queryAll()`, `transaction()` +- Helpers CRUD complets : `create()`, `find()`, `findOne()`, `findById()`, `update()`, `updateById()`, `delete()`, `deleteWhere()`, `count()`, `exists()` +- Protection contre l'injection SQL : requêtes paramétrées `$1, $2, ...`, jamais de concaténation +- Contrôle d'accès aux colonnes via liste blanche (`allowedColumns`) — empêche le mass assignment +- Validation et échappement des identifiants SQL (noms de tables et colonnes) +- Gestion SSL configurable : vérification complète en production, souple en développement +- Erreurs opaques : seul le code SQLSTATE est exposé, jamais les détails internes + +**Règle absolue :** Aucun code ailleurs dans le CMS ne communique directement avec la base de données. Tout passe par ce core. + +--- + +### Email + +**`@zen/core/email`** + +L'unique système d'envoi de courriels du CMS. Alimenté par Resend. Toute l'application envoie ses courriels via ce core. + +**Ce qu'il fait :** +- Envoi de courriels simples : `sendEmail({ to, subject, html })` +- Envoi en lot : `sendBatchEmails(emailArray)` +- Expéditeur configurable (adresse, nom d'affichage) +- Template de base React Email (`BaseLayout`) : logo, marque, pied de page, lien de support +- Réponses standardisées `{ success, data, error }` + +**Templates fournis :** +- `BaseLayout` — Enveloppe visuelle commune à tous les courriels du CMS (logo, couleurs, pied de page) + +**Règle absolue :** Tout courriel envoyé par le CMS passe par ce core. + +--- + +### Cron + +**`@zen/core/cron`** + +Le registre central de toutes les tâches planifiées. Chaque tâche récurrente de l'application s'enregistre ici — jamais en dehors. + +**Ce qu'il fait :** +- Planification de tâches avec expressions cron standard (`schedule(name, expression, handler)`) +- Support des fuseaux horaires (défaut : `ZEN_TIMEZONE`) +- Survie aux hot reloads Next.js via stockage global (`Symbol.for`) +- Remplacement automatique si une tâche du même nom est enregistrée deux fois +- Déclenchement manuel : `trigger(name)` pour exécuter une tâche immédiatement +- Introspection : `getJobs()`, `getStatus()`, `isRunning(name)` +- Gestion des erreurs : un échec de tâche ne plante pas le scheduler + +**Règle absolue :** Toutes les tâches cron de l'application sont enregistrées via ce core. + +--- + +### Storage + +**`@zen/core/storage`** + +La gestion complète du stockage de fichiers, compatible S3 (Cloudflare R2 ou Backblaze B2). Toute l'application lit et écrit des fichiers via ce core. + +**Ce qu'il fait :** +- Upload, téléchargement, suppression, copie et déplacement de fichiers +- Upload d'images avec cache longue durée (`max-age=31536000`) +- Suppression en lot optimisée (S3 batch delete) +- URLs pré-signées pour accès direct (GET ou PUT) +- Liste paginée des fichiers avec préfixe et continuation token +- Signature AWS Signature V4 pour toutes les requêtes +- Protection contre le path traversal (`..`, `.`, segments vides, null bytes) +- Contrôle d'accès via policies : préfixes publics vs chemins protégés (session + rôle) +- Headers de sécurité : `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY` +- Téléchargement forcé pour les fichiers non-images (prévient l'exécution en navigateur) +- Utilitaires : validation de type, validation de taille, nommage unique, extension, MIME type + +**Règle absolue :** Tout accès fichier passe par ce core. + +--- + +### Payments + +**`@zen/core/payments`** + +L'intégration Stripe pour les paiements. Tout ce qui touche à la facturation ou aux transactions dans l'application passe par ce core. + +**Ce qu'il fait :** +- Sessions de checkout Stripe (`createCheckoutSession`) +- PaymentIntents pour paiements personnalisés (`createPaymentIntent`) +- Gestion des clients Stripe (`createCustomer`, `getOrCreateCustomer`) +- Récupération des sessions et intentions de paiement +- Listing des moyens de paiement d'un client +- Vérification des webhooks Stripe (`verifyWebhookSignature`) +- Remboursements (`createRefund`) +- Initialisation paresseuse du client Stripe (seulement si configuré) +- Clé publiable exposée pour le front-end (`getPublishableKey`) + +--- + +### PDF + +**`@zen/core/pdf`** + +La génération de fichiers PDF à partir de composants React. Tout PDF produit par l'application passe par ce core. + +**Ce qu'il fait :** +- Rendu de documents PDF depuis des composants React (`renderToBuffer`) +- Réexporte l'API complète de `@react-pdf/renderer` : `Document`, `Page`, `View`, `Text`, `Image`, `Link`, `StyleSheet`, `Font` +- Utilitaire de nommage : `getFilename(prefix, identifier, date?)` → `"invoice-12345-2024-01-15.pdf"` + +--- + +### Toast + +**`@zen/core/toast`** + +Le système de notifications visuelles de l'application. Que ce soit dans l'Admin ou sur le front-end, c'est l'unique système de toast à utiliser. + +**Ce qu'il fait :** +- Contexte React via `` et `useToast()` +- Quatre types de notifications : `success`, `error`, `warning`, `info` +- Disparition automatique avec durées configurables par type +- Animation de sortie (fade-out 300ms) +- Flag `dismissible` par notification +- `` pour le rendu dans l'arbre React + +**Règle absolue :** Un seul système de toast dans toute l'application — celui-ci. + +--- + +## `src/features` — Les fonctionnalités du CMS + +Les features utilisent les cores pour implémenter les fonctionnalités centrales du CMS. Elles ont accès à l'API, à la base de données, aux courriels, etc. + +--- + +### Auth + +**`@zen/core/auth`** + +Le système d'authentification du CMS. Toute authentification d'utilisateur dans le site passe par ici. + +**Ce qu'il fait :** +- Inscription et connexion d'utilisateurs +- Hachage des mots de passe avec `scrypt` (natif Node.js) + sel aléatoire + comparaison résistante aux timing attacks +- Gestion de sessions : création, validation, suppression, rafraîchissement automatique (<20 jours → 30 jours) +- Vérification d'adresse courriel par token +- Réinitialisation de mot de passe par lien sécurisé +- Middleware de protection de routes : `protect()`, `checkAuth()`, `requireRole()` +- Server Actions : `loginAction`, `registerAction`, `logoutAction`, `forgotPasswordAction`, `resetPasswordAction`, `verifyEmailAction` +- Tables gérées : `zen_auth_users`, `zen_auth_sessions`, `zen_auth_email_verifications`, `zen_auth_password_resets` + +**Règle absolue :** Toute authentification de site passe par cette feature. + +--- + +### Admin + +**`@zen/core/admin`** + +L'interface d'administration centrale. Tableau de bord visuel pour gérer le site et ses modules. + +**Ce qu'il fait :** +- Protection des routes admin : `protectAdmin()`, `isAdmin()` +- Pages catch-all pour l'interface admin (`AdminPagesClient`, `AdminPagesLayout`) +- Navigation construite côté serveur (`buildNavigationSections`) +- Gestion des utilisateurs depuis l'interface + +**Navigation :** +- Tableau de bord → `/admin/dashboard` +- Utilisateurs → `/admin/users` + +--- + +### Provider + +**`@zen/core/provider`** + +Le provider React racine du CMS. Il s'insère dans le layout du site et active tout ce dont le CMS a besoin côté client. + +**Ce qu'il fait :** +- Enveloppe l'application dans `` avec son `` +- Un seul composant à poser dans le layout : `` + +--- + +## `src/shared` — Utilitaires partagés + +Tout ce qui est utile à travers le CMS sans appartenir à un core ou une feature. + +### Composants UI (`src/shared/components/`) + +Bibliothèque de composants React stylisés, utilisés dans l'Admin et les pages du CMS : + +`Badge`, `StatusBadge`, `TypeBadge`, `Button`, `Card`, `Input`, `Loading`, `LoadingState`, `Modal`, `Pagination`, `Select`, `StatCard`, `Table`, `Textarea`, `MarkdownEditor`, `PasswordStrengthIndicator`, `FilterTabs`, `Breadcrumb` + +### Utilitaires (`src/shared/lib/`, `src/shared/utils/`) + +- **`appConfig`** — Lecture centralisée de la configuration (`getAppName`, `getAppConfig`, `getPublicBaseUrl`) +- **`logger`** — Console stylisée pour les logs (`step`, `done`, `warn`, `fail`, `info`) +- **`dates`** — Manipulation de dates en UTC (`formatDateForDisplay`, `getDaysBetween`, `isOverdue`, etc.) +- **`metadata`** — Génération de métadonnées Next.js (`generateMetadata`, `generateTitle`, `generateRobots`) +- **`rateLimit`** — Limitation de débit partagée (`checkRateLimit`) avec préréglages par action +- **`currency`** — Formatage monétaire (`formatCurrency`, `getCurrencySymbol`) + +### Icons (`src/shared/Icons.js`) + +Bibliothèque de plus de 1000 icônes (style Untitled UI). + +### Styles (`src/shared/styles/zen.css`) + +Feuille de style CSS de base du CMS. + +--- + +## Initialisation + +Dans `instrumentation.js` du projet Next.js : + +```js +export async function register() { + if (process.env.NEXT_RUNTIME === 'nodejs') { + const { initializeZen } = await import('@zen/core'); + await initializeZen(); + } +} +``` + +Dans `app/layout.js` : + +```jsx +import { ZenProvider } from '@zen/core/provider'; + +export default function RootLayout({ children }) { + return ( + + + + {children} + + + + ); +} +``` + +Dans `app/zen/api/[...path]/route.js` : + +```js +export { GET, POST, PUT, PATCH, DELETE } from '@zen/core/zen/api'; +``` + +--- + +## Variables d'environnement + +| Variable | Description | +|---|---| +| `ZEN_NAME` | Nom de l'application | +| `ZEN_TIMEZONE` | Fuseau horaire IANA (défaut : `America/Toronto`) | +| `ZEN_CURRENCY` | Code monétaire (défaut : `CAD`) | +| `ZEN_CURRENCY_SYMBOL` | Symbole monétaire (défaut : `$`) | +| `NEXT_PUBLIC_URL` | URL de base (production) | +| `NEXT_PUBLIC_URL_DEV` | URL de base (développement) | +| `ZEN_DATABASE_URL` | Chaîne de connexion PostgreSQL (production) | +| `ZEN_DATABASE_URL_DEV` | Chaîne de connexion PostgreSQL (développement) | +| `ZEN_DB_SSL_DISABLED` | Désactiver TLS (local uniquement) | +| `ZEN_EMAIL_RESEND_APIKEY` | Clé API Resend | +| `ZEN_EMAIL_FROM_ADDRESS` | Adresse d'expédition | +| `ZEN_EMAIL_FROM_NAME` | Nom d'affichage de l'expéditeur | +| `ZEN_STORAGE_PROVIDER` | `r2` ou `backblaze` | +| `ZEN_STORAGE_ENDPOINT` | Endpoint S3-compatible | +| `ZEN_STORAGE_ACCESS_KEY` | Clé d'accès stockage | +| `ZEN_STORAGE_SECRET_KEY` | Clé secrète stockage | +| `ZEN_STORAGE_BUCKET` | Nom du bucket | +| `STRIPE_SECRET_KEY` | Clé secrète Stripe | +| `STRIPE_PUBLISHABLE_KEY` | Clé publiable Stripe | +| `STRIPE_WEBHOOK_SECRET` | Secret webhook Stripe | + +--- + +## CLI base de données + +```bash +npx zen-db init # Créer toutes les tables +npx zen-db test # Tester la connexion +npx zen-db drop # Supprimer toutes les tables (confirmation requise) +``` + +--- + +## Flux d'une requête + +``` +Navigateur / Client + ↓ +app/zen/api/[...path]/route.js ← catch-all Next.js + ↓ +core/api — router.js ← CSRF, rate limit, auth + ↓ +Handler (feature) ← logique métier + ↓ +core/database, core/storage, ← accès aux ressources +core/email, core/payments… + ↓ +Réponse standardisée ← apiSuccess() / apiError() +``` + +--- + +## Résumé des règles absolues + +| Domaine | Règle | +|---|---| +| API | Toutes les routes HTTP passent par `core/api`. Aucune autre route API. | +| Base de données | Tout accès DB passe par `core/database`. Jamais de requêtes directes. | +| Courriels | Tout envoi de courriel passe par `core/email`. | +| Cron | Toutes les tâches planifiées s'enregistrent dans `core/cron`. | +| Stockage | Tout accès fichier passe par `core/storage`. | +| Notifications | Un seul système de toast dans toute l'app : `core/toast`. | +| Authentification | Toute auth de site passe par `features/auth`. | diff --git a/src/cli/database.js b/src/cli/database.js index 5c02327..4edc55c 100644 --- a/src/cli/database.js +++ b/src/cli/database.js @@ -69,18 +69,7 @@ async function runCLI() { const { initFeatures } = await import('../features/init.js'); const featuresResult = await initFeatures(); - // Module tables are initialized per-module, if present - let modulesResult = { created: [], skipped: [] }; - try { - const { initModules } = await import('../modules/init.js'); - modulesResult = await initModules(); - } catch { - // Modules may not be present in all project setups — silently skip - } - - const totalCreated = featuresResult.created.length + modulesResult.created.length; - const totalSkipped = featuresResult.skipped.length + modulesResult.skipped.length; - done(`DB ready — ${totalCreated} tables created, ${totalSkipped} skipped`); + done(`DB ready — ${featuresResult.created.length} tables created, ${featuresResult.skipped.length} skipped`); break; } diff --git a/src/features/admin/actions/statsActions.js b/src/features/admin/actions/statsActions.js index 7d8fceb..0cf42ab 100644 --- a/src/features/admin/actions/statsActions.js +++ b/src/features/admin/actions/statsActions.js @@ -1,34 +1,26 @@ /** * Admin Stats Actions * Server-side actions for core dashboard statistics - * - * Module-specific stats are handled by each module's dashboard actions. - * See src/modules/{module}/dashboard/statsActions.js - * + * * Usage in your Next.js app: - * + * * ```javascript * // app/(admin)/admin/[...admin]/page.js * import { protectAdmin } from '@zen/core/admin'; - * import { getDashboardStats, getModuleDashboardStats } from '@zen/core/admin/actions'; + * import { getDashboardStats } from '@zen/core/admin/actions'; * import { AdminPagesClient } from '@zen/core/admin/pages'; - * + * * export default async function AdminPage({ params }) { * const { user } = await protectAdmin(); - * - * // Fetch core dashboard stats + * * const statsResult = await getDashboardStats(); * const dashboardStats = statsResult.success ? statsResult.stats : null; - * - * // Fetch module dashboard stats (for dynamic widgets) - * const moduleStats = await getModuleDashboardStats(); - * + * * return ( - * * ); * } @@ -72,9 +64,9 @@ export async function getDashboardStats() { }; } catch (error) { fail(`Error getting dashboard stats: ${error.message}`); - return { - success: false, - error: error.message || 'Failed to get dashboard statistics' + return { + success: false, + error: error.message || 'Failed to get dashboard statistics' }; } } diff --git a/src/features/admin/components/AdminPages.js b/src/features/admin/components/AdminPages.js index 186f5db..9053752 100644 --- a/src/features/admin/components/AdminPages.js +++ b/src/features/admin/components/AdminPages.js @@ -1,85 +1,24 @@ 'use client'; -/** - * Admin Pages Component - * - * This component handles both core admin pages and module pages. - * Module pages are loaded dynamically on the client where hooks work properly. - */ - -import { Suspense } from 'react'; import DashboardPage from './pages/DashboardPage.js'; import UsersPage from './pages/UsersPage.js'; import UserEditPage from './pages/UserEditPage.js'; import ProfilePage from './pages/ProfilePage.js'; -import { getModulePageLoader } from '../../../modules/modules.pages.js'; -// Loading component for suspense -function PageLoading() { - return ( -
-
-
- ); -} +export default function AdminPagesClient({ params, user, dashboardStats = null }) { + const parts = params?.admin || []; + const page = parts[0] || 'dashboard'; -export default function AdminPagesClient({ - params, - user, - dashboardStats = null, - moduleStats = {}, - modulePageInfo = null, - routeInfo = null, - enabledModules = {} -}) { - // If this is a module page, render it with lazy loading - if (modulePageInfo && routeInfo) { - const LazyComponent = getModulePageLoader(modulePageInfo.module, modulePageInfo.path); - if (LazyComponent) { - // Build props for the page - const pageProps = { user }; - if (routeInfo.action === 'edit' && routeInfo.id) { - // Add ID props for edit pages (modules may use different prop names) - pageProps.id = routeInfo.id; - pageProps.invoiceId = routeInfo.id; - pageProps.clientId = routeInfo.id; - pageProps.itemId = routeInfo.id; - pageProps.categoryId = routeInfo.id; - pageProps.transactionId = routeInfo.id; - pageProps.recurrenceId = routeInfo.id; - pageProps.templateId = routeInfo.id; - pageProps.postId = routeInfo.id; - } - - return ( - }> - - - ); - } + if (page === 'users' && parts[1] === 'edit' && parts[2]) { + return ; } - // Determine core page from routeInfo or params - let currentPage = 'dashboard'; - if (routeInfo?.path) { - const parts = routeInfo.path.split('/').filter(Boolean); - currentPage = parts[1] || 'dashboard'; // /admin/[page] - } else if (params?.admin) { - currentPage = params.admin[0] || 'dashboard'; - } - - // Core page components mapping (non-module pages) - const usersPageComponent = routeInfo?.action === 'edit' && routeInfo?.id - ? () => - : () => ; - const corePages = { - dashboard: () => , - users: usersPageComponent, + dashboard: () => , + users: () => , profile: () => , }; - // Render the appropriate core page or default to dashboard - const CorePageComponent = corePages[currentPage]; - return CorePageComponent ? : ; + const CorePageComponent = corePages[page]; + return CorePageComponent ? : ; } diff --git a/src/features/admin/components/pages/DashboardPage.js b/src/features/admin/components/pages/DashboardPage.js index f93e0f4..b8fc722 100644 --- a/src/features/admin/components/pages/DashboardPage.js +++ b/src/features/admin/components/pages/DashboardPage.js @@ -1,33 +1,11 @@ 'use client'; -/** - * Admin Dashboard Page - * Displays core stats and dynamically loads module dashboard widgets - */ - -import { Suspense } from 'react'; import { StatCard } from '../../../../shared/components'; import { UserMultiple02Icon } from '../../../../shared/Icons.js'; -import { getModuleDashboardWidgets } from '../../../../modules/modules.pages.js'; -/** - * Loading placeholder for widgets - */ -function WidgetLoading() { - return ( -
- ); -} - -export default function DashboardPage({ user, stats, moduleStats = {}, enabledModules = {} }) { +export default function DashboardPage({ user, stats }) { const loading = !stats; - // Get only enabled module dashboard widgets - const allModuleWidgets = getModuleDashboardWidgets(); - const moduleWidgets = Object.fromEntries( - Object.entries(allModuleWidgets).filter(([moduleName]) => enabledModules[moduleName]) - ); - return (
@@ -40,16 +18,6 @@ export default function DashboardPage({ user, stats, moduleStats = {}, enabledMo
- {/* Module dashboard widgets (dynamically loaded) */} - {Object.entries(moduleWidgets).map(([moduleName, widgets]) => ( - widgets.map((Widget, index) => ( - }> - - - )) - ))} - - {/* Core stats - always shown */} { +const UserEditPage = ({ userId, user }) => { const router = useRouter(); const toast = useToast(); - const clientsModuleActive = Boolean(enabledModules?.clients); const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); - const [clients, setClients] = useState([]); const [formData, setFormData] = useState({ name: '', role: 'user', email_verified: 'false', - client_id: '' }); const [errors, setErrors] = useState({}); @@ -40,15 +37,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => { loadUser(); }, [userId]); - useEffect(() => { - if (clientsModuleActive) { - fetch('/zen/api/admin/clients?limit=500', { credentials: 'include' }) - .then(res => res.json()) - .then(data => data.clients ? setClients(data.clients) : setClients([])) - .catch(() => setClients([])); - } - }, [clientsModuleActive]); - const loadUser = async () => { try { setLoading(true); @@ -64,7 +52,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => { name: data.user.name || '', role: data.user.role || 'user', email_verified: data.user.email_verified ? 'true' : 'false', - client_id: data.linkedClient ? String(data.linkedClient.id) : '' })); } else { toast.error(data.message || 'Utilisateur introuvable'); @@ -107,7 +94,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => { name: formData.name.trim(), role: formData.role, email_verified: formData.email_verified === 'true', - ...(clientsModuleActive && { client_id: formData.client_id ? parseInt(formData.client_id, 10) : null }) }) }); const data = await response.json(); @@ -209,21 +195,6 @@ const UserEditPage = ({ userId, user, enabledModules = {} }) => { onChange={(value) => handleInputChange('email_verified', value)} options={emailVerifiedOptions} /> - - {clientsModuleActive && ( -