diff --git a/.env.example b/.env.example
index 896fa60..bf254c3 100644
--- a/.env.example
+++ b/.env.example
@@ -49,4 +49,7 @@ ZEN_PUBLIC_LOGO_BLACK=
ZEN_PUBLIC_LOGO_URL=
# OTHERS
-NEXT_TELEMETRY_DISABLED=1
\ No newline at end of file
+NEXT_TELEMETRY_DISABLED=1
+
+# DEVKIT (developer tools)
+ZEN_DEVKIT=false
\ No newline at end of file
diff --git a/src/features/admin/AdminPage.client.js b/src/features/admin/AdminPage.client.js
index 388283b..260a05c 100644
--- a/src/features/admin/AdminPage.client.js
+++ b/src/features/admin/AdminPage.client.js
@@ -8,8 +8,9 @@ import './pages/ProfilePage.client.js';
import './pages/SettingsPage.client.js';
import './pages/ConfirmEmailChangePage.client.js';
import './widgets/index.client.js';
+import './devkit/DevkitPage.client.js';
-export default function AdminPageClient({ params, user, widgetData, appConfig }) {
+export default function AdminPageClient({ params, user, widgetData, appConfig, devkitEnabled }) {
const parts = params?.admin || [];
const [first] = parts;
@@ -25,5 +26,8 @@ export default function AdminPageClient({ params, user, widgetData, appConfig })
if (slug === 'settings') {
return ;
}
+ if (slug === 'devkit') {
+ return ;
+ }
return ;
}
diff --git a/src/features/admin/AdminPage.server.js b/src/features/admin/AdminPage.server.js
index ab77ac0..f019332 100644
--- a/src/features/admin/AdminPage.server.js
+++ b/src/features/admin/AdminPage.server.js
@@ -2,12 +2,14 @@ import AdminPageClient from './AdminPage.client.js';
import { protectAdmin } from './protect.js';
import { collectWidgetData } from './registry.js';
import { getAppConfig, getPublicBaseUrl } from '@zen/core';
+import { isDevkitEnabled } from '../../shared/lib/appConfig.js';
export default async function AdminPage({ params }) {
const resolvedParams = await params;
const session = await protectAdmin();
const widgetData = await collectWidgetData();
const appConfig = { ...getAppConfig(), siteUrl: getPublicBaseUrl() };
+ const devkitEnabled = isDevkitEnabled();
return (
);
}
diff --git a/src/features/admin/devkit/ComponentsPage.client.js b/src/features/admin/devkit/ComponentsPage.client.js
new file mode 100644
index 0000000..abecb14
--- /dev/null
+++ b/src/features/admin/devkit/ComponentsPage.client.js
@@ -0,0 +1,173 @@
+'use client';
+
+import {
+ Button,
+ Card,
+ Badge,
+ StatusBadge,
+ Input,
+ Select,
+ Textarea,
+ Switch,
+ StatCard,
+ Loading,
+} from '@zen/core/shared/components';
+import { UserCircle02Icon } from '@zen/core/shared/icons';
+import AdminHeader from '../components/AdminHeader.js';
+
+function PreviewBlock({ title, children }) {
+ return (
+
+
{title}
+
+ {children}
+
+
+ );
+}
+
+export default function ComponentsPage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default
+ Primary
+ Success
+ Warning
+ Danger
+ Info
+ Purple
+ Pink
+ Orange
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {['default', 'elevated', 'outline', 'success', 'info', 'warning', 'danger'].map(v => (
+
+ {v}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {}} />
+ {}} />
+ {}} />
+ {}} />
+ {}} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {}} />
+ {}} />
+ {}} />
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/admin/devkit/DevkitPage.client.js b/src/features/admin/devkit/DevkitPage.client.js
new file mode 100644
index 0000000..e562309
--- /dev/null
+++ b/src/features/admin/devkit/DevkitPage.client.js
@@ -0,0 +1,23 @@
+'use client';
+
+import { registerPage } from '../registry.js';
+import ComponentsPage from './ComponentsPage.client.js';
+import IconsPage from './IconsPage.client.js';
+
+function DevkitPage({ params, devkitEnabled }) {
+ if (!devkitEnabled) {
+ return (
+
+ DevKit désactivé. Définir ZEN_DEVKIT=true pour activer.
+
+ );
+ }
+
+ const subPage = params?.[1] || 'components';
+
+ if (subPage === 'icons') return ;
+ return ;
+}
+
+export default DevkitPage;
+registerPage({ slug: 'devkit', title: 'DevKit', Component: DevkitPage });
diff --git a/src/features/admin/devkit/IconsPage.client.js b/src/features/admin/devkit/IconsPage.client.js
new file mode 100644
index 0000000..687225e
--- /dev/null
+++ b/src/features/admin/devkit/IconsPage.client.js
@@ -0,0 +1,73 @@
+'use client';
+
+import { useState, useMemo } from 'react';
+import * as Icons from '@zen/core/shared/icons';
+import { useToast } from '@zen/core/toast';
+import AdminHeader from '../components/AdminHeader.js';
+
+const ALL_ICONS = Object.entries(Icons);
+
+export default function IconsPage() {
+ const [query, setQuery] = useState('');
+ const toast = useToast();
+
+ const filtered = useMemo(() => {
+ if (!query.trim()) return ALL_ICONS;
+ const q = query.trim().toLowerCase();
+ return ALL_ICONS.filter(([name]) => name.toLowerCase().includes(q));
+ }, [query]);
+
+ const handleCopy = (name) => {
+ navigator.clipboard.writeText(`<${name} className="w-5 h-5" />`);
+ toast.success(`${name} copié`);
+ };
+
+ return (
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder="Rechercher une icône..."
+ className="w-full rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-4 py-2.5 text-sm text-neutral-900 dark:text-white placeholder-neutral-400 dark:placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-blue-700/40"
+ />
+ {query && (
+
+ )}
+
+
+ {filtered.length === 0 ? (
+
+ Aucune icône trouvée pour “{query}”
+
+ ) : (
+
+ {filtered.map(([name, IconComponent]) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/features/admin/navigation.js b/src/features/admin/navigation.js
index ef70f50..5a97eb3 100644
--- a/src/features/admin/navigation.js
+++ b/src/features/admin/navigation.js
@@ -4,6 +4,7 @@ import {
getNavSections,
getNavItems,
} from './registry.js';
+import { isDevkitEnabled } from '../../shared/lib/appConfig.js';
// Sections et items core — enregistrés à l'import de ce module.
registerNavSection({ id: 'dashboard', title: 'Tableau de bord', icon: 'DashboardSquare03Icon', order: 10 });
@@ -14,6 +15,12 @@ registerNavItem({ id: 'users', label: 'Utilisateurs', icon: 'UserMultiple0
registerNavItem({ id: 'roles', label: 'Rôles', icon: 'Crown03Icon', href: '/admin/roles', sectionId: 'system', order: 20 });
registerNavItem({ id: 'settings', label: 'Paramètres', icon: 'Settings02Icon', href: '/admin/settings', position: 'bottom', order: 10 });
+if (isDevkitEnabled()) {
+ registerNavSection({ id: 'devkit', title: 'DevKit', icon: 'Wrench01Icon', order: 90 });
+ registerNavItem({ id: 'devkit-components', label: 'Composants', icon: 'Layers01Icon', href: '/admin/devkit/components', sectionId: 'devkit', order: 10 });
+ registerNavItem({ id: 'devkit-icons', label: 'Icônes', icon: 'Image01Icon', href: '/admin/devkit/icons', sectionId: 'devkit', order: 20 });
+}
+
/**
* Build sections for AdminSidebar. Items are sérialisables (pas de composants),
* icônes en chaînes résolues côté client.
diff --git a/src/index.js b/src/index.js
index 34865fa..7797c29 100644
--- a/src/index.js
+++ b/src/index.js
@@ -27,7 +27,7 @@ export * as pdf from "./core/pdf/index.js";
// Do not export here to avoid mixing client/server boundaries
// Export app configuration utilities
-export { getAppName, getAppConfig, getSessionCookieName, getPublicBaseUrl } from "./shared/lib/appConfig.js";
+export { getAppName, getAppConfig, getSessionCookieName, getPublicBaseUrl, isDevkitEnabled } from "./shared/lib/appConfig.js";
// Export initialization utilities
export { initializeZen, resetZenInitialization } from "./shared/lib/init.js";
diff --git a/src/shared/lib/appConfig.js b/src/shared/lib/appConfig.js
index 71b1094..54b1cf3 100644
--- a/src/shared/lib/appConfig.js
+++ b/src/shared/lib/appConfig.js
@@ -48,3 +48,7 @@ export function getAppConfig() {
currencySymbol: process.env.ZEN_CURRENCY_SYMBOL || '$',
};
}
+
+export function isDevkitEnabled() {
+ return process.env.ZEN_DEVKIT === 'true';
+}