@lastbrain/app 0.1.37 → 0.1.40

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.
@@ -243,5 +243,5 @@ export function DocUsageCustom() {
243
243
  <p>Description...</p>
244
244
  </div>
245
245
  );
246
- }` }) }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Exporter la documentation" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "src/index.ts" }), " ", ":"] }), _jsx(Alert, { hideIcon: true, color: "primary", className: "p-4 mb-4", children: _jsx("pre", { className: "whitespace-pre-wrap", children: `export { MonModuleDoc } from "./components/Doc.js";` }) })] })] }), _jsxs(Card, { id: "section-links", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Link, { size: 24 }), "Liens utiles & remerciements \uD83D\uDE4F"] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://nextjs.org/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Next.js" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Documentation officielle" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://nextjs.org/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://supabase.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Supabase" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Documentation officielle" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://supabase.com/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://www.heroui.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Heroui" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Composants UI" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://www.heroui.com/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://tailwindcss.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Tailwind CSS" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Framework CSS" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://tailwindcss.com/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://lucide.dev/", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Lucide Icons" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Icones SVG" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://lucide.dev/" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://stripe.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Stripe" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Syst\u00E8me de paiement" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://stripe.com/docs" })] }) })] }) })] }), _jsxs(Card, { id: "section-modules", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(FileText, { size: 24 }), "Modules"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Les modules LastBrain permettent d'ajouter des fonctionnalit\u00E9s compl\u00E8tes \u00E0 votre application de mani\u00E8re modulaire." }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Modules Disponibles" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-ai](../packages/module-ai/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-ai" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 1 auth, 1 admin"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": user_token_ledger, user_prompts"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module ai" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module ai` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-ai/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-auth](../packages/module-auth/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-auth" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 3 publique(s), 4 auth, 2 admin"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": user_profil, user_address, user_notifications"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module auth" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module auth` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-auth/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-tasks](../packages/module-tasks/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-tasks" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 2 auth, 1 admin"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": tasks, tasks_logs"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module tasks" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module tasks` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-tasks/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Commandes" }), _jsx("h4", { className: "font-medium mb-2", children: "Installer un module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module nom-du-module` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h4", { className: "font-medium mb-2", children: "Cr\u00E9er un nouveau module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain create-module nom-du-module` }), _jsx("h4", { className: "font-medium mb-2", children: "G\u00E9n\u00E9rer la documentation des modules" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm generate:module-docs` }), _jsx("h4", { className: "font-medium mb-2", children: "Supprimer un module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain remove-module nom-du-module` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "D\u00E9veloppement de modules" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Pour cr\u00E9er un nouveau module, consultez la", " ", _jsx("a", { href: "./004_CREATE_MODULE.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "documentation de cr\u00E9ation de modules" }), "."] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "La documentation de chaque module est g\u00E9n\u00E9r\u00E9e automatiquement depuis :" }), _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: ["Le fichier", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "module-name.build.config.ts" }), " ", "pour les pages et APIs"] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsx("span", { children: "Les fichiers de migration SQL pour les tables" })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: ["Le README.md est auto-g\u00E9n\u00E9r\u00E9 avec", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "pnpm generate:module-docs" })] })] })] })] })] })] }));
246
+ }` }) }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Exporter la documentation" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Dans", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "src/index.ts" }), " ", ":"] }), _jsx(Alert, { hideIcon: true, color: "primary", className: "p-4 mb-4", children: _jsx("pre", { className: "whitespace-pre-wrap", children: `export { MonModuleDoc } from "./components/Doc.js";` }) })] })] }), _jsxs(Card, { id: "section-links", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Link, { size: 24 }), "Liens utiles & remerciements \uD83D\uDE4F"] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://nextjs.org/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Next.js" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Documentation officielle" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://nextjs.org/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://supabase.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Supabase" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Documentation officielle" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://supabase.com/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://www.heroui.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Heroui" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Composants UI" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://www.heroui.com/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://tailwindcss.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Tailwind CSS" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Framework CSS" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://tailwindcss.com/docs" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://lucide.dev/", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Lucide Icons" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Icones SVG" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://lucide.dev/" })] }) }), _jsx(Card, { className: "hover:shadow-lg transition-shadow", isPressable: true, as: "a", href: "https://stripe.com/docs", target: "_blank", rel: "noopener noreferrer", children: _jsxs(CardBody, { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Stripe" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "Syst\u00E8me de paiement" }), _jsx("p", { className: "text-xs text-blue-600 dark:text-blue-400 truncate", children: "https://stripe.com/docs" })] }) })] }) })] }), _jsxs(Card, { id: "section-modules", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(FileText, { size: 24 }), "Modules"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Les modules LastBrain permettent d'ajouter des fonctionnalit\u00E9s compl\u00E8tes \u00E0 votre application de mani\u00E8re modulaire." }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Modules Disponibles" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-ai](../packages/module-ai/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-ai" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 1 auth, 1 admin"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": user_token_ledger, user_prompts"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module ai" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module ai` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-ai/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-auth](../packages/module-auth/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-auth" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 3 publique(s), 4 auth, 2 admin"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": user_profil, user_address, user_notifications"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module auth" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module auth` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-auth/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-recipes](../packages/module-recipes/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-recipes" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 4 auth"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": recipes, recipe_favorites, recipe_comments, recipe_image_queue, recipe_embeddings"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module recipes" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module recipes` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-recipes/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h4", { className: "font-medium mb-2", children: "[module-tasks](../packages/module-tasks/README.md)" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "@lastbrain/module-tasks" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Pages" }), ": 2 auth, 1 admin"] }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: [_jsx("strong", { children: "Tables" }), ": tasks, tasks_logs"] }), _jsx("h4", { className: "font-medium mb-2", children: "Installation du module tasks" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module tasks` }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: _jsx("a", { href: "../packages/module-tasks/README.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "\uD83D\uDCD6 Documentation compl\u00E8te \u2192" }) }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "---" }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "Commandes" }), _jsx("h4", { className: "font-medium mb-2", children: "Installer un module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain add-module nom-du-module` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h4", { className: "font-medium mb-2", children: "Cr\u00E9er un nouveau module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain create-module nom-du-module` }), _jsx("h4", { className: "font-medium mb-2", children: "G\u00E9n\u00E9rer la documentation des modules" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm generate:module-docs` }), _jsx("h4", { className: "font-medium mb-2", children: "Supprimer un module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm lastbrain remove-module nom-du-module` }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: `pnpm build:modules` }), _jsx("h3", { className: "text-lg font-semibold mb-2", children: "D\u00E9veloppement de modules" }), _jsxs("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: ["Pour cr\u00E9er un nouveau module, consultez la", " ", _jsx("a", { href: "./004_CREATE_MODULE.md", className: "text-blue-600 hover:underline", target: "_blank", rel: "noopener noreferrer", children: "documentation de cr\u00E9ation de modules" }), "."] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "La documentation de chaque module est g\u00E9n\u00E9r\u00E9e automatiquement depuis :" }), _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: ["Le fichier", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "module-name.build.config.ts" }), " ", "pour les pages et APIs"] })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsx("span", { children: "Les fichiers de migration SQL pour les tables" })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-green-600", children: "\u2705" }), _jsxs("span", { children: ["Le README.md est auto-g\u00E9n\u00E9r\u00E9 avec", " ", _jsx("code", { className: "text-sm bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded", children: "pnpm generate:module-docs" })] })] })] })] })] })] }));
247
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/app",
3
- "version": "0.1.37",
3
+ "version": "0.1.40",
4
4
  "description": "Framework modulaire Next.js avec CLI et système de modules",
