@lastbrain/app 0.1.47 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/app-shell/layout.d.ts +1 -1
  2. package/dist/app-shell/layout.d.ts.map +1 -1
  3. package/dist/app-shell/layout.js +1 -1
  4. package/dist/components/NotificationContainer.js +1 -1
  5. package/dist/index.d.ts +19 -19
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +19 -19
  8. package/dist/layouts/AdminLayoutWithSidebar.d.ts +3 -2
  9. package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
  10. package/dist/layouts/AdminLayoutWithSidebar.js +5 -5
  11. package/dist/layouts/AppProviders.d.ts +1 -1
  12. package/dist/layouts/AppProviders.d.ts.map +1 -1
  13. package/dist/layouts/AppProviders.js +3 -3
  14. package/dist/layouts/AuthLayoutWithSidebar.d.ts +7 -4
  15. package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
  16. package/dist/layouts/AuthLayoutWithSidebar.js +5 -5
  17. package/dist/layouts/PublicLayoutWithSidebar.js +2 -2
  18. package/dist/layouts/RootLayout.js +1 -1
  19. package/dist/scripts/init-app.d.ts.map +1 -1
  20. package/dist/scripts/init-app.js +457 -39
  21. package/dist/scripts/module-add.js +1 -1
  22. package/dist/scripts/module-build.d.ts.map +1 -1
  23. package/dist/scripts/module-build.js +36 -15
  24. package/dist/scripts/module-create.js +8 -8
  25. package/dist/scripts/module-delete.d.ts.map +1 -1
  26. package/dist/scripts/module-delete.js +1 -1
  27. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  28. package/dist/templates/DefaultDoc.js +1 -1
  29. package/dist/templates/DocPage.js +1 -1
  30. package/dist/templates/DocsPageWithModules.js +1 -1
  31. package/dist/types/menu.d.ts +23 -0
  32. package/dist/types/menu.d.ts.map +1 -0
  33. package/dist/types/menu.js +1 -0
  34. package/package.json +9 -1
  35. package/src/app-shell/layout.tsx +1 -1
  36. package/src/components/NotificationContainer.tsx +1 -1
  37. package/src/index.ts +24 -19
  38. package/src/layouts/AdminLayoutWithSidebar.tsx +11 -3
  39. package/src/layouts/AppProviders.tsx +4 -4
  40. package/src/layouts/AuthLayoutWithSidebar.tsx +18 -4
  41. package/src/layouts/PublicLayoutWithSidebar.tsx +2 -2
  42. package/src/layouts/RootLayout.tsx +1 -1
  43. package/src/scripts/init-app.ts +479 -49
  44. package/src/scripts/module-add.ts +1 -1
  45. package/src/scripts/module-build.ts +46 -22
  46. package/src/scripts/module-create.ts +8 -8
  47. package/src/scripts/module-delete.ts +1 -4
  48. package/src/templates/DefaultDoc.tsx +264 -0
  49. package/src/templates/DocPage.tsx +1 -1
  50. package/src/templates/DocsPageWithModules.tsx +1 -1
  51. package/src/types/menu.ts +18 -0
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
4
4
  // Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
5
5
  const projectRoot = process.env.PROJECT_ROOT || process.cwd();
6
6
  // Si on est dans une app, monter jusqu'à la racine du monorepo
7
- const monorepoRoot = projectRoot.includes("/apps/")
7
+ const _monorepoRoot = projectRoot.includes("/apps/")
8
8
  ? path.resolve(projectRoot, "..", "..")
9
9
  : projectRoot;
10
10
  const appDirectory = path.join(projectRoot, "app");
@@ -176,6 +176,11 @@ function buildPage(moduleConfig, page) {
176
176
  const isUserDetailPage = page.section === "admin" &&
177
177
  page.path.includes("users/[id]") &&
178
178
  page.componentExport === "UserPage";
179
+ // Détecter les pages légales qui utilisent cookies (privacy, terms, returns)
180
+ const isLegalPage = page.section === "public" &&
181
+ (page.path.includes("privacy") ||
182
+ page.path.includes("terms") ||
183
+ page.path.includes("returns"));
179
184
  let content;
180
185
  if (isUserDetailPage) {
181
186
  // Page spéciale SSR avec injection des user tabs
@@ -214,7 +219,18 @@ const ${page.componentExport} = dynamic(
214
219
  { ssr: false }
215
220
  );
216
221
 
217
- export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"} {
222
+ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<string, unknown>)" : "()"} {
223
+ return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
224
+ }
225
+ `;
226
+ }
227
+ else if (isLegalPage) {
228
+ content = `// GENERATED BY LASTBRAIN MODULE BUILD
229
+ import { ${page.componentExport} } from "${moduleConfig.moduleName}";
230
+
231
+ export const dynamic = 'force-dynamic';
232
+
233
+ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<string, unknown>)" : "()"} {
218
234
  return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
219
235
  }
220
236
  `;
@@ -223,7 +239,7 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"
223
239
  content = `// GENERATED BY LASTBRAIN MODULE BUILD
224
240
  import { ${page.componentExport} } from "${moduleConfig.moduleName}";
225
241
 
