@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,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLocation, useNavigate } from "react-router-dom";
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Badge, Icon, } from "@nsxbet/admin-ui";
4
+ import { useI18n } from "../../hooks/useI18n";
5
+ export function ModuleOverview({ modules }) {
6
+ const location = useLocation();
7
+ const navigate = useNavigate();
8
+ const { t, i18n } = useI18n();
9
+ // Helper to translate titles
10
+ // titleKey format: "namespace:key" (e.g., "tasks:module.title")
11
+ const translateTitle = (title, titleKey) => {
12
+ if (titleKey && titleKey.includes(':')) {
13
+ // Parse "namespace:key" format
14
+ const [ns, key] = titleKey.split(':');
15
+ // Use i18n.t with explicit namespace option
16
+ const translated = i18n.t(key, { ns, defaultValue: title });
17
+ return translated;
18
+ }
19
+ if (titleKey) {
20
+ // Fallback: try using the titleKey directly
21
+ const translated = t(titleKey, { defaultValue: title });
22
+ return translated;
23
+ }
24
+ return title;
25
+ };
26
+ // Extract moduleId from path: /_modules/xxx -> xxx
27
+ const moduleId = location.pathname.replace(/^\/_modules\//, "");
28
+ const module = modules.find((m) => m.id === moduleId);
29
+ if (!module) {
30
+ return (_jsx("div", { className: "p-6 max-w-3xl mx-auto space-y-6", children: _jsx(Card, { className: "border-destructive", children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-center gap-3 text-destructive", children: [_jsx(Icon, { name: "alert-circle", className: "h-6 w-6" }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-bold", children: t('errors.moduleNotFound') }), _jsx("p", { className: "text-muted-foreground mt-1", children: t('errors.moduleNotFoundDescription', { moduleId }) })] })] }) }) }) }));
31
+ }
32
+ const statusVariant = module.status === "active"
33
+ ? "success"
34
+ : module.status === "deprecated"
35
+ ? "warning"
36
+ : "secondary";
37
+ const moduleTitle = translateTitle(module.title, module.titleKey);
38
+ const moduleDescription = translateTitle(module.description, module.descriptionKey);
39
+ return (_jsxs("div", { className: "p-6 max-w-3xl mx-auto space-y-6", children: [_jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex-shrink-0 p-3 rounded-lg bg-muted", children: _jsx(Icon, { name: module.icon || "package", className: "h-8 w-8" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-3 mb-1", children: [_jsx("h1", { className: "text-3xl font-bold", children: moduleTitle }), _jsx(Badge, { variant: statusVariant, className: "capitalize", children: module.status })] }), moduleDescription && (_jsx("p", { className: "text-muted-foreground", children: moduleDescription }))] })] }), module.commands && module.commands.length > 0 && (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "zap", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('moduleOverview.actions') })] }), _jsx(CardDescription, { children: t('moduleOverview.actionsDescription') })] }), _jsx(CardContent, { children: _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: module.commands.map((command) => (_jsxs("button", { onClick: () => navigate(command.route), className: "flex items-center gap-3 rounded-lg border border-border bg-card p-4 text-left transition-colors hover:bg-accent hover:border-accent", children: [_jsx("div", { className: "flex-shrink-0 p-2 rounded-md bg-muted", children: _jsx(Icon, { name: command.icon || "file-text", className: "h-5 w-5" }) }), _jsx("span", { className: "font-medium", children: translateTitle(command.title, command.titleKey) })] }, command.id))) }) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "info", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('moduleOverview.information') })] }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between py-2 border-b border-border", children: [_jsxs("dt", { className: "text-sm font-medium text-muted-foreground flex items-center gap-2", children: [_jsx(Icon, { name: "folder", className: "h-4 w-4" }), t('moduleOverview.category')] }), _jsx("dd", { className: "text-sm font-medium", children: module.category })] }), _jsxs("div", { className: "flex items-center justify-between py-2 border-b border-border", children: [_jsxs("dt", { className: "text-sm font-medium text-muted-foreground flex items-center gap-2", children: [_jsx(Icon, { name: "activity", className: "h-4 w-4" }), t('moduleOverview.status')] }), _jsx("dd", { children: _jsx(Badge, { variant: statusVariant, className: "capitalize", children: module.status }) })] }), module.owners?.team && (_jsxs("div", { className: "flex items-center justify-between py-2 border-b border-border", children: [_jsxs("dt", { className: "text-sm font-medium text-muted-foreground flex items-center gap-2", children: [_jsx(Icon, { name: "users", className: "h-4 w-4" }), t('moduleOverview.owner')] }), _jsx("dd", { className: "text-sm font-medium", children: module.owners.team })] })), module.owners?.supportChannel && (_jsxs("div", { className: "flex items-center justify-between py-2", children: [_jsxs("dt", { className: "text-sm font-medium text-muted-foreground flex items-center gap-2", children: [_jsx(Icon, { name: "message-circle", className: "h-4 w-4" }), t('moduleOverview.support')] }), _jsx("dd", { className: "text-sm font-medium", children: module.owners.supportChannel })] }))] }) })] })] }));
40
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ProfilePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProfilePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfilePage.d.ts","sourceRoot":"","sources":["../../../src/shell/components/ProfilePage.tsx"],"names":[],"mappings":"AA0BA,wBAAgB,WAAW,4CAgK1B"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { useNavigate } from "react-router-dom";
4
+ import { useAuth } from "../../hooks/useAuth";
5
+ import { useI18n } from "../../hooks/useI18n";
6
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Badge, Icon, Avatar, AvatarFallback, } from "@nsxbet/admin-ui";
7
+ export function ProfilePage() {
8
+ const { getUser } = useAuth();
9
+ const { t } = useI18n();
10
+ const user = getUser();
11
+ const navigate = useNavigate();
12
+ const [pinnedItems, setPinnedItems] = useState([]);
13
+ // Load pinned items from localStorage
14
+ useEffect(() => {
15
+ try {
16
+ const stored = localStorage.getItem("adminPlatform.pinnedCommands");
17
+ if (stored) {
18
+ const parsed = JSON.parse(stored);
19
+ setPinnedItems(Array.isArray(parsed) ? parsed : []);
20
+ }
21
+ }
22
+ catch {
23
+ // Ignore localStorage errors
24
+ }
25
+ }, []);
26
+ const handlePinnedClick = (route) => {
27
+ navigate(route);
28
+ };
29
+ return (_jsxs("div", { className: "p-6 max-w-3xl mx-auto space-y-6", "data-testid": "profile-page", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-1", "data-testid": "profile-heading", children: t('profilePage.title') }), _jsx("p", { className: "text-muted-foreground", children: t('profilePage.userInfo') })] }), _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "user", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('profilePage.userInfo') })] }) }), _jsxs(CardContent, { children: [_jsxs("div", { className: "flex items-center gap-4 mb-6", children: [_jsx(Avatar, { className: "h-16 w-16", children: _jsx(AvatarFallback, { className: "text-2xl font-bold bg-primary text-primary-foreground", children: user?.displayName?.charAt(0).toUpperCase() || "U" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-xl font-semibold", children: user?.displayName || "User" }), _jsx("p", { className: "text-muted-foreground", children: user?.email || "No email" })] })] }), _jsxs("dl", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between py-2 border-b border-border", children: [_jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: t('profilePage.name') }), _jsx("dd", { "data-testid": "profile-display-name", className: `text-sm font-medium ${!user?.displayName ? "text-muted-foreground" : ""}`, children: user?.displayName || "N/A" })] }), _jsxs("div", { className: "flex items-center justify-between py-2 border-b border-border", children: [_jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: t('profilePage.email') }), _jsx("dd", { "data-testid": "profile-email", className: `text-sm font-medium ${!user?.email ? "text-muted-foreground" : ""}`, children: user?.email || "N/A" })] }), _jsxs("div", { className: "flex items-center justify-between py-2", children: [_jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: t('profilePage.roles') }), _jsx("dd", { "data-testid": "profile-roles", children: _jsx(Badge, { variant: "secondary", children: "Admin" }) })] })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "pin", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('profilePage.pinnedPages') })] }), _jsx(CardDescription, { children: t('profilePage.pinnedPages') })] }), _jsx(CardContent, { children: pinnedItems.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-2", children: t('profilePage.noPinnedPages') })) : (_jsx("ul", { "data-testid": "pinned-pages-list", className: "space-y-1", children: pinnedItems.map((item) => (_jsx("li", { children: _jsxs("button", { "data-testid": `pinned-page-${item.commandId}`, onClick: () => handlePinnedClick(item.route), className: "w-full flex items-center gap-3 text-left px-3 py-2.5 rounded-md hover:bg-accent transition-colors", children: [_jsx(Icon, { name: item.commandIcon || item.moduleIcon || "file-text", className: "h-4 w-4 text-muted-foreground" }), _jsxs("span", { className: "text-sm", children: [item.moduleTitle, " ", _jsx("span", { className: "text-muted-foreground", children: "/" }), " ", item.commandTitle] })] }) }, `${item.moduleId}-${item.commandId}`))) })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "shield", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('profilePage.auditInfo') })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between py-2", children: [_jsx("span", { className: "text-sm font-medium text-muted-foreground", children: t('profilePage.userId') }), _jsx("span", { "data-testid": "profile-user-id", className: `text-sm font-mono px-2 py-1 rounded ${user?.id ? "bg-muted" : "text-muted-foreground"}`, children: user?.id || "N/A" })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('profilePage.auditInfo') })] })] })] }));
30
+ }
@@ -0,0 +1,8 @@
1
+ import type { RegistryClient } from "../../registry";
2
+ interface RegistryPageProps {
3
+ apiUrl?: string;
4
+ registryClient?: RegistryClient;
5
+ }
6
+ export declare function RegistryPage({ apiUrl, registryClient }: RegistryPageProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=RegistryPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistryPage.d.ts","sourceRoot":"","sources":["../../../src/shell/components/RegistryPage.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEvE,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,iBAAiB,2CAiZzE"}
@@ -0,0 +1,129 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback } from "react";
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Switch, Label, Button, Icon, Badge, Input, Textarea, Tabs, TabsList, TabsTrigger, TabsContent, } from "@nsxbet/admin-ui";
4
+ export function RegistryPage({ apiUrl, registryClient }) {
5
+ const [modules, setModules] = useState([]);
6
+ const [isLoading, setIsLoading] = useState(true);
7
+ const [error, setError] = useState(null);
8
+ const [newModuleUrl, setNewModuleUrl] = useState("");
9
+ const [manifestJson, setManifestJson] = useState("");
10
+ const [isAdding, setIsAdding] = useState(false);
11
+ // Fetch modules using registry client
12
+ const fetchModules = useCallback(async () => {
13
+ if (!registryClient) {
14
+ setError("Registry client not available.");
15
+ setIsLoading(false);
16
+ return;
17
+ }
18
+ try {
19
+ setIsLoading(true);
20
+ setError(null);
21
+ const data = await registryClient.modules.findAll();
22
+ setModules(data);
23
+ }
24
+ catch (err) {
25
+ setError(err instanceof Error ? err.message : "Failed to load modules");
26
+ }
27
+ finally {
28
+ setIsLoading(false);
29
+ }
30
+ }, [registryClient]);
31
+ useEffect(() => {
32
+ fetchModules();
33
+ }, [fetchModules]);
34
+ // Add module by URL
35
+ const handleAddModuleByUrl = async () => {
36
+ if (!newModuleUrl.trim() || !registryClient) {
37
+ return;
38
+ }
39
+ try {
40
+ setIsAdding(true);
41
+ setError(null);
42
+ await registryClient.modules.register(newModuleUrl.trim());
43
+ setNewModuleUrl("");
44
+ await fetchModules();
45
+ }
46
+ catch (err) {
47
+ setError(err instanceof Error ? err.message : "Failed to add module");
48
+ }
49
+ finally {
50
+ setIsAdding(false);
51
+ }
52
+ };
53
+ // Add module by manifest JSON
54
+ const handleAddModuleByManifest = async () => {
55
+ if (!manifestJson.trim() || !registryClient)
56
+ return;
57
+ try {
58
+ setIsAdding(true);
59
+ setError(null);
60
+ // Parse the manifest JSON
61
+ let manifest;
62
+ try {
63
+ manifest = JSON.parse(manifestJson.trim());
64
+ }
65
+ catch {
66
+ throw new Error("Invalid JSON format. Please check your manifest.");
67
+ }
68
+ await registryClient.modules.registerFromManifest(manifest, "");
69
+ setManifestJson("");
70
+ await fetchModules();
71
+ }
72
+ catch (err) {
73
+ setError(err instanceof Error ? err.message : "Failed to add module");
74
+ }
75
+ finally {
76
+ setIsAdding(false);
77
+ }
78
+ };
79
+ // Toggle module enabled state
80
+ const handleToggleEnabled = async (module) => {
81
+ if (!registryClient)
82
+ return;
83
+ try {
84
+ setError(null);
85
+ await registryClient.modules.update(module.id, {
86
+ enabled: !module.enabled,
87
+ });
88
+ await fetchModules();
89
+ }
90
+ catch (err) {
91
+ setError(err instanceof Error ? err.message : "Failed to update module");
92
+ }
93
+ };
94
+ // Delete module
95
+ const handleDeleteModule = async (module) => {
96
+ if (!registryClient)
97
+ return;
98
+ const confirmed = window.confirm(`Are you sure you want to delete "${module.manifest.title}"? This action cannot be undone.`);
99
+ if (!confirmed)
100
+ return;
101
+ try {
102
+ setError(null);
103
+ await registryClient.modules.delete(module.id);
104
+ await fetchModules();
105
+ }
106
+ catch (err) {
107
+ setError(err instanceof Error ? err.message : "Failed to delete module");
108
+ }
109
+ };
110
+ if (!registryClient) {
111
+ return (_jsxs("div", { className: "p-6 max-w-4xl mx-auto space-y-6", "data-testid": "registry-page", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-1", "data-testid": "registry-heading", children: "Module Registry" }), _jsx("p", { className: "text-muted-foreground", children: "Manage registered modules" })] }), _jsx(Card, { className: "border-destructive", children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-center gap-3 text-destructive", children: [_jsx(Icon, { name: "alert-circle", className: "h-5 w-5" }), _jsx("p", { children: "Registry client not available. Please check configuration." })] }) }) })] }));
112
+ }
113
+ return (_jsxs("div", { className: "p-6 max-w-4xl mx-auto space-y-6", "data-testid": "registry-page", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-1", "data-testid": "registry-heading", children: "Module Registry" }), _jsx("p", { className: "text-muted-foreground", children: "Manage registered modules" })] }), _jsx(Badge, { variant: apiUrl ? "default" : "secondary", children: apiUrl ? "API Mode" : "In-Memory Mode" })] }), error && (_jsx(Card, { className: "border-destructive bg-destructive/10", children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-center gap-3 text-destructive", children: [_jsx(Icon, { name: "alert-circle", className: "h-5 w-5" }), _jsx("p", { children: error })] }) }) })), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "plus", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: "Add Module" })] }), _jsx(CardDescription, { children: "Register a new module by URL or manifest" })] }), _jsx(CardContent, { children: _jsxs(Tabs, { defaultValue: "url", className: "w-full", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2 mb-4", children: [_jsxs(TabsTrigger, { value: "url", children: [_jsx(Icon, { name: "link", className: "h-4 w-4 mr-2" }), "From URL"] }), _jsxs(TabsTrigger, { value: "manifest", children: [_jsx(Icon, { name: "file-text", className: "h-4 w-4 mr-2" }), "From Manifest"] })] }), _jsx(TabsContent, { value: "url", children: _jsxs("form", { onSubmit: (e) => {
114
+ e.preventDefault();
115
+ handleAddModuleByUrl();
116
+ }, className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "module-url", children: "Module Base URL" }), _jsx(Input, { id: "module-url", type: "text", placeholder: "http://localhost:3003/dist", value: newModuleUrl, onChange: (e) => setNewModuleUrl(e.target.value), disabled: isAdding }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Enter the base URL where the module is served. The system will fetch admin.module.json from this URL." })] }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { type: "submit", disabled: !newModuleUrl.trim() || isAdding, children: isAdding ? (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "loader-2", className: "h-4 w-4 mr-2 animate-spin" }), "Adding..."] })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "plus", className: "h-4 w-4 mr-2" }), "Add Module"] })) }) })] }) }), _jsx(TabsContent, { value: "manifest", children: _jsxs("form", { onSubmit: (e) => {
117
+ e.preventDefault();
118
+ handleAddModuleByManifest();
119
+ }, className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "module-manifest", children: "Module Manifest (JSON)" }), _jsx(Textarea, { id: "module-manifest", placeholder: `{
120
+ "id": "@admin/my-module",
121
+ "title": "My Module",
122
+ "description": "Module description",
123
+ "category": "Tools",
124
+ "icon": "package",
125
+ "routeBase": "/admin/my-module",
126
+ "keywords": ["keyword1", "keyword2"],
127
+ "status": "active"
128
+ }`, value: manifestJson, onChange: (e) => setManifestJson(e.target.value), disabled: isAdding, rows: 10, className: "font-mono text-sm" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Paste the module manifest JSON. Required fields: id, title, routeBase." })] }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { type: "submit", disabled: !manifestJson.trim() || isAdding, children: isAdding ? (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "loader-2", className: "h-4 w-4 mr-2 animate-spin" }), "Adding..."] })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "plus", className: "h-4 w-4 mr-2" }), "Add Module"] })) }) })] }) })] }) })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "package", className: "h-5 w-5 text-muted-foreground" }), _jsxs("h2", { className: "text-lg font-semibold", children: ["Registered Modules", " ", !isLoading && (_jsxs("span", { className: "text-muted-foreground font-normal", children: ["(", modules.length, ")"] }))] })] }), isLoading ? (_jsx(Card, { children: _jsxs(CardContent, { className: "py-8 text-center", children: [_jsx(Icon, { name: "loader-2", className: "h-8 w-8 mx-auto mb-3 animate-spin text-muted-foreground" }), _jsx("p", { className: "text-muted-foreground", children: "Loading modules..." })] }) })) : modules.length === 0 ? (_jsx(Card, { children: _jsxs(CardContent, { className: "py-8 text-center", children: [_jsx(Icon, { name: "package", className: "h-8 w-8 mx-auto mb-3 text-muted-foreground" }), _jsx("p", { className: "text-muted-foreground", children: "No modules registered yet." }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Add a module using the form above." })] }) })) : (_jsx("div", { className: "space-y-3", children: modules.map((module) => (_jsx(Card, { className: !module.enabled ? "opacity-60 border-dashed" : undefined, children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-start gap-4 flex-1 min-w-0", children: [_jsx("div", { className: "flex-shrink-0 p-2 rounded-lg bg-muted", children: _jsx(Icon, { name: module.manifest.icon || "package", className: "h-6 w-6" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx("h3", { className: "font-semibold truncate", children: module.manifest.title }), _jsx(Badge, { variant: module.enabled ? "success" : "secondary", children: module.enabled ? "Enabled" : "Disabled" })] }), _jsx("p", { className: "text-sm text-muted-foreground mb-3", children: module.manifest.description || "No description" }), _jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(Icon, { name: "hash", className: "h-3 w-3" }), module.moduleId] }), _jsxs("span", { className: "flex items-center gap-1", children: [_jsx(Icon, { name: "folder", className: "h-3 w-3" }), module.manifest.category || "General"] }), _jsxs("span", { className: "flex items-center gap-1", children: [_jsx(Icon, { name: "navigation", className: "h-3 w-3" }), module.manifest.routeBase] }), module.baseUrl && (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(Icon, { name: "link", className: "h-3 w-3" }), module.baseUrl] }))] })] })] }), _jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-sm text-muted-foreground", children: "Enabled" }), _jsx(Switch, { checked: module.enabled, onCheckedChange: () => handleToggleEnabled(module) })] }), _jsx(Button, { variant: "destructive", size: "sm", onClick: () => handleDeleteModule(module), children: _jsx(Icon, { name: "trash-2", className: "h-4 w-4" }) })] })] }) }) }, module.id))) }))] })] }));
129
+ }
@@ -0,0 +1,2 @@
1
+ export declare function SettingsPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=SettingsPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsPage.d.ts","sourceRoot":"","sources":["../../../src/shell/components/SettingsPage.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,YAAY,4CAsO3B"}
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { useTheme } from "./theme-provider";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Switch, Label, Button, Icon, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@nsxbet/admin-ui";
5
+ import { useI18n } from "../../hooks/useI18n";
6
+ export function SettingsPage() {
7
+ const { theme, setTheme } = useTheme();
8
+ const { t, locale } = useI18n();
9
+ const [showPinned, setShowPinned] = useState(true);
10
+ // Load preferences from localStorage
11
+ useEffect(() => {
12
+ try {
13
+ const pinnedPref = localStorage.getItem("adminPlatform.showPinned");
14
+ if (pinnedPref !== null) {
15
+ setShowPinned(JSON.parse(pinnedPref));
16
+ }
17
+ }
18
+ catch {
19
+ // Ignore localStorage errors
20
+ }
21
+ }, []);
22
+ const handleShowPinnedChange = (checked) => {
23
+ setShowPinned(checked);
24
+ try {
25
+ localStorage.setItem("adminPlatform.showPinned", JSON.stringify(checked));
26
+ // Dispatch storage event for other components to react
27
+ window.dispatchEvent(new StorageEvent("storage", {
28
+ key: "adminPlatform.showPinned",
29
+ newValue: JSON.stringify(checked),
30
+ }));
31
+ }
32
+ catch {
33
+ // Ignore localStorage errors
34
+ }
35
+ };
36
+ const handleClearPinned = () => {
37
+ try {
38
+ localStorage.removeItem("adminPlatform.pinnedCommands");
39
+ // Dispatch storage event for other components to react
40
+ window.dispatchEvent(new StorageEvent("storage", {
41
+ key: "adminPlatform.pinnedCommands",
42
+ newValue: null,
43
+ }));
44
+ }
45
+ catch {
46
+ // Ignore localStorage errors
47
+ }
48
+ };
49
+ const getLanguageName = (loc) => {
50
+ switch (loc) {
51
+ case 'pt-BR': return 'Português (Brasil)';
52
+ case 'en-US': return 'English (US)';
53
+ case 'es': return 'Español';
54
+ case 'ro': return 'Română';
55
+ default: return loc;
56
+ }
57
+ };
58
+ return (_jsxs("div", { className: "p-6 max-w-3xl mx-auto space-y-6", "data-testid": "settings-page", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-1", "data-testid": "settings-heading", children: t('settingsPage.title') }), _jsx("p", { className: "text-muted-foreground", children: t('settingsPage.appearanceDescription') })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "palette", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('settingsPage.appearance') })] }), _jsx(CardDescription, { children: t('settingsPage.appearanceDescription') })] }), _jsx(CardContent, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { className: "text-sm font-medium", children: t('settingsPage.theme') }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('settingsPage.appearanceDescription') })] }), _jsxs(Select, { "data-testid": "theme-select", value: theme, onValueChange: (value) => setTheme(value), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: t('settingsPage.theme') }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "light", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "sun", className: "h-4 w-4" }), t('topBar.themeLight')] }) }), _jsx(SelectItem, { value: "dark", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "moon", className: "h-4 w-4" }), t('topBar.themeDark')] }) }), _jsx(SelectItem, { value: "system", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "settings", className: "h-4 w-4" }), t('topBar.themeSystem')] }) })] })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "panel-left", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('settingsPage.navigation') })] }), _jsx(CardDescription, { children: t('settingsPage.navigationDescription') })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { className: "text-sm font-medium", children: t('settingsPage.showPinnedItems') }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('settingsPage.showPinnedItemsDescription') })] }), _jsx(Switch, { "data-testid": "show-pinned-toggle", checked: showPinned, onCheckedChange: handleShowPinnedChange })] }), _jsx("p", { className: "text-xs text-muted-foreground italic pt-2 border-t border-border", children: t('settingsPage.pinnedItemsNote') })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "database", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('settingsPage.dataManagement') })] }), _jsx(CardDescription, { children: t('settingsPage.dataManagementDescription') })] }), _jsx(CardContent, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { className: "text-sm font-medium", children: t('settingsPage.clearPinnedItems') }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('settingsPage.clearPinnedItemsConfirm') })] }), _jsxs(Button, { variant: "destructive", size: "sm", onClick: handleClearPinned, children: [_jsx(Icon, { name: "trash-2", className: "h-4 w-4 mr-2" }), t('common.delete')] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "globe", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('topBar.language') })] }), _jsx(CardDescription, { children: t('topBar.language') })] }), _jsx(CardContent, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { className: "text-sm font-medium", children: t('topBar.language') }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('topBar.language') })] }), _jsx("span", { className: "text-sm text-muted-foreground", children: getLanguageName(locale) })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "info", className: "h-5 w-5 text-muted-foreground" }), _jsx(CardTitle, { className: "text-lg", children: t('settingsPage.about') })] }), _jsx(CardDescription, { children: t('settingsPage.about') })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between py-2 border-b border-border", children: [_jsx("span", { className: "text-sm font-medium text-muted-foreground", children: t('settingsPage.version') }), _jsx("span", { className: "text-sm font-mono bg-muted px-2 py-1 rounded", children: "0.1.0" })] }), _jsxs("div", { className: "flex items-center justify-between py-2", children: [_jsx("span", { className: "text-sm font-medium text-muted-foreground", children: t('settingsPage.environment') }), _jsx("span", { className: "text-sm font-mono bg-muted px-2 py-1 rounded", children: window.__ENV__
59
+ ?.ENVIRONMENT || "local" })] })] })] })] }));
60
+ }
@@ -0,0 +1,8 @@
1
+ export interface TopBarProps {
2
+ onSearchClick?: () => void;
3
+ environment?: string;
4
+ locale?: string;
5
+ onLocaleChange?: (locale: string) => void;
6
+ }
7
+ export declare function TopBar({ onSearchClick, environment, locale: externalLocale, onLocaleChange, }: TopBarProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=TopBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TopBar.d.ts","sourceRoot":"","sources":["../../../src/shell/components/TopBar.tsx"],"names":[],"mappings":"AAuCA,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,wBAAgB,MAAM,CAAC,EACrB,aAAa,EACb,WAAqB,EACrB,MAAM,EAAE,cAAc,EACtB,cAAc,GACf,EAAE,WAAW,2CAuGb"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Search, Moon, Sun, Check } from "lucide-react";
4
+ import { useTheme } from "./theme-provider";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Badge, Button, } from "@nsxbet/admin-ui";
6
+ import { SUPPORTED_LOCALES, LOCALE_FLAGS, LOCALE_NAMES } from "../../i18n";
7
+ import { useI18n } from "../../hooks/useI18n";
8
+ const ENV_COLORS = {
9
+ local: "bg-gray-500",
10
+ dev: "bg-blue-500",
11
+ e2e: "bg-purple-500",
12
+ staging: "bg-yellow-500",
13
+ production: "bg-red-500",
14
+ };
15
+ // Environment-specific header background colors
16
+ const ENV_HEADER_COLORS_LIGHT = {
17
+ local: "bg-blue-50 border-blue-200",
18
+ dev: "bg-green-50 border-green-200",
19
+ e2e: "bg-purple-50 border-purple-200",
20
+ staging: "bg-yellow-50 border-yellow-200",
21
+ production: "bg-red-100 border-red-300",
22
+ };
23
+ const ENV_HEADER_COLORS_DARK = {
24
+ local: "bg-blue-950 border-blue-800",
25
+ dev: "bg-green-950 border-green-800",
26
+ e2e: "bg-purple-950 border-purple-800",
27
+ staging: "bg-yellow-950 border-yellow-800",
28
+ production: "bg-red-950 border-red-800",
29
+ };
30
+ export function TopBar({ onSearchClick, environment = "local", locale: externalLocale, onLocaleChange, }) {
31
+ const { theme, setTheme } = useTheme();
32
+ const { t } = useI18n();
33
+ const [locale, setLocale] = useState(externalLocale || "pt-BR");
34
+ useEffect(() => {
35
+ if (externalLocale && SUPPORTED_LOCALES.includes(externalLocale)) {
36
+ setLocale(externalLocale);
37
+ }
38
+ }, [externalLocale]);
39
+ const handleLocaleChange = (newLocale) => {
40
+ setLocale(newLocale);
41
+ onLocaleChange?.(newLocale);
42
+ };
43
+ const toggleTheme = () => {
44
+ const root = window.document.documentElement;
45
+ const isDark = root.classList.contains("dark");
46
+ setTheme(isDark ? "light" : "dark");
47
+ };
48
+ // Determine effective theme (resolve "system" to actual theme)
49
+ const effectiveTheme = theme === "system"
50
+ ? window.matchMedia("(prefers-color-scheme: dark)").matches
51
+ ? "dark"
52
+ : "light"
53
+ : theme;
54
+ // Select color classes based on effective theme
55
+ const headerColorClasses = effectiveTheme === "dark"
56
+ ? ENV_HEADER_COLORS_DARK[environment] || "bg-background border-b"
57
+ : ENV_HEADER_COLORS_LIGHT[environment] || "bg-background border-b";
58
+ // Get flag for current locale
59
+ const currentFlag = LOCALE_FLAGS[locale] || "🌐";
60
+ return (_jsxs("header", { className: `flex h-14 items-center justify-between px-4 ${headerColorClasses}`, role: "banner", "data-environment": environment, children: [_jsx(Badge, { className: `${ENV_COLORS[environment]} uppercase text-white`, children: environment }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs(Button, { variant: "outline", onClick: onSearchClick, "data-testid": "search-button", className: "relative h-8 w-40 justify-start rounded-md bg-muted/50 px-3 text-sm font-normal text-muted-foreground shadow-none sm:w-48", children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), _jsx("span", { children: t('topBar.search') }), _jsxs("kbd", { className: "pointer-events-none absolute right-1.5 top-1 hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex", children: [_jsx("span", { className: "text-xs", children: "\u2318" }), "K"] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "flex items-center gap-1 text-xl transition-opacity hover:opacity-80 focus:outline-none", "data-testid": "language-selector", title: t('topBar.language'), children: _jsx("span", { children: currentFlag }) }), _jsx(DropdownMenuContent, { align: "end", className: "min-w-[180px]", children: SUPPORTED_LOCALES.map((localeOption) => (_jsxs(DropdownMenuItem, { onClick: () => handleLocaleChange(localeOption), className: "flex items-center justify-between", "data-testid": `language-option-${localeOption}`, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("span", { className: "mr-2 text-lg", children: LOCALE_FLAGS[localeOption] }), _jsx("span", { children: LOCALE_NAMES[localeOption] })] }), locale === localeOption && (_jsx(Check, { className: "h-4 w-4 text-primary" }))] }, localeOption))) })] }), _jsxs(Button, { variant: "ghost", size: "icon", onClick: toggleTheme, title: t('topBar.theme'), children: [_jsx(Sun, { className: "h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" }), _jsx(Moon, { className: "absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" }), _jsx("span", { className: "sr-only", children: t('topBar.theme') })] })] })] }));
61
+ }
@@ -0,0 +1,10 @@
1
+ export { TopBar } from "./TopBar";
2
+ export type { TopBarProps } from "./TopBar";
3
+ export { LeftNav } from "./LeftNav";
4
+ export { MainContent } from "./MainContent";
5
+ export { CommandPalette } from "./CommandPalette";
6
+ export type { CommandPaletteProps } from "./CommandPalette";
7
+ export { ThemeProvider, useTheme } from "./theme-provider";
8
+ export { RegistryPage } from "./RegistryPage";
9
+ export { HomePage } from "./HomePage";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shell/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { TopBar } from "./TopBar";
2
+ export { LeftNav } from "./LeftNav";
3
+ export { MainContent } from "./MainContent";
4
+ export { CommandPalette } from "./CommandPalette";
5
+ export { ThemeProvider, useTheme } from "./theme-provider";
6
+ export { RegistryPage } from "./RegistryPage";
7
+ export { HomePage } from "./HomePage";
@@ -0,0 +1,15 @@
1
+ type Theme = "dark" | "light" | "system";
2
+ type ThemeProviderProps = {
3
+ children: React.ReactNode;
4
+ defaultTheme?: Theme;
5
+ storageKey?: string;
6
+ };
7
+ type ThemeProviderState = {
8
+ theme: Theme;
9
+ setTheme: (theme: Theme) => void;
10
+ };
11
+ export declare function ThemeProvider({ children, defaultTheme, // Brasa Design System is dark-first
12
+ storageKey, ...props }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare const useTheme: () => ThemeProviderState;
14
+ export {};
15
+ //# sourceMappingURL=theme-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../../src/shell/components/theme-provider.tsx"],"names":[],"mappings":"AAEA,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AASF,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAqB,EAAE,oCAAoC;AAC3D,UAA6B,EAC7B,GAAG,KAAK,EACT,EAAE,kBAAkB,2CAoCpB;AAGD,eAAO,MAAM,QAAQ,0BAOpB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ const initialState = {
4
+ theme: "system",
5
+ setTheme: () => null,
6
+ };
7
+ const ThemeProviderContext = createContext(initialState);
8
+ export function ThemeProvider({ children, defaultTheme = "dark", // Brasa Design System is dark-first
9
+ storageKey = "admin-ui-theme", ...props }) {
10
+ const [theme, setTheme] = useState(() => localStorage.getItem(storageKey) || defaultTheme);
11
+ useEffect(() => {
12
+ const root = window.document.documentElement;
13
+ root.classList.remove("light", "dark");
14
+ if (theme === "system") {
15
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
16
+ .matches
17
+ ? "dark"
18
+ : "light";
19
+ root.classList.add(systemTheme);
20
+ return;
21
+ }
22
+ root.classList.add(theme);
23
+ }, [theme]);
24
+ const value = {
25
+ theme,
26
+ setTheme: (theme) => {
27
+ localStorage.setItem(storageKey, theme);
28
+ setTheme(theme);
29
+ },
30
+ };
31
+ return (_jsx(ThemeProviderContext.Provider, { ...props, value: value, children: children }));
32
+ }
33
+ // eslint-disable-next-line react-refresh/only-export-components
34
+ export const useTheme = () => {
35
+ const context = useContext(ThemeProviderContext);
36
+ if (context === undefined)
37
+ throw new Error("useTheme must be used within a ThemeProvider");
38
+ return context;
39
+ };
@@ -0,0 +1,9 @@
1
+ export { AdminShell } from "./AdminShell";
2
+ export type { AdminShellProps } from "./AdminShell";
3
+ export { TopBar, LeftNav, MainContent, CommandPalette, ThemeProvider, useTheme, } from "./components";
4
+ export type { TopBarProps, CommandPaletteProps } from "./components";
5
+ export type { Module, ModuleCommand, ModuleStatus, Catalog, ImportMap, SearchableItem, SearchResult, } from "./types";
6
+ export { initTelemetry, track, trackError } from "./telemetry";
7
+ export { fuzzySearch } from "./search/fuzzy";
8
+ export type { SearchOptions } from "./search/fuzzy";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,OAAO,EACL,MAAM,EACN,OAAO,EACP,WAAW,EACX,cAAc,EACd,aAAa,EACb,QAAQ,GACT,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGrE,YAAY,EACV,MAAM,EACN,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACT,cAAc,EACd,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG/D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ // Main shell component
2
+ export { AdminShell } from "./AdminShell";
3
+ // Shell sub-components (for customization)
4
+ export { TopBar, LeftNav, MainContent, CommandPalette, ThemeProvider, useTheme, } from "./components";
5
+ // Telemetry
6
+ export { initTelemetry, track, trackError } from "./telemetry";
7
+ // Search
8
+ export { fuzzySearch } from "./search/fuzzy";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Fuzzy search implementation for command palette
3
+ * Supports weighted scoring across multiple fields
4
+ */
5
+ import type { SearchableItem, SearchResult } from "../types";
6
+ export interface SearchOptions {
7
+ maxResults?: number;
8
+ }
9
+ /**
10
+ * Perform fuzzy search across searchable items
11
+ *
12
+ * @param items - Array of items to search
13
+ * @param query - Search query string
14
+ * @param options - Search options (maxResults)
15
+ * @returns Sorted array of search results with scores
16
+ */
17
+ export declare function fuzzySearch(items: SearchableItem[], query: string, options?: SearchOptions): SearchResult[];
18
+ //# sourceMappingURL=fuzzy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fuzzy.d.ts","sourceRoot":"","sources":["../../../src/shell/search/fuzzy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAmHD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EAAE,EACvB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,YAAY,EAAE,CAyBhB"}