5
5
  "private": false,
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  "@tailwindcss/postcss": "^4.1.17",
52
52
  "@types/fs-extra": "^11.0.4",
53
53
  "@types/inquirer": "^9.0.9",
54
- "@types/node": "^20.0.0",
54
+ "@types/node": "^24.10.1",
55
55
  "inquirer": "^13.0.1",
56
56
  "postcss": "^8.4.0",
57
57
  "postcss-cli": "^11.0.0",
package/src/cli.ts CHANGED
@@ -97,8 +97,13 @@ program
97
97
  program
98
98
  .command("module:build")
99
99
  .description("Build les configurations de modules")
100
- .action(async () => {
100
+ .option("--debug", "Affiche tous les logs détaillés")
101
+ .action(async (options) => {
101
102
  try {
103
+ // Passer le flag debug via process.argv pour que module-build puisse le lire
104
+ if (options.debug && !process.argv.includes("--debug")) {
105
+ process.argv.push("--debug");
106
+ }
102
107
  const { runModuleBuild } = await import("./scripts/module-build.js");
103
108
  await runModuleBuild();
104
109
  } catch (error) {
@@ -4,6 +4,7 @@ import { AdminLayout } from "./AdminLayout.js";
4
4
  import { AppAside, AppAsideSkeleton } from "@lastbrain/ui";
5
5
  import { useAuthSession } from "../auth/useAuthSession.js";
6
6
  import { usePathname } from "next/navigation";
7
+ import { useEffect, useState } from "react";
7
8
 
8
9
  interface AdminLayoutWithSidebarProps {
9
10
  children: React.ReactNode;
@@ -18,6 +19,53 @@ export function AdminLayoutWithSidebar({
18
19
  }: AdminLayoutWithSidebarProps) {
19
20
  const { isSuperAdmin, loading, user } = useAuthSession();
20
21
  const pathname = usePathname();
22
+ const [isCollapsed, setIsCollapsed] = useState(() => {
23
+ if (typeof window !== "undefined") {
24
+ const savedState = localStorage.getItem("aside-collapsed");
25
+ return savedState !== null ? JSON.parse(savedState) : false;
26
+ }
27
+ return false;
28
+ });
29
+ const [mounted, setMounted] = useState(false);
30
+
31
+ useEffect(() => {
32
+ // eslint-disable-next-line react-hooks/set-state-in-effect
33
+ setMounted(true);
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ if (typeof window !== "undefined") {
38
+ // Écouter les changements du localStorage depuis le même onglet
39
+ const handleStorageChange = (_e: StorageEvent | CustomEvent) => {
40
+ const savedState = localStorage.getItem("aside-collapsed");
41
+ if (savedState !== null) {
42
+ // Utiliser queueMicrotask pour déférer le setState
43
+ queueMicrotask(() => {
44
+ setIsCollapsed(JSON.parse(savedState));
45
+ });
46
+ }
47
+ };
48
+
49
+ // Écouter l'événement storage (changements depuis d'autres onglets)
50
+ window.addEventListener("storage", handleStorageChange as EventListener);
51
+ // Écouter l'événement custom pour les changements du même onglet
52
+ window.addEventListener(
53
+ "localStorage-changed",
54
+ handleStorageChange as EventListener,
55
+ );
56
+
57
+ return () => {
58
+ window.removeEventListener(
59
+ "storage",
60
+ handleStorageChange as EventListener,
61
+ );
62
+ window.removeEventListener(
63
+ "localStorage-changed",
64
+ handleStorageChange as EventListener,
65
+ );
66
+ };
67
+ }
68
+ }, []);
21
69
 
22
70
  // Détecter si on est dans la section admin pour le skeleton
23
71
  const isAdminSection = pathname.startsWith("/admin");
@@ -51,7 +99,9 @@ export function AdminLayoutWithSidebar({
51
99
  )}
52
100
 
53
101
  {/* Contenu principal avec marge pour la sidebar */}
54
- <div className="flex-1 lg:ml-72">
102
+ <div
103
+ className={`flex-1 transition-all duration-300 ${!mounted ? "lg:ml-72" : isCollapsed ? "lg:ml-20" : "lg:ml-72"}`}
104
+ >
55
105
  <AdminLayout>{children}</AdminLayout>
56
106
  </div>
57
107
  </div>
@@ -3,6 +3,7 @@
3
3
  import { AuthLayout } from "./AuthLayout.js";
4
4
  import { AppAside, AppAsideSkeleton } from "@lastbrain/ui";
5
5
  import { useAuthSession } from "../auth/useAuthSession.js";
6
+ import { useEffect, useState } from "react";
6
7
 
7
8
  interface AuthLayoutWithSidebarProps {
8
9
  children: React.ReactNode;
@@ -16,6 +17,53 @@ export function AuthLayoutWithSidebar({
16
17
  className = "",
17
18
  }: AuthLayoutWithSidebarProps) {
18
19
  const { isSuperAdmin, loading, user } = useAuthSession();
20
+ const [isCollapsed, setIsCollapsed] = useState(() => {
21
+ if (typeof window !== "undefined") {
22
+ const savedState = localStorage.getItem("aside-collapsed");
23
+ return savedState !== null ? JSON.parse(savedState) : false;
24
+ }
25
+ return false;
26
+ });
27
+ const [mounted, setMounted] = useState(false);
28
+
29
+ useEffect(() => {
30
+ // eslint-disable-next-line react-hooks/set-state-in-effect
31
+ setMounted(true);
32
+ }, []);
33
+
34
+ useEffect(() => {
35
+ if (typeof window !== "undefined") {
36
+ // Écouter les changements du localStorage depuis le même onglet
37
+ const handleStorageChange = (_e: StorageEvent | CustomEvent) => {
38
+ const savedState = localStorage.getItem("aside-collapsed");
39
+ if (savedState !== null) {
40
+ // Utiliser queueMicrotask pour déférer le setState
41
+ queueMicrotask(() => {
42
+ setIsCollapsed(JSON.parse(savedState));
43
+ });
44
+ }
45
+ };
46
+
47
+ // Écouter l'événement storage (changements depuis d'autres onglets)
48
+ window.addEventListener("storage", handleStorageChange as EventListener);
49
+ // Écouter l'événement custom pour les changements du même onglet
50
+ window.addEventListener(
51
+ "localStorage-changed",
52
+ handleStorageChange as EventListener,
53
+ );
54
+
55
+ return () => {
56
+ window.removeEventListener(
57
+ "storage",
58
+ handleStorageChange as EventListener,
59
+ );
60
+ window.removeEventListener(
61
+ "localStorage-changed",
62
+ handleStorageChange as EventListener,
63
+ );
64
+ };
65
+ }
66
+ }, []);
19
67
 
20
68
  // Pour la section auth, isAdminSection sera false
21
69
  const isAdminSection = false;
@@ -48,7 +96,9 @@ export function AuthLayoutWithSidebar({
48
96
  />
49
97
  )}
50
98
  {/* Contenu principal avec marge pour la sidebar */}
51
- <div className="flex-1 lg:ml-72">
99
+ <div
100
+ className={`flex-1 transition-all duration-300 ${!mounted ? "lg:ml-72" : isCollapsed ? "lg:ml-20" : "lg:ml-72"}`}
101
+ >
52
102
  <AuthLayout>{children}</AuthLayout>
53
103
  </div>
54
104
  </div>
@@ -1,5 +1,5 @@
1
1
  "use client";
2
2
 
3
3
  export function PublicLayout({ children }: { children: React.ReactNode }) {
4
- return <section className=" ">{children}</section>;
4
+ return <section className="pt-16 px-2 md:px-0 ">{children}</section>;
5
5
  }
@@ -3,6 +3,7 @@
3
3
  import { PublicLayout } from "./PublicLayout.js";
4
4
  import { AppAside, AppAsideSkeleton } from "@lastbrain/ui";
5
5
  import { useAuthSession } from "../auth/useAuthSession.js";
6
+ import { useEffect, useState } from "react";
6
7
 
7
8
  interface PublicLayoutWithSidebarProps {
8
9
  children: React.ReactNode;
@@ -16,6 +17,53 @@ export function PublicLayoutWithSidebar({
16
17
  className = "",
17
18
  }: PublicLayoutWithSidebarProps) {
18
19
  const { user, loading } = useAuthSession();
20
+ const [isCollapsed, setIsCollapsed] = useState(() => {
21
+ if (typeof window !== "undefined") {
22
+ const savedState = localStorage.getItem("aside-collapsed");
23
+ return savedState !== null ? JSON.parse(savedState) : false;
24
+ }
25
+ return false;
26
+ });
27
+ const [mounted, setMounted] = useState(false);
28
+
29
+ useEffect(() => {
30
+ // eslint-disable-next-line react-hooks/set-state-in-effect
31
+ setMounted(true);
32
+ }, []);
33
+
34
+ useEffect(() => {
35
+ if (typeof window !== "undefined") {
36
+ // Écouter les changements du localStorage depuis le même onglet
37
+ const handleStorageChange = (_e: StorageEvent | CustomEvent) => {
38
+ const savedState = localStorage.getItem("aside-collapsed");
39
+ if (savedState !== null) {
40
+ // Utiliser queueMicrotask pour déférer le setState
41
+ queueMicrotask(() => {
42
+ setIsCollapsed(JSON.parse(savedState));
43
+ });
44
+ }
45
+ };
46
+
47
+ // Écouter l'événement storage (changements depuis d'autres onglets)
48
+ window.addEventListener("storage", handleStorageChange as EventListener);
49
+ // Écouter l'événement custom pour les changements du même onglet
50
+ window.addEventListener(
51
+ "localStorage-changed",
52
+ handleStorageChange as EventListener,
53
+ );
54
+
55
+ return () => {
56
+ window.removeEventListener(
57
+ "storage",
58
+ handleStorageChange as EventListener,
59
+ );
60
+ window.removeEventListener(
61
+ "localStorage-changed",
62
+ handleStorageChange as EventListener,
63
+ );
64
+ };
65
+ }
66
+ }, []);
19
67
 
20
68
  // Pour la section public, isAdminSection sera false
21
69
  const isAdminSection = false;
@@ -48,7 +96,9 @@ export function PublicLayoutWithSidebar({
48
96
  />
49
97
  )}
50
98
  {/* Contenu principal avec marge pour la sidebar */}
51
- <div className="flex-1 lg:ml-72">
99
+ <div
100
+ className={`flex-1 transition-all duration-300 ${!mounted ? "lg:ml-72" : isCollapsed ? "lg:ml-20" : "lg:ml-72"}`}
101
+ >
52
102
  <PublicLayout>{children}</PublicLayout>
53
103
  </div>
54
104
  </div>
@@ -903,7 +903,7 @@ async function createConfigFiles(
903
903
  ) {
904
904
  console.log(chalk.yellow("\n⚙️ Création des fichiers de configuration..."));
905
905
 
906
- // middleware.ts - Protection des routes /auth/* et /admin/*
906
+ // middleware.ts - Protection des routes /auth/*, /admin/* et /api/admin/*
907
907
  const middlewarePath = path.join(targetDir, "middleware.ts");
908
908
  if (!fs.existsSync(middlewarePath) || force) {
909
909
  const middleware = `import { type NextRequest, NextResponse } from "next/server";
@@ -911,6 +911,7 @@ import { createMiddlewareClient } from "@lastbrain/core/server";
911
911
 
912
912
  export async function middleware(request: NextRequest) {
913
913
  const { pathname } = request.nextUrl;
914
+ const isApi = pathname.startsWith("/api/");
914
915
 
915
916
  // Protéger les routes /auth/* (espace membre)
916
917
  if (pathname.startsWith("/auth")) {
@@ -920,33 +921,56 @@ export async function middleware(request: NextRequest) {
920
921
  data: { session },
921
922
  } = await supabase.auth.getSession();
922
923
 
923
- // Pas de session → redirection vers /signin
924
+ // Pas de session → nettoyage des cookies + redirection vers /auth/signin
924
925
  if (!session) {
925
- const redirectUrl = new URL("/signin", request.url);
926
+ const redirectUrl = new URL("/auth/signin", request.url);
926
927
  redirectUrl.searchParams.set("redirect", pathname);
927
- return NextResponse.redirect(redirectUrl);
928
+ const res = NextResponse.redirect(redirectUrl);
929
+ res.cookies.delete("sb-access-token");
930
+ res.cookies.delete("sb-refresh-token");
931
+ res.cookies.delete("sb:token");
932
+ res.cookies.delete("sb:refresh-token");
933
+ return res;
928
934
  }
929
935
 
930
936
  return response;
931
937
  } catch (error) {
932
938
  console.error("Middleware auth error:", error);
933
- return NextResponse.redirect(new URL("/signin", request.url));
939
+ const res = NextResponse.redirect(new URL("/auth/signin", request.url));
940
+ res.cookies.delete("sb-access-token");
941
+ res.cookies.delete("sb-refresh-token");
942
+ res.cookies.delete("sb:token");
943
+ res.cookies.delete("sb:refresh-token");
944
+ return res;
934
945
  }
935
946
  }
936
947
 
937
- // Protéger les routes /admin/* (superadmin uniquement)
938
- if (pathname.startsWith("/admin")) {
948
+ // Protéger les routes /admin/* et /api/admin/* (superadmin uniquement)
949
+ if (pathname.startsWith("/admin") || pathname.startsWith("/api/admin")) {
939
950
  try {
940
951
  const { supabase, response } = createMiddlewareClient(request);
941
952
  const {
942
953
  data: { session },
943
954
  } = await supabase.auth.getSession();
944
955
 
945
- // Pas de session → redirection vers /signin
956
+ // Pas de session → 401 JSON pour API, sinon redirection vers /auth/signin avec nettoyage cookies
946
957
  if (!session) {
947
- const redirectUrl = new URL("/signin", request.url);
958
+ if (isApi) {
959
+ const res = NextResponse.json({ error: "Non authentifié" }, { status: 401 });
960
+ res.cookies.delete("sb-access-token");
961
+ res.cookies.delete("sb-refresh-token");
962
+ res.cookies.delete("sb:token");
963
+ res.cookies.delete("sb:refresh-token");
964
+ return res;
965
+ }
966
+ const redirectUrl = new URL("/auth/signin", request.url);
948
967
  redirectUrl.searchParams.set("redirect", pathname);
949
- return NextResponse.redirect(redirectUrl);
968
+ const res = NextResponse.redirect(redirectUrl);
969
+ res.cookies.delete("sb-access-token");
970
+ res.cookies.delete("sb-refresh-token");
971
+ res.cookies.delete("sb:token");
972
+ res.cookies.delete("sb:refresh-token");
973
+ return res;
950
974
  }
951
975
 
952
976
  // Vérifier si l'utilisateur est superadmin
@@ -957,13 +981,24 @@ export async function middleware(request: NextRequest) {
957
981
 
958
982
  if (error || !isSuperAdmin) {
959
983
  console.error("Access denied: not a superadmin", error);
984
+ if (isApi) {
985
+ return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
986
+ }
960
987
  return NextResponse.redirect(new URL("/", request.url));
961
988
  }
962
989
 
963
990
  return response;
964
991
  } catch (error) {
965
992
  console.error("Middleware admin error:", error);
966
- return NextResponse.redirect(new URL("/", request.url));
993
+ if (isApi) {
994
+ return NextResponse.json({ error: "Erreur middleware" }, { status: 500 });
995
+ }
996
+ const res = NextResponse.redirect(new URL("/", request.url));
997
+ res.cookies.delete("sb-access-token");
998
+ res.cookies.delete("sb-refresh-token");
999
+ res.cookies.delete("sb:token");
1000
+ res.cookies.delete("sb:refresh-token");
1001
+ return res;
967
1002
  }
968
1003
  }
969
1004
 
@@ -985,7 +1020,9 @@ export const config = {
985
1020
  `;
986
1021
  await fs.writeFile(middlewarePath, middleware);
987
1022
  console.log(
988
- chalk.green("✓ middleware.ts créé (protection /auth/* et /admin/*)"),
1023
+ chalk.green(
1024
+ "✓ middleware.ts créé (protection /auth/*, /admin/* et /api/admin/*)",
1025
+ ),
989
1026
  );
990
1027
  }
991
1028
 
@@ -1561,6 +1598,13 @@ export const BUCKET_CONFIGS: Record<string, BucketConfig> = {
1561
1598
  return filePath.startsWith(\`\${userId}/\`);
1562
1599
  },
1563
1600
  },
1601
+ recipes: {
1602
+ name: "recipes",
1603
+ isPublic: true,
1604
+ description: "Public recipe images accessible by slug",
1605
+ allowedFileTypes: ["image/jpeg", "image/png", "image/webp", "image/gif"],
1606
+ maxFileSize: 50 * 1024 * 1024, // 50MB
1607
+ },
1564
1608
  // Example for future buckets:
1565
1609
  // public: {
1566
1610
  // name: "public",
@@ -205,15 +205,94 @@ export async function addModule(moduleName: string, targetDir: string) {
205
205
  chalk.yellow("\n⬆️ Application des nouvelles migrations..."),
206
206
  );
207
207
  try {
208
- execSync("supabase migration up", {
208
+ // Essayer en capturant la sortie pour diagnostiquer les erreurs fréquentes
209
+ execSync("supabase migration up --local", {
209
210
  cwd: targetDir,
210
- stdio: "inherit",
211
+ stdio: "pipe",
211
212
  });
212
213
  console.log(chalk.green("✓ Migrations appliquées"));
213
- } catch {
214
- console.error(
215
- chalk.red("❌ Erreur lors de l'application des migrations"),
214
+ } catch (error: any) {
215
+ const output = String(
216
+ error?.stderr || error?.stdout || error?.message || "",
217
+ );
218
+ const mismatch = output.includes(
219
+ "Remote migration versions not found in local migrations directory",
216
220
  );
221
+
222
+ if (mismatch) {
223
+ console.warn(
224
+ chalk.yellow("⚠️ Historique des migrations désynchronisé."),
225
+ );
226
+
227
+ // Extraire TOUTES les dates de migration depuis le message d'erreur
228
+ const repairMatch = output.match(
229
+ /supabase migration repair --status reverted ([\s\S]+?)\n/,
230
+ );
231
+ let migrationDates: string[] = [];
232
+
233
+ if (repairMatch && repairMatch[1]) {
234
+ // Extraire toutes les dates de la commande suggérée
235
+ migrationDates = repairMatch[1].trim().split(/\s+/);
236
+ } else {
237
+ // Fallback: chercher toutes les dates dans le message
238
+ const allDates = output.match(/20\d{6,14}/g);
239
+ migrationDates = allDates ? [...new Set(allDates)] : [];
240
+ }
241
+
242
+ if (migrationDates.length === 0) {
243
+ console.error(
244
+ chalk.red("❌ Impossible d'extraire les dates de migration"),
245
+ );
246
+ console.log(
247
+ chalk.gray("\nFaites un reset complet:\n supabase db reset\n"),
248
+ );
249
+ } else {
250
+ // Réparation automatique de l'historique
251
+ const datesStr = migrationDates.join(" ");
252
+ console.log(
253
+ chalk.yellow(`\n🔧 Réparation automatique de l'historique...`),
254
+ );
255
+ console.log(chalk.gray(` Dates: ${datesStr}`));
256
+
257
+ try {
258
+ execSync(
259
+ `supabase migration repair --status reverted ${datesStr} --local`,
260
+ {
261
+ cwd: targetDir,
262
+ stdio: "inherit",
263
+ },
264
+ );
265
+ console.log(chalk.green("✓ Historique réparé"));
266
+
267
+ console.log(
268
+ chalk.yellow("\n⬆️ Application des nouvelles migrations..."),
269
+ );
270
+ execSync("supabase migration up --local", {
271
+ cwd: targetDir,
272
+ stdio: "inherit",
273
+ });
274
+ console.log(chalk.green("✓ Migrations appliquées"));
275
+ } catch (e) {
276
+ console.error(
277
+ chalk.red("❌ Échec de la réparation automatique"),
278
+ );
279
+ console.log(
280
+ chalk.gray(
281
+ `\nVous pouvez essayer manuellement:\n supabase migration repair --status reverted ${datesStr} --local\n supabase migration up --local`,
282
+ ),
283
+ );
284
+ console.log(
285
+ chalk.gray(
286
+ `\nOu faire un reset complet:\n supabase db reset\n`,
287
+ ),
288
+ );
289
+ }
290
+ }
291
+ } else {
292
+ console.error(
293
+ chalk.red("❌ Erreur lors de l'application des migrations"),
294
+ );
295
+ }
217
296
  }
218
297
  } else {
219
298
  console.log(
@@ -287,13 +366,13 @@ export async function addModule(moduleName: string, targetDir: string) {
287
366
  // 7. Générer automatiquement les routes du module
288
367
  console.log(chalk.yellow("🔧 Génération des routes du module..."));
289
368
  try {
290
- execSync("pnpm build:modules", { cwd: targetDir, stdio: "inherit" });
369
+ execSync("pnpm run build:modules", { cwd: targetDir, stdio: "inherit" });
291
370
  console.log(chalk.green("✓ Routes du module générées"));
292
371
  } catch {
293
372
  console.error(chalk.red("❌ Erreur lors de la génération des routes"));
294
373
  console.error(
295
374
  chalk.gray(
296
- "Vous pouvez les générer manuellement avec: pnpm build:modules",
375
+ "Vous pouvez les générer manuellement avec: cd apps/<votre-app> && pnpm run build:modules",
297
376
  ),
298
377
  );
299
378
  }