@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.
Files changed (159) hide show
  1. package/README.md +680 -0
  2. package/dist/auth/client/in-memory.d.ts +27 -0
  3. package/dist/auth/client/in-memory.d.ts.map +1 -0
  4. package/dist/auth/client/in-memory.js +242 -0
  5. package/dist/auth/client/index.d.ts +7 -0
  6. package/dist/auth/client/index.d.ts.map +1 -0
  7. package/dist/auth/client/index.js +7 -0
  8. package/dist/auth/client/interface.d.ts +115 -0
  9. package/dist/auth/client/interface.d.ts.map +1 -0
  10. package/dist/auth/client/interface.js +7 -0
  11. package/dist/auth/client/keycloak.d.ts +19 -0
  12. package/dist/auth/client/keycloak.d.ts.map +1 -0
  13. package/dist/auth/client/keycloak.js +126 -0
  14. package/dist/auth/components/UserSelector.d.ts +19 -0
  15. package/dist/auth/components/UserSelector.d.ts.map +1 -0
  16. package/dist/auth/components/UserSelector.js +100 -0
  17. package/dist/auth/components/index.d.ts +5 -0
  18. package/dist/auth/components/index.d.ts.map +1 -0
  19. package/dist/auth/components/index.js +4 -0
  20. package/dist/auth/index.d.ts +7 -0
  21. package/dist/auth/index.d.ts.map +1 -0
  22. package/dist/auth/index.js +7 -0
  23. package/dist/components/AuthProvider.d.ts +48 -0
  24. package/dist/components/AuthProvider.d.ts.map +1 -0
  25. package/dist/components/AuthProvider.js +117 -0
  26. package/dist/hooks/useAuth.d.ts +21 -0
  27. package/dist/hooks/useAuth.d.ts.map +1 -0
  28. package/dist/hooks/useAuth.js +34 -0
  29. package/dist/hooks/useFetch.d.ts +8 -0
  30. package/dist/hooks/useFetch.d.ts.map +1 -0
  31. package/dist/hooks/useFetch.js +31 -0
  32. package/dist/hooks/useI18n.d.ts +46 -0
  33. package/dist/hooks/useI18n.d.ts.map +1 -0
  34. package/dist/hooks/useI18n.js +95 -0
  35. package/dist/hooks/usePlatformAPI.d.ts +12 -0
  36. package/dist/hooks/usePlatformAPI.d.ts.map +1 -0
  37. package/dist/hooks/usePlatformAPI.js +10 -0
  38. package/dist/hooks/useTelemetry.d.ts +17 -0
  39. package/dist/hooks/useTelemetry.d.ts.map +1 -0
  40. package/dist/hooks/useTelemetry.js +36 -0
  41. package/dist/i18n/config.d.ts +26 -0
  42. package/dist/i18n/config.d.ts.map +1 -0
  43. package/dist/i18n/config.js +92 -0
  44. package/dist/i18n/index.d.ts +6 -0
  45. package/dist/i18n/index.d.ts.map +1 -0
  46. package/dist/i18n/index.js +4 -0
  47. package/dist/i18n/locales/en-US.json +144 -0
  48. package/dist/i18n/locales/es.json +144 -0
  49. package/dist/i18n/locales/pt-BR.json +144 -0
  50. package/dist/i18n/locales/ro.json +144 -0
  51. package/dist/index.d.ts +27 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +30 -0
  54. package/dist/registry/AdminShellRegistry.d.ts +140 -0
  55. package/dist/registry/AdminShellRegistry.d.ts.map +1 -0
  56. package/dist/registry/AdminShellRegistry.js +237 -0
  57. package/dist/registry/client/http.d.ts +21 -0
  58. package/dist/registry/client/http.d.ts.map +1 -0
  59. package/dist/registry/client/http.js +107 -0
  60. package/dist/registry/client/in-memory.d.ts +36 -0
  61. package/dist/registry/client/in-memory.d.ts.map +1 -0
  62. package/dist/registry/client/in-memory.js +242 -0
  63. package/dist/registry/client/index.d.ts +7 -0
  64. package/dist/registry/client/index.d.ts.map +1 -0
  65. package/dist/registry/client/index.js +5 -0
  66. package/dist/registry/client/interface.d.ts +96 -0
  67. package/dist/registry/client/interface.d.ts.map +1 -0
  68. package/dist/registry/client/interface.js +7 -0
  69. package/dist/registry/index.d.ts +12 -0
  70. package/dist/registry/index.d.ts.map +1 -0
  71. package/dist/registry/index.js +8 -0
  72. package/dist/registry/types/index.d.ts +9 -0
  73. package/dist/registry/types/index.d.ts.map +1 -0
  74. package/dist/registry/types/index.js +6 -0
  75. package/dist/registry/types/manifest.d.ts +98 -0
  76. package/dist/registry/types/manifest.d.ts.map +1 -0
  77. package/dist/registry/types/manifest.js +81 -0
  78. package/dist/registry/types/module.d.ts +115 -0
  79. package/dist/registry/types/module.d.ts.map +1 -0
  80. package/dist/registry/types/module.js +6 -0
  81. package/dist/router/DynamicModule.d.ts +50 -0
  82. package/dist/router/DynamicModule.d.ts.map +1 -0
  83. package/dist/router/DynamicModule.js +141 -0
  84. package/dist/router/index.d.ts +2 -0
  85. package/dist/router/index.d.ts.map +1 -0
  86. package/dist/router/index.js +1 -0
  87. package/dist/shell/AdminShell.d.ts +38 -0
  88. package/dist/shell/AdminShell.d.ts.map +1 -0
  89. package/dist/shell/AdminShell.js +299 -0
  90. package/dist/shell/BackofficeShell.d.ts +38 -0
  91. package/dist/shell/BackofficeShell.d.ts.map +1 -0
  92. package/dist/shell/BackofficeShell.js +299 -0
  93. package/dist/shell/components/CommandPalette.d.ts +8 -0
  94. package/dist/shell/components/CommandPalette.d.ts.map +1 -0
  95. package/dist/shell/components/CommandPalette.js +197 -0
  96. package/dist/shell/components/HomePage.d.ts +2 -0
  97. package/dist/shell/components/HomePage.d.ts.map +1 -0
  98. package/dist/shell/components/HomePage.js +32 -0
  99. package/dist/shell/components/LeftNav.d.ts +7 -0
  100. package/dist/shell/components/LeftNav.d.ts.map +1 -0
  101. package/dist/shell/components/LeftNav.js +247 -0
  102. package/dist/shell/components/MainContent.d.ts +9 -0
  103. package/dist/shell/components/MainContent.d.ts.map +1 -0
  104. package/dist/shell/components/MainContent.js +88 -0
  105. package/dist/shell/components/ModuleOverview.d.ts +7 -0
  106. package/dist/shell/components/ModuleOverview.d.ts.map +1 -0
  107. package/dist/shell/components/ModuleOverview.js +40 -0
  108. package/dist/shell/components/ProfilePage.d.ts +2 -0
  109. package/dist/shell/components/ProfilePage.d.ts.map +1 -0
  110. package/dist/shell/components/ProfilePage.js +30 -0
  111. package/dist/shell/components/RegistryPage.d.ts +8 -0
  112. package/dist/shell/components/RegistryPage.d.ts.map +1 -0
  113. package/dist/shell/components/RegistryPage.js +129 -0
  114. package/dist/shell/components/SettingsPage.d.ts +2 -0
  115. package/dist/shell/components/SettingsPage.d.ts.map +1 -0
  116. package/dist/shell/components/SettingsPage.js +60 -0
  117. package/dist/shell/components/TopBar.d.ts +8 -0
  118. package/dist/shell/components/TopBar.d.ts.map +1 -0
  119. package/dist/shell/components/TopBar.js +61 -0
  120. package/dist/shell/components/index.d.ts +10 -0
  121. package/dist/shell/components/index.d.ts.map +1 -0
  122. package/dist/shell/components/index.js +7 -0
  123. package/dist/shell/components/theme-provider.d.ts +15 -0
  124. package/dist/shell/components/theme-provider.d.ts.map +1 -0
  125. package/dist/shell/components/theme-provider.js +39 -0
  126. package/dist/shell/index.d.ts +9 -0
  127. package/dist/shell/index.d.ts.map +1 -0
  128. package/dist/shell/index.js +8 -0
  129. package/dist/shell/search/fuzzy.d.ts +18 -0
  130. package/dist/shell/search/fuzzy.d.ts.map +1 -0
  131. package/dist/shell/search/fuzzy.js +121 -0
  132. package/dist/shell/search/index.d.ts +3 -0
  133. package/dist/shell/search/index.d.ts.map +1 -0
  134. package/dist/shell/search/index.js +1 -0
  135. package/dist/shell/telemetry.d.ts +7 -0
  136. package/dist/shell/telemetry.d.ts.map +1 -0
  137. package/dist/shell/telemetry.js +25 -0
  138. package/dist/shell/types.d.ts +110 -0
  139. package/dist/shell/types.d.ts.map +1 -0
  140. package/dist/shell/types.js +4 -0
  141. package/dist/tailwind/index.d.ts +20 -0
  142. package/dist/tailwind/index.d.ts.map +1 -0
  143. package/dist/tailwind/index.js +42 -0
  144. package/dist/types/keycloak.d.ts +26 -0
  145. package/dist/types/keycloak.d.ts.map +1 -0
  146. package/dist/types/keycloak.js +1 -0
  147. package/dist/types/platform.d.ts +83 -0
  148. package/dist/types/platform.d.ts.map +1 -0
  149. package/dist/types/platform.js +5 -0
  150. package/dist/vite/config.d.ts +71 -0
  151. package/dist/vite/config.d.ts.map +1 -0
  152. package/dist/vite/config.js +87 -0
  153. package/dist/vite/index.d.ts +18 -0
  154. package/dist/vite/index.d.ts.map +1 -0
  155. package/dist/vite/index.js +17 -0
  156. package/dist/vite/plugins.d.ts +44 -0
  157. package/dist/vite/plugins.d.ts.map +1 -0
  158. package/dist/vite/plugins.js +74 -0
  159. 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"}