@lastbrain/app 0.1.30 → 0.1.31
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 +159 -5
- 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 +172 -5
- 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);
|
|
@@ -328,7 +329,7 @@ async function addDependencies(
|
|
|
328
329
|
"@lastbrain/core": versions.core,
|
|
329
330
|
"@lastbrain/ui": versions.ui,
|
|
330
331
|
"next-themes": "^0.4.6",
|
|
331
|
-
|
|
332
|
+
pino: "^10.1.0",
|
|
332
333
|
"pino-pretty": "^13.1.2",
|
|
333
334
|
};
|
|
334
335
|
|
|
@@ -443,7 +444,7 @@ import { HeroUIProvider } from "@heroui/system";
|
|
|
443
444
|
|
|
444
445
|
import { ThemeProvider } from "next-themes";
|
|
445
446
|
import { useRouter } from "next/navigation";
|
|
446
|
-
import { AppProviders } from "
|
|
447
|
+
import { AppProviders } from "../components/AppProviders";
|
|
447
448
|
import type { PropsWithChildren } from "react";
|
|
448
449
|
import { AppHeader } from "../components/AppHeader";
|
|
449
450
|
|
|
@@ -481,7 +482,7 @@ export default function RootLayout({ children }: PropsWithChildren<{}>) {
|
|
|
481
482
|
|
|
482
483
|
import "../styles/globals.css";
|
|
483
484
|
import { ThemeProvider } from "next-themes";
|
|
484
|
-
import { AppProviders } from "
|
|
485
|
+
import { AppProviders } from "../components/AppProviders";
|
|
485
486
|
import type { PropsWithChildren } from "react";
|
|
486
487
|
import { AppHeader } from "../components/AppHeader";
|
|
487
488
|
|
|
@@ -591,6 +592,9 @@ export default function NotFound() {
|
|
|
591
592
|
|
|
592
593
|
// Créer le composant AppAside
|
|
593
594
|
await createAppAside(targetDir, force);
|
|
595
|
+
|
|
596
|
+
// Créer le wrapper AppProviders avec configuration realtime
|
|
597
|
+
await createAppProvidersWrapper(targetDir, force);
|
|
594
598
|
}
|
|
595
599
|
|
|
596
600
|
async function createRoute(
|
|
@@ -723,11 +727,13 @@ import { Header } from "@lastbrain/ui";
|
|
|
723
727
|
import { menuConfig } from "../config/menu";
|
|
724
728
|
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
725
729
|
import { useRouter } from "next/navigation";
|
|
726
|
-
import { useAuthSession } from "@lastbrain/app";
|
|
730
|
+
import { useAuthSession, useNotificationsContext } from "@lastbrain/app";
|
|
727
731
|
|
|
728
732
|
export function AppHeader() {
|
|
729
733
|
const router = useRouter();
|
|
730
734
|
const { user, isSuperAdmin } = useAuthSession();
|
|
735
|
+
const { data, loading, markAsRead, markAllAsRead, deleteNotification } =
|
|
736
|
+
useNotificationsContext();
|
|
731
737
|
|
|
732
738
|
const handleLogout = async () => {
|
|
733
739
|
await supabaseBrowserClient.auth.signOut();
|
|
@@ -743,6 +749,12 @@ export function AppHeader() {
|
|
|
743
749
|
brandName="LastBrain App"
|
|
744
750
|
brandHref="/"
|
|
745
751
|
isSuperAdmin={isSuperAdmin}
|
|
752
|
+
notifications={data.notifications}
|
|
753
|
+
unreadCount={data.unreadCount}
|
|
754
|
+
notificationsLoading={loading}
|
|
755
|
+
onMarkAsRead={markAsRead}
|
|
756
|
+
onMarkAllAsRead={markAllAsRead}
|
|
757
|
+
onDeleteNotification={deleteNotification}
|
|
746
758
|
/>
|
|
747
759
|
);
|
|
748
760
|
}
|
|
@@ -798,6 +810,36 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
|
|
|
798
810
|
}
|
|
799
811
|
}
|
|
800
812
|
|
|
813
|
+
async function createAppProvidersWrapper(targetDir: string, force: boolean) {
|
|
814
|
+
const componentsDir = path.join(targetDir, "components");
|
|
815
|
+
await fs.ensureDir(componentsDir);
|
|
816
|
+
|
|
817
|
+
const providersPath = path.join(componentsDir, "AppProviders.tsx");
|
|
818
|
+
if (!fs.existsSync(providersPath) || force) {
|
|
819
|
+
const providersContent = `"use client";
|
|
820
|
+
|
|
821
|
+
import { AppProviders as BaseAppProviders } from "@lastbrain/app";
|
|
822
|
+
import { realtimeConfig } from "../config/realtime";
|
|
823
|
+
import type { ReactNode } from "react";
|
|
824
|
+
|
|
825
|
+
export function AppProviders({ children }: { children: ReactNode }) {
|
|
826
|
+
return (
|
|
827
|
+
<BaseAppProviders realtimeConfig={realtimeConfig}>
|
|
828
|
+
{children}
|
|
829
|
+
</BaseAppProviders>
|
|
830
|
+
);
|
|
831
|
+
}`;
|
|
832
|
+
await fs.writeFile(providersPath, providersContent);
|
|
833
|
+
console.log(chalk.green("✓ components/AppProviders.tsx créé"));
|
|
834
|
+
} else {
|
|
835
|
+
console.log(
|
|
836
|
+
chalk.gray(
|
|
837
|
+
" components/AppProviders.tsx existe déjà (utilisez --force pour écraser)",
|
|
838
|
+
),
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
801
843
|
async function createConfigFiles(
|
|
802
844
|
targetDir: string,
|
|
803
845
|
force: boolean,
|
|
@@ -984,6 +1026,7 @@ export default config;
|
|
|
984
1026
|
const tsconfigPath = path.join(targetDir, "tsconfig.json");
|
|
985
1027
|
if (!fs.existsSync(tsconfigPath) || force) {
|
|
986
1028
|
const tsconfig = {
|
|
1029
|
+
extends: "../../tsconfig.base.json",
|
|
987
1030
|
compilerOptions: {
|
|
988
1031
|
target: "ES2020",
|
|
989
1032
|
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
@@ -1003,9 +1046,10 @@ export default config;
|
|
|
1003
1046
|
paths: {
|
|
1004
1047
|
"@/*": ["./*"],
|
|
1005
1048
|
},
|
|
1049
|
+
types: [],
|
|
1006
1050
|
},
|
|
1007
1051
|
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1008
|
-
exclude: ["node_modules"],
|
|
1052
|
+
exclude: ["node_modules", "dist", ".next", "out"],
|
|
1009
1053
|
};
|
|
1010
1054
|
await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
|
|
1011
1055
|
console.log(chalk.green("✓ tsconfig.json créé"));
|
|
@@ -1036,6 +1080,107 @@ export const menuConfig: MenuConfig = {
|
|
|
1036
1080
|
await fs.writeFile(menuConfigPath, menuConfig);
|
|
1037
1081
|
console.log(chalk.green("✓ config/menu.ts créé"));
|
|
1038
1082
|
}
|
|
1083
|
+
|
|
1084
|
+
// Créer les hooks
|
|
1085
|
+
await createHooksDirectory(targetDir, force);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
async function createHooksDirectory(targetDir: string, force: boolean) {
|
|
1089
|
+
const hooksDir = path.join(targetDir, "hooks");
|
|
1090
|
+
await fs.ensureDir(hooksDir);
|
|
1091
|
+
|
|
1092
|
+
// Créer le hook useNotifications
|
|
1093
|
+
const useNotificationsPath = path.join(hooksDir, "useNotifications.ts");
|
|
1094
|
+
if (!fs.existsSync(useNotificationsPath) || force) {
|
|
1095
|
+
const hookContent = `"use client";
|
|
1096
|
+
|
|
1097
|
+
import { useEffect, useState } from "react";
|
|
1098
|
+
import { useNotificationRealtime } from "@lastbrain/core";
|
|
1099
|
+
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
1100
|
+
import { useAuth } from "@lastbrain/app";
|
|
1101
|
+
|
|
1102
|
+
export interface Notification {
|
|
1103
|
+
id: string;
|
|
1104
|
+
title: string;
|
|
1105
|
+
message: string;
|
|
1106
|
+
read: boolean;
|
|
1107
|
+
created_at: string;
|
|
1108
|
+
owner_id: string;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export function useNotifications() {
|
|
1112
|
+
const { user } = useAuth();
|
|
1113
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
1114
|
+
const [loading, setLoading] = useState(true);
|
|
1115
|
+
const [error, setError] = useState<Error | null>(null);
|
|
1116
|
+
|
|
1117
|
+
// Écouter les changements en temps réel
|
|
1118
|
+
const realtimeSignal = useNotificationRealtime();
|
|
1119
|
+
|
|
1120
|
+
// Charger les notifications initiales
|
|
1121
|
+
const fetchNotifications = async () => {
|
|
1122
|
+
if (!user?.id) return;
|
|
1123
|
+
|
|
1124
|
+
try {
|
|
1125
|
+
setLoading(true);
|
|
1126
|
+
const { data, error } = await supabaseBrowserClient
|
|
1127
|
+
.from("user_notifications")
|
|
1128
|
+
.select("*")
|
|
1129
|
+
.eq("owner_id", user.id)
|
|
1130
|
+
.order("created_at", { ascending: false });
|
|
1131
|
+
|
|
1132
|
+
if (error) throw error;
|
|
1133
|
+
setNotifications(data || []);
|
|
1134
|
+
setError(null);
|
|
1135
|
+
} catch (err) {
|
|
1136
|
+
setError(err as Error);
|
|
1137
|
+
} finally {
|
|
1138
|
+
setLoading(false);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// Charger au montage et quand l'utilisateur change
|
|
1143
|
+
useEffect(() => {
|
|
1144
|
+
fetchNotifications();
|
|
1145
|
+
}, [user?.id]);
|
|
1146
|
+
|
|
1147
|
+
// Recharger quand les données changent en temps réel
|
|
1148
|
+
useEffect(() => {
|
|
1149
|
+
if (realtimeSignal.hasUpdates && user?.id) {
|
|
1150
|
+
console.log("🔔 Rechargement des notifications suite à un changement");
|
|
1151
|
+
fetchNotifications();
|
|
1152
|
+
}
|
|
1153
|
+
}, [realtimeSignal.tick, user?.id]);
|
|
1154
|
+
|
|
1155
|
+
return {
|
|
1156
|
+
notifications,
|
|
1157
|
+
loading,
|
|
1158
|
+
error,
|
|
1159
|
+
refetch: fetchNotifications,
|
|
1160
|
+
hasUpdates: realtimeSignal.hasUpdates,
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
`;
|
|
1164
|
+
await fs.writeFile(useNotificationsPath, hookContent);
|
|
1165
|
+
console.log(chalk.green("✓ hooks/useNotifications.ts créé"));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Créer un placeholder pour realtime.ts qui sera généré par build:modules
|
|
1169
|
+
const realtimePath = path.join(targetDir, "config", "realtime.ts");
|
|
1170
|
+
if (!fs.existsSync(realtimePath) || force) {
|
|
1171
|
+
const realtimeContent = `import type { ModuleRealtimeConfig } from "@lastbrain/core";
|
|
1172
|
+
|
|
1173
|
+
// GENERATED FILE - DO NOT EDIT MANUALLY
|
|
1174
|
+
// Ce fichier sera automatiquement généré lors du build:modules
|
|
1175
|
+
// Exécutez "pnpm build:modules" pour créer la configuration realtime
|
|
1176
|
+
|
|
1177
|
+
export const realtimeConfig: ModuleRealtimeConfig[] = [];
|
|
1178
|
+
|
|
1179
|
+
export default realtimeConfig;
|
|
1180
|
+
`;
|
|
1181
|
+
await fs.writeFile(realtimePath, realtimeContent);
|
|
1182
|
+
console.log(chalk.green("✓ config/realtime.ts placeholder créé"));
|
|
1183
|
+
}
|
|
1039
1184
|
}
|
|
1040
1185
|
|
|
1041
1186
|
async function createGitIgnore(targetDir: string, force: boolean) {
|
|
@@ -1145,6 +1290,28 @@ SUPABASE_SERVICE_ROLE_KEY=YOUR_LOCAL_SERVICE_ROLE_KEY
|
|
|
1145
1290
|
}
|
|
1146
1291
|
}
|
|
1147
1292
|
|
|
1293
|
+
async function createEnvLocal(targetDir: string, force: boolean) {
|
|
1294
|
+
const envLocalPath = path.join(targetDir, ".env.local");
|
|
1295
|
+
|
|
1296
|
+
if (!fs.existsSync(envLocalPath) || force) {
|
|
1297
|
+
console.log(chalk.yellow("\n🔐 Création de .env.local..."));
|
|
1298
|
+
|
|
1299
|
+
const envContent = `# Supabase Configuration
|
|
1300
|
+
# Valeurs par défaut pour le développement local
|
|
1301
|
+
|
|
1302
|
+
# Supabase Local (par défaut)
|
|
1303
|
+
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
|
1304
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...
|
|
1305
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...
|
|
1306
|
+
|
|
1307
|
+
# OpenAI Configuration (clé factice pour éviter les erreurs de build)
|
|
1308
|
+
OPENAI_API_KEY=sk-fake-openai-key-for-development-replace-with-real-key
|
|
1309
|
+
`;
|
|
1310
|
+
await fs.writeFile(envLocalPath, envContent);
|
|
1311
|
+
console.log(chalk.green("✓ .env.local créé"));
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1148
1315
|
async function createSupabaseStructure(targetDir: string, force: boolean) {
|
|
1149
1316
|
console.log(chalk.yellow("\n🗄️ Création de la structure Supabase..."));
|
|
1150
1317
|
|
|
@@ -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
|
}
|