226
- export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"} {
242
+ export default function ${wrapperName}${hasDynamicParams ? "(props: Record<string, unknown>)" : "()"} {
227
243
  return <${page.componentExport} ${hasDynamicParams ? "{...props}" : ""} />;
228
244
  }
229
245
  `;
@@ -832,25 +848,30 @@ async function generateUserTabsConfig(moduleConfigs) {
832
848
  moduleName: config.moduleName,
833
849
  })))
834
850
  .sort((a, b) => (a.order || 0) - (b.order || 0));
851
+ let appContent;
835
852
  if (userTabsConfigs.length === 0) {
836
- console.log("⏭️ No user tabs configuration found in modules");
837
- return;
853
+ console.log("⏭️ No user tabs configuration found in modules, creating empty config");
854
+ // Créer un fichier vide avec l'interface correcte
855
+ const timestamp = new Date().toISOString();
856
+ appContent = `// GENERATED FILE - DO NOT EDIT MANUALLY\n// User tabs configuration\n// Generated at: ${timestamp}\n\n"use client";\n\nimport type React from "react";\n\nexport interface ModuleUserTab {\n key: string;\n title: string;\n icon?: string;\n component: React.ComponentType<{ userId: string }>;\n}\n\nexport const moduleUserTabs: ModuleUserTab[] = [];\n\nexport default moduleUserTabs;\n`;
838
857
  }
839
- // Générer les imports statiques (Next/dynamic pour chaque composant)
840
- const importsForApp = userTabsConfigs
841
- .map((tab) => `const ${tab.componentExport} = dynamic(() => import("${tab.moduleName}").then(mod => ({ default: mod.${tab.componentExport} })), { ssr: true });`)
842
- .join("\n");
843
- // Générer le tableau des tabs
844
- const tabsArray = userTabsConfigs
845
- .map((tab) => ` {
858
+ else {
859
+ // Générer les imports statiques (Next/dynamic pour chaque composant)
860
+ const importsForApp = userTabsConfigs
861
+ .map((tab) => `const ${tab.componentExport} = dynamic(() => import("${tab.moduleName}").then(mod => ({ default: mod.${tab.componentExport} })), { ssr: true });`)
862
+ .join("\n");
863
+ // Générer le tableau des tabs
864
+ const tabsArray = userTabsConfigs
865
+ .map((tab) => ` {
846
866
  key: "${tab.key}",
847
867
  title: "${tab.title}",
848
868
  icon: "${tab.icon || ""}",
849
869
  component: ${tab.componentExport},
850
870
  }`)
851
- .join(",\n");
852
- const timestamp = new Date().toISOString();
853
- const appContent = `// GENERATED FILE - DO NOT EDIT MANUALLY\n// User tabs configuration\n// Generated at: ${timestamp}\n\n"use client";\n\nimport dynamic from "next/dynamic";\nimport type React from "react";\n\n${importsForApp}\n\nexport interface ModuleUserTab {\n key: string;\n title: string;\n icon?: string;\n component: React.ComponentType<{ userId: string }>;\n}\n\nexport const moduleUserTabs: ModuleUserTab[] = [\n${tabsArray}\n];\n\nexport default moduleUserTabs;\n`;
871
+ .join(",\n");
872
+ const timestamp = new Date().toISOString();
873
+ appContent = `// GENERATED FILE - DO NOT EDIT MANUALLY\n// User tabs configuration\n// Generated at: ${timestamp}\n\n"use client";\n\nimport dynamic from "next/dynamic";\nimport type React from "react";\n\n${importsForApp}\n\nexport interface ModuleUserTab {\n key: string;\n title: string;\n icon?: string;\n component: React.ComponentType<{ userId: string }>;\n}\n\nexport const moduleUserTabs: ModuleUserTab[] = [\n${tabsArray}\n];\n\nexport default moduleUserTabs;\n`;
874
+ }
854
875
  // Créer le fichier de configuration (uniquement dans /config)
855
876
  const outputPath = path.join(projectRoot, "config", "user-tabs.ts");
856
877
  const configDir = path.dirname(outputPath);
@@ -321,7 +321,7 @@ export { default as buildConfig } from "./${moduleNameOnly}.build.config";
321
321
  /**
322
322
  * Génère le contenu du fichier server.ts
323
323
  */
