@lastbrain/app 0.1.7 → 0.1.9

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 (40) hide show
  1. package/dist/scripts/init-app.js +3 -3
  2. package/dist/styles.css +2 -0
  3. package/package.json +11 -3
  4. package/src/app-shell/(admin)/layout.tsx +13 -0
  5. package/src/app-shell/(auth)/layout.tsx +13 -0
  6. package/src/app-shell/(public)/page.tsx +11 -0
  7. package/src/app-shell/layout.tsx +5 -0
  8. package/src/app-shell/not-found.tsx +28 -0
  9. package/src/auth/authHelpers.ts +24 -0
  10. package/src/auth/useAuthSession.ts +54 -0
  11. package/src/cli.ts +96 -0
  12. package/src/index.ts +21 -0
  13. package/src/layouts/AdminLayout.tsx +7 -0
  14. package/src/layouts/AppProviders.tsx +61 -0
  15. package/src/layouts/AuthLayout.tsx +7 -0
  16. package/src/layouts/PublicLayout.tsx +7 -0
  17. package/src/layouts/RootLayout.tsx +27 -0
  18. package/src/modules/module-loader.ts +14 -0
  19. package/src/scripts/README.md +262 -0
  20. package/src/scripts/db-init.ts +338 -0
  21. package/src/scripts/db-migrations-sync.ts +86 -0
  22. package/src/scripts/dev-sync.ts +218 -0
  23. package/src/scripts/init-app.ts +1077 -0
  24. package/src/scripts/module-add.ts +242 -0
  25. package/src/scripts/module-build.ts +502 -0
  26. package/src/scripts/module-create.ts +809 -0
  27. package/src/scripts/module-list.ts +37 -0
  28. package/src/scripts/module-remove.ts +367 -0
  29. package/src/scripts/readme-build.ts +60 -0
  30. package/src/styles.css +3 -0
  31. package/src/templates/AuthGuidePage.tsx +68 -0
  32. package/src/templates/DefaultDoc.tsx +462 -0
  33. package/src/templates/DocPage.tsx +381 -0
  34. package/src/templates/DocsPageWithModules.tsx +22 -0
  35. package/src/templates/MigrationsGuidePage.tsx +61 -0
  36. package/src/templates/ModuleGuidePage.tsx +71 -0
  37. package/src/templates/SimpleDocPage.tsx +587 -0
  38. package/src/templates/SimpleHomePage.tsx +385 -0
  39. package/src/templates/env.example/.env.example +6 -0
  40. package/src/templates/migrations/20201010100000_app_base.sql +228 -0
