@nsxbet/admin-sdk 0.5.0 → 0.6.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/CHECKLIST.md +40 -10
- package/README.md +337 -36
- package/dist/auth/client/gateway-token.d.ts +19 -0
- package/dist/auth/client/gateway-token.js +89 -0
- package/dist/auth/client/in-memory.d.ts +5 -1
- package/dist/auth/client/in-memory.js +75 -38
- package/dist/auth/client/index.d.ts +0 -1
- package/dist/auth/client/interface.d.ts +6 -3
- package/dist/auth/client/keycloak.d.ts +0 -1
- package/dist/auth/client/keycloak.js +6 -3
- package/dist/auth/components/UserSelector.d.ts +0 -1
- package/dist/auth/components/UserSelector.js +89 -7
- package/dist/auth/components/index.d.ts +0 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/components/AuthProvider.d.ts +0 -1
- package/dist/components/Timestamp.d.ts +7 -0
- package/dist/components/Timestamp.js +50 -0
- package/dist/hooks/useAuth.d.ts +0 -1
- package/dist/hooks/useAuth.js +1 -1
- package/dist/hooks/useFetch.d.ts +0 -1
- package/dist/hooks/useI18n.d.ts +0 -1
- package/dist/hooks/usePlatformAPI.d.ts +0 -1
- package/dist/hooks/useTelemetry.d.ts +0 -1
- package/dist/hooks/useTimestamp.d.ts +8 -0
- package/dist/hooks/useTimestamp.js +122 -0
- package/dist/i18n/config.d.ts +20 -2
- package/dist/i18n/config.js +48 -0
- package/dist/i18n/index.d.ts +2 -3
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/locales/en-US.json +95 -18
- package/dist/i18n/locales/es.json +95 -18
- package/dist/i18n/locales/pt-BR.json +95 -18
- package/dist/i18n/locales/ro.json +95 -18
- package/dist/index.d.ts +11 -7
- package/dist/index.js +5 -1
- package/dist/registry/AdminShellRegistry.d.ts +1 -2
- package/dist/registry/cache/cached-catalog.d.ts +11 -0
- package/dist/registry/cache/cached-catalog.js +42 -0
- package/dist/registry/cache/catalog-cache.d.ts +10 -0
- package/dist/registry/cache/catalog-cache.js +58 -0
- package/dist/registry/cache/index.d.ts +5 -0
- package/dist/registry/cache/index.js +3 -0
- package/dist/registry/cache/types.d.ts +20 -0
- package/dist/registry/cache/types.js +3 -0
- package/dist/registry/client/http.d.ts +0 -1
- package/dist/registry/client/http.js +13 -0
- package/dist/registry/client/in-memory.d.ts +0 -1
- package/dist/registry/client/in-memory.js +117 -12
- package/dist/registry/client/index.d.ts +0 -1
- package/dist/registry/client/interface.d.ts +21 -6
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.js +4 -0
- package/dist/registry/types/index.d.ts +2 -3
- package/dist/registry/types/manifest.d.ts +20 -24
- package/dist/registry/types/manifest.js +17 -18
- package/dist/registry/types/module.d.ts +43 -14
- package/dist/registry/useRegistryPolling.d.ts +15 -0
- package/dist/registry/useRegistryPolling.js +66 -0
- package/dist/router/DynamicModule.d.ts +6 -22
- package/dist/router/DynamicModule.js +25 -48
- package/dist/router/ModuleErrorBoundary.d.ts +39 -0
- package/dist/router/ModuleErrorBoundary.js +101 -0
- package/dist/router/index.d.ts +1 -1
- package/dist/router/url-allowlist.d.ts +22 -0
- package/dist/router/url-allowlist.js +65 -0
- package/dist/shell/AdminShell.d.ts +0 -1
- package/dist/shell/AdminShell.js +178 -43
- package/dist/shell/BackofficeShell.d.ts +0 -1
- package/dist/shell/BackofficeShell.js +59 -25
- package/dist/shell/components/CommandPalette.d.ts +0 -1
- package/dist/shell/components/CommandPalette.js +26 -50
- package/dist/shell/components/DevtoolsPanel.d.ts +11 -0
- package/dist/shell/components/DevtoolsPanel.js +145 -0
- package/dist/shell/components/HomePage.d.ts +0 -1
- package/dist/shell/components/HomePage.js +9 -4
- package/dist/shell/components/LeftNav.d.ts +0 -1
- package/dist/shell/components/LeftNav.js +91 -93
- package/dist/shell/components/MainContent.d.ts +3 -2
- package/dist/shell/components/MainContent.js +8 -23
- package/dist/shell/components/ModuleOverview.d.ts +0 -1
- package/dist/shell/components/ModuleOverview.js +4 -20
- package/dist/shell/components/ProfilePage.d.ts +0 -1
- package/dist/shell/components/ProfilePage.js +1 -1
- package/dist/shell/components/RegistryPage.d.ts +0 -1
- package/dist/shell/components/RegistryPage.js +154 -64
- package/dist/shell/components/RegistryStatusBanner.d.ts +6 -0
- package/dist/shell/components/RegistryStatusBanner.js +31 -0
- package/dist/shell/components/RegistryUnavailable.d.ts +4 -0
- package/dist/shell/components/RegistryUnavailable.js +7 -0
- package/dist/shell/components/SettingsPage.d.ts +0 -1
- package/dist/shell/components/StackedPanel.d.ts +15 -0
- package/dist/shell/components/StackedPanel.js +45 -0
- package/dist/shell/components/TopBar.d.ts +4 -2
- package/dist/shell/components/TopBar.js +9 -3
- package/dist/shell/components/UpdateBanner.d.ts +5 -0
- package/dist/shell/components/UpdateBanner.js +8 -0
- package/dist/shell/components/index.d.ts +4 -1
- package/dist/shell/components/index.js +2 -0
- package/dist/shell/components/theme-provider.d.ts +0 -1
- package/dist/shell/components/theme-provider.js +8 -5
- package/dist/shell/hooks/useCspViolations.d.ts +12 -0
- package/dist/shell/hooks/useCspViolations.js +34 -0
- package/dist/shell/index.d.ts +1 -2
- package/dist/shell/polling-config.d.ts +10 -0
- package/dist/shell/polling-config.js +26 -0
- package/dist/shell/search/fuzzy.d.ts +0 -1
- package/dist/shell/search/index.d.ts +0 -1
- package/dist/shell/telemetry.d.ts +0 -1
- package/dist/shell/types.d.ts +34 -18
- package/dist/tailwind/index.d.ts +0 -1
- package/dist/types/keycloak.d.ts +0 -1
- package/dist/types/platform.d.ts +12 -1
- package/dist/vite/AdminShellSharedDeps.d.ts +64 -0
- package/dist/vite/AdminShellSharedDeps.js +215 -0
- package/dist/vite/config.d.ts +10 -2
- package/dist/vite/config.js +13 -10
- package/dist/vite/i18n-plugin.d.ts +13 -0
- package/dist/vite/i18n-plugin.js +81 -0
- package/dist/vite/index.d.ts +2 -1
- package/dist/vite/index.js +2 -0
- package/dist/vite/plugins.d.ts +0 -1
- package/package.json +6 -2
- package/dist/auth/client/in-memory.d.ts.map +0 -1
- package/dist/auth/client/index.d.ts.map +0 -1
- package/dist/auth/client/interface.d.ts.map +0 -1
- package/dist/auth/client/keycloak.d.ts.map +0 -1
- package/dist/auth/components/UserSelector.d.ts.map +0 -1
- package/dist/auth/components/index.d.ts.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/components/AuthProvider.d.ts.map +0 -1
- package/dist/hooks/useAuth.d.ts.map +0 -1
- package/dist/hooks/useFetch.d.ts.map +0 -1
- package/dist/hooks/useI18n.d.ts.map +0 -1
- package/dist/hooks/usePlatformAPI.d.ts.map +0 -1
- package/dist/hooks/useTelemetry.d.ts.map +0 -1
- package/dist/i18n/config.d.ts.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/registry/AdminShellRegistry.d.ts.map +0 -1
- package/dist/registry/client/http.d.ts.map +0 -1
- package/dist/registry/client/in-memory.d.ts.map +0 -1
- package/dist/registry/client/index.d.ts.map +0 -1
- package/dist/registry/client/interface.d.ts.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/types/index.d.ts.map +0 -1
- package/dist/registry/types/manifest.d.ts.map +0 -1
- package/dist/registry/types/module.d.ts.map +0 -1
- package/dist/router/DynamicModule.d.ts.map +0 -1
- package/dist/router/index.d.ts.map +0 -1
- package/dist/shell/AdminShell.d.ts.map +0 -1
- package/dist/shell/BackofficeShell.d.ts.map +0 -1
- package/dist/shell/components/CommandPalette.d.ts.map +0 -1
- package/dist/shell/components/HomePage.d.ts.map +0 -1
- package/dist/shell/components/LeftNav.d.ts.map +0 -1
- package/dist/shell/components/MainContent.d.ts.map +0 -1
- package/dist/shell/components/ModuleOverview.d.ts.map +0 -1
- package/dist/shell/components/ProfilePage.d.ts.map +0 -1
- package/dist/shell/components/RegistryPage.d.ts.map +0 -1
- package/dist/shell/components/SettingsPage.d.ts.map +0 -1
- package/dist/shell/components/TopBar.d.ts.map +0 -1
- package/dist/shell/components/index.d.ts.map +0 -1
- package/dist/shell/components/theme-provider.d.ts.map +0 -1
- package/dist/shell/index.d.ts.map +0 -1
- package/dist/shell/search/fuzzy.d.ts.map +0 -1
- package/dist/shell/search/index.d.ts.map +0 -1
- package/dist/shell/telemetry.d.ts.map +0 -1
- package/dist/shell/types.d.ts.map +0 -1
- package/dist/tailwind/index.d.ts.map +0 -1
- package/dist/types/keycloak.d.ts.map +0 -1
- package/dist/types/platform.d.ts.map +0 -1
- package/dist/vite/config.d.ts.map +0 -1
- package/dist/vite/index.d.ts.map +0 -1
- package/dist/vite/plugins.d.ts.map +0 -1
package/CHECKLIST.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- [ ] `vite.config.ts` — Vite build config
|
|
13
13
|
- [ ] `index.html` — HTML entry point
|
|
14
14
|
- [ ] `src/spa.tsx` — Shell entry (default export)
|
|
15
|
-
- [ ] `src/
|
|
15
|
+
- [ ] `src/main.tsx` — Dev entry (AdminShell wrapper)
|
|
16
16
|
- [ ] `src/App.tsx` — Main app component (Routes)
|
|
17
17
|
- [ ] `src/index.css` — Styles with Tailwind directives
|
|
18
18
|
- [ ] `tailwind.config.js` — Tailwind config
|
|
@@ -20,17 +20,36 @@
|
|
|
20
20
|
- [ ] `tsconfig.json` — TypeScript config
|
|
21
21
|
- [ ] `tsconfig.node.json` — Node TypeScript config
|
|
22
22
|
- [ ] `src/globals.d.ts` — Global type declarations
|
|
23
|
+
- [ ] `src/i18n/locales/en-US.json` — English (US) translations
|
|
24
|
+
- [ ] `src/i18n/locales/pt-BR.json` — Portuguese (Brazil) translations
|
|
25
|
+
- [ ] `src/i18n/locales/es.json` — Spanish translations
|
|
26
|
+
- [ ] `src/i18n/locales/ro.json` — Romanian translations
|
|
27
|
+
- [ ] `.env.staging` — Gateway environment config (staging mode)
|
|
28
|
+
|
|
29
|
+
## Environment Configuration
|
|
30
|
+
|
|
31
|
+
- [ ] `.env.staging` exists with `VITE_ADMIN_GATEWAY_URL` configured
|
|
32
|
+
- [ ] `dev` script uses `--mode staging` to load `.env.staging` by default
|
|
33
|
+
|
|
34
|
+
Expected `.env.staging`:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Gateway URL for BFF token integration (InMemory auth)
|
|
38
|
+
# Fetches real signed JWTs from the admin gateway instead of mock tokens.
|
|
39
|
+
# Staging: https://admin-bff-stg.nsx.dev | Homol: https://admin-bff-homol.nsx.dev | Local: http://localhost:8080
|
|
40
|
+
VITE_ADMIN_GATEWAY_URL=https://admin-bff-stg.nsx.dev
|
|
41
|
+
```
|
|
23
42
|
|
|
24
43
|
## Module Manifest (`admin.module.json`)
|
|
25
44
|
|
|
26
45
|
- [ ] Has `id` field (format: `@admin/<name>`)
|
|
27
|
-
- [ ] Has `title` field
|
|
46
|
+
- [ ] Has `title` field (localized object with en-US, pt-BR, es, ro)
|
|
28
47
|
- [ ] Has `routeBase` field (starts with `/`)
|
|
29
|
-
- [ ] Has `description` field (
|
|
48
|
+
- [ ] Has `description` field (required, localized object with all 4 locales)
|
|
30
49
|
- [ ] Has `version` field (recommended)
|
|
31
50
|
- [ ] Has `category` field (recommended)
|
|
32
51
|
- [ ] Has `permissions` object (recommended, values are string arrays)
|
|
33
|
-
- [ ] Has `commands` array (recommended, each with `id`, `title
|
|
52
|
+
- [ ] Has `commands` array (recommended, each with `id`, `title` (localized), `route`)
|
|
34
53
|
- [ ] All command `route` values start with the module's `routeBase`
|
|
35
54
|
|
|
36
55
|
Expected structure:
|
|
@@ -38,12 +57,14 @@ Expected structure:
|
|
|
38
57
|
```json
|
|
39
58
|
{
|
|
40
59
|
"id": "@admin/my-module",
|
|
41
|
-
"title": "My Module",
|
|
60
|
+
"title": { "en-US": "My Module", "pt-BR": "Meu Módulo", "es": "Mi Módulo", "ro": "Modulul Meu" },
|
|
42
61
|
"routeBase": "/my-module",
|
|
43
|
-
"description": "My Module admin module",
|
|
62
|
+
"description": { "en-US": "My Module admin module", "pt-BR": "...", "es": "...", "ro": "..." },
|
|
44
63
|
"version": "1.0.0",
|
|
45
64
|
"category": "Tools",
|
|
46
|
-
"commands": [
|
|
65
|
+
"commands": [
|
|
66
|
+
{ "id": "home", "title": { "en-US": "Home", "pt-BR": "Início", "es": "Inicio", "ro": "Acasă" }, "route": "/my-module/home" }
|
|
67
|
+
],
|
|
47
68
|
"permissions": {
|
|
48
69
|
"view": ["admin.my-module.view"],
|
|
49
70
|
"edit": ["admin.my-module.edit"],
|
|
@@ -52,6 +73,14 @@ Expected structure:
|
|
|
52
73
|
}
|
|
53
74
|
```
|
|
54
75
|
|
|
76
|
+
## i18n
|
|
77
|
+
|
|
78
|
+
- [ ] All 4 locale files exist: `src/i18n/locales/en-US.json`, `pt-BR.json`, `es.json`, `ro.json`
|
|
79
|
+
- [ ] Manifest `title` and `description` are localized objects (not plain strings)
|
|
80
|
+
- [ ] Each command `title` is a localized object
|
|
81
|
+
- [ ] No manual i18n registration in `main.tsx` or `spa.tsx` (Vite plugin auto-injects)
|
|
82
|
+
- [ ] No `import "./i18n"` in `App.tsx`
|
|
83
|
+
|
|
55
84
|
## Dependencies (`package.json`)
|
|
56
85
|
|
|
57
86
|
- [ ] `"type": "module"` is set
|
|
@@ -162,7 +191,7 @@ import { App } from "./App";
|
|
|
162
191
|
export default App;
|
|
163
192
|
```
|
|
164
193
|
|
|
165
|
-
### `src/
|
|
194
|
+
### `src/main.tsx`
|
|
166
195
|
|
|
167
196
|
- [ ] Uses `AdminShell` from `@nsxbet/admin-sdk`
|
|
168
197
|
- [ ] Imports manifest and passes to `modules` prop
|
|
@@ -220,7 +249,7 @@ If your module was scaffolded by Lovable, the following differences apply:
|
|
|
220
249
|
- [ ] `vite.config.ts` uses `adminModule()` spread into the existing plugins array (option B above), NOT `defineModuleConfig`
|
|
221
250
|
- [ ] React plugin is `@vitejs/plugin-react-swc` (Lovable default) — this is supported
|
|
222
251
|
- [ ] `index.html` entry point references `/src/main.tsx` (Lovable default) — this serves as the standalone dev entry
|
|
223
|
-
- [ ] `src/main.tsx` wraps your App with `AdminShell` (same role as `src/
|
|
252
|
+
- [ ] `src/main.tsx` wraps your App with `AdminShell` (same role as `src/main.tsx` in non-Lovable modules)
|
|
224
253
|
- [ ] `src/spa.tsx` still required — `export default App` for shell mode
|
|
225
254
|
- [ ] `admin.module.json` still required at project root
|
|
226
255
|
- [ ] `module.manifest.json` is auto-generated at build time by `adminModule()` — do NOT manually copy `admin.module.json` to `dist/`
|
|
@@ -294,8 +323,9 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
|
294
323
|
|
|
295
324
|
- [ ] No `@supabase/supabase-js` or `supabase` imports in `src/**`
|
|
296
325
|
- [ ] No `firebase` imports in `src/**`
|
|
297
|
-
- [ ] No `BrowserRouter` or `MemoryRouter` in `src/**` (except `
|
|
326
|
+
- [ ] No `BrowserRouter` or `MemoryRouter` in `src/**` (except `main.tsx`)
|
|
298
327
|
- [ ] No `useNavigate` imported from `@nsxbet/admin-sdk` (use `react-router-dom`)
|
|
299
328
|
- [ ] No `@supabase/supabase-js` or `firebase` in `package.json` dependencies
|
|
300
329
|
- [ ] No raw `fetch()` or `window.fetch()` calls — use `useFetch()` from the SDK
|
|
330
|
+
- [ ] No direct date formatting (`toLocaleDateString`, `toLocaleTimeString`, `Intl.DateTimeFormat`, dayjs/moment `.format()`, date-fns `format`) — use `<Timestamp />` or `useTimestamp()` from the SDK
|
|
301
331
|
- [ ] Do NOT scaffold shadcn/ui components locally — use `@nsxbet/admin-ui` which ships them pre-configured
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npx @nsxbet/admin-cli check
|
|
|
41
41
|
npx @nsxbet/admin-cli check --fix
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
The CLI scaffolds all
|
|
44
|
+
The CLI scaffolds all 17 required files (including 4 locale files) with correct configuration, dependency versions, and SDK integration. It also validates existing modules against the canonical reference.
|
|
45
45
|
|
|
46
46
|
> **Required for LLMs:** After creating or modifying a module, run `npx @nsxbet/admin-cli checklist --output CHECKLIST.md` and verify every item in the generated file before considering the setup complete. The checklist is a self-contained verification guide—no CLI installation needed to consume it.
|
|
47
47
|
|
|
@@ -74,7 +74,7 @@ This SDK enables you to build admin modules that integrate with the NSX Admin sh
|
|
|
74
74
|
| File | Purpose | When used |
|
|
75
75
|
|------|---------|-----------|
|
|
76
76
|
| `src/spa.tsx` | Default export of App | Shell loads this via React.lazy |
|
|
77
|
-
| `src/
|
|
77
|
+
| `src/main.tsx` | Full app with AdminShell wrapper | Local development (`npm run dev`) |
|
|
78
78
|
|
|
79
79
|
## Complete Module Example
|
|
80
80
|
|
|
@@ -94,26 +94,53 @@ export default defineModuleConfig({
|
|
|
94
94
|
|
|
95
95
|
### File: `admin.module.json`
|
|
96
96
|
|
|
97
|
+
Title, description, and command titles must be **localized objects** with all 4 locales (`en-US`, `pt-BR`, `es`, `ro`):
|
|
98
|
+
|
|
97
99
|
```json
|
|
98
100
|
{
|
|
99
101
|
"id": "@admin/my-module",
|
|
100
|
-
"title":
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
"title": {
|
|
103
|
+
"en-US": "My Module",
|
|
104
|
+
"pt-BR": "Meu Módulo",
|
|
105
|
+
"es": "Mi Módulo",
|
|
106
|
+
"ro": "Modulul Meu"
|
|
107
|
+
},
|
|
108
|
+
"description": {
|
|
109
|
+
"en-US": "Description of what this module does",
|
|
110
|
+
"pt-BR": "Descrição do que este módulo faz",
|
|
111
|
+
"es": "Descripción de lo que hace este módulo",
|
|
112
|
+
"ro": "Descrierea a ceea ce face acest modul"
|
|
113
|
+
},
|
|
103
114
|
"category": "Tools",
|
|
104
115
|
"icon": "clipboard-list",
|
|
105
116
|
"routeBase": "/my-module",
|
|
106
117
|
"keywords": ["my", "module", "example"],
|
|
118
|
+
"navigation": {
|
|
119
|
+
"style": "stacked",
|
|
120
|
+
"sections": [
|
|
121
|
+
{ "id": "general", "label": { "en-US": "General", "pt-BR": "Geral", "es": "General", "ro": "General" } }
|
|
122
|
+
]
|
|
123
|
+
},
|
|
107
124
|
"commands": [
|
|
108
125
|
{
|
|
109
126
|
"id": "list",
|
|
110
|
-
"title":
|
|
127
|
+
"title": {
|
|
128
|
+
"en-US": "List Items",
|
|
129
|
+
"pt-BR": "Listar Itens",
|
|
130
|
+
"es": "Listar Elementos",
|
|
131
|
+
"ro": "Lista Elemente"
|
|
132
|
+
},
|
|
111
133
|
"route": "/my-module/list",
|
|
112
134
|
"icon": "file-text"
|
|
113
135
|
},
|
|
114
136
|
{
|
|
115
137
|
"id": "new",
|
|
116
|
-
"title":
|
|
138
|
+
"title": {
|
|
139
|
+
"en-US": "New Item",
|
|
140
|
+
"pt-BR": "Novo Item",
|
|
141
|
+
"es": "Nuevo Elemento",
|
|
142
|
+
"ro": "Element Nou"
|
|
143
|
+
},
|
|
117
144
|
"route": "/my-module/new",
|
|
118
145
|
"icon": "plus"
|
|
119
146
|
}
|
|
@@ -142,7 +169,7 @@ import { App } from "./App";
|
|
|
142
169
|
export default App;
|
|
143
170
|
```
|
|
144
171
|
|
|
145
|
-
### File: `src/
|
|
172
|
+
### File: `src/main.tsx`
|
|
146
173
|
|
|
147
174
|
```tsx
|
|
148
175
|
import React from "react";
|
|
@@ -150,7 +177,6 @@ import ReactDOM from "react-dom/client";
|
|
|
150
177
|
import {
|
|
151
178
|
AdminShell,
|
|
152
179
|
initI18n,
|
|
153
|
-
i18n,
|
|
154
180
|
createInMemoryAuthClient,
|
|
155
181
|
createMockUsersFromRoles,
|
|
156
182
|
} from "@nsxbet/admin-sdk";
|
|
@@ -160,19 +186,10 @@ import manifest from "../admin.module.json";
|
|
|
160
186
|
|
|
161
187
|
import "./index.css";
|
|
162
188
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
import ptBR from "./i18n/locales/pt-BR.json";
|
|
166
|
-
|
|
167
|
-
// Initialize i18n BEFORE shell renders
|
|
189
|
+
// Initialize i18n BEFORE shell renders.
|
|
190
|
+
// The Vite plugin (admin-module-i18n) auto-injects module translation registration into spa.tsx and main.tsx.
|
|
168
191
|
initI18n();
|
|
169
192
|
|
|
170
|
-
// Register module translations with namespace matching your module
|
|
171
|
-
const NAMESPACE = "mymodule";
|
|
172
|
-
i18n.addResourceBundle("en-US", NAMESPACE, enUS, true, true);
|
|
173
|
-
i18n.addResourceBundle("pt-BR", NAMESPACE, ptBR, true, true);
|
|
174
|
-
|
|
175
|
-
// Type assertion for JSON import
|
|
176
193
|
const moduleManifest = manifest as AdminModuleManifest;
|
|
177
194
|
|
|
178
195
|
// Check environment variable to toggle between mock auth and Keycloak
|
|
@@ -288,25 +305,27 @@ export function ItemList() {
|
|
|
288
305
|
@tailwind utilities;
|
|
289
306
|
```
|
|
290
307
|
|
|
291
|
-
### Directory: `src/i18n/` (
|
|
308
|
+
### Directory: `src/i18n/` (required for translations)
|
|
309
|
+
|
|
310
|
+
All 4 locale files are **required** (`en-US`, `pt-BR`, `es`, `ro`). The Vite plugin validates this at build time.
|
|
292
311
|
|
|
293
312
|
Structure:
|
|
294
313
|
|
|
295
314
|
```
|
|
296
315
|
src/i18n/
|
|
297
316
|
locales/
|
|
298
|
-
en-US.json
|
|
299
|
-
pt-BR.json
|
|
317
|
+
en-US.json
|
|
318
|
+
pt-BR.json
|
|
319
|
+
es.json
|
|
320
|
+
ro.json
|
|
300
321
|
```
|
|
301
322
|
|
|
302
|
-
|
|
323
|
+
**No manual registration needed.** The `admin-module-i18n` Vite plugin (included in `defineModuleConfig`) auto-injects `registerModuleTranslations` into `spa.tsx` and `main.tsx` at build/serve time. Just create the locale files.
|
|
324
|
+
|
|
325
|
+
Example `src/i18n/locales/en-US.json` (content translations only — nav titles live in `admin.module.json`):
|
|
303
326
|
|
|
304
327
|
```json
|
|
305
328
|
{
|
|
306
|
-
"module": {
|
|
307
|
-
"title": "My Module",
|
|
308
|
-
"description": "Description of my module"
|
|
309
|
-
},
|
|
310
329
|
"list": {
|
|
311
330
|
"title": "All Items",
|
|
312
331
|
"noItems": "No items found.",
|
|
@@ -327,13 +346,14 @@ Use translations in components:
|
|
|
327
346
|
import { useI18n } from "@nsxbet/admin-sdk";
|
|
328
347
|
|
|
329
348
|
function MyComponent() {
|
|
330
|
-
const { t } = useI18n();
|
|
349
|
+
const { t } = useI18n("my-module");
|
|
331
350
|
|
|
332
|
-
|
|
333
|
-
return <h1>{t("mymodule:list.title")}</h1>;
|
|
351
|
+
return <h1>{t("list.title")}</h1>;
|
|
334
352
|
}
|
|
335
353
|
```
|
|
336
354
|
|
|
355
|
+
The namespace is derived from your module id (e.g. `@admin/my-module` → `my-module`).
|
|
356
|
+
|
|
337
357
|
### File: `tailwind.config.js`
|
|
338
358
|
|
|
339
359
|
Use `withAdminSdk` which automatically includes the UI preset and SDK/UI content paths:
|
|
@@ -423,7 +443,7 @@ export default withAdminSdk({
|
|
|
423
443
|
</head>
|
|
424
444
|
<body>
|
|
425
445
|
<div id="root"></div>
|
|
426
|
-
<script type="module" src="/src/
|
|
446
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
427
447
|
</body>
|
|
428
448
|
</html>
|
|
429
449
|
```
|
|
@@ -449,6 +469,7 @@ TypeScript declarations for environment variables and platform API:
|
|
|
449
469
|
declare global {
|
|
450
470
|
interface ImportMetaEnv {
|
|
451
471
|
readonly VITE_MOCK_AUTH?: string;
|
|
472
|
+
readonly VITE_ALLOWED_MODULE_ORIGINS?: string;
|
|
452
473
|
}
|
|
453
474
|
|
|
454
475
|
interface ImportMeta {
|
|
@@ -497,6 +518,11 @@ VITE_MOCK_AUTH=true
|
|
|
497
518
|
|
|
498
519
|
# Set to "false" to use Keycloak authentication
|
|
499
520
|
# VITE_MOCK_AUTH=false
|
|
521
|
+
|
|
522
|
+
# Module URL allowlist (shell mode only). Comma-separated patterns.
|
|
523
|
+
# Supports *.domain wildcard (e.g. *.nsx.dev matches modules.nsx.dev, nsx.dev).
|
|
524
|
+
# In dev mode, localhost and 127.0.0.1 are always allowed.
|
|
525
|
+
# VITE_ALLOWED_MODULE_ORIGINS=*.nsx.dev,*.nsx.services
|
|
500
526
|
```
|
|
501
527
|
|
|
502
528
|
## Manifest Schema (`admin.module.json`)
|
|
@@ -507,10 +533,10 @@ VITE_MOCK_AUTH=true
|
|
|
507
533
|
| `title` | string | ✅ | Human-readable title |
|
|
508
534
|
| `routeBase` | string | ✅ | Base route path (must start with `/`) |
|
|
509
535
|
| `description` | string | | What the module does |
|
|
510
|
-
| `version` | string | | Semantic version |
|
|
511
536
|
| `category` | string | | Navigation grouping |
|
|
512
537
|
| `icon` | string | | Lucide icon name in kebab-case |
|
|
513
538
|
| `keywords` | string[] | | Search keywords |
|
|
539
|
+
| `navigation` | object | | Navigation config (`style`, `sections`) |
|
|
514
540
|
| `commands` | Command[] | | Available actions |
|
|
515
541
|
| `permissions` | object | | Permission configuration |
|
|
516
542
|
| `owners` | object | | Team ownership info |
|
|
@@ -524,6 +550,41 @@ VITE_MOCK_AUTH=true
|
|
|
524
550
|
| `route` | string | ✅ | Full route path |
|
|
525
551
|
| `icon` | string | | Lucide icon name |
|
|
526
552
|
| `keywords` | string[] | | Search keywords |
|
|
553
|
+
| `section` | string | | Section ID for stacked navigation grouping |
|
|
554
|
+
|
|
555
|
+
### Stacked Navigation
|
|
556
|
+
|
|
557
|
+
Modules with many commands can use stacked navigation for a dedicated sidebar panel with sectioned command grouping.
|
|
558
|
+
|
|
559
|
+
**Enable stacked navigation** by adding a `navigation` field to `admin.module.json`:
|
|
560
|
+
|
|
561
|
+
```json
|
|
562
|
+
{
|
|
563
|
+
"navigation": {
|
|
564
|
+
"style": "stacked",
|
|
565
|
+
"sections": [
|
|
566
|
+
{
|
|
567
|
+
"id": "general",
|
|
568
|
+
"label": { "en-US": "General", "pt-BR": "Geral", "es": "General", "ro": "General" }
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
"id": "advanced",
|
|
572
|
+
"label": { "en-US": "Advanced", "pt-BR": "Avançado", "es": "Avanzado", "ro": "Avansat" }
|
|
573
|
+
}
|
|
574
|
+
]
|
|
575
|
+
},
|
|
576
|
+
"commands": [
|
|
577
|
+
{ "id": "list", "title": {...}, "route": "/mod/list", "section": "general" },
|
|
578
|
+
{ "id": "settings", "title": {...}, "route": "/mod/settings", "section": "advanced" }
|
|
579
|
+
]
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
- `style`: `"stacked"` enables the dedicated panel; `"collapsible"` (default) keeps the inline expand behavior
|
|
584
|
+
- `sections`: Array of section definitions with `id` and localized `label`
|
|
585
|
+
- Commands reference sections via the `section` field matching a section `id`
|
|
586
|
+
- Commands without a `section` appear in an implicit top-level group
|
|
587
|
+
- The stacked panel is URL-driven: navigating to the module's `routeBase` activates it
|
|
527
588
|
|
|
528
589
|
### Icon Names
|
|
529
590
|
|
|
@@ -694,7 +755,10 @@ import adminPlugin from "@nsxbet/eslint-plugin-admin";
|
|
|
694
755
|
export default [adminPlugin.configs.recommended];
|
|
695
756
|
```
|
|
696
757
|
|
|
697
|
-
This enables all recommended rules
|
|
758
|
+
This enables all recommended rules:
|
|
759
|
+
|
|
760
|
+
- **`@nsxbet/no-raw-fetch`** (error) — flags direct `fetch()`/`window.fetch()` calls that bypass authentication. Use `useFetch()` instead.
|
|
761
|
+
- **`@nsxbet/no-raw-date-format`** (warn) — flags direct date formatting (`toLocaleDateString`, `toLocaleTimeString`, `Intl.DateTimeFormat`, dayjs/moment `.format()`, date-fns `format`) that bypasses the platform timezone preference. Use `<Timestamp />` or `useTimestamp()` instead.
|
|
698
762
|
|
|
699
763
|
## SDK Hooks
|
|
700
764
|
|
|
@@ -719,7 +783,7 @@ function MyComponent() {
|
|
|
719
783
|
|
|
720
784
|
| Method | Returns | Description |
|
|
721
785
|
|--------|---------|-------------|
|
|
722
|
-
| `hasPermission(perm)` | boolean | Check if user has permission |
|
|
786
|
+
| `hasPermission(perm)` | boolean | Check if user has permission. Returns `false` during Keycloak initialization until auth completes. |
|
|
723
787
|
| `getUser()` | User | Get current user info |
|
|
724
788
|
| `getAccessToken()` | Promise\<string\> | Get JWT token |
|
|
725
789
|
| `logout()` | void | Log out user |
|
|
@@ -799,6 +863,132 @@ function MyComponent() {
|
|
|
799
863
|
}
|
|
800
864
|
```
|
|
801
865
|
|
|
866
|
+
### `useTimestamp()`
|
|
867
|
+
|
|
868
|
+
Timezone-aware date formatting that respects the shell's UTC/Local preference.
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
import { useTimestamp, Timestamp } from "@nsxbet/admin-sdk";
|
|
872
|
+
|
|
873
|
+
function MyComponent() {
|
|
874
|
+
const { mode, setMode, formatDate, timezone } = useTimestamp();
|
|
875
|
+
|
|
876
|
+
return (
|
|
877
|
+
<div>
|
|
878
|
+
<p>Timezone: {timezone} ({mode})</p>
|
|
879
|
+
<p>Formatted: {formatDate(new Date(), "datetime")}</p>
|
|
880
|
+
<Timestamp value={new Date()} format="date" />
|
|
881
|
+
</div>
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
| Property | Type | Description |
|
|
887
|
+
|----------|------|-------------|
|
|
888
|
+
| `mode` | `"utc" \| "local"` | Current timezone mode |
|
|
889
|
+
| `setMode` | `(mode) => void` | Change the timezone mode |
|
|
890
|
+
| `formatDate` | `(date, format?) => string` | Format a date with current mode and locale |
|
|
891
|
+
| `timezone` | `string` | Resolved IANA timezone string (`"UTC"` or browser local) |
|
|
892
|
+
|
|
893
|
+
**Format presets:**
|
|
894
|
+
|
|
895
|
+
| Preset | Example (UTC, en-US) | Description |
|
|
896
|
+
|--------|----------------------|-------------|
|
|
897
|
+
| `"datetime"` (default) | "Mar 16, 2026, 2:30:05 PM UTC" | Full date and time |
|
|
898
|
+
| `"date"` | "Mar 16, 2026" | Date only |
|
|
899
|
+
| `"time"` | "2:30:05 PM UTC" | Time only |
|
|
900
|
+
| `"relative"` | "5 minutes ago" | Relative to now (timezone-independent) |
|
|
901
|
+
|
|
902
|
+
In shell mode, reads from `window.__ADMIN_PLATFORM_API__.timestamp`. In standalone mode, falls back to `localStorage` key `admin-timezone-mode` (default: `"local"`).
|
|
903
|
+
|
|
904
|
+
### `<Timestamp />` Component
|
|
905
|
+
|
|
906
|
+
Renders a formatted date that automatically respects the shell's timezone preference.
|
|
907
|
+
|
|
908
|
+
```tsx
|
|
909
|
+
import { Timestamp } from "@nsxbet/admin-sdk";
|
|
910
|
+
|
|
911
|
+
// Basic usage (datetime format)
|
|
912
|
+
<Timestamp value={new Date("2026-03-16T14:30:05Z")} />
|
|
913
|
+
|
|
914
|
+
// Date only
|
|
915
|
+
<Timestamp value={createdAt} format="date" />
|
|
916
|
+
|
|
917
|
+
// Relative time
|
|
918
|
+
<Timestamp value={updatedAt} format="relative" />
|
|
919
|
+
|
|
920
|
+
// String input (auto-parsed)
|
|
921
|
+
<Timestamp value="2026-03-16T14:30:05Z" format="time" />
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
| Prop | Type | Default | Description |
|
|
925
|
+
|------|------|---------|-------------|
|
|
926
|
+
| `value` | `Date \| string` | required | The date to display |
|
|
927
|
+
| `format` | `TimestampFormat` | `"datetime"` | Format preset |
|
|
928
|
+
| `className` | `string` | | CSS class for the `<time>` element |
|
|
929
|
+
|
|
930
|
+
Renders a semantic `<time>` element with a `dateTime` attribute. Hovering shows a tooltip with the date in the opposite timezone mode.
|
|
931
|
+
|
|
932
|
+
**Migration guide:**
|
|
933
|
+
|
|
934
|
+
| Before | After |
|
|
935
|
+
|--------|-------|
|
|
936
|
+
| `date.toLocaleDateString()` | `<Timestamp value={date} format="date" />` |
|
|
937
|
+
| `date.toLocaleString()` | `<Timestamp value={date} />` |
|
|
938
|
+
| `date.toLocaleTimeString()` | `<Timestamp value={date} format="time" />` |
|
|
939
|
+
| `new Intl.DateTimeFormat(...).format(date)` | `<Timestamp value={date} />` |
|
|
940
|
+
|
|
941
|
+
### `useRegistryPolling()`
|
|
942
|
+
|
|
943
|
+
Detect catalog changes via lightweight version polling. Used by the shell to show an "Updates available" banner without forcing a reload.
|
|
944
|
+
|
|
945
|
+
```typescript
|
|
946
|
+
import { useRegistryPolling } from "@nsxbet/admin-sdk";
|
|
947
|
+
|
|
948
|
+
const { hasUpdates, dismiss } = useRegistryPolling({
|
|
949
|
+
registryClient,
|
|
950
|
+
initialVersion: catalog.version,
|
|
951
|
+
interval: 60000, // poll every 60s, 0 to disable
|
|
952
|
+
});
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
| Property | Type | Description |
|
|
956
|
+
|----------|------|-------------|
|
|
957
|
+
| `hasUpdates` | boolean | `true` when the server version differs from the loaded version |
|
|
958
|
+
| `dismiss` | () => void | Hide the banner for the current version; re-shows on newer versions |
|
|
959
|
+
|
|
960
|
+
**Options:**
|
|
961
|
+
|
|
962
|
+
| Option | Type | Description |
|
|
963
|
+
|--------|------|-------------|
|
|
964
|
+
| `registryClient` | RegistryClient | The registry client instance |
|
|
965
|
+
| `initialVersion` | string | Version string from the initial `catalog.get()` response |
|
|
966
|
+
| `interval` | number \| undefined | Polling interval in ms. `0` or `undefined` disables polling |
|
|
967
|
+
|
|
968
|
+
The hook integrates the Page Visibility API — polling pauses when the tab is hidden and resumes with an immediate check when the tab becomes visible again. Network errors are silently ignored.
|
|
969
|
+
|
|
970
|
+
#### `catalog.version()`
|
|
971
|
+
|
|
972
|
+
Both HTTP and in-memory registry clients expose a `catalog.version()` method:
|
|
973
|
+
|
|
974
|
+
```typescript
|
|
975
|
+
const { version, generatedAt } = await registryClient.catalog.version();
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
The HTTP client calls `GET /api/catalog/version`. The in-memory client derives a version from the local mutation state.
|
|
979
|
+
|
|
980
|
+
#### Environment configuration
|
|
981
|
+
|
|
982
|
+
The polling interval is resolved from `REGISTRY_POLL_INTERVAL` (milliseconds):
|
|
983
|
+
|
|
984
|
+
| Source | Example |
|
|
985
|
+
|--------|---------|
|
|
986
|
+
| `window.__ENV__.REGISTRY_POLL_INTERVAL` | Docker runtime injection |
|
|
987
|
+
| `import.meta.env.VITE_REGISTRY_POLL_INTERVAL` | Vite env var |
|
|
988
|
+
| Environment default | `local`: 60s, `staging`: 5min, `production`: 15min |
|
|
989
|
+
|
|
990
|
+
Set `REGISTRY_POLL_INTERVAL=0` to disable polling entirely.
|
|
991
|
+
|
|
802
992
|
### Navigation
|
|
803
993
|
|
|
804
994
|
**Use `useNavigate` from `react-router-dom` directly:**
|
|
@@ -847,7 +1037,7 @@ const mockUsers = createMockUsersFromRoles({
|
|
|
847
1037
|
// Create auth client with custom users
|
|
848
1038
|
const authClient = createInMemoryAuthClient({ users: mockUsers });
|
|
849
1039
|
|
|
850
|
-
// Use in
|
|
1040
|
+
// Use in main.tsx
|
|
851
1041
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
852
1042
|
<AdminShell authClient={authClient} modules={[manifest]}>
|
|
853
1043
|
<App />
|
|
@@ -892,6 +1082,48 @@ const authClient = createInMemoryAuthClient({ users: customUsers });
|
|
|
892
1082
|
|--------|------|---------|-------------|
|
|
893
1083
|
| `users` | MockUser[] | **required** | Mock users available for selection |
|
|
894
1084
|
| `storageKey` | string | `"@nsxbet/auth"` | localStorage key for persistence |
|
|
1085
|
+
| `gatewayUrl` | string \| null | `VITE_ADMIN_GATEWAY_URL` env var | Admin gateway URL for real JWT tokens |
|
|
1086
|
+
| `tokenTimeout` | number | `5000` | Timeout in ms for gateway token fetch |
|
|
1087
|
+
|
|
1088
|
+
### Gateway Token Integration (BFF)
|
|
1089
|
+
|
|
1090
|
+
When `VITE_ADMIN_GATEWAY_URL` is set (or `gatewayUrl` is passed explicitly), the InMemory auth client fetches real signed JWTs from the admin gateway instead of returning mock token strings. This enables end-to-end testing against backends that validate tokens.
|
|
1091
|
+
|
|
1092
|
+
**How it works:**
|
|
1093
|
+
|
|
1094
|
+
1. Developer clicks a user card in the UserSelector
|
|
1095
|
+
2. The client calls `GET {gatewayUrl}/auth/token?sub=...&email=...&roles=...&scopes=...`
|
|
1096
|
+
3. The gateway returns a signed JWT which is cached in memory
|
|
1097
|
+
4. `getAccessToken()` returns the cached JWT for all subsequent API calls
|
|
1098
|
+
5. When the token nears expiry (within 60s), a background refresh is triggered
|
|
1099
|
+
|
|
1100
|
+
**Gateway URL resolution order:**
|
|
1101
|
+
|
|
1102
|
+
1. Explicit `gatewayUrl` option passed to `createInMemoryAuthClient()`
|
|
1103
|
+
2. `import.meta.env.VITE_ADMIN_GATEWAY_URL` environment variable
|
|
1104
|
+
3. If neither is set, BFF integration is disabled (mock tokens, current behavior)
|
|
1105
|
+
|
|
1106
|
+
**Error handling:**
|
|
1107
|
+
|
|
1108
|
+
The UserSelector shows loading, error, and timeout states during the token fetch. If the gateway is unreachable, the developer can:
|
|
1109
|
+
- **Retry** the token fetch
|
|
1110
|
+
- **Continue with mock token** to fall back to legacy behavior
|
|
1111
|
+
- **Go back** to the user selection list
|
|
1112
|
+
|
|
1113
|
+
```bash
|
|
1114
|
+
# Add to your .env file to enable gateway token integration
|
|
1115
|
+
VITE_ADMIN_GATEWAY_URL=https://admin-bff-stg.nsx.dev
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
## Module URL Allowlist (Shell Mode)
|
|
1119
|
+
|
|
1120
|
+
When the shell loads modules dynamically from URLs, it validates each URL against `VITE_ALLOWED_MODULE_ORIGINS` before `import()` or `loadScript()`.
|
|
1121
|
+
|
|
1122
|
+
**Format:** Comma-separated patterns supporting `*.domain` wildcard (e.g. `*.nsx.dev` matches `modules.nsx.dev`, `cdn.nsx.dev`, and apex `nsx.dev`).
|
|
1123
|
+
|
|
1124
|
+
**Dev mode:** `localhost` and `127.0.0.1` are always allowed regardless of the allowlist, so local dev servers work without configuration.
|
|
1125
|
+
|
|
1126
|
+
**Production:** Set `VITE_ALLOWED_MODULE_ORIGINS` in your build environment. If unset or empty, all module loads fail with a clear error.
|
|
895
1127
|
|
|
896
1128
|
## Keycloak Configuration (Production Auth)
|
|
897
1129
|
|
|
@@ -929,6 +1161,10 @@ Or pass the `keycloak` prop directly without `authClient`:
|
|
|
929
1161
|
</AdminShell>
|
|
930
1162
|
```
|
|
931
1163
|
|
|
1164
|
+
### Mock Auth Production Guard
|
|
1165
|
+
|
|
1166
|
+
In production builds, AdminShell requires authentication configuration. If you omit both `authClient` and `keycloak` props, the shell throws an error unless you explicitly set `VITE_MOCK_AUTH=true`. When mock auth is used in production with `VITE_MOCK_AUTH=true`, a console warning is logged. Always use Keycloak (or another real auth client) for production deployments.
|
|
1167
|
+
|
|
932
1168
|
### Keycloak Configuration Options
|
|
933
1169
|
|
|
934
1170
|
| Option | Type | Description |
|
|
@@ -1026,6 +1262,29 @@ const fetch = useFetch();
|
|
|
1026
1262
|
const data = await fetch("/api/internal-endpoint");
|
|
1027
1263
|
```
|
|
1028
1264
|
|
|
1265
|
+
### ❌ DO NOT format dates directly
|
|
1266
|
+
|
|
1267
|
+
```typescript
|
|
1268
|
+
// WRONG - bypasses platform timezone preference
|
|
1269
|
+
date.toLocaleDateString(); // ❌
|
|
1270
|
+
date.toLocaleTimeString(); // ❌
|
|
1271
|
+
new Intl.DateTimeFormat("en-US").format(date); // ❌
|
|
1272
|
+
dayjs(date).format("YYYY-MM-DD"); // ❌
|
|
1273
|
+
import { format } from "date-fns"; format(date, "PP"); // ❌
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
```tsx
|
|
1277
|
+
// CORRECT - use <Timestamp /> or useTimestamp()
|
|
1278
|
+
import { Timestamp, useTimestamp } from "@nsxbet/admin-sdk";
|
|
1279
|
+
|
|
1280
|
+
<Timestamp value={date} format="date" /> // ✅
|
|
1281
|
+
|
|
1282
|
+
const { formatDate } = useTimestamp();
|
|
1283
|
+
formatDate(date, "datetime"); // ✅
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
Direct formatting bypasses the platform timezone preference, causing inconsistent timestamp display across modules. The `@nsxbet/no-raw-date-format` ESLint rule enforces this.
|
|
1287
|
+
|
|
1029
1288
|
### ❌ DO NOT import useNavigate from SDK
|
|
1030
1289
|
|
|
1031
1290
|
```typescript
|
|
@@ -1091,6 +1350,43 @@ bun run build
|
|
|
1091
1350
|
bun run preview
|
|
1092
1351
|
```
|
|
1093
1352
|
|
|
1353
|
+
## Error Boundary with Module Ownership
|
|
1354
|
+
|
|
1355
|
+
When a module crashes at runtime, the error boundary displays ownership information to help with incident triage. The `DynamicModule` component accepts an optional `moduleInfo` prop that provides module metadata to the error boundary.
|
|
1356
|
+
|
|
1357
|
+
```tsx
|
|
1358
|
+
<DynamicModule
|
|
1359
|
+
baseUrl="http://localhost:5001"
|
|
1360
|
+
moduleInfo={{
|
|
1361
|
+
id: "@admin/payments",
|
|
1362
|
+
title: { "en-US": "Payments", "pt-BR": "Pagamentos", "es": "Pagos", "ro": "Plăți" },
|
|
1363
|
+
owners: { team: "Payments", supportChannel: "#payments-support" },
|
|
1364
|
+
}}
|
|
1365
|
+
/>
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
When a module error occurs, the error boundary will:
|
|
1369
|
+
- Display the module name (localized for the current locale)
|
|
1370
|
+
- Show the owner team and support channel (if provided)
|
|
1371
|
+
- Render the support channel as a clickable link if it starts with `http`/`https`
|
|
1372
|
+
- Provide both "Try Again" (re-render) and "Reload Page" (full reload) buttons
|
|
1373
|
+
- Report the error to telemetry with module attribution (`moduleId`, `ownerTeam`, `errorType`)
|
|
1374
|
+
- Capture unhandled promise rejections while the module is mounted and report them via telemetry
|
|
1375
|
+
|
|
1376
|
+
If `owners.team` is empty, the ownership section is omitted and the error boundary shows a standard error UI.
|
|
1377
|
+
|
|
1378
|
+
## Platform API — Timestamp Namespace
|
|
1379
|
+
|
|
1380
|
+
The shell exposes `window.__ADMIN_PLATFORM_API__.timestamp` for timezone preference management:
|
|
1381
|
+
|
|
1382
|
+
| Property | Type | Description |
|
|
1383
|
+
|----------|------|-------------|
|
|
1384
|
+
| `mode` | `"utc" \| "local"` | Current timezone mode |
|
|
1385
|
+
| `setMode(mode)` | `(TimezoneMode) => void` | Change the timezone mode |
|
|
1386
|
+
| `onModeChange(cb)` | `(callback) => () => void` | Subscribe to changes; returns unsubscribe |
|
|
1387
|
+
|
|
1388
|
+
> **Note:** Modules should use `useTimestamp()` or `<Timestamp />` rather than accessing the Platform API directly.
|
|
1389
|
+
|
|
1094
1390
|
## Types
|
|
1095
1391
|
|
|
1096
1392
|
```typescript
|
|
@@ -1100,6 +1396,11 @@ import type {
|
|
|
1100
1396
|
PlatformAPI,
|
|
1101
1397
|
User,
|
|
1102
1398
|
Breadcrumb,
|
|
1399
|
+
ModuleInfo,
|
|
1400
|
+
ErrorBoundaryProps,
|
|
1401
|
+
TimezoneMode,
|
|
1402
|
+
TimestampFormat,
|
|
1403
|
+
UseTimestampResult,
|
|
1103
1404
|
} from "@nsxbet/admin-sdk";
|
|
1104
1405
|
```
|
|
1105
1406
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MockUser } from './interface';
|
|
2
|
+
export declare class GatewayTimeoutError extends Error {
|
|
3
|
+
readonly gatewayUrl: string;
|
|
4
|
+
readonly timeoutMs: number;
|
|
5
|
+
constructor(gatewayUrl: string, timeoutMs: number);
|
|
6
|
+
}
|
|
7
|
+
export declare class GatewayFetchError extends Error {
|
|
8
|
+
readonly gatewayUrl: string;
|
|
9
|
+
readonly statusCode?: number | undefined;
|
|
10
|
+
readonly originalError?: unknown | undefined;
|
|
11
|
+
constructor(gatewayUrl: string, statusCode?: number | undefined, originalError?: unknown | undefined);
|
|
12
|
+
}
|
|
13
|
+
export interface GatewayTokenResult {
|
|
14
|
+
token: string;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function fetchGatewayToken(gatewayUrl: string, user: MockUser, options?: {
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}): Promise<GatewayTokenResult>;
|