324
- function generateServerTs(tables) {
324
+ function _generateServerTs(tables) {
325
325
  const exports = [];
326
326
  for (const table of tables) {
327
327
  for (const section of table.sections) {
@@ -1116,7 +1116,7 @@ export function getAvailableModuleNames(): string[] {
1116
1116
  const arrayMatch = content.match(/export const AVAILABLE_MODULES: ModuleMetadata\[\] = \[([\s\S]*?)\];/);
1117
1117
  if (arrayMatch) {
1118
1118
  const arrayContent = arrayMatch[1];
1119
- const lastItem = arrayContent.trim().split("\n").pop();
1119
+ const _lastItem = arrayContent.trim().split("\n").pop();
1120
1120
  // Ajouter le nouveau module à la fin du tableau
1121
1121
  const newArrayContent = arrayContent.trimEnd() + "\n" + moduleEntry + "\n";
1122
1122
  content = content.replace(/export const AVAILABLE_MODULES: ModuleMetadata\[\] = \[([\s\S]*?)\];/, `export const AVAILABLE_MODULES: ModuleMetadata[] = [${newArrayContent}];`);
@@ -1127,8 +1127,8 @@ export function getAvailableModuleNames(): string[] {
1127
1127
  console.log(chalk.yellow(" ⚠️ Format du registre non reconnu, ajout manuel requis"));
1128
1128
  }
1129
1129
  }
1130
- catch (error) {
1131
- console.log(chalk.yellow(` ⚠️ Erreur lors de la mise à jour du registre: ${error}`));
1130
+ catch (_error) {
1131
+ console.log(chalk.yellow(` ⚠️ Erreur lors de la mise à jour du registre: ${_error}`));
1132
1132
  console.log(chalk.gray(" Vous devrez ajouter manuellement le module au registre"));
1133
1133
  }
1134
1134
  }
@@ -1243,7 +1243,7 @@ export async function createModuleStructure(config, rootDir) {
1243
1243
  });
1244
1244
  console.log(chalk.green("\n✅ Dépendances installées avec succès!"));
1245
1245
  }
1246
- catch (error) {
1246
+ catch (_error) {
1247
1247
  console.log(chalk.yellow("\n⚠️ Erreur lors de l'installation, veuillez exécuter manuellement:"));
1248
1248
  console.log(chalk.gray(` cd ${moduleDir} && pnpm install`));
1249
1249
  }
@@ -1259,7 +1259,7 @@ export async function createModuleStructure(config, rootDir) {
1259
1259
  });
1260
1260
  console.log(chalk.green("✓ Module compilé"));
1261
1261
  }
1262
- catch (error) {
1262
+ catch (_error) {
1263
1263
  console.log(chalk.yellow("⚠️ Build automatique échoué, exécutez: cd"), config.slug, "&& pnpm build");
1264
1264
  }
1265
1265
  console.log(chalk.green(`\n✅ Module ${config.slug} créé avec succès!\n`));
@@ -1413,8 +1413,8 @@ export async function createModule() {
1413
1413
  try {
1414
1414
  rootDir = findWorkspaceRoot();
1415
1415
  }
1416
- catch (error) {
1417
- console.error(chalk.red("❌ " + error.message));
1416
+ catch (_error) {
1417
+ console.error(chalk.red("❌ " + _error.message));
1418
1418
  process.exit(1);
1419
1419
  }
1420
1420
  // Créer le module
@@ -1 +1 @@
1
- {"version":3,"file":"module-delete.d.ts","sourceRoot":"","sources":["../../src/scripts/module-delete.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAsB,YAAY,kBAkLjC"}
1
+ {"version":3,"file":"module-delete.d.ts","sourceRoot":"","sources":["../../src/scripts/module-delete.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAsB,YAAY,kBAkLjC"}
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import chalk from "chalk";
4
4
  import inquirer from "inquirer";
5
5
  import { fileURLToPath } from "url";
6
- import { AVAILABLE_MODULES, } from "@lastbrain/core/config/modules";
6
+ import { AVAILABLE_MODULES } from "@lastbrain/core/config/modules";
7
7
  const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = path.dirname(__filename);
9
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultDoc.d.ts","sourceRoot":"","sources":["../../src/templates/DefaultDoc.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,oBAAoB,4CAkqDnC"}
1
+ {"version":3,"file":"DefaultDoc.d.ts","sourceRoot":"","sources":["../../src/templates/DefaultDoc.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,oBAAoB,4CA06DnC"}
@@ -28,7 +28,7 @@ export function DefaultDocumentation() {
28
28
  handler: "./api/mon-api",
29
29
  },
30
30
  ],
31
- };` }) })] })] }), _jsxs(Card, { id: "section-database", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Database, { size: 24 }), "Base de donn\u00E9es"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Supabase local" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "LastBrain utilise Supabase pour la base de donn\u00E9es et l'authentification." }), _jsx("h4", { className: "font-medium mb-2", children: "D\u00E9marrer Supabase" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm db:init` }), _jsx("h4", { className: "font-medium mb-2", children: "Synchroniser les migrations" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm db:migrations:sync` }), _jsx("h4", { className: "font-medium mb-2", children: "Cr\u00E9er une migration" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "supabase migration new ma_migration" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Migrations des modules" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Les modules peuvent avoir leurs propres migrations dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "supabase/migrations/" }), ". Elles sont automatiquement synchronis\u00E9es lors du build."] })] })] }), _jsxs(Card, { id: "section-admin", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(User, { size: 24 }), "Cr\u00E9er un Compte Administrateur"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "1. Cr\u00E9er un compte" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Allez sur", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/signup" }), " ", "et cr\u00E9ez votre compte"] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "2. Promouvoir en administrateur" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Ex\u00E9cutez cette requ\u00EAte SQL dans le Studio Supabase (", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "http://localhost:54323" }), ") ou via psql :"] }), _jsxs(Snippet, { hideSymbol: true, color: "primary", className: "text-sm mb-2", children: [_jsx("span", { children: `update auth.users` }), _jsx("span", { children: `set raw_app_meta_data = jsonb_set(` }), _jsx("span", { children: ` coalesce(raw_app_meta_data, '{}'::jsonb),` }), _jsx("span", { children: ` '{roles}',` }), _jsx("span", { children: ` '["admin"]'::jsonb` }), _jsx("span", { children: `)` }), _jsx("span", { children: `where email = 'votre@email.com';` })] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "3. Acc\u00E9der \u00E0 l'interface admin" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Vous pouvez maintenant acc\u00E9der \u00E0", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/admin" })] })] })] }), _jsxs(Card, { id: "section-development", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Rocket, { size: 24 }), "D\u00E9veloppement"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Lancer le serveur de d\u00E9veloppement" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm dev` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "G\u00E9n\u00E9rer les routes des modules" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "\u00C0 ex\u00E9cuter apr\u00E8s avoir ajout\u00E9 ou modifi\u00E9 des modules" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Build de production" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "D\u00E9velopper un module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `cd packages/module-auth` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm dev` })] })] }), _jsxs(Card, { id: "section-routes", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Shield, { size: 24 }), "Routes Prot\u00E9g\u00E9es"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Le middleware prot\u00E8ge automatiquement vos routes selon ces r\u00E8gles :" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Routes" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: [_jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/auth/*" }), " ", "\u2192 Authentification requise"] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: [_jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/admin/*" }), " ", "\u2192 Superadmin uniquement"] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: [_jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/docs/*" }), " ", "\u2192 Acc\u00E8s public"] })] })] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le middleware se trouve dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "middleware.ts" })] })] })] }), _jsxs(Card, { id: "section-workflow", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Zap, { size: 24 }), "Workflow de D\u00E9veloppement"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "1. Modifier un module" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["\u00C9ditez les fichiers dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "packages/module-*/src" })] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "2. Compiler le module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `cd packages/module-auth` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "3. R\u00E9g\u00E9n\u00E9rer les pages" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `cd apps/my-app` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "4. Appliquer les migrations" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "supabase migration up" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "5. Tester" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm dev` })] })] }), _jsxs(Card, { id: "section-ui", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Palette, { size: 24 }), "Interface utilisateur"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Composants NextUI" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le package", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "@lastbrain/ui" }), " ", "r\u00E9exporte les composants NextUI avec une configuration Tailwind personnalis\u00E9e."] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Th\u00E8mes" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le mode sombre/clair est g\u00E9r\u00E9 par", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "next-themes" }), ". Le switch est disponible dans le header de l'application."] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Tailwind CSS v4" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le projet utilise Tailwind CSS v4 avec un preset personnalis\u00E9 partag\u00E9 via", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "@lastbrain/ui/tailwind.preset" }), "."] })] })] }), _jsxs(Card, { id: "section-storage", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(HardDrive, { size: 24 }), "Syst\u00E8me de Proxy Storage"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "LastBrain int\u00E8gre un syst\u00E8me de proxy pour les fichiers Supabase Storage qui permet d'utiliser des URLs propres avec contr\u00F4le d'acc\u00E8s granulaire." }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "URLs Proxy vs URLs Supabase" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("strong", { children: "\u274C URL Supabase classique (longue)" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "https://xxx.supabase.co/storage/v1/object/public/avatar/user_128_123456.webp" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("strong", { children: "\u2705 URL Proxy (propre)" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "/api/storage/avatar/user_128_123456.webp" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Configuration des Buckets" }), _jsx("h4", { className: "font-medium mb-2", children: "\uD83D\uDCC2 avatar (Public)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Photos de profil et avatars \u2022 10MB max \u2022 Types: JPEG, PNG, WebP, GIF" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "/api/storage/avatar/user_128_123456.webp" }), _jsx("h4", { className: "font-medium mb-2", children: "\uD83D\uDD12 app (Priv\u00E9)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Fichiers priv\u00E9s des utilisateurs \u2022 100MB max \u2022 Authentification requise" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "/api/storage/app/user-id/documents/file.pdf" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Utilisation dans le code" }), _jsx(Alert, { hideIcon: true, color: "primary", className: "p-4 mb-4", children: _jsx("pre", { className: "whitespace-pre-wrap", children: `// Upload d'un fichier
31
+ };` }) })] })] }), _jsxs(Card, { id: "section-database", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Database, { size: 24 }), "Base de donn\u00E9es"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Supabase local" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "LastBrain utilise Supabase pour la base de donn\u00E9es et l'authentification." }), _jsx("h4", { className: "font-medium mb-2", children: "D\u00E9marrer Supabase" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm db:init` }), _jsx("h4", { className: "font-medium mb-2", children: "Synchroniser les migrations" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm db:migrations:sync` }), _jsx("h4", { className: "font-medium mb-2", children: "Cr\u00E9er une migration" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "supabase migration new ma_migration" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Migrations des modules" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Les modules peuvent avoir leurs propres migrations dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "supabase/migrations/" }), ". Elles sont automatiquement synchronis\u00E9es lors du build."] })] })] }), _jsxs(Card, { id: "section-admin", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(User, { size: 24 }), "Cr\u00E9er un Compte Administrateur"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "1. Cr\u00E9er un compte" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Allez sur", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/signup" }), " ", "et cr\u00E9ez votre compte"] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "2. Promouvoir en administrateur" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Ex\u00E9cutez cette requ\u00EAte SQL dans le Studio Supabase (", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "http://localhost:54323" }), ") ou via psql :"] }), _jsxs(Snippet, { hideSymbol: true, color: "primary", className: "text-sm mb-2", children: [_jsx("span", { children: `update auth.users` }), _jsx("span", { children: `set raw_app_meta_data = jsonb_set(` }), _jsx("span", { children: ` coalesce(raw_app_meta_data, '{}'::jsonb),` }), _jsx("span", { children: ` '{roles}',` }), _jsx("span", { children: ` '["admin"]'::jsonb` }), _jsx("span", { children: `)` }), _jsx("span", { children: `where email = 'votre@email.com';` })] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "3. Acc\u00E9der \u00E0 l'interface admin" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Vous pouvez maintenant acc\u00E9der \u00E0", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/admin" })] })] })] }), _jsxs(Card, { id: "section-development", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Rocket, { size: 24 }), "D\u00E9veloppement"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Lancer le serveur de d\u00E9veloppement" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm dev` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "G\u00E9n\u00E9rer les routes des modules" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "\u00C0 ex\u00E9cuter apr\u00E8s avoir ajout\u00E9 ou modifi\u00E9 des modules" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Build de production" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "D\u00E9velopper un module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `cd packages/module-auth` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm dev` })] })] }), _jsxs(Card, { id: "section-routes", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Shield, { size: 24 }), "Routes Prot\u00E9g\u00E9es"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Le middleware prot\u00E8ge automatiquement vos routes selon ces r\u00E8gles :" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Routes" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: [_jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/auth/*" }), " ", "\u2192 Authentification requise"] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: [_jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/admin/*" }), " ", "\u2192 Superadmin uniquement"] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: [_jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/docs/*" }), " ", "\u2192 Acc\u00E8s public"] })] })] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le middleware se trouve dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "middleware.ts" })] })] })] }), _jsxs(Card, { id: "section-workflow", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Zap, { size: 24 }), "Workflow de D\u00E9veloppement"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "1. Modifier un module" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["\u00C9ditez les fichiers dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "packages/module-*/src" })] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "2. Compiler le module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `cd packages/module-auth` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "3. R\u00E9g\u00E9n\u00E9rer les pages" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `cd apps/my-app` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "4. Appliquer les migrations" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "supabase migration up" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "5. Tester" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm dev` })] })] }), _jsxs(Card, { id: "section-ui", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Palette, { size: 24 }), "Interface utilisateur"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Composants NextUI" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le package", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "@lastbrain/ui" }), " ", "r\u00E9exporte les composants NextUI avec une configuration Tailwind personnalis\u00E9e."] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Th\u00E8mes" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le mode sombre/clair est g\u00E9r\u00E9 par", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "next-themes" }), ". Le switch est disponible dans le header de l'application."] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Tailwind CSS v4" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Le projet utilise Tailwind CSS v4 avec un preset personnalis\u00E9 partag\u00E9 via", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "@lastbrain/ui/tailwind.preset" }), "."] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "1. Ajouter une page et un menu custom" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "1. Cr\u00E9e une page dans l'app cible (exemple public) :" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "```tsx"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "// apps/lastbrain/app/nouvelle-page/page.tsx"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "export default function NouvellePage() &lbrace;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "return ", _jsx("div", { children: "Nouvelle Page" }), ";"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "&rbrace;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "```"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["2. D\u00E9clare le menu dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "apps/lastbrain/config/menu-custom.ts" }), " ", "(fichier optionnel, non \u00E9cras\u00E9 par les outils) :"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "```ts"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "import type &lbrace; MenuCustom &rbrace; from \"./menu-custom\";"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "export const menuCustom: MenuCustom = &lbrace;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "public: ["] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "&lbrace;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "title: \"Nouvelle Page\","] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "description: \"Une page custom\","] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "icon: \"Plus\","] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "path: \"/nouvelle-page\","] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "order: 100,"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "&rbrace;,"] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: " ]," }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "auth: [],"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "admin: [],"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "&rbrace;;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "```"] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "3. Choisis le pr\u00E9fixe de route pour la section :" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "-", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/" }), " ", "pour les pages publiques (header + palette + AppAside public)."] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "-", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/auth/..." }), " ", "pour les pages authentifi\u00E9es (header + palette + AppAside auth)."] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "-", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/admin/..." }), " ", "pour les pages admin (header + palette + AppAside admin)."] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["4. (Optionnel) Ajoute", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "order" }), ",", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "icon" }), ",", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "description" }), " ", "ou", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "shortcutDisplay" }), " ", "pour contr\u00F4ler le tri, l'ic\u00F4ne Lucide et l'affichage dans la palette."] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["5. Aucun rebuild sp\u00E9cial : le syst\u00E8me charge automatiquement", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "menu-custom.ts" }), " ", "et fusionne les menus custom avec la config existante dans le Header, le Aside et la palette (KeyboardKey)."] }), _jsx("h4", { className: "font-medium mb-2", children: "Cas d'usage rapides" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: ["Page publique custom : place ton fichier dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "apps/lastbrain/app/(custom)/nouvelle-page/page.tsx" }), " ", "et ajoute l'entr\u00E9e dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "menuCustom.public" }), " ", "avec", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "path: \"/nouvelle-page\"" }), "."] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: ["Page auth custom : place la page dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "apps/lastbrain/app/(custom)/auth/ma-page/page.tsx" }), " ", "avec un", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "path: \"/auth/ma-page\"" }), " ", "dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "menuCustom.auth" }), "."] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: ["Page admin custom : m\u00EAme principe avec un chemin", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/admin/..." }), " ", "et une entr\u00E9e dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "menuCustom.admin" }), "."] })] })] }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "2. Masquer des menus et bloquer les routes (menu-ignored)" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["1. Cr\u00E9e", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "apps/lastbrain/config/menu-ignored.ts" }), " ", "si absent :"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "```ts"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "export const menuIgnored = &lbrace;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "public: [&lbrace; title: \"Boutique\", path: \"/shop\" &rbrace;],"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "auth: ["] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "&lbrace; title: \"Mes commandes\", path: \"/auth/core-order/account/orders\" &rbrace;,"] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: " ]," }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "&rbrace;;"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [" ", "```"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["2. Le Header, l'AppAside et la palette excluent ces entr\u00E9es par", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "title" }), "."] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["3. Le middleware redirige toute route correspondante (", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "path" }), " ", "exact ou pr\u00E9fixe) vers", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "/404" }), "."] })] })] }), _jsxs(Card, { id: "section-storage", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(HardDrive, { size: 24 }), "Syst\u00E8me de Proxy Storage"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "LastBrain int\u00E8gre un syst\u00E8me de proxy pour les fichiers Supabase Storage qui permet d'utiliser des URLs propres avec contr\u00F4le d'acc\u00E8s granulaire." }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "URLs Proxy vs URLs Supabase" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("strong", { children: "\u274C URL Supabase classique (longue)" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "https://xxx.supabase.co/storage/v1/object/public/avatar/user_128_123456.webp" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("strong", { children: "\u2705 URL Proxy (propre)" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "/api/storage/avatar/user_128_123456.webp" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Configuration des Buckets" }), _jsx("h4", { className: "font-medium mb-2", children: "\uD83D\uDCC2 avatar (Public)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Photos de profil et avatars \u2022 10MB max \u2022 Types: JPEG, PNG, WebP, GIF" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "/api/storage/avatar/user_128_123456.webp" }), _jsx("h4", { className: "font-medium mb-2", children: "\uD83D\uDD12 app (Priv\u00E9)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Fichiers priv\u00E9s des utilisateurs \u2022 100MB max \u2022 Authentification requise" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "/api/storage/app/user-id/documents/file.pdf" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Utilisation dans le code" }), _jsx(Alert, { hideIcon: true, color: "primary", className: "p-4 mb-4", children: _jsx("pre", { className: "whitespace-pre-wrap", children: `// Upload d'un fichier
32
32
  import { uploadFile } from "@/api/storage";
33
33
 
34
34
  const proxyUrl = await uploadFile(
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  import { useState, useEffect } from "react";
5
5
  import { Card, CardBody, Listbox, ListboxItem, Chip, Button, Drawer, DrawerContent, DrawerHeader, DrawerBody, Accordion, AccordionItem, } from "@lastbrain/ui";
6
6
  import { Menu, Home, Sparkles, Rocket, Building2, Package, Database, Palette, BookOpen, Link, Blocks, HardDrive, RotateCcw, } from "lucide-react";
7
- import { DefaultDocumentation } from "./DefaultDoc.js";
7
+ import { DefaultDocumentation } from "./DefaultDoc";
8
8
  const NavigationListbox = ({ navigationItems, selectedModule, scrollToSection, setSelectedModule, }) => (_jsx(Listbox, { "aria-label": "Navigation", selectionMode: "single", variant: "solid", selectedKeys: selectedModule ? [selectedModule] : [], onSelectionChange: (keys) => {
9
9
  const key = Array.from(keys)[0];
10
10
  if (key) {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { DocPage } from "./DocPage.js";
2
+ import { DocPage } from "./DocPage";
3
3
  export function DocsPageWithModules() {
4
4
  // This will be replaced by module-build script with actual module configs
5
5
  // MODULES_CONFIG_PLACEHOLDER
@@ -0,0 +1,23 @@
1
+ import type { MenuItem } from "@lastbrain/ui";
2
+ /**
3
+ * Menu items to hide from display and block routes
4
+ */
5
+ export interface MenuIgnored {
6
+ public: {
7
+ title: string;
8
+ path: string;
9
+ }[];
10
+ auth: {
11
+ title: string;
12
+ path: string;
13
+ }[];
14
+ }
15
+ /**
16
+ * Custom menu items to add without creating a module
17
+ */
18
+ export interface MenuCustom {
19
+ public?: MenuItem[];
20
+ auth?: MenuItem[];
21
+ admin?: MenuItem[];
22
+ }
23
+ //# sourceMappingURL=menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/types/menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;CACpB"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/app",
3
- "version": "0.1.47",
3
+ "version": "1.0.2",
4
4
  "description": "Framework modulaire Next.js avec CLI et système de modules",
5
5
  "private": false,
6
6
  "type": "module",
@@ -8,6 +8,7 @@
8
8
  "type": "public"
9
9
  },
10
10
  "main": "dist/index.js",
11
+ "module": "src/index.ts",
11
12
  "types": "dist/index.d.ts",
12
13
  "keywords": [
13
14
  "nextjs",
@@ -33,6 +34,13 @@
33
34
  "dist",
34
35
  "src"
35
36
  ],
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.js",
41
+ "default": "./dist/index.js"
42
+ }
43
+ },
36
44
  "dependencies": {
37
45
  "@lastbrain/core": "^0.1.0",
38
46
  "@lastbrain/ui": "^0.1.0",
@@ -1,5 +1,5 @@
1
1
  "use client";
2
2
 
3
- import { RootLayout } from "../layouts/RootLayout.js";
3
+ import { RootLayout } from "../layouts/RootLayout";
4
4
 
5
5
  export default RootLayout;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { Notification } from "@lastbrain/ui";
4
- import { useNotifications } from "../layouts/AppProviders.js";
4
+ import { useNotifications } from "../layouts/AppProviders";
5
5
 
6
6
  export function NotificationContainer() {
7
7
  const { data, loading, markAsRead, markAllAsRead, deleteNotification } =
package/src/index.ts CHANGED
@@ -3,28 +3,33 @@ export {
3
3
  useAuth,
4
4
  useModules,
5
5
  useNotifications as useNotificationsContext,
6
- } from "./layouts/AppProviders.js";
7
- export { useNotifications } from "./hooks/useNotifications.js";
8
- export { NotificationContainer } from "./components/NotificationContainer.js";
9
- export { useAuthSession } from "./auth/useAuthSession.js";
10
- export { signOut, signIn } from "./auth/authHelpers.js";
11
- export { RootLayout } from "./layouts/RootLayout.js";
12
- export { PublicLayout } from "./layouts/PublicLayout.js";
13
- export { PublicLayoutWithSidebar } from "./layouts/PublicLayoutWithSidebar.js";
14
- export { AuthLayout } from "./layouts/AuthLayout.js";
15
- export { AuthLayoutWithSidebar } from "./layouts/AuthLayoutWithSidebar.js";
16
- export { AdminLayout } from "./layouts/AdminLayout.js";
17
- export { AdminLayoutWithSidebar } from "./layouts/AdminLayoutWithSidebar.js";
18
- export { getModuleConfigs } from "./modules/module-loader.js";
6
+ } from "./layouts/AppProviders";
7
+ export { useNotifications } from "./hooks/useNotifications";
8
+ export { NotificationContainer } from "./components/NotificationContainer";
9
+ export { useAuthSession } from "./auth/useAuthSession";
10
+ export { signOut, signIn } from "./auth/authHelpers";
11
+ export { RootLayout } from "./layouts/RootLayout";
12
+ export { PublicLayout } from "./layouts/PublicLayout";
13
+ export { PublicLayoutWithSidebar } from "./layouts/PublicLayoutWithSidebar";
14
+ export { AuthLayout } from "./layouts/AuthLayout";
15
+ export {
16
+ AuthLayoutWithSidebar,
17
+ type AuthLayoutWithSidebarProps,
18
+ type MenuIgnored,
19
+ type MenuCustom,
20
+ } from "./layouts/AuthLayoutWithSidebar";
21
+ export { AdminLayout } from "./layouts/AdminLayout";
22
+ export { AdminLayoutWithSidebar } from "./layouts/AdminLayoutWithSidebar";
23
+ export { getModuleConfigs } from "./modules/module-loader";
19
24
 
20
25
  // Components
21
26
  export { AppAside } from "@lastbrain/ui";
22
27
  export type { AppAsideMenuItem, AppAsideMenuConfig } from "@lastbrain/ui";
23
28
  // Templates de pages (starter + docs)
24
29
 
25
- export { SimpleHomePage } from "./templates/SimpleHomePage.js";
26
- export { DocPage } from "./templates/DocPage.js";
27
- export { SimpleDocPage } from "./templates/SimpleDocPage.js";
28
- export { ModuleGuidePage } from "./templates/ModuleGuidePage.js";
29
- export { AuthGuidePage } from "./templates/AuthGuidePage.js";
30
- export { MigrationsGuidePage } from "./templates/MigrationsGuidePage.js";
30
+ export { SimpleHomePage } from "./templates/SimpleHomePage";
31
+ export { DocPage } from "./templates/DocPage";
32
+ export { SimpleDocPage } from "./templates/SimpleDocPage";
33
+ export { ModuleGuidePage } from "./templates/ModuleGuidePage";
34
+ export { AuthGuidePage } from "./templates/AuthGuidePage";
35
+ export { MigrationsGuidePage } from "./templates/MigrationsGuidePage";
@@ -1,8 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { AdminLayout } from "./AdminLayout.js";
4
- import { AppAside, AppAsideSkeleton, type MenuConfig } from "@lastbrain/ui";
5
- import { useAuthSession } from "../auth/useAuthSession.js";
3
+ import { AdminLayout } from "./AdminLayout";
4
+ import {
5
+ AppAside,
6
+ AppAsideSkeleton,
7
+ type MenuConfig,
8
+ type MenuItem,
9
+ } from "@lastbrain/ui";
10
+ import { useAuthSession } from "../auth/useAuthSession";
6
11
  import { usePathname } from "next/navigation";
7
12
  import { useEffect, useState } from "react";
8
13
 
@@ -10,12 +15,14 @@ interface AdminLayoutWithSidebarProps {
10
15
  children: React.ReactNode;
11
16
  menuConfig?: MenuConfig;
12
17
  className?: string;
18
+ menuCustom?: MenuItem[];
13
19
  }
14
20
 
15
21
  export function AdminLayoutWithSidebar({
16
22
  children,
17
23
  menuConfig,
18
24
  className = "",
25
+ menuCustom,
19
26
  }: AdminLayoutWithSidebarProps) {
20
27
  const { isSuperAdmin, loading, user } = useAuthSession();
21
28
  const pathname = usePathname();
@@ -102,6 +109,7 @@ export function AdminLayoutWithSidebar({
102
109
  isSuperAdmin={isSuperAdmin}
103
110
  isAuthenticated={!!user}
104
111
  className={className}
112
+ {...(menuCustom ? { menuCustom } : {})}
105
113
  />
106
114
  )}
107
115
 
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
 
3
3
  import { createContext, useContext, useMemo } from "react";
4
- import { getModuleConfigs } from "../modules/module-loader.js";
4
+ import { getModuleConfigs } from "../modules/module-loader";
5
5
  import { ToastProvider } from "@lastbrain/ui";
6
6
  import { RealtimeProvider } from "@lastbrain/core";
7
- import { useAuthSession } from "../auth/useAuthSession.js";
8
- import { useNotifications as useNotificationsHook } from "../hooks/useNotifications.js";
7
+ import { useAuthSession } from "../auth/useAuthSession";
8
+ import { useNotifications as useNotificationsHook } from "../hooks/useNotifications";
9
9
  import type { User } from "@supabase/supabase-js";
10
10
  import type { ModuleRealtimeConfig } from "@lastbrain/core";
11
- import type { NotificationsData } from "../hooks/useNotifications.js";
11
+ import type { NotificationsData } from "../hooks/useNotifications";
12
12
 
13
13
  const ModuleContext = createContext(getModuleConfigs());
14
14
  const NotificationContext = createContext<{
@@ -1,20 +1,32 @@
1
1
  "use client";
2
2
 
3
- import { AuthLayout } from "./AuthLayout.js";
4
- import { AppAside, AppAsideSkeleton, type MenuConfig } from "@lastbrain/ui";
5
- import { useAuthSession } from "../auth/useAuthSession.js";
3
+ import { AuthLayout } from "./AuthLayout";
4
+ import {
5
+ AppAside,
6
+ AppAsideSkeleton,
7
+ type MenuConfig,
8
+ type MenuItem,
9
+ } from "@lastbrain/ui";
10
+ import { useAuthSession } from "../auth/useAuthSession";
11
+ import type { MenuIgnored, MenuCustom } from "../types/menu";
6
12
  import { useEffect, useState } from "react";
7
13
 
8
- interface AuthLayoutWithSidebarProps {
14
+ export type { MenuIgnored, MenuCustom };
15
+
16
+ export interface AuthLayoutWithSidebarProps {
9
17
  children: React.ReactNode;
10
18
  menuConfig?: MenuConfig;
11
19
  className?: string;
20
+ menuIgnored?: MenuIgnored;
21
+ menuCustom?: MenuItem[];
12
22
  }
13
23
 
14
24
  export function AuthLayoutWithSidebar({
15
25
  children,
16
26
  menuConfig,
17
27
  className = "",
28
+ menuIgnored,
29
+ menuCustom,
18
30
  }: AuthLayoutWithSidebarProps) {
19
31
  const { isSuperAdmin, loading, user } = useAuthSession();
20
32
  const [isCollapsed, setIsCollapsed] = useState(() => {
@@ -100,6 +112,8 @@ export function AuthLayoutWithSidebar({
100
112
  isSuperAdmin={isSuperAdmin}
101
113
  isAuthenticated={!!user}
102
114
  className={className}
115
+ {...(menuIgnored ? { menuIgnored } : {})}
116
+ {...(menuCustom ? { menuCustom } : {})}
103
117
  />
104
118
  )}
105
119
  {/* Contenu principal avec marge pour la sidebar */}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { PublicLayout } from "./PublicLayout.js";
3
+ import { PublicLayout } from "./PublicLayout";
4
4
  import { AppAside, AppAsideSkeleton, type MenuConfig } from "@lastbrain/ui";
5
- import { useAuthSession } from "../auth/useAuthSession.js";
5
+ import { useAuthSession } from "../auth/useAuthSession";
6
6
  import { useEffect, useState } from "react";
7
7
 
8
8
  interface PublicLayoutWithSidebarProps {
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { ThemeProvider } from "next-themes";
4
- import { AppProviders } from "./AppProviders.js";
4
+ import { AppProviders } from "./AppProviders";
5
5
  import type { ModuleRealtimeConfig } from "@lastbrain/core";
6
6
 
7
7
  // Note: L'app Next.js doit importer son propre globals.css dans son layout