@@ -0,0 +1,381 @@
1
+ // GENERATED BY LASTBRAIN TEMPLATE (Documentation Page)
2
+ "use client";
3
+ import React, { useState, useEffect } from "react";
4
+ import {
5
+ Card,
6
+ CardBody,
7
+ CardHeader,
8
+ Listbox,
9
+ ListboxItem,
10
+ Chip,
11
+ Button,
12
+ Drawer,
13
+ DrawerContent,
14
+ DrawerHeader,
15
+ DrawerBody,
16
+ Snippet,
17
+ } from "@lastbrain/ui";
18
+ import {
19
+ Menu,
20
+ Home,
21
+ Sparkles,
22
+ Rocket,
23
+ Building2,
24
+ Package,
25
+ Database,
26
+ Palette,
27
+ BookOpen,
28
+ Link,
29
+ Blocks,
30
+ } from "lucide-react";
31
+ import { DefaultDocumentation } from "./DefaultDoc.js";
32
+
33
+ interface ModuleDocConfig {
34
+ id: string;
35
+ name: string;
36
+ description: string;
37
+ content: React.ReactNode;
38
+ available: boolean;
39
+ color?:
40
+ | "primary"
41
+ | "secondary"
42
+ | "success"
43
+ | "danger"
44
+ | "warning"
45
+ | "default"
46
+ | undefined;
47
+ number?: number;
48
+ }
49
+
50
+ interface DocPageProps {
51
+ modules?: ModuleDocConfig[];
52
+ defaultContent?: React.ReactNode;
53
+ }
54
+
55
+ export function DocPage({ modules = [], defaultContent }: DocPageProps) {
56
+ const [selectedModule, setSelectedModule] = useState<string>("default");
57
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
58
+
59
+ // Détecter la section visible lors du scroll
60
+ useEffect(() => {
61
+ const handleScroll = () => {
62
+ const sections = [
63
+ "default",
64
+ "section-welcome",
65
+ "section-quickstart",
66
+ "section-architecture",
67
+ "section-create-module",
68
+ "section-database",
69
+ "section-ui",
70
+ "section-module-docs",
71
+ "section-links",
72
+ ...(modules.length > 0 ? ["section-modules"] : []),
73
+ ...modules.map((m) => `module-${m.id}`),
74
+ ];
75
+
76
+ // Trouver la section actuellement visible
77
+ let currentSection = "default";
78
+
79
+ // Si on est tout en haut de la page
80
+ if (window.scrollY < 100) {
81
+ currentSection = "default";
82
+ } else {
83
+ // Parcourir les sections pour trouver celle qui est visible
84
+ for (const sectionId of sections) {
85
+ if (sectionId === "default") continue;
86
+
87
+ const element = document.getElementById(sectionId);
88
+ if (element) {
89
+ const rect = element.getBoundingClientRect();
90
+ // Section est visible si son top est dans la moitié supérieure de l'écran
91
+ if (rect.top <= window.innerHeight / 3 && rect.bottom >= 0) {
92
+ currentSection = sectionId.startsWith("module-")
93
+ ? sectionId.replace("module-", "")
94
+ : sectionId;
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ setSelectedModule(currentSection);
102
+ };
103
+
104
+ // Écouter le scroll
105
+ window.addEventListener("scroll", handleScroll);
106
+ // Appeler une fois au chargement
107
+ handleScroll();
108
+
109
+ return () => window.removeEventListener("scroll", handleScroll);
110
+ }, [modules]);
111
+
112
+ const scrollToSection = (sectionId: string) => {
113
+ setIsDrawerOpen(false); // Fermer le drawer après sélection
114
+ if (sectionId === "default") {
115
+ window.scrollTo({ top: 0, behavior: "smooth" });
116
+ return;
117
+ }
118
+
119
+ const elementId = sectionId.startsWith("section-")
120
+ ? sectionId
121
+ : `module-${sectionId}`;
122
+ const element = document.getElementById(elementId);
123
+ if (element) {
124
+ const yOffset = -70;
125
+ const y =
126
+ element.getBoundingClientRect().top + window.pageYOffset + yOffset;
127
+ window.scrollTo({ top: y, behavior: "smooth" });
128
+ }
129
+ };
130
+
131
+ const navigationItems = [
132
+ {
133
+ id: "default",
134
+ name: "Documentation",
135
+ description: "Accueil",
136
+ icon: Home,
137
+ },
138
+ {
139
+ id: "section-welcome",
140
+ name: "Bienvenue",
141
+ description: "",
142
+ icon: Sparkles,
143
+ },
144
+ {
145
+ id: "section-quickstart",
146
+ name: "Démarrage rapide",
147
+ description: "",
148
+ icon: Rocket,
149
+ },
150
+ {
151
+ id: "section-architecture",
152
+ name: "Architecture",
153
+ description: "",
154
+ icon: Building2,
155
+ },
156
+ {
157
+ id: "section-create-module",
158
+ name: "Créer un module",
159
+ description: "",
160
+ icon: Package,
161
+ },
162
+ {
163
+ id: "section-database",
164
+ name: "Base de données",
165
+ description: "",
166
+ icon: Database,
167
+ },
168
+ {
169
+ id: "section-ui",
170
+ name: "Interface utilisateur",
171
+ description: "",
172
+ icon: Palette,
173
+ },
174
+ {
175
+ id: "section-module-docs",
176
+ name: "Doc des modules",
177
+ description: "",
178
+ icon: BookOpen,
179
+ },
180
+ { id: "section-links", name: "Liens utiles", description: "", icon: Link },
181
+ ...(modules.length > 0
182
+ ? [
183
+ {
184
+ id: "section-modules",
185
+ name: "Modules disponibles",
186
+ description: "",
187
+ icon: Blocks,
188
+ number: modules.length,
189
+ },
190
+ ]
191
+ : []),
192
+ // Afficher seulement les modules actifs dans la navigation
193
+ ...modules
194
+ .filter((m) => m.available)
195
+ .map((m) => ({
196
+ id: m.id,
197
+ name: m.name,
198
+ description: m.description,
199
+ icon: Package,
200
+ color: "primary",
201
+ })),
202
+ ];
203
+
204
+ const NavigationListbox = () => (
205
+ <Listbox
206
+ aria-label="Navigation"
207
+ selectionMode="single"
208
+ selectedKeys={selectedModule ? [selectedModule] : []}
209
+ onSelectionChange={(keys) => {
210
+ const key = Array.from(keys)[0] as string;
211
+ if (key) {
212
+ scrollToSection(key);
213
+ } else {
214
+ setSelectedModule("");
215
+ window.scrollTo({ top: 0, behavior: "smooth" });
216
+ }
217
+ }}
218
+ items={navigationItems}
219
+ >
220
+ {(item: any) => {
221
+ const IconComponent = item.icon;
222
+ return (
223
+ <ListboxItem
224
+ key={item.id}
225
+ textValue={item.name}
226
+ description={item.description}
227
+ color={item.color}
228
+ variant="solid"
229
+ endContent={
230
+ item.number && (
231
+ <Chip size="sm" color="primary">
232
+ {item.number ?? 0}
233
+ </Chip>
234
+ )
235
+ }
236
+ className={`${
237
+ selectedModule === item.id ? "bg-default-200/40" : ""
238
+ }`}
239
+ startContent={<IconComponent size={18} className="shrink-0" />}
240
+ >
241
+ {item.name}
242
+ </ListboxItem>
243
+ );
244
+ }}
245
+ </Listbox>
246
+ );
247
+
248
+ return (
249
+ <div className="w-full pt-8 md:pt-12 pb-24 lg:pb-8">
250
+ <div className="container mx-auto md:px-4 py-8">
251
+ {/* Mobile menu button */}
252
+ <div className="fixed w-full h-16 left-0 bottom-0 bg-background/20 backdrop-blur-lg z-50 lg:hidden p-2">
253
+ <div className="flex justify-center">
254
+ <Button
255
+ isIconOnly
256
+ variant="solid"
257
+ onPress={() => setIsDrawerOpen(true)}
258
+ >
259
+ <Menu size={24} />
260
+ </Button>
261
+ </div>
262
+ </div>
263
+
264
+ {/* Mobile Drawer */}
265
+ <Drawer
266
+ isOpen={isDrawerOpen}
267
+ onOpenChange={setIsDrawerOpen}
268
+ placement="left"
269
+ >
270
+ <DrawerContent>
271
+ <DrawerHeader>
272
+ <h2 className="text-xl font-semibold">Navigation</h2>
273
+ </DrawerHeader>
274
+ <DrawerBody>
275
+ <NavigationListbox />
276
+ </DrawerBody>
277
+ </DrawerContent>
278
+ </Drawer>
279
+
280
+ <div className="flex gap-8">
281
+ {/* Desktop Navigation sidebar */}
282
+ <aside className="hidden lg:block w-64 shrink-0 sticky top-18 self-start">
283
+ <Card>
284
+ <CardHeader className="pb-2">
285
+ <h2 className="text-xl font-semibold">Navigation</h2>
286
+ </CardHeader>
287
+ <CardBody>
288
+ <NavigationListbox />
289
+ </CardBody>
290
+ </Card>
291
+ </aside>
292
+
293
+ {/* Main content */}
294
+ <main className="flex-1 w-full min-w-0 space-y-8">
295
+ {/* Default documentation */}
296
+ {defaultContent ? (
297
+ <div>{defaultContent}</div>
298
+ ) : (
299
+ <DefaultDocumentation />
300
+ )}
301
+
302
+ {/* Modules section */}
303
+ {modules.length > 0 && (
304
+ <div className="space-y-6">
305
+ <Card id="section-modules" className="scroll-mt-32">
306
+ <CardHeader>
307
+ <h2 className="text-2xl font-semibold">
308
+ Modules disponibles
309
+ </h2>
310
+ </CardHeader>
311
+ <CardBody>
312
+ <p className="text-slate-600 dark:text-slate-400 mb-4">
313
+ Voici la liste de tous les modules disponibles dans
314
+ LastBrain. Les modules actifs sont utilisés dans votre
315
+ application.
316
+ </p>
317
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
318
+ {modules.map((module) => (
319
+ <Card
320
+ key={module.id}
321
+ isPressable={module.available}
322
+ onPress={() =>
323
+ module.available && scrollToSection(module.id)
324
+ }
325
+ className={`${
326
+ module.available
327
+ ? "cursor-pointer hover:shadow-lg"
328
+ : "opacity-70"
329
+ } transition-shadow`}
330
+ >
331
+ <CardBody>
332
+ <div className="flex items-start justify-between mb-2">
333
+ <h3 className="text-lg font-semibold">
334
+ {module.name}
335
+ </h3>
336
+ <Chip
337
+ size="sm"
338
+ color={module.available ? "success" : "warning"}
339
+ variant="flat"
340
+ >
341
+ {module.available ? "Actif" : "Inactif"}
342
+ </Chip>
343
+ </div>
344
+ <p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
345
+ {module.description}
346
+ </p>
347
+ {!module.available && (
348
+ <div className="flex justify-between items-center text-xs text-default-700 mt-2">
349
+ <span>Pour activer : </span>
350
+ <Snippet hideSymbol color="primary">
351
+ {`pnpm lastbrain add-module ${module.id}`}
352
+ </Snippet>
353
+ </div>
354
+ )}
355
+ </CardBody>
356
+ </Card>
357
+ ))}
358
+ </div>
359
+ </CardBody>
360
+ </Card>
361
+
362
+ {/* Module documentation - seulement pour les modules actifs */}
363
+ {modules
364
+ .filter((m) => m.available)
365
+ .map((module) => (
366
+ <div
367
+ key={module.id}
368
+ id={`module-${module.id}`}
369
+ className="scroll-mt-32"
370
+ >
371
+ {module.content}
372
+ </div>
373
+ ))}
374
+ </div>
375
+ )}
376
+ </main>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ );
381
+ }
@@ -0,0 +1,22 @@
1
+ // Template for docs page with dynamic module loading
2
+ import React from "react";
3
+ import { DocPage } from "./DocPage.js";
4
+
5
+ // This will be replaced by module-build script with actual imports
6
+ // MODULES_IMPORTS_PLACEHOLDER
7
+
8
+ interface ModuleDocConfig {
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ content: React.ReactNode;
13
+ available: boolean;
14
+ }
15
+
16
+ export function DocsPageWithModules() {
17
+ // This will be replaced by module-build script with actual module configs
18
+ // MODULES_CONFIG_PLACEHOLDER
19
+ const modules: ModuleDocConfig[] = [];
20
+
21
+ return <DocPage modules={modules} />;
22
+ }
@@ -0,0 +1,61 @@
1
+ // GENERATED BY LASTBRAIN TEMPLATE (Migrations Guide)
2
+ import React from "react";
3
+ import { Card, Code, Divider, Chip } from "@lastbrain/ui";
4
+
5
+ export function MigrationsGuidePage() {
6
+ return (
7
+ <div className="max-w-5xl mx-auto px-6 py-10 space-y-10">
8
+ <header className="space-y-3">
9
+ <h1 className="text-3xl font-bold">Guide Migrations</h1>
10
+ <p className="text-slate-600 dark:text-slate-300 text-sm">
11
+ Gestion séparée des scripts UP et DOWN par module.
12
+ </p>
13
+ <div className="flex gap-2 flex-wrap">
14
+ <Chip color="primary">UP</Chip>
15
+ <Chip color="secondary">DOWN</Chip>
16
+ <Chip color="success">Reset</Chip>
17
+ <Chip color="warning">Push</Chip>
18
+ </div>
19
+ </header>
20
+ <Divider />
21
+ <Card className="p-6 space-y-3">
22
+ <h2 className="text-lg font-semibold">Emplacement</h2>
23
+ <Code className="text-xs block">supabase/migrations/ (UP)</Code>
24
+ <Code className="text-xs block">supabase/migrations-down/ (DOWN)</Code>
25
+ </Card>
26
+ <Card className="p-6 space-y-3">
27
+ <h2 className="text-lg font-semibold">Cycle</h2>
28
+ <ol className="list-decimal pl-5 text-sm space-y-1 text-slate-600 dark:text-slate-400">
29
+ <li>Ajouter fichier SQL (ex: 002_add_profiles.sql)</li>
30
+ <li>Rebuild module si nouveaux scripts copiés</li>
31
+ <li>
32
+ <Code className="text-xs inline">supabase db push</Code> pour
33
+ appliquer
34
+ </li>
35
+ <li>
36
+ <Code className="text-xs inline">supabase db reset</Code> en dev
37
+ complet
38
+ </li>
39
+ <li>DOWNs exécutés via remove-module (option down)</li>
40
+ </ol>
41
+ </Card>
42
+ <Card className="p-6 space-y-2">
43
+ <h2 className="text-lg font-semibold">Exemple fichier UP</h2>
44
+ <Code className="text-xs whitespace-pre-wrap">{`-- 002_add_profiles.sql
45
+ CREATE TABLE profiles (
46
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
47
+ email text NOT NULL UNIQUE,
48
+ created_at timestamptz DEFAULT now()
49
+ );`}</Code>
50
+ </Card>
51
+ <Card className="p-6 space-y-2">
52
+ <h2 className="text-lg font-semibold">Exemple fichier DOWN</h2>
53
+ <Code className="text-xs whitespace-pre-wrap">{`-- 002_add_profiles.down.sql
54
+ DROP TABLE IF EXISTS profiles CASCADE;`}</Code>
55
+ </Card>
56
+ <footer className="text-center text-xs text-slate-500 dark:text-slate-500">
57
+ Évitez reset en production; privilégiez push contrôlé.
58
+ </footer>
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,71 @@
1
+ // GENERATED BY LASTBRAIN TEMPLATE (Module Guide)
2
+ import React from "react";
3
+ import { Card, Code, Divider, Chip } from "@lastbrain/ui";
4
+
5
+ export function ModuleGuidePage() {
6
+ return (
7
+ <div className="max-w-5xl mx-auto px-6 py-10 space-y-10">
8
+ <header className="space-y-3">
9
+ <h1 className="text-3xl font-bold">Guide Modules</h1>
10
+ <p className="text-slate-600 dark:text-slate-300 text-sm">
11
+ Créez, enregistrez et déployez des modules LastBrain réutilisables.
12
+ </p>
13
+ <div className="flex gap-2 flex-wrap">
14
+ <Chip color="primary">Pages</Chip>
15
+ <Chip color="secondary">APIs</Chip>
16
+ <Chip color="success">Migrations</Chip>
17
+ </div>
18
+ </header>
19
+ <Divider />
20
+ <Card className="p-6 space-y-4">
21
+ <h2 className="text-lg font-semibold">Structure d'un module</h2>
22
+ <Code className="text-xs block">packages/module-xyz/</Code>
23
+ <Code className="text-xs block">├─ src/</Code>
24
+ <Code className="text-xs block">│ ├─ index.ts</Code>
25
+ <Code className="text-xs block">│ ├─ xyz.build.config.ts</Code>
26
+ <Code className="text-xs block">│ ├─ web/public/XyzPage.tsx</Code>
27
+ <Code className="text-xs block">│ └─ api/public/info.ts</Code>
28
+ <Code className="text-xs block">
29
+ └─ supabase/migrations/001_xyz_init.sql
30
+ </Code>
31
+ </Card>
32
+ <Card className="p-6 space-y-3">
33
+ <h2 className="text-lg font-semibold">Config de build</h2>
34
+ <Code className="text-xs whitespace-pre-wrap">{`const xyzBuildConfig = {
35
+ moduleName: "@lastbrain/module-xyz",
36
+ pages: [
37
+ { section: "public", path: "/xyz", componentExport: "XyzPage" }
38
+ ],
39
+ apis: [
40
+ { method: "GET", path: "/api/xyz/info", handlerExport: "GET", entryPoint: "api/public/info", authRequired: false }
41
+ ],
42
+ migrations: { enabled: true, priority: 30, path: "supabase/migrations", files: ["001_xyz_init.sql"] }
43
+ };`}</Code>
44
+ </Card>
45
+ <Card className="p-6 space-y-2">
46
+ <h2 className="text-lg font-semibold">Cycle</h2>
47
+ <ol className="list-decimal pl-5 text-sm space-y-1 text-slate-600 dark:text-slate-400">
48
+ <li>Créer dossier module + config</li>
49
+ <li>
50
+ Exporter composants/API dans <code>index.ts</code>
51
+ </li>
52
+ <li>
53
+ <Code className="text-xs inline">pnpm build</Code> dans le module
54
+ </li>
55
+ <li>
56
+ <Code className="text-xs inline">
57
+ pnpm lastbrain add-module xyz
58
+ </Code>
59
+ </li>
60
+ <li>
61
+ <Code className="text-xs inline">pnpm build:modules</Code>
62
+ </li>
63
+ <li>
64
+ Appliquer migrations si besoin (
65
+ <Code className="text-xs inline">supabase db push</Code>)
66
+ </li>
67
+ </ol>
68
+ </Card>
69
+ </div>
70
+ );
71
+ }