@lastbrain/app 0.1.30 → 0.1.32
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.
- package/dist/auth/useAuthSession.d.ts.map +1 -1
- package/dist/auth/useAuthSession.js +8 -2
- package/dist/components/NotificationContainer.d.ts +2 -0
- package/dist/components/NotificationContainer.d.ts.map +1 -0
- package/dist/components/NotificationContainer.js +8 -0
- package/dist/hooks/useNotifications.d.ts +29 -0
- package/dist/hooks/useNotifications.d.ts.map +1 -0
- package/dist/hooks/useNotifications.js +165 -0
- package/dist/hooks/useNotificationsSimple.d.ts +20 -0
- package/dist/hooks/useNotificationsSimple.d.ts.map +1 -0
- package/dist/hooks/useNotificationsSimple.js +37 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AdminLayoutWithSidebar.js +13 -4
- package/dist/layouts/AppProviders.d.ts +15 -2
- package/dist/layouts/AppProviders.d.ts.map +1 -1
- package/dist/layouts/AppProviders.js +21 -8
- package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AuthLayoutWithSidebar.js +10 -2
- package/dist/layouts/PublicLayout.js +1 -1
- package/dist/layouts/PublicLayoutWithSidebar.d.ts +8 -0
- package/dist/layouts/PublicLayoutWithSidebar.d.ts.map +1 -0
- package/dist/layouts/PublicLayoutWithSidebar.js +19 -0
- package/dist/layouts/RootLayout.d.ts +3 -1
- package/dist/layouts/RootLayout.d.ts.map +1 -1
- package/dist/layouts/RootLayout.js +3 -2
- package/dist/scripts/init-app.d.ts.map +1 -1
- package/dist/scripts/init-app.js +209 -23
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +52 -0
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +64 -1
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +7 -0
- package/package.json +1 -2
- package/src/auth/useAuthSession.ts +6 -2
- package/src/components/NotificationContainer.tsx +20 -0
- package/src/hooks/useNotifications.ts +216 -0
- package/src/index.ts +4 -1
- package/src/layouts/AdminLayoutWithSidebar.tsx +18 -6
- package/src/layouts/AppProviders.tsx +50 -13
- package/src/layouts/AuthLayoutWithSidebar.tsx +16 -4
- package/src/layouts/PublicLayout.tsx +1 -1
- package/src/layouts/PublicLayoutWithSidebar.tsx +56 -0
- package/src/layouts/RootLayout.tsx +10 -2
- package/src/scripts/generate-realtime-config.mjs +183 -0
- package/src/scripts/init-app.ts +237 -23
- package/src/scripts/module-build.ts +63 -0
- package/src/templates/DefaultDoc.tsx +265 -0
- package/src/templates/DocPage.tsx +7 -0
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import { ThemeProvider } from "next-themes";
|
|
4
4
|
import { AppProviders } from "./AppProviders.js";
|
|
5
|
+
import type { ModuleRealtimeConfig } from "@lastbrain/core";
|
|
5
6
|
|
|
6
7
|
// Note: L'app Next.js doit importer son propre globals.css dans son layout
|
|
7
|
-
|
|
8
|
+
// Note: La configuration realtime doit être fournie par l'app qui utilise ce layout
|
|
9
|
+
export function RootLayout({
|
|
10
|
+
children,
|
|
11
|
+
realtimeConfig = [],
|
|
12
|
+
}: {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
realtimeConfig?: ModuleRealtimeConfig[];
|
|
15
|
+
}) {
|
|
8
16
|
return (
|
|
9
17
|
<html lang="fr" suppressHydrationWarning>
|
|
10
18
|
<body className="min-h-screen">
|
|
@@ -14,7 +22,7 @@ export function RootLayout({ children }: { children: React.ReactNode }) {
|
|
|
14
22
|
enableSystem={false}
|
|
15
23
|
storageKey="lastbrain-theme"
|
|
16
24
|
>
|
|
17
|
-
<AppProviders>
|
|
25
|
+
<AppProviders realtimeConfig={realtimeConfig}>
|
|
18
26
|
<div className=" min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
|
|
19
27
|
{children}
|
|
20
28
|
</div>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* global console, process */
|
|
3
|
+
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Script de génération de configuration realtime globale
|
|
13
|
+
* Lit les build.config de tous les modules et génère un fichier TypeScript de configuration
|
|
14
|
+
* pour le RealtimeProvider
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
async function loadModuleBuildConfig(modulePath) {
|
|
18
|
+
try {
|
|
19
|
+
// Charger directement le build.config depuis la version compilée
|
|
20
|
+
const configPath = path.join(modulePath, 'dist');
|
|
21
|
+
if (!fs.existsSync(configPath)) {
|
|
22
|
+
console.warn(`⚠️ Module non compilé: ${modulePath}/dist`);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Chercher le fichier principal exporté
|
|
27
|
+
const packageJsonPath = path.join(modulePath, 'package.json');
|
|
28
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
33
|
+
const main = packageJson.main || 'dist/index.js';
|
|
34
|
+
const mainPath = path.resolve(modulePath, main);
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(mainPath)) {
|
|
37
|
+
console.warn(`⚠️ Fichier principal non trouvé: ${mainPath}`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Charger le module
|
|
42
|
+
const moduleUrl = `file://${mainPath}`;
|
|
43
|
+
const moduleExports = await import(moduleUrl);
|
|
44
|
+
const buildConfig = moduleExports.default || moduleExports;
|
|
45
|
+
|
|
46
|
+
if (buildConfig && typeof buildConfig === 'object' && buildConfig.realtime) {
|
|
47
|
+
console.log(`✅ Configuration realtime trouvée pour ${buildConfig.moduleName}`);
|
|
48
|
+
return buildConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn(`⚠️ Erreur lors du chargement de ${modulePath}:`, error.message);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function findModules(rootPath) {
|
|
59
|
+
const packagesPath = path.join(rootPath, 'packages');
|
|
60
|
+
const modules = [];
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(packagesPath)) {
|
|
63
|
+
console.error('❌ Dossier packages non trouvé');
|
|
64
|
+
return modules;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const entries = fs.readdirSync(packagesPath);
|
|
68
|
+
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const entryPath = path.join(packagesPath, entry);
|
|
71
|
+
const stat = fs.statSync(entryPath);
|
|
72
|
+
|
|
73
|
+
if (stat.isDirectory() && entry.startsWith('module-')) {
|
|
74
|
+
const packageJsonPath = path.join(entryPath, 'package.json');
|
|
75
|
+
|
|
76
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
77
|
+
modules.push(entryPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return modules;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function generateTypeScriptConfig(realtimeConfigs, targetPath) {
|
|
86
|
+
const imports = `import type { ModuleRealtimeConfig } from "@lastbrain/core";`;
|
|
87
|
+
|
|
88
|
+
const configData = {
|
|
89
|
+
modules: realtimeConfigs,
|
|
90
|
+
generatedAt: new Date().toISOString(),
|
|
91
|
+
version: "1.0.0"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const content = `${imports}
|
|
95
|
+
|
|
96
|
+
// GENERATED FILE - DO NOT EDIT MANUALLY
|
|
97
|
+
// Generated at: ${configData.generatedAt}
|
|
98
|
+
|
|
99
|
+
export const realtimeConfig: ModuleRealtimeConfig[] = ${JSON.stringify(configData.modules, null, 2)};
|
|
100
|
+
|
|
101
|
+
export default realtimeConfig;
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
// Créer le dossier config s'il n'existe pas
|
|
105
|
+
const configDir = path.dirname(targetPath);
|
|
106
|
+
if (!fs.existsSync(configDir)) {
|
|
107
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Écrire le fichier TypeScript
|
|
111
|
+
fs.writeFileSync(targetPath, content);
|
|
112
|
+
|
|
113
|
+
return configData;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function generateRealtimeConfig(targetAppPath) {
|
|
117
|
+
console.log('🔄 Génération de la configuration realtime...');
|
|
118
|
+
|
|
119
|
+
// Trouver le dossier racine du projet
|
|
120
|
+
const rootPath = path.resolve(__dirname, '../../../..');
|
|
121
|
+
console.log(`📁 Répertoire racine: ${rootPath}`);
|
|
122
|
+
|
|
123
|
+
// Trouver tous les modules
|
|
124
|
+
const modulePaths = await findModules(rootPath);
|
|
125
|
+
console.log(`📦 Modules trouvés: ${modulePaths.length}`);
|
|
126
|
+
|
|
127
|
+
const realtimeConfigs = [];
|
|
128
|
+
|
|
129
|
+
// Charger les configurations de chaque module
|
|
130
|
+
for (const modulePath of modulePaths) {
|
|
131
|
+
console.log(`🔍 Analyse du module: ${path.basename(modulePath)}`);
|
|
132
|
+
|
|
133
|
+
const buildConfig = await loadModuleBuildConfig(modulePath);
|
|
134
|
+
|
|
135
|
+
if (buildConfig?.realtime) {
|
|
136
|
+
console.log(`✅ Configuration realtime trouvée pour ${buildConfig.moduleName}`);
|
|
137
|
+
realtimeConfigs.push(buildConfig.realtime);
|
|
138
|
+
} else {
|
|
139
|
+
console.log(`ℹ️ Pas de configuration realtime pour ${path.basename(modulePath)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Générer le fichier TypeScript de configuration
|
|
144
|
+
const outputPath = path.join(targetAppPath, 'config', 'realtime.ts');
|
|
145
|
+
const configData = generateTypeScriptConfig(realtimeConfigs, outputPath);
|
|
146
|
+
|
|
147
|
+
console.log('✅ Configuration realtime générée avec succès!');
|
|
148
|
+
console.log(`📄 Fichier: ${outputPath}`);
|
|
149
|
+
console.log(`📊 Modules avec realtime: ${realtimeConfigs.length}`);
|
|
150
|
+
|
|
151
|
+
// Afficher un résumé
|
|
152
|
+
realtimeConfigs.forEach(module => {
|
|
153
|
+
console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
|
|
154
|
+
module.tables.forEach(table => {
|
|
155
|
+
console.log(` • ${table.schema}.${table.table} (${table.event})`);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return configData;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Exécution si le script est appelé directement
|
|
163
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
164
|
+
const targetAppPath = process.argv[2];
|
|
165
|
+
|
|
166
|
+
if (!targetAppPath) {
|
|
167
|
+
console.error('❌ Chemin de l\'app cible requis');
|
|
168
|
+
console.log('Usage: node generate-realtime-config.mjs <target-app-path>');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
generateRealtimeConfig(targetAppPath)
|
|
173
|
+
.then(() => {
|
|
174
|
+
console.log('🎉 Génération terminée!');
|
|
175
|
+
process.exit(0);
|
|
176
|
+
})
|
|
177
|
+
.catch((error) => {
|
|
178
|
+
console.error('❌ Erreur lors de la génération:', error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export { generateRealtimeConfig };
|
package/src/scripts/init-app.ts
CHANGED
|
@@ -90,6 +90,7 @@ export async function initApp(options: InitAppOptions) {
|
|
|
90
90
|
// 6. Créer .gitignore et .env.local.example
|
|
91
91
|
await createGitIgnore(targetDir, force);
|
|
92
92
|
await createEnvExample(targetDir, force);
|
|
93
|
+
await createEnvLocal(targetDir, force);
|
|
93
94
|
|
|
94
95
|
// 7. Créer la structure Supabase avec migrations
|
|
95
96
|
await createSupabaseStructure(targetDir, force);
|
|
@@ -251,6 +252,7 @@ function getLastBrainVersions(targetDir: string): {
|
|
|
251
252
|
core: string;
|
|
252
253
|
ui: string;
|
|
253
254
|
moduleAuth: string;
|
|
255
|
+
moduleAi: string;
|
|
254
256
|
} {
|
|
255
257
|
const targetIsInMonorepo =
|
|
256
258
|
fs.existsSync(
|
|
@@ -265,34 +267,74 @@ function getLastBrainVersions(targetDir: string): {
|
|
|
265
267
|
core: "workspace:*",
|
|
266
268
|
ui: "workspace:*",
|
|
267
269
|
moduleAuth: "workspace:*",
|
|
270
|
+
moduleAi: "workspace:*",
|
|
268
271
|
};
|
|
269
272
|
}
|
|
270
273
|
|
|
271
|
-
// Hors monorepo, lire les versions depuis
|
|
274
|
+
// Hors monorepo, essayer de lire les versions depuis le monorepo source
|
|
272
275
|
try {
|
|
273
|
-
//
|
|
274
|
-
const
|
|
276
|
+
// Détecter si on est dans le contexte du package CLI en build
|
|
277
|
+
const cliPkgPath = path.join(__dirname, "../../package.json");
|
|
278
|
+
|
|
279
|
+
if (fs.existsSync(cliPkgPath)) {
|
|
280
|
+
const cliPkg = JSON.parse(fs.readFileSync(cliPkgPath, "utf-8"));
|
|
281
|
+
const cliVersion = `^${cliPkg.version}`;
|
|
282
|
+
|
|
283
|
+
// Essayer de lire les versions depuis le monorepo (si disponible)
|
|
284
|
+
const monorepoRoot = path.join(__dirname, "../../../../");
|
|
285
|
+
const moduleAuthPkgPath = path.join(
|
|
286
|
+
monorepoRoot,
|
|
287
|
+
"packages/module-auth/package.json",
|
|
288
|
+
);
|
|
289
|
+
const moduleAiPkgPath = path.join(
|
|
290
|
+
monorepoRoot,
|
|
291
|
+
"packages/module-ai/package.json",
|
|
292
|
+
);
|
|
293
|
+
const corePkgPath = path.join(monorepoRoot, "packages/core/package.json");
|
|
294
|
+
const uiPkgPath = path.join(monorepoRoot, "packages/ui/package.json");
|
|
295
|
+
|
|
296
|
+
let moduleAuthVersion = cliVersion;
|
|
297
|
+
let moduleAiVersion = cliVersion;
|
|
298
|
+
let coreVersion = cliPkg.dependencies?.["@lastbrain/core"] || cliVersion;
|
|
299
|
+
let uiVersion = cliPkg.dependencies?.["@lastbrain/ui"] || cliVersion;
|
|
300
|
+
|
|
301
|
+
// Lire les vraies versions depuis les package.json du monorepo
|
|
302
|
+
if (fs.existsSync(moduleAuthPkgPath)) {
|
|
303
|
+
const moduleAuthPkg = JSON.parse(
|
|
304
|
+
fs.readFileSync(moduleAuthPkgPath, "utf-8"),
|
|
305
|
+
);
|
|
306
|
+
moduleAuthVersion = `^${moduleAuthPkg.version}`;
|
|
307
|
+
}
|
|
275
308
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
309
|
+
if (fs.existsSync(moduleAiPkgPath)) {
|
|
310
|
+
const moduleAiPkg = JSON.parse(
|
|
311
|
+
fs.readFileSync(moduleAiPkgPath, "utf-8"),
|
|
312
|
+
);
|
|
313
|
+
moduleAiVersion = `^${moduleAiPkg.version}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (fs.existsSync(corePkgPath)) {
|
|
317
|
+
const corePkg = JSON.parse(fs.readFileSync(corePkgPath, "utf-8"));
|
|
318
|
+
coreVersion = `^${corePkg.version}`;
|
|
319
|
+
}
|
|
279
320
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
321
|
+
if (fs.existsSync(uiPkgPath)) {
|
|
322
|
+
const uiPkg = JSON.parse(fs.readFileSync(uiPkgPath, "utf-8"));
|
|
323
|
+
uiVersion = `^${uiPkg.version}`;
|
|
324
|
+
}
|
|
284
325
|
|
|
285
326
|
return {
|
|
286
|
-
app:
|
|
287
|
-
core:
|
|
288
|
-
ui:
|
|
289
|
-
moduleAuth:
|
|
327
|
+
app: cliVersion,
|
|
328
|
+
core: coreVersion,
|
|
329
|
+
ui: uiVersion,
|
|
330
|
+
moduleAuth: moduleAuthVersion,
|
|
331
|
+
moduleAi: moduleAiVersion,
|
|
290
332
|
};
|
|
291
333
|
}
|
|
292
|
-
} catch {
|
|
334
|
+
} catch (error) {
|
|
293
335
|
console.warn(
|
|
294
336
|
chalk.yellow(
|
|
295
|
-
|
|
337
|
+
`⚠️ Impossible de lire les versions locales (${error}), utilisation de 'latest'`,
|
|
296
338
|
),
|
|
297
339
|
);
|
|
298
340
|
}
|
|
@@ -303,6 +345,7 @@ function getLastBrainVersions(targetDir: string): {
|
|
|
303
345
|
core: "latest",
|
|
304
346
|
ui: "latest",
|
|
305
347
|
moduleAuth: "latest",
|
|
348
|
+
moduleAi: "latest",
|
|
306
349
|
};
|
|
307
350
|
}
|
|
308
351
|
|
|
@@ -328,7 +371,7 @@ async function addDependencies(
|
|
|
328
371
|
"@lastbrain/core": versions.core,
|
|
329
372
|
"@lastbrain/ui": versions.ui,
|
|
330
373
|
"next-themes": "^0.4.6",
|
|
331
|
-
|
|
374
|
+
pino: "^10.1.0",
|
|
332
375
|
"pino-pretty": "^13.1.2",
|
|
333
376
|
};
|
|
334
377
|
|
|
@@ -341,8 +384,13 @@ async function addDependencies(
|
|
|
341
384
|
for (const moduleName of selectedModules) {
|
|
342
385
|
const moduleInfo = AVAILABLE_MODULES.find((m) => m.name === moduleName);
|
|
343
386
|
if (moduleInfo && moduleInfo.package !== "@lastbrain/module-auth") {
|
|
344
|
-
//
|
|
345
|
-
|
|
387
|
+
// Utiliser les vraies versions au lieu de "latest"
|
|
388
|
+
if (moduleInfo.package === "@lastbrain/module-ai") {
|
|
389
|
+
requiredDeps[moduleInfo.package] = versions.moduleAi;
|
|
390
|
+
} else {
|
|
391
|
+
// Pour les futurs modules, utiliser "latest" en attendant
|
|
392
|
+
requiredDeps[moduleInfo.package] = "latest";
|
|
393
|
+
}
|
|
346
394
|
}
|
|
347
395
|
}
|
|
348
396
|
|
|
@@ -443,7 +491,7 @@ import { HeroUIProvider } from "@heroui/system";
|
|
|
443
491
|
|
|
444
492
|
import { ThemeProvider } from "next-themes";
|
|
445
493
|
import { useRouter } from "next/navigation";
|
|
446
|
-
import { AppProviders } from "
|
|
494
|
+
import { AppProviders } from "../components/AppProviders";
|
|
447
495
|
import type { PropsWithChildren } from "react";
|
|
448
496
|
import { AppHeader } from "../components/AppHeader";
|
|
449
497
|
|
|
@@ -481,7 +529,7 @@ export default function RootLayout({ children }: PropsWithChildren<{}>) {
|
|
|
481
529
|
|
|
482
530
|
import "../styles/globals.css";
|
|
483
531
|
import { ThemeProvider } from "next-themes";
|
|
484
|
-
import { AppProviders } from "
|
|
532
|
+
import { AppProviders } from "../components/AppProviders";
|
|
485
533
|
import type { PropsWithChildren } from "react";
|
|
486
534
|
import { AppHeader } from "../components/AppHeader";
|
|
487
535
|
|
|
@@ -591,6 +639,9 @@ export default function NotFound() {
|
|
|
591
639
|
|
|
592
640
|
// Créer le composant AppAside
|
|
593
641
|
await createAppAside(targetDir, force);
|
|
642
|
+
|
|
643
|
+
// Créer le wrapper AppProviders avec configuration realtime
|
|
644
|
+
await createAppProvidersWrapper(targetDir, force);
|
|
594
645
|
}
|
|
595
646
|
|
|
596
647
|
async function createRoute(
|
|
@@ -723,11 +774,13 @@ import { Header } from "@lastbrain/ui";
|
|
|
723
774
|
import { menuConfig } from "../config/menu";
|
|
724
775
|
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
725
776
|
import { useRouter } from "next/navigation";
|
|
726
|
-
import { useAuthSession } from "@lastbrain/app";
|
|
777
|
+
import { useAuthSession, useNotificationsContext } from "@lastbrain/app";
|
|
727
778
|
|
|
728
779
|
export function AppHeader() {
|
|
729
780
|
const router = useRouter();
|
|
730
781
|
const { user, isSuperAdmin } = useAuthSession();
|
|
782
|
+
const { data, loading, markAsRead, markAllAsRead, deleteNotification } =
|
|
783
|
+
useNotificationsContext();
|
|
731
784
|
|
|
732
785
|
const handleLogout = async () => {
|
|
733
786
|
await supabaseBrowserClient.auth.signOut();
|
|
@@ -743,6 +796,12 @@ export function AppHeader() {
|
|
|
743
796
|
brandName="LastBrain App"
|
|
744
797
|
brandHref="/"
|
|
745
798
|
isSuperAdmin={isSuperAdmin}
|
|
799
|
+
notifications={data.notifications}
|
|
800
|
+
unreadCount={data.unreadCount}
|
|
801
|
+
notificationsLoading={loading}
|
|
802
|
+
onMarkAsRead={markAsRead}
|
|
803
|
+
onMarkAllAsRead={markAllAsRead}
|
|
804
|
+
onDeleteNotification={deleteNotification}
|
|
746
805
|
/>
|
|
747
806
|
);
|
|
748
807
|
}
|
|
@@ -798,6 +857,36 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
|
|
|
798
857
|
}
|
|
799
858
|
}
|
|
800
859
|
|
|
860
|
+
async function createAppProvidersWrapper(targetDir: string, force: boolean) {
|
|
861
|
+
const componentsDir = path.join(targetDir, "components");
|
|
862
|
+
await fs.ensureDir(componentsDir);
|
|
863
|
+
|
|
864
|
+
const providersPath = path.join(componentsDir, "AppProviders.tsx");
|
|
865
|
+
if (!fs.existsSync(providersPath) || force) {
|
|
866
|
+
const providersContent = `"use client";
|
|
867
|
+
|
|
868
|
+
import { AppProviders as BaseAppProviders } from "@lastbrain/app";
|
|
869
|
+
import { realtimeConfig } from "../config/realtime";
|
|
870
|
+
import type { ReactNode } from "react";
|
|
871
|
+
|
|
872
|
+
export function AppProviders({ children }: { children: ReactNode }) {
|
|
873
|
+
return (
|
|
874
|
+
<BaseAppProviders realtimeConfig={realtimeConfig}>
|
|
875
|
+
{children}
|
|
876
|
+
</BaseAppProviders>
|
|
877
|
+
);
|
|
878
|
+
}`;
|
|
879
|
+
await fs.writeFile(providersPath, providersContent);
|
|
880
|
+
console.log(chalk.green("✓ components/AppProviders.tsx créé"));
|
|
881
|
+
} else {
|
|
882
|
+
console.log(
|
|
883
|
+
chalk.gray(
|
|
884
|
+
" components/AppProviders.tsx existe déjà (utilisez --force pour écraser)",
|
|
885
|
+
),
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
801
890
|
async function createConfigFiles(
|
|
802
891
|
targetDir: string,
|
|
803
892
|
force: boolean,
|
|
@@ -984,6 +1073,7 @@ export default config;
|
|
|
984
1073
|
const tsconfigPath = path.join(targetDir, "tsconfig.json");
|
|
985
1074
|
if (!fs.existsSync(tsconfigPath) || force) {
|
|
986
1075
|
const tsconfig = {
|
|
1076
|
+
extends: "../../tsconfig.base.json",
|
|
987
1077
|
compilerOptions: {
|
|
988
1078
|
target: "ES2020",
|
|
989
1079
|
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
@@ -1003,9 +1093,10 @@ export default config;
|
|
|
1003
1093
|
paths: {
|
|
1004
1094
|
"@/*": ["./*"],
|
|
1005
1095
|
},
|
|
1096
|
+
types: [],
|
|
1006
1097
|
},
|
|
1007
1098
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1008
|
-
exclude: ["node_modules"],
|
|
1099
|
+
exclude: ["node_modules", "dist", ".next", "out"],
|
|
1009
1100
|
};
|
|
1010
1101
|
await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
|
|
1011
1102
|
console.log(chalk.green("✓ tsconfig.json créé"));
|
|
@@ -1036,6 +1127,107 @@ export const menuConfig: MenuConfig = {
|
|
|
1036
1127
|
await fs.writeFile(menuConfigPath, menuConfig);
|
|
1037
1128
|
console.log(chalk.green("✓ config/menu.ts créé"));
|
|
1038
1129
|
}
|
|
1130
|
+
|
|
1131
|
+
// Créer les hooks
|
|
1132
|
+
await createHooksDirectory(targetDir, force);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
async function createHooksDirectory(targetDir: string, force: boolean) {
|
|
1136
|
+
const hooksDir = path.join(targetDir, "hooks");
|
|
1137
|
+
await fs.ensureDir(hooksDir);
|
|
1138
|
+
|
|
1139
|
+
// Créer le hook useNotifications
|
|
1140
|
+
const useNotificationsPath = path.join(hooksDir, "useNotifications.ts");
|
|
1141
|
+
if (!fs.existsSync(useNotificationsPath) || force) {
|
|
1142
|
+
const hookContent = `"use client";
|
|
1143
|
+
|
|
1144
|
+
import { useEffect, useState } from "react";
|
|
1145
|
+
import { useNotificationRealtime } from "@lastbrain/core";
|
|
1146
|
+
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
1147
|
+
import { useAuth } from "@lastbrain/app";
|
|
1148
|
+
|
|
1149
|
+
export interface Notification {
|
|
1150
|
+
id: string;
|
|
1151
|
+
title: string;
|
|
1152
|
+
message: string;
|
|
1153
|
+
read: boolean;
|
|
1154
|
+
created_at: string;
|
|
1155
|
+
owner_id: string;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
export function useNotifications() {
|
|
1159
|
+
const { user } = useAuth();
|
|
1160
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
1161
|
+
const [loading, setLoading] = useState(true);
|
|
1162
|
+
const [error, setError] = useState<Error | null>(null);
|
|
1163
|
+
|
|
1164
|
+
// Écouter les changements en temps réel
|
|
1165
|
+
const realtimeSignal = useNotificationRealtime();
|
|
1166
|
+
|
|
1167
|
+
// Charger les notifications initiales
|
|
1168
|
+
const fetchNotifications = async () => {
|
|
1169
|
+
if (!user?.id) return;
|
|
1170
|
+
|
|
1171
|
+
try {
|
|
1172
|
+
setLoading(true);
|
|
1173
|
+
const { data, error } = await supabaseBrowserClient
|
|
1174
|
+
.from("user_notifications")
|
|
1175
|
+
.select("*")
|
|
1176
|
+
.eq("owner_id", user.id)
|
|
1177
|
+
.order("created_at", { ascending: false });
|
|
1178
|
+
|
|
1179
|
+
if (error) throw error;
|
|
1180
|
+
setNotifications(data || []);
|
|
1181
|
+
setError(null);
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
setError(err as Error);
|
|
1184
|
+
} finally {
|
|
1185
|
+
setLoading(false);
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
// Charger au montage et quand l'utilisateur change
|
|
1190
|
+
useEffect(() => {
|
|
1191
|
+
fetchNotifications();
|
|
1192
|
+
}, [user?.id]);
|
|
1193
|
+
|
|
1194
|
+
// Recharger quand les données changent en temps réel
|
|
1195
|
+
useEffect(() => {
|
|
1196
|
+
if (realtimeSignal.hasUpdates && user?.id) {
|
|
1197
|
+
console.log("🔔 Rechargement des notifications suite à un changement");
|
|
1198
|
+
fetchNotifications();
|
|
1199
|
+
}
|
|
1200
|
+
}, [realtimeSignal.tick, user?.id]);
|
|
1201
|
+
|
|
1202
|
+
return {
|
|
1203
|
+
notifications,
|
|
1204
|
+
loading,
|
|
1205
|
+
error,
|
|
1206
|
+
refetch: fetchNotifications,
|
|
1207
|
+
hasUpdates: realtimeSignal.hasUpdates,
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
`;
|
|
1211
|
+
await fs.writeFile(useNotificationsPath, hookContent);
|
|
1212
|
+
console.log(chalk.green("✓ hooks/useNotifications.ts créé"));
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Créer un placeholder pour realtime.ts qui sera généré par build:modules
|
|
1216
|
+
const realtimePath = path.join(targetDir, "config", "realtime.ts");
|
|
1217
|
+
if (!fs.existsSync(realtimePath) || force) {
|
|
1218
|
+
const realtimeContent = `import type { ModuleRealtimeConfig } from "@lastbrain/core";
|
|
1219
|
+
|
|
1220
|
+
// GENERATED FILE - DO NOT EDIT MANUALLY
|
|
1221
|
+
// Ce fichier sera automatiquement généré lors du build:modules
|
|
1222
|
+
// Exécutez "pnpm build:modules" pour créer la configuration realtime
|
|
1223
|
+
|
|
1224
|
+
export const realtimeConfig: ModuleRealtimeConfig[] = [];
|
|
1225
|
+
|
|
1226
|
+
export default realtimeConfig;
|
|
1227
|
+
`;
|
|
1228
|
+
await fs.writeFile(realtimePath, realtimeContent);
|
|
1229
|
+
console.log(chalk.green("✓ config/realtime.ts placeholder créé"));
|
|
1230
|
+
}
|
|
1039
1231
|
}
|
|
1040
1232
|
|
|
1041
1233
|
async function createGitIgnore(targetDir: string, force: boolean) {
|
|
@@ -1145,6 +1337,28 @@ SUPABASE_SERVICE_ROLE_KEY=YOUR_LOCAL_SERVICE_ROLE_KEY
|
|
|
1145
1337
|
}
|
|
1146
1338
|
}
|
|
1147
1339
|
|
|
1340
|
+
async function createEnvLocal(targetDir: string, force: boolean) {
|
|
1341
|
+
const envLocalPath = path.join(targetDir, ".env.local");
|
|
1342
|
+
|
|
1343
|
+
if (!fs.existsSync(envLocalPath) || force) {
|
|
1344
|
+
console.log(chalk.yellow("\n🔐 Création de .env.local..."));
|
|
1345
|
+
|
|
1346
|
+
const envContent = `# Supabase Configuration
|
|
1347
|
+
# Valeurs par défaut pour le développement local
|
|
1348
|
+
|
|
1349
|
+
# Supabase Local (par défaut)
|
|
1350
|
+
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
|
1351
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...
|
|
1352
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...
|
|
1353
|
+
|
|
1354
|
+
# OpenAI Configuration (clé factice pour éviter les erreurs de build)
|
|
1355
|
+
OPENAI_API_KEY=sk-fake-openai-key-for-development-replace-with-real-key
|
|
1356
|
+
`;
|
|
1357
|
+
await fs.writeFile(envLocalPath, envContent);
|
|
1358
|
+
console.log(chalk.green("✓ .env.local créé"));
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1148
1362
|
async function createSupabaseStructure(targetDir: string, force: boolean) {
|
|
1149
1363
|
console.log(chalk.yellow("\n🗄️ Création de la structure Supabase..."));
|
|
1150
1364
|
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ModuleMenuItemConfig,
|
|
9
9
|
ModulePageConfig,
|
|
10
10
|
ModuleSection,
|
|
11
|
+
ModuleRealtimeConfig,
|
|
11
12
|
} from "@lastbrain/core";
|
|
12
13
|
|
|
13
14
|
// Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
|
|
@@ -779,6 +780,64 @@ export default function AdminLayout({
|
|
|
779
780
|
}
|
|
780
781
|
}
|
|
781
782
|
|
|
783
|
+
async function generateRealtimeConfig(moduleConfigs: ModuleBuildConfig[]) {
|
|
784
|
+
try {
|
|
785
|
+
// Extraire les configurations realtime des modules
|
|
786
|
+
const realtimeConfigs: ModuleRealtimeConfig[] = moduleConfigs
|
|
787
|
+
.filter((config) => config.realtime)
|
|
788
|
+
.map((config) => config.realtime!);
|
|
789
|
+
|
|
790
|
+
if (realtimeConfigs.length === 0) {
|
|
791
|
+
console.log("⏭️ No realtime configuration found in modules");
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Générer le contenu TypeScript
|
|
796
|
+
const imports = `import type { ModuleRealtimeConfig } from "@lastbrain/core";`;
|
|
797
|
+
|
|
798
|
+
const configData = {
|
|
799
|
+
modules: realtimeConfigs,
|
|
800
|
+
generatedAt: new Date().toISOString(),
|
|
801
|
+
version: "1.0.0",
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const content = `${imports}
|
|
805
|
+
|
|
806
|
+
// GENERATED FILE - DO NOT EDIT MANUALLY
|
|
807
|
+
// Generated at: ${configData.generatedAt}
|
|
808
|
+
|
|
809
|
+
export const realtimeConfig: ModuleRealtimeConfig[] = ${JSON.stringify(configData.modules, null, 2)};
|
|
810
|
+
|
|
811
|
+
export default realtimeConfig;
|
|
812
|
+
`;
|
|
813
|
+
|
|
814
|
+
// Créer le fichier de configuration
|
|
815
|
+
const outputPath = path.join(projectRoot, "config", "realtime.ts");
|
|
816
|
+
const configDir = path.dirname(outputPath);
|
|
817
|
+
|
|
818
|
+
// Créer le dossier config s'il n'existe pas
|
|
819
|
+
if (!fs.existsSync(configDir)) {
|
|
820
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Écrire le fichier TypeScript
|
|
824
|
+
fs.writeFileSync(outputPath, content);
|
|
825
|
+
|
|
826
|
+
console.log(`✅ Generated realtime configuration: ${outputPath}`);
|
|
827
|
+
console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
|
|
828
|
+
|
|
829
|
+
// Afficher un résumé
|
|
830
|
+
realtimeConfigs.forEach((module) => {
|
|
831
|
+
console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
|
|
832
|
+
module.tables.forEach((table) => {
|
|
833
|
+
console.log(` • ${table.schema}.${table.table} (${table.event})`);
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
} catch (error) {
|
|
837
|
+
console.error("❌ Error generating realtime configuration:", error);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
782
841
|
export async function runModuleBuild() {
|
|
783
842
|
ensureDirectory(appDirectory);
|
|
784
843
|
|
|
@@ -823,4 +882,8 @@ export async function runModuleBuild() {
|
|
|
823
882
|
generateAppAside();
|
|
824
883
|
generateLayouts();
|
|
825
884
|
copyModuleMigrations(moduleConfigs);
|
|
885
|
+
|
|
886
|
+
// Générer la configuration realtime
|
|
887
|
+
console.log("🔄 Generating realtime configuration...");
|
|
888
|
+
await generateRealtimeConfig(moduleConfigs);
|
|
826
889
|
}
|