@nsxbet/admin-sdk 0.1.0
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/README.md +680 -0
- package/dist/auth/client/in-memory.d.ts +27 -0
- package/dist/auth/client/in-memory.d.ts.map +1 -0
- package/dist/auth/client/in-memory.js +242 -0
- package/dist/auth/client/index.d.ts +7 -0
- package/dist/auth/client/index.d.ts.map +1 -0
- package/dist/auth/client/index.js +7 -0
- package/dist/auth/client/interface.d.ts +115 -0
- package/dist/auth/client/interface.d.ts.map +1 -0
- package/dist/auth/client/interface.js +7 -0
- package/dist/auth/client/keycloak.d.ts +19 -0
- package/dist/auth/client/keycloak.d.ts.map +1 -0
- package/dist/auth/client/keycloak.js +126 -0
- package/dist/auth/components/UserSelector.d.ts +19 -0
- package/dist/auth/components/UserSelector.d.ts.map +1 -0
- package/dist/auth/components/UserSelector.js +100 -0
- package/dist/auth/components/index.d.ts +5 -0
- package/dist/auth/components/index.d.ts.map +1 -0
- package/dist/auth/components/index.js +4 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +7 -0
- package/dist/components/AuthProvider.d.ts +48 -0
- package/dist/components/AuthProvider.d.ts.map +1 -0
- package/dist/components/AuthProvider.js +117 -0
- package/dist/hooks/useAuth.d.ts +21 -0
- package/dist/hooks/useAuth.d.ts.map +1 -0
- package/dist/hooks/useAuth.js +34 -0
- package/dist/hooks/useFetch.d.ts +8 -0
- package/dist/hooks/useFetch.d.ts.map +1 -0
- package/dist/hooks/useFetch.js +31 -0
- package/dist/hooks/useI18n.d.ts +46 -0
- package/dist/hooks/useI18n.d.ts.map +1 -0
- package/dist/hooks/useI18n.js +95 -0
- package/dist/hooks/usePlatformAPI.d.ts +12 -0
- package/dist/hooks/usePlatformAPI.d.ts.map +1 -0
- package/dist/hooks/usePlatformAPI.js +10 -0
- package/dist/hooks/useTelemetry.d.ts +17 -0
- package/dist/hooks/useTelemetry.d.ts.map +1 -0
- package/dist/hooks/useTelemetry.js +36 -0
- package/dist/i18n/config.d.ts +26 -0
- package/dist/i18n/config.d.ts.map +1 -0
- package/dist/i18n/config.js +92 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/locales/en-US.json +144 -0
- package/dist/i18n/locales/es.json +144 -0
- package/dist/i18n/locales/pt-BR.json +144 -0
- package/dist/i18n/locales/ro.json +144 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/registry/AdminShellRegistry.d.ts +140 -0
- package/dist/registry/AdminShellRegistry.d.ts.map +1 -0
- package/dist/registry/AdminShellRegistry.js +237 -0
- package/dist/registry/client/http.d.ts +21 -0
- package/dist/registry/client/http.d.ts.map +1 -0
- package/dist/registry/client/http.js +107 -0
- package/dist/registry/client/in-memory.d.ts +36 -0
- package/dist/registry/client/in-memory.d.ts.map +1 -0
- package/dist/registry/client/in-memory.js +242 -0
- package/dist/registry/client/index.d.ts +7 -0
- package/dist/registry/client/index.d.ts.map +1 -0
- package/dist/registry/client/index.js +5 -0
- package/dist/registry/client/interface.d.ts +96 -0
- package/dist/registry/client/interface.d.ts.map +1 -0
- package/dist/registry/client/interface.js +7 -0
- package/dist/registry/index.d.ts +12 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/types/index.d.ts +9 -0
- package/dist/registry/types/index.d.ts.map +1 -0
- package/dist/registry/types/index.js +6 -0
- package/dist/registry/types/manifest.d.ts +98 -0
- package/dist/registry/types/manifest.d.ts.map +1 -0
- package/dist/registry/types/manifest.js +81 -0
- package/dist/registry/types/module.d.ts +115 -0
- package/dist/registry/types/module.d.ts.map +1 -0
- package/dist/registry/types/module.js +6 -0
- package/dist/router/DynamicModule.d.ts +50 -0
- package/dist/router/DynamicModule.d.ts.map +1 -0
- package/dist/router/DynamicModule.js +141 -0
- package/dist/router/index.d.ts +2 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +1 -0
- package/dist/shell/AdminShell.d.ts +38 -0
- package/dist/shell/AdminShell.d.ts.map +1 -0
- package/dist/shell/AdminShell.js +299 -0
- package/dist/shell/BackofficeShell.d.ts +38 -0
- package/dist/shell/BackofficeShell.d.ts.map +1 -0
- package/dist/shell/BackofficeShell.js +299 -0
- package/dist/shell/components/CommandPalette.d.ts +8 -0
- package/dist/shell/components/CommandPalette.d.ts.map +1 -0
- package/dist/shell/components/CommandPalette.js +197 -0
- package/dist/shell/components/HomePage.d.ts +2 -0
- package/dist/shell/components/HomePage.d.ts.map +1 -0
- package/dist/shell/components/HomePage.js +32 -0
- package/dist/shell/components/LeftNav.d.ts +7 -0
- package/dist/shell/components/LeftNav.d.ts.map +1 -0
- package/dist/shell/components/LeftNav.js +247 -0
- package/dist/shell/components/MainContent.d.ts +9 -0
- package/dist/shell/components/MainContent.d.ts.map +1 -0
- package/dist/shell/components/MainContent.js +88 -0
- package/dist/shell/components/ModuleOverview.d.ts +7 -0
- package/dist/shell/components/ModuleOverview.d.ts.map +1 -0
- package/dist/shell/components/ModuleOverview.js +40 -0
- package/dist/shell/components/ProfilePage.d.ts +2 -0
- package/dist/shell/components/ProfilePage.d.ts.map +1 -0
- package/dist/shell/components/ProfilePage.js +30 -0
- package/dist/shell/components/RegistryPage.d.ts +8 -0
- package/dist/shell/components/RegistryPage.d.ts.map +1 -0
- package/dist/shell/components/RegistryPage.js +129 -0
- package/dist/shell/components/SettingsPage.d.ts +2 -0
- package/dist/shell/components/SettingsPage.d.ts.map +1 -0
- package/dist/shell/components/SettingsPage.js +60 -0
- package/dist/shell/components/TopBar.d.ts +8 -0
- package/dist/shell/components/TopBar.d.ts.map +1 -0
- package/dist/shell/components/TopBar.js +61 -0
- package/dist/shell/components/index.d.ts +10 -0
- package/dist/shell/components/index.d.ts.map +1 -0
- package/dist/shell/components/index.js +7 -0
- package/dist/shell/components/theme-provider.d.ts +15 -0
- package/dist/shell/components/theme-provider.d.ts.map +1 -0
- package/dist/shell/components/theme-provider.js +39 -0
- package/dist/shell/index.d.ts +9 -0
- package/dist/shell/index.d.ts.map +1 -0
- package/dist/shell/index.js +8 -0
- package/dist/shell/search/fuzzy.d.ts +18 -0
- package/dist/shell/search/fuzzy.d.ts.map +1 -0
- package/dist/shell/search/fuzzy.js +121 -0
- package/dist/shell/search/index.d.ts +3 -0
- package/dist/shell/search/index.d.ts.map +1 -0
- package/dist/shell/search/index.js +1 -0
- package/dist/shell/telemetry.d.ts +7 -0
- package/dist/shell/telemetry.d.ts.map +1 -0
- package/dist/shell/telemetry.js +25 -0
- package/dist/shell/types.d.ts +110 -0
- package/dist/shell/types.d.ts.map +1 -0
- package/dist/shell/types.js +4 -0
- package/dist/tailwind/index.d.ts +20 -0
- package/dist/tailwind/index.d.ts.map +1 -0
- package/dist/tailwind/index.js +42 -0
- package/dist/types/keycloak.d.ts +26 -0
- package/dist/types/keycloak.d.ts.map +1 -0
- package/dist/types/keycloak.js +1 -0
- package/dist/types/platform.d.ts +83 -0
- package/dist/types/platform.d.ts.map +1 -0
- package/dist/types/platform.js +5 -0
- package/dist/vite/config.d.ts +71 -0
- package/dist/vite/config.d.ts.map +1 -0
- package/dist/vite/config.js +87 -0
- package/dist/vite/index.d.ts +18 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +17 -0
- package/dist/vite/plugins.d.ts +44 -0
- package/dist/vite/plugins.d.ts.map +1 -0
- package/dist/vite/plugins.js +74 -0
- package/package.json +86 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
3
|
+
import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom";
|
|
4
|
+
import { I18nextProvider } from "react-i18next";
|
|
5
|
+
import { TopBar } from "./components/TopBar";
|
|
6
|
+
import { LeftNav } from "./components/LeftNav";
|
|
7
|
+
import { MainContent } from "./components/MainContent";
|
|
8
|
+
import { CommandPalette } from "./components/CommandPalette";
|
|
9
|
+
import { ThemeProvider } from "./components/theme-provider";
|
|
10
|
+
import { ModuleOverview } from "./components/ModuleOverview";
|
|
11
|
+
import { ProfilePage } from "./components/ProfilePage";
|
|
12
|
+
import { SettingsPage } from "./components/SettingsPage";
|
|
13
|
+
import { RegistryPage } from "./components/RegistryPage";
|
|
14
|
+
import { HomePage } from "./components/HomePage";
|
|
15
|
+
import { AuthProvider, useAuthContext } from "../components/AuthProvider";
|
|
16
|
+
import { createInMemoryAuthClient } from "../auth/client/in-memory";
|
|
17
|
+
import { createKeycloakAuthClient } from "../auth/client/keycloak";
|
|
18
|
+
import { createInMemoryRegistryClient } from "../registry/client/in-memory";
|
|
19
|
+
import { DynamicModule } from "../router/DynamicModule";
|
|
20
|
+
import { SidebarProvider, SidebarInset } from "@nsxbet/admin-ui";
|
|
21
|
+
import { initTelemetry, track, trackError } from "./telemetry";
|
|
22
|
+
import { initI18n, saveLocale, isSupportedLocale, i18n } from "../i18n";
|
|
23
|
+
/**
|
|
24
|
+
* Convert AdminModuleManifest to Module for internal use
|
|
25
|
+
*/
|
|
26
|
+
function manifestToModule(manifest, baseUrl) {
|
|
27
|
+
return {
|
|
28
|
+
id: manifest.id,
|
|
29
|
+
title: manifest.title,
|
|
30
|
+
titleKey: manifest.titleKey,
|
|
31
|
+
description: manifest.description || "",
|
|
32
|
+
descriptionKey: manifest.descriptionKey,
|
|
33
|
+
category: manifest.category || "Modules",
|
|
34
|
+
routeBase: manifest.routeBase,
|
|
35
|
+
baseUrl: baseUrl || "",
|
|
36
|
+
keywords: manifest.keywords || [],
|
|
37
|
+
permissions: {
|
|
38
|
+
view: manifest.permissions?.view || [],
|
|
39
|
+
dangerous: manifest.permissions?.dangerous,
|
|
40
|
+
},
|
|
41
|
+
owners: {
|
|
42
|
+
team: manifest.owners?.team || "Platform",
|
|
43
|
+
supportChannel: manifest.owners?.supportChannel || "",
|
|
44
|
+
},
|
|
45
|
+
status: manifest.status || "active",
|
|
46
|
+
navOrder: manifest.navOrder,
|
|
47
|
+
icon: manifest.icon,
|
|
48
|
+
commands: manifest.commands?.map((cmd) => ({
|
|
49
|
+
id: cmd.id,
|
|
50
|
+
title: cmd.title,
|
|
51
|
+
titleKey: cmd.titleKey,
|
|
52
|
+
route: cmd.route,
|
|
53
|
+
icon: cmd.icon,
|
|
54
|
+
keywords: cmd.keywords,
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Convert CatalogModule to Module for internal use
|
|
60
|
+
*/
|
|
61
|
+
function catalogModuleToModule(m) {
|
|
62
|
+
return {
|
|
63
|
+
id: m.id,
|
|
64
|
+
title: m.title,
|
|
65
|
+
titleKey: m.titleKey,
|
|
66
|
+
description: m.description,
|
|
67
|
+
descriptionKey: m.descriptionKey,
|
|
68
|
+
category: m.category,
|
|
69
|
+
routeBase: m.routeBase,
|
|
70
|
+
baseUrl: m.baseUrl,
|
|
71
|
+
keywords: m.keywords,
|
|
72
|
+
permissions: m.permissions,
|
|
73
|
+
owners: m.owners,
|
|
74
|
+
status: m.status,
|
|
75
|
+
navOrder: m.navOrder,
|
|
76
|
+
icon: m.icon,
|
|
77
|
+
commands: m.commands?.map(cmd => ({
|
|
78
|
+
...cmd,
|
|
79
|
+
titleKey: cmd.titleKey,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Inner shell component that has access to React Router hooks
|
|
85
|
+
*/
|
|
86
|
+
function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, }) {
|
|
87
|
+
const navigate = useNavigate();
|
|
88
|
+
const auth = useAuthContext();
|
|
89
|
+
// Set up the platform API for modules to use
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const platformAPI = {
|
|
92
|
+
env: environment,
|
|
93
|
+
locale: locale,
|
|
94
|
+
auth: {
|
|
95
|
+
getAccessToken: auth.getAccessToken,
|
|
96
|
+
hasPermission: auth.hasPermission,
|
|
97
|
+
getUser: () => auth.user || { id: "", email: "", displayName: "" },
|
|
98
|
+
logout: () => {
|
|
99
|
+
auth.logout();
|
|
100
|
+
// Navigate to root after logout (for Keycloak, this will redirect)
|
|
101
|
+
navigate("/");
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
nav: {
|
|
105
|
+
navigate: (path) => {
|
|
106
|
+
navigate(path);
|
|
107
|
+
},
|
|
108
|
+
setBreadcrumbs: (_items) => {
|
|
109
|
+
// Breadcrumbs are managed by MainContent based on route
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
i18n: {
|
|
113
|
+
locale: locale,
|
|
114
|
+
setLocale: onLocaleChange,
|
|
115
|
+
onLocaleChange: (callback) => {
|
|
116
|
+
// Simple implementation - just call callback immediately with current locale
|
|
117
|
+
callback(locale);
|
|
118
|
+
return () => { }; // Return unsubscribe function
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
telemetry: {
|
|
122
|
+
track: track,
|
|
123
|
+
trackError: trackError,
|
|
124
|
+
},
|
|
125
|
+
fetch: async (input, init) => {
|
|
126
|
+
// Add auth token to requests
|
|
127
|
+
const token = await auth.getAccessToken();
|
|
128
|
+
const headers = new Headers(init?.headers);
|
|
129
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
130
|
+
return fetch(input, { ...init, headers });
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
window.__ADMIN_PLATFORM_API__ = platformAPI;
|
|
134
|
+
return () => {
|
|
135
|
+
delete window.__ADMIN_PLATFORM_API__;
|
|
136
|
+
};
|
|
137
|
+
}, [environment, locale, navigate, onLocaleChange, auth]);
|
|
138
|
+
// Load initial sidebar state from localStorage
|
|
139
|
+
const getInitialSidebarState = () => {
|
|
140
|
+
try {
|
|
141
|
+
const stored = localStorage.getItem("adminPlatform.sidebarCollapsed");
|
|
142
|
+
if (stored) {
|
|
143
|
+
return !JSON.parse(stored); // stored is "collapsed", we need "open"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Ignore
|
|
148
|
+
}
|
|
149
|
+
return true; // Default to open
|
|
150
|
+
};
|
|
151
|
+
// Controlled sidebar state
|
|
152
|
+
const [sidebarOpen, setSidebarOpen] = useState(getInitialSidebarState);
|
|
153
|
+
// Save sidebar state to localStorage when it changes
|
|
154
|
+
const handleSidebarChange = (open) => {
|
|
155
|
+
setSidebarOpen(open);
|
|
156
|
+
try {
|
|
157
|
+
localStorage.setItem("adminPlatform.sidebarCollapsed", JSON.stringify(!open));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Ignore
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return (_jsxs(_Fragment, { children: [_jsxs(SidebarProvider, { open: sidebarOpen, onOpenChange: handleSidebarChange, children: [_jsx(LeftNav, { modules: modules }), _jsxs(SidebarInset, { className: "flex flex-col", children: [_jsx(TopBar, { onSearchClick: onSearchClick, environment: environment, locale: locale, onLocaleChange: onLocaleChange }), _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(MainContent, { modules: modules, children: _jsx(HomePage, {}) }) }), _jsx(Route, { path: "/_profile", element: _jsx(MainContent, { modules: modules, children: _jsx(ProfilePage, {}) }) }), _jsx(Route, { path: "/_settings", element: _jsx(MainContent, { modules: modules, children: _jsx(SettingsPage, {}) }) }), _jsx(Route, { path: "/_registry", element: _jsx(MainContent, { modules: modules, children: _jsx(RegistryPage, { apiUrl: apiUrl, registryClient: registryClient }) }) }), _jsx(Route, { path: "/_modules/*", element: _jsx(MainContent, { modules: modules, children: _jsx(ModuleOverview, { modules: modules }) }) }), isStandaloneMode
|
|
164
|
+
? // Standalone mode: render children (module is imported directly)
|
|
165
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: children }) }, module.id)))
|
|
166
|
+
: // Shell mode: load modules dynamically via React.lazy
|
|
167
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: module.baseUrl ? (_jsx(DynamicModule, { baseUrl: module.baseUrl })) : (_jsxs("div", { className: "text-muted-foreground", children: ["Module ", module.id, " has no baseUrl configured"] })) }) }, module.id))), _jsx(Route, { path: "*", element: _jsx(MainContent, { modules: modules, children: isStandaloneMode ? children : null }) })] })] })] }), _jsx(CommandPalette, { open: commandPaletteOpen, onOpenChange: onCommandPaletteChange, catalog: catalog })] }));
|
|
168
|
+
}
|
|
169
|
+
export function AdminShell({ modules: manifests = [], children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry = true, environment = "local", }) {
|
|
170
|
+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
171
|
+
const [locale, setLocale] = useState(() => {
|
|
172
|
+
// Initialize i18n and get the current language
|
|
173
|
+
initI18n();
|
|
174
|
+
return i18n.language || "pt-BR";
|
|
175
|
+
});
|
|
176
|
+
const [apiModules, setApiModules] = useState([]);
|
|
177
|
+
const [isLoading, setIsLoading] = useState(!!registryClient);
|
|
178
|
+
// Handle locale change - update both state and i18next
|
|
179
|
+
const handleLocaleChange = useCallback((newLocale) => {
|
|
180
|
+
if (isSupportedLocale(newLocale)) {
|
|
181
|
+
i18n.changeLanguage(newLocale);
|
|
182
|
+
saveLocale(newLocale);
|
|
183
|
+
setLocale(newLocale);
|
|
184
|
+
}
|
|
185
|
+
}, []);
|
|
186
|
+
// Sync locale state with i18next language changes
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
const handleLanguageChanged = (lng) => {
|
|
189
|
+
setLocale(lng);
|
|
190
|
+
};
|
|
191
|
+
i18n.on("languageChanged", handleLanguageChanged);
|
|
192
|
+
return () => {
|
|
193
|
+
i18n.off("languageChanged", handleLanguageChanged);
|
|
194
|
+
};
|
|
195
|
+
}, []);
|
|
196
|
+
// Determine if we're in standalone mode (module imported directly) or shell mode (dynamic loading)
|
|
197
|
+
const isStandaloneMode = !registryClient && manifests.length > 0;
|
|
198
|
+
// Create or use provided auth client
|
|
199
|
+
const authClient = useMemo(() => {
|
|
200
|
+
if (providedAuthClient) {
|
|
201
|
+
return providedAuthClient;
|
|
202
|
+
}
|
|
203
|
+
// Check if we should use mock auth
|
|
204
|
+
const useMockAuth = typeof window !== 'undefined' &&
|
|
205
|
+
(import.meta.env.VITE_MOCK_AUTH === 'true' || !keycloak);
|
|
206
|
+
if (useMockAuth) {
|
|
207
|
+
return createInMemoryAuthClient();
|
|
208
|
+
}
|
|
209
|
+
// Use Keycloak
|
|
210
|
+
return createKeycloakAuthClient({
|
|
211
|
+
config: keycloak,
|
|
212
|
+
});
|
|
213
|
+
}, [providedAuthClient, keycloak]);
|
|
214
|
+
// Initialize telemetry
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
initTelemetry(environment);
|
|
217
|
+
}, [environment]);
|
|
218
|
+
// Initialize Keycloak configuration (for legacy support)
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (keycloak) {
|
|
221
|
+
window.__KEYCLOAK_CONFIG__ = {
|
|
222
|
+
url: keycloak.url,
|
|
223
|
+
realm: keycloak.realm,
|
|
224
|
+
clientId: keycloak.clientId,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}, [keycloak]);
|
|
228
|
+
// Initialize in-memory registry if enabled and no registryClient
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (!registryClient && inMemoryRegistry && manifests.length > 0) {
|
|
231
|
+
createInMemoryRegistryClient({
|
|
232
|
+
seed: manifests,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}, [registryClient, inMemoryRegistry, manifests]);
|
|
236
|
+
// Fetch modules from registry client (modules load via React.lazy)
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (!registryClient) {
|
|
239
|
+
setIsLoading(false);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
let mounted = true;
|
|
243
|
+
async function fetchModules() {
|
|
244
|
+
try {
|
|
245
|
+
// Fetch catalog
|
|
246
|
+
const catalog = await registryClient.catalog.get();
|
|
247
|
+
if (!mounted)
|
|
248
|
+
return;
|
|
249
|
+
// Convert catalog modules to Module type for UI
|
|
250
|
+
const modules = catalog.modules.map(catalogModuleToModule);
|
|
251
|
+
setApiModules(modules);
|
|
252
|
+
setIsLoading(false);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error("[Shell] Failed to fetch modules from API:", error);
|
|
256
|
+
if (mounted) {
|
|
257
|
+
setIsLoading(false);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
fetchModules();
|
|
262
|
+
return () => {
|
|
263
|
+
mounted = false;
|
|
264
|
+
};
|
|
265
|
+
}, [registryClient]);
|
|
266
|
+
// Convert manifests to modules (for standalone mode)
|
|
267
|
+
const manifestModules = useMemo(() => {
|
|
268
|
+
return manifests.map((m) => manifestToModule(m));
|
|
269
|
+
}, [manifests]);
|
|
270
|
+
// Use API modules if registry client is provided, otherwise use manifest modules
|
|
271
|
+
const modules = registryClient ? apiModules : manifestModules;
|
|
272
|
+
// Create catalog for command palette
|
|
273
|
+
const catalog = useMemo(() => {
|
|
274
|
+
return {
|
|
275
|
+
version: "1.0.0",
|
|
276
|
+
generatedAt: new Date().toISOString(),
|
|
277
|
+
modules: modules,
|
|
278
|
+
};
|
|
279
|
+
}, [modules]);
|
|
280
|
+
// Keyboard shortcut for command palette
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
const handleKeyDown = (e) => {
|
|
283
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
setCommandPaletteOpen((prev) => !prev);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
289
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
290
|
+
}, []);
|
|
291
|
+
const handleSearchClick = useCallback(() => {
|
|
292
|
+
setCommandPaletteOpen(true);
|
|
293
|
+
}, []);
|
|
294
|
+
// Show loading state while fetching from API
|
|
295
|
+
if (isLoading) {
|
|
296
|
+
return (_jsx("div", { className: "flex h-screen items-center justify-center", children: _jsx("div", { className: "text-muted-foreground", children: "Loading modules..." }) }));
|
|
297
|
+
}
|
|
298
|
+
return (_jsx(BrowserRouter, { children: _jsx(I18nextProvider, { i18n: i18n, children: _jsx(ThemeProvider, { children: _jsx(AuthProvider, { authClient: authClient, children: _jsx(ShellContent, { modules: modules, environment: environment, locale: locale, onLocaleChange: handleLocaleChange, onSearchClick: handleSearchClick, catalog: catalog, commandPaletteOpen: commandPaletteOpen, onCommandPaletteChange: setCommandPaletteOpen, apiUrl: apiUrl, registryClient: registryClient, isStandaloneMode: isStandaloneMode, children: children }) }) }) }) }));
|
|
299
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AuthClient } from "../auth/client/interface";
|
|
2
|
+
import type { AdminModuleManifest, RegistryClient } from "../registry";
|
|
3
|
+
export interface AdminShellProps {
|
|
4
|
+
/**
|
|
5
|
+
* Module manifests to load (used for standalone mode when no registryClient)
|
|
6
|
+
* In standalone mode, modules are rendered via children prop
|
|
7
|
+
*/
|
|
8
|
+
modules?: AdminModuleManifest[];
|
|
9
|
+
/**
|
|
10
|
+
* Content to render for standalone module development.
|
|
11
|
+
* Only used in standalone mode (when modules prop is provided)
|
|
12
|
+
*/
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* Keycloak configuration
|
|
16
|
+
* If not provided, uses in-memory (mock) authentication
|
|
17
|
+
*/
|
|
18
|
+
keycloak?: {
|
|
19
|
+
url: string;
|
|
20
|
+
realm: string;
|
|
21
|
+
clientId: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Auth client to use for authentication.
|
|
25
|
+
* If not provided, creates one based on keycloak prop or environment.
|
|
26
|
+
*/
|
|
27
|
+
authClient?: AuthClient;
|
|
28
|
+
/** Registry client for fetching modules from API */
|
|
29
|
+
registryClient?: RegistryClient;
|
|
30
|
+
/** API URL for registry management (e.g., "http://localhost:4000/api") */
|
|
31
|
+
apiUrl?: string;
|
|
32
|
+
/** Use in-memory registry (default: true, ignored if registryClient is provided) */
|
|
33
|
+
inMemoryRegistry?: boolean;
|
|
34
|
+
/** Environment (default: "local") */
|
|
35
|
+
environment?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function AdminShell({ modules: manifests, children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry, environment, }: AdminShellProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
//# sourceMappingURL=BackofficeShell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackofficeShell.d.ts","sourceRoot":"","sources":["../../src/shell/BackofficeShell.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAMtF,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA8RD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EAAE,SAAc,EACvB,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,kBAAkB,EAC9B,cAAc,EACd,MAAM,EACN,gBAAuB,EACvB,WAAqB,GACtB,EAAE,eAAe,2CAsLjB"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
3
|
+
import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom";
|
|
4
|
+
import { I18nextProvider } from "react-i18next";
|
|
5
|
+
import { TopBar } from "./components/TopBar";
|
|
6
|
+
import { LeftNav } from "./components/LeftNav";
|
|
7
|
+
import { MainContent } from "./components/MainContent";
|
|
8
|
+
import { CommandPalette } from "./components/CommandPalette";
|
|
9
|
+
import { ThemeProvider } from "./components/theme-provider";
|
|
10
|
+
import { ModuleOverview } from "./components/ModuleOverview";
|
|
11
|
+
import { ProfilePage } from "./components/ProfilePage";
|
|
12
|
+
import { SettingsPage } from "./components/SettingsPage";
|
|
13
|
+
import { RegistryPage } from "./components/RegistryPage";
|
|
14
|
+
import { HomePage } from "./components/HomePage";
|
|
15
|
+
import { AuthProvider, useAuthContext } from "../components/AuthProvider";
|
|
16
|
+
import { createInMemoryAuthClient } from "../auth/client/in-memory";
|
|
17
|
+
import { createKeycloakAuthClient } from "../auth/client/keycloak";
|
|
18
|
+
import { createInMemoryRegistryClient } from "../registry/client/in-memory";
|
|
19
|
+
import { DynamicModule } from "../router/DynamicModule";
|
|
20
|
+
import { SidebarProvider, SidebarInset } from "@nsxbet/admin-ui";
|
|
21
|
+
import { initTelemetry, track, trackError } from "./telemetry";
|
|
22
|
+
import { initI18n, saveLocale, isSupportedLocale, i18n } from "../i18n";
|
|
23
|
+
/**
|
|
24
|
+
* Convert AdminModuleManifest to Module for internal use
|
|
25
|
+
*/
|
|
26
|
+
function manifestToModule(manifest, baseUrl) {
|
|
27
|
+
return {
|
|
28
|
+
id: manifest.id,
|
|
29
|
+
title: manifest.title,
|
|
30
|
+
titleKey: manifest.titleKey,
|
|
31
|
+
description: manifest.description || "",
|
|
32
|
+
descriptionKey: manifest.descriptionKey,
|
|
33
|
+
category: manifest.category || "Modules",
|
|
34
|
+
routeBase: manifest.routeBase,
|
|
35
|
+
baseUrl: baseUrl || "",
|
|
36
|
+
keywords: manifest.keywords || [],
|
|
37
|
+
permissions: {
|
|
38
|
+
view: manifest.permissions?.view || [],
|
|
39
|
+
dangerous: manifest.permissions?.dangerous,
|
|
40
|
+
},
|
|
41
|
+
owners: {
|
|
42
|
+
team: manifest.owners?.team || "Platform",
|
|
43
|
+
supportChannel: manifest.owners?.supportChannel || "",
|
|
44
|
+
},
|
|
45
|
+
status: manifest.status || "active",
|
|
46
|
+
navOrder: manifest.navOrder,
|
|
47
|
+
icon: manifest.icon,
|
|
48
|
+
commands: manifest.commands?.map((cmd) => ({
|
|
49
|
+
id: cmd.id,
|
|
50
|
+
title: cmd.title,
|
|
51
|
+
titleKey: cmd.titleKey,
|
|
52
|
+
route: cmd.route,
|
|
53
|
+
icon: cmd.icon,
|
|
54
|
+
keywords: cmd.keywords,
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Convert CatalogModule to Module for internal use
|
|
60
|
+
*/
|
|
61
|
+
function catalogModuleToModule(m) {
|
|
62
|
+
return {
|
|
63
|
+
id: m.id,
|
|
64
|
+
title: m.title,
|
|
65
|
+
titleKey: m.titleKey,
|
|
66
|
+
description: m.description,
|
|
67
|
+
descriptionKey: m.descriptionKey,
|
|
68
|
+
category: m.category,
|
|
69
|
+
routeBase: m.routeBase,
|
|
70
|
+
baseUrl: m.baseUrl,
|
|
71
|
+
keywords: m.keywords,
|
|
72
|
+
permissions: m.permissions,
|
|
73
|
+
owners: m.owners,
|
|
74
|
+
status: m.status,
|
|
75
|
+
navOrder: m.navOrder,
|
|
76
|
+
icon: m.icon,
|
|
77
|
+
commands: m.commands?.map(cmd => ({
|
|
78
|
+
...cmd,
|
|
79
|
+
titleKey: cmd.titleKey,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Inner shell component that has access to React Router hooks
|
|
85
|
+
*/
|
|
86
|
+
function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, }) {
|
|
87
|
+
const navigate = useNavigate();
|
|
88
|
+
const auth = useAuthContext();
|
|
89
|
+
// Set up the platform API for modules to use
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const platformAPI = {
|
|
92
|
+
env: environment,
|
|
93
|
+
locale: locale,
|
|
94
|
+
auth: {
|
|
95
|
+
getAccessToken: auth.getAccessToken,
|
|
96
|
+
hasPermission: auth.hasPermission,
|
|
97
|
+
getUser: () => auth.user || { id: "", email: "", displayName: "" },
|
|
98
|
+
logout: () => {
|
|
99
|
+
auth.logout();
|
|
100
|
+
// Navigate to root after logout (for Keycloak, this will redirect)
|
|
101
|
+
navigate("/");
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
nav: {
|
|
105
|
+
navigate: (path) => {
|
|
106
|
+
navigate(path);
|
|
107
|
+
},
|
|
108
|
+
setBreadcrumbs: (_items) => {
|
|
109
|
+
// Breadcrumbs are managed by MainContent based on route
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
i18n: {
|
|
113
|
+
locale: locale,
|
|
114
|
+
setLocale: onLocaleChange,
|
|
115
|
+
onLocaleChange: (callback) => {
|
|
116
|
+
// Simple implementation - just call callback immediately with current locale
|
|
117
|
+
callback(locale);
|
|
118
|
+
return () => { }; // Return unsubscribe function
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
telemetry: {
|
|
122
|
+
track: track,
|
|
123
|
+
trackError: trackError,
|
|
124
|
+
},
|
|
125
|
+
fetch: async (input, init) => {
|
|
126
|
+
// Add auth token to requests
|
|
127
|
+
const token = await auth.getAccessToken();
|
|
128
|
+
const headers = new Headers(init?.headers);
|
|
129
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
130
|
+
return fetch(input, { ...init, headers });
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
window.__ADMIN_PLATFORM_API__ = platformAPI;
|
|
134
|
+
return () => {
|
|
135
|
+
delete window.__ADMIN_PLATFORM_API__;
|
|
136
|
+
};
|
|
137
|
+
}, [environment, locale, navigate, onLocaleChange, auth]);
|
|
138
|
+
// Load initial sidebar state from localStorage
|
|
139
|
+
const getInitialSidebarState = () => {
|
|
140
|
+
try {
|
|
141
|
+
const stored = localStorage.getItem("adminPlatform.sidebarCollapsed");
|
|
142
|
+
if (stored) {
|
|
143
|
+
return !JSON.parse(stored); // stored is "collapsed", we need "open"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Ignore
|
|
148
|
+
}
|
|
149
|
+
return true; // Default to open
|
|
150
|
+
};
|
|
151
|
+
// Controlled sidebar state
|
|
152
|
+
const [sidebarOpen, setSidebarOpen] = useState(getInitialSidebarState);
|
|
153
|
+
// Save sidebar state to localStorage when it changes
|
|
154
|
+
const handleSidebarChange = (open) => {
|
|
155
|
+
setSidebarOpen(open);
|
|
156
|
+
try {
|
|
157
|
+
localStorage.setItem("adminPlatform.sidebarCollapsed", JSON.stringify(!open));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Ignore
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return (_jsxs(_Fragment, { children: [_jsxs(SidebarProvider, { open: sidebarOpen, onOpenChange: handleSidebarChange, children: [_jsx(LeftNav, { modules: modules }), _jsxs(SidebarInset, { className: "flex flex-col", children: [_jsx(TopBar, { onSearchClick: onSearchClick, environment: environment, locale: locale, onLocaleChange: onLocaleChange }), _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(MainContent, { modules: modules, children: _jsx(HomePage, {}) }) }), _jsx(Route, { path: "/_profile", element: _jsx(MainContent, { modules: modules, children: _jsx(ProfilePage, {}) }) }), _jsx(Route, { path: "/_settings", element: _jsx(MainContent, { modules: modules, children: _jsx(SettingsPage, {}) }) }), _jsx(Route, { path: "/_registry", element: _jsx(MainContent, { modules: modules, children: _jsx(RegistryPage, { apiUrl: apiUrl, registryClient: registryClient }) }) }), _jsx(Route, { path: "/_modules/*", element: _jsx(MainContent, { modules: modules, children: _jsx(ModuleOverview, { modules: modules }) }) }), isStandaloneMode
|
|
164
|
+
? // Standalone mode: render children (module is imported directly)
|
|
165
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: children }) }, module.id)))
|
|
166
|
+
: // Shell mode: load modules dynamically via React.lazy
|
|
167
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: module.baseUrl ? (_jsx(DynamicModule, { baseUrl: module.baseUrl })) : (_jsxs("div", { className: "text-muted-foreground", children: ["Module ", module.id, " has no baseUrl configured"] })) }) }, module.id))), _jsx(Route, { path: "*", element: _jsx(MainContent, { modules: modules, children: isStandaloneMode ? children : null }) })] })] })] }), _jsx(CommandPalette, { open: commandPaletteOpen, onOpenChange: onCommandPaletteChange, catalog: catalog })] }));
|
|
168
|
+
}
|
|
169
|
+
export function AdminShell({ modules: manifests = [], children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry = true, environment = "local", }) {
|
|
170
|
+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
171
|
+
const [locale, setLocale] = useState(() => {
|
|
172
|
+
// Initialize i18n and get the current language
|
|
173
|
+
initI18n();
|
|
174
|
+
return i18n.language || "pt-BR";
|
|
175
|
+
});
|
|
176
|
+
const [apiModules, setApiModules] = useState([]);
|
|
177
|
+
const [isLoading, setIsLoading] = useState(!!registryClient);
|
|
178
|
+
// Handle locale change - update both state and i18next
|
|
179
|
+
const handleLocaleChange = useCallback((newLocale) => {
|
|
180
|
+
if (isSupportedLocale(newLocale)) {
|
|
181
|
+
i18n.changeLanguage(newLocale);
|
|
182
|
+
saveLocale(newLocale);
|
|
183
|
+
setLocale(newLocale);
|
|
184
|
+
}
|
|
185
|
+
}, []);
|
|
186
|
+
// Sync locale state with i18next language changes
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
const handleLanguageChanged = (lng) => {
|
|
189
|
+
setLocale(lng);
|
|
190
|
+
};
|
|
191
|
+
i18n.on("languageChanged", handleLanguageChanged);
|
|
192
|
+
return () => {
|
|
193
|
+
i18n.off("languageChanged", handleLanguageChanged);
|
|
194
|
+
};
|
|
195
|
+
}, []);
|
|
196
|
+
// Determine if we're in standalone mode (module imported directly) or shell mode (dynamic loading)
|
|
197
|
+
const isStandaloneMode = !registryClient && manifests.length > 0;
|
|
198
|
+
// Create or use provided auth client
|
|
199
|
+
const authClient = useMemo(() => {
|
|
200
|
+
if (providedAuthClient) {
|
|
201
|
+
return providedAuthClient;
|
|
202
|
+
}
|
|
203
|
+
// Check if we should use mock auth
|
|
204
|
+
const useMockAuth = typeof window !== 'undefined' &&
|
|
205
|
+
(import.meta.env.VITE_MOCK_AUTH === 'true' || !keycloak);
|
|
206
|
+
if (useMockAuth) {
|
|
207
|
+
return createInMemoryAuthClient();
|
|
208
|
+
}
|
|
209
|
+
// Use Keycloak
|
|
210
|
+
return createKeycloakAuthClient({
|
|
211
|
+
config: keycloak,
|
|
212
|
+
});
|
|
213
|
+
}, [providedAuthClient, keycloak]);
|
|
214
|
+
// Initialize telemetry
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
initTelemetry(environment);
|
|
217
|
+
}, [environment]);
|
|
218
|
+
// Initialize Keycloak configuration (for legacy support)
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (keycloak) {
|
|
221
|
+
window.__KEYCLOAK_CONFIG__ = {
|
|
222
|
+
url: keycloak.url,
|
|
223
|
+
realm: keycloak.realm,
|
|
224
|
+
clientId: keycloak.clientId,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}, [keycloak]);
|
|
228
|
+
// Initialize in-memory registry if enabled and no registryClient
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (!registryClient && inMemoryRegistry && manifests.length > 0) {
|
|
231
|
+
createInMemoryRegistryClient({
|
|
232
|
+
seed: manifests,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}, [registryClient, inMemoryRegistry, manifests]);
|
|
236
|
+
// Fetch modules from registry client (modules load via React.lazy)
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (!registryClient) {
|
|
239
|
+
setIsLoading(false);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
let mounted = true;
|
|
243
|
+
async function fetchModules() {
|
|
244
|
+
try {
|
|
245
|
+
// Fetch catalog
|
|
246
|
+
const catalog = await registryClient.catalog.get();
|
|
247
|
+
if (!mounted)
|
|
248
|
+
return;
|
|
249
|
+
// Convert catalog modules to Module type for UI
|
|
250
|
+
const modules = catalog.modules.map(catalogModuleToModule);
|
|
251
|
+
setApiModules(modules);
|
|
252
|
+
setIsLoading(false);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error("[Shell] Failed to fetch modules from API:", error);
|
|
256
|
+
if (mounted) {
|
|
257
|
+
setIsLoading(false);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
fetchModules();
|
|
262
|
+
return () => {
|
|
263
|
+
mounted = false;
|
|
264
|
+
};
|
|
265
|
+
}, [registryClient]);
|
|
266
|
+
// Convert manifests to modules (for standalone mode)
|
|
267
|
+
const manifestModules = useMemo(() => {
|
|
268
|
+
return manifests.map((m) => manifestToModule(m));
|
|
269
|
+
}, [manifests]);
|
|
270
|
+
// Use API modules if registry client is provided, otherwise use manifest modules
|
|
271
|
+
const modules = registryClient ? apiModules : manifestModules;
|
|
272
|
+
// Create catalog for command palette
|
|
273
|
+
const catalog = useMemo(() => {
|
|
274
|
+
return {
|
|
275
|
+
version: "1.0.0",
|
|
276
|
+
generatedAt: new Date().toISOString(),
|
|
277
|
+
modules: modules,
|
|
278
|
+
};
|
|
279
|
+
}, [modules]);
|
|
280
|
+
// Keyboard shortcut for command palette
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
const handleKeyDown = (e) => {
|
|
283
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
setCommandPaletteOpen((prev) => !prev);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
289
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
290
|
+
}, []);
|
|
291
|
+
const handleSearchClick = useCallback(() => {
|
|
292
|
+
setCommandPaletteOpen(true);
|
|
293
|
+
}, []);
|
|
294
|
+
// Show loading state while fetching from API
|
|
295
|
+
if (isLoading) {
|
|
296
|
+
return (_jsx("div", { className: "flex h-screen items-center justify-center", children: _jsx("div", { className: "text-muted-foreground", children: "Loading modules..." }) }));
|
|
297
|
+
}
|
|
298
|
+
return (_jsx(BrowserRouter, { children: _jsx(I18nextProvider, { i18n: i18n, children: _jsx(ThemeProvider, { children: _jsx(AuthProvider, { authClient: authClient, children: _jsx(ShellContent, { modules: modules, environment: environment, locale: locale, onLocaleChange: handleLocaleChange, onSearchClick: handleSearchClick, catalog: catalog, commandPaletteOpen: commandPaletteOpen, onCommandPaletteChange: setCommandPaletteOpen, apiUrl: apiUrl, registryClient: registryClient, isStandaloneMode: isStandaloneMode, children: children }) }) }) }) }));
|
|
299
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Catalog } from "../types";
|
|
2
|
+
export interface CommandPaletteProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
catalog: Catalog | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function CommandPalette({ open, onOpenChange, catalog, }: CommandPaletteProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=CommandPalette.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../../../src/shell/components/CommandPalette.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAgC,MAAM,UAAU,CAAC;AA8CtE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,YAAY,EACZ,OAAO,GACR,EAAE,mBAAmB,2CAwTrB"}
|