@nsxbet/admin-sdk 0.6.0 → 0.7.1
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 +12 -0
- package/README.md +34 -0
- package/dist/auth/client/private-network-guidance.d.ts +2 -0
- package/dist/auth/client/private-network-guidance.js +38 -0
- package/dist/auth/components/UserSelector.d.ts +29 -0
- package/dist/auth/components/UserSelector.js +36 -8
- package/dist/auth/components/UserSelector.stories.d.ts +9 -0
- package/dist/auth/components/UserSelector.stories.js +70 -0
- package/dist/components/AuthProvider.js +10 -7
- package/dist/hooks/useAuth.js +25 -20
- package/dist/hooks/useCallbackRef.d.ts +7 -0
- package/dist/hooks/useCallbackRef.js +14 -0
- package/dist/hooks/usePlatformAPI.d.ts +0 -3
- package/dist/hooks/usePlatformAPI.js +6 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/registry/AdminShellRegistry.js +4 -3
- package/dist/shell/components/theme-provider.js +6 -8
- package/dist/vite/config.d.ts +15 -1
- package/dist/vite/config.js +29 -1
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1 -1
- package/package.json +10 -2
package/CHECKLIST.md
CHANGED
|
@@ -256,6 +256,18 @@ If your module was scaffolded by Lovable, the following differences apply:
|
|
|
256
256
|
- [ ] Lovable's existing `server`, `resolve.alias`, and plugin settings are preserved — `adminModule()` only affects `vite build`
|
|
257
257
|
- [ ] Build script can be `"vite build"` (Lovable default) or `"tsc && vite build"` — both work
|
|
258
258
|
|
|
259
|
+
### Environment Configuration (automatic)
|
|
260
|
+
|
|
261
|
+
`adminModule()` automatically injects `VITE_ADMIN_GATEWAY_URL` with the staging gateway URL (`https://admin-bff-stg.nsx.dev`). No `.env.staging` file or `--mode staging` script is needed for Lovable projects.
|
|
262
|
+
|
|
263
|
+
- [ ] Do NOT create `.env.staging` — the Vite plugin handles it
|
|
264
|
+
- [ ] Do NOT add `--mode staging` to the dev script
|
|
265
|
+
|
|
266
|
+
To override the gateway URL, either:
|
|
267
|
+
- Set `VITE_ADMIN_GATEWAY_URL` in a `.env` or `.env.local` file
|
|
268
|
+
- Pass `gatewayUrl` option: `...adminModule({ gatewayUrl: "http://localhost:8080" })`
|
|
269
|
+
- Disable injection: `...adminModule({ gatewayUrl: null })`
|
|
270
|
+
|
|
259
271
|
Expected `vite.config.ts` for Lovable:
|
|
260
272
|
|
|
261
273
|
```typescript
|
package/README.md
CHANGED
|
@@ -676,6 +676,38 @@ export default defineConfig(({ mode }) => ({
|
|
|
676
676
|
| `entry` | string | `"./src/spa.tsx"` | Entry file path |
|
|
677
677
|
| `outDir` | string | `"dist"` | Output directory |
|
|
678
678
|
| `additionalExternals` | string[] | `[]` | Extra externals |
|
|
679
|
+
| `gatewayUrl` | `string \| null` | `undefined` | Admin gateway URL for env injection. `undefined` = auto-inject staging URL, `string` = inject custom URL, `null` = disable injection. Explicit env vars always take precedence. |
|
|
680
|
+
|
|
681
|
+
### Automatic Environment Injection
|
|
682
|
+
|
|
683
|
+
`adminModule()` includes an env injection plugin that automatically sets `VITE_ADMIN_GATEWAY_URL` at build time. This ensures Lovable projects get real JWT tokens from the staging gateway without any manual `.env` configuration.
|
|
684
|
+
|
|
685
|
+
**Precedence order** (highest to lowest):
|
|
686
|
+
|
|
687
|
+
1. Explicit env var (`.env`, `.env.local`, shell environment)
|
|
688
|
+
2. `gatewayUrl` option passed to `adminModule()` or `defineModuleConfig()`
|
|
689
|
+
3. Default: `https://admin-bff-stg.nsx.dev` (staging)
|
|
690
|
+
|
|
691
|
+
**Known gateway URLs:**
|
|
692
|
+
|
|
693
|
+
| Environment | URL |
|
|
694
|
+
|---|---|
|
|
695
|
+
| Staging | `https://admin-bff-stg.nsx.dev` |
|
|
696
|
+
| Homol | `https://admin-bff-homol.nsx.dev` |
|
|
697
|
+
| Local | `http://localhost:8080` |
|
|
698
|
+
|
|
699
|
+
**Examples:**
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
// Default: staging URL auto-injected
|
|
703
|
+
...adminModule()
|
|
704
|
+
|
|
705
|
+
// Custom URL
|
|
706
|
+
...adminModule({ gatewayUrl: "http://localhost:8080" })
|
|
707
|
+
|
|
708
|
+
// Disable injection entirely
|
|
709
|
+
...adminModule({ gatewayUrl: null })
|
|
710
|
+
```
|
|
679
711
|
|
|
680
712
|
### Shared Externals
|
|
681
713
|
|
|
@@ -1103,6 +1135,8 @@ When `VITE_ADMIN_GATEWAY_URL` is set (or `gatewayUrl` is passed explicitly), the
|
|
|
1103
1135
|
2. `import.meta.env.VITE_ADMIN_GATEWAY_URL` environment variable
|
|
1104
1136
|
3. If neither is set, BFF integration is disabled (mock tokens, current behavior)
|
|
1105
1137
|
|
|
1138
|
+
> **Vite plugin auto-injection:** When using `adminModule()` or `defineModuleConfig()` from `@nsxbet/admin-sdk/vite`, the `VITE_ADMIN_GATEWAY_URL` environment variable is automatically set to the staging gateway URL. No `.env.staging` file or `--mode staging` script is needed. See the [Vite Configuration](#vite-configuration) section for override options.
|
|
1139
|
+
|
|
1106
1140
|
**Error handling:**
|
|
1107
1141
|
|
|
1108
1142
|
The UserSelector shows loading, error, and timeout states during the token fetch. If the gateway is unreachable, the developer can:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const LOVABLE_DOMAINS = [".lovableproject.com", ".lovable.app"];
|
|
2
|
+
export function isLovableContext() {
|
|
3
|
+
try {
|
|
4
|
+
const { hostname } = window.location;
|
|
5
|
+
return LOVABLE_DOMAINS.some((domain) => hostname.endsWith(domain));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function logPrivateNetworkGuidance(previewUrl) {
|
|
12
|
+
try {
|
|
13
|
+
const flagsUrl = "chrome://flags/#block-insecure-private-network-requests";
|
|
14
|
+
console.groupCollapsed("%c⚠️ Private Network Access — Lovable Preview", "color: #f59e0b; font-weight: bold; font-size: 14px");
|
|
15
|
+
console.warn([
|
|
16
|
+
"Chrome is blocking requests from this Lovable iframe to your local/private network API.",
|
|
17
|
+
"The gateway token fetch failed because the browser's Private Network Access policy",
|
|
18
|
+
"prevents public origins from reaching private IPs without explicit permission.",
|
|
19
|
+
"",
|
|
20
|
+
"━━━ Option 1 (Recommended) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
21
|
+
`Open this preview URL directly in a new tab:`,
|
|
22
|
+
` 👉 ${previewUrl}`,
|
|
23
|
+
"",
|
|
24
|
+
"This takes the page out of the iframe so Chrome can show the local network",
|
|
25
|
+
"access permission prompt.",
|
|
26
|
+
"",
|
|
27
|
+
"━━━ Option 2 (Alternative) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
28
|
+
"Disable the Chrome flag (less secure, but works globally):",
|
|
29
|
+
` 🔧 ${flagsUrl}`,
|
|
30
|
+
"",
|
|
31
|
+
'Set "Block insecure private network requests" to Disabled, then relaunch Chrome.',
|
|
32
|
+
].join("\n"));
|
|
33
|
+
console.groupEnd();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// best-effort — never throw from guidance logging
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -11,6 +11,35 @@ interface UserSelectorProps {
|
|
|
11
11
|
/** Callback when user is selected */
|
|
12
12
|
onUserSelected?: () => void;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Loading screen shown while fetching a BFF token.
|
|
16
|
+
*/
|
|
17
|
+
export declare function LoginLoadingScreen({ userName }: {
|
|
18
|
+
userName: string;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* Error screen shown when the gateway returns an error or is unreachable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function LoginErrorScreen({ errorMessage, gatewayUrl, isLovable, onRetry, onFallback, onBack, retrying, }: {
|
|
24
|
+
errorMessage: string;
|
|
25
|
+
gatewayUrl: string | null;
|
|
26
|
+
isLovable: boolean;
|
|
27
|
+
onRetry: () => void;
|
|
28
|
+
onFallback: () => void;
|
|
29
|
+
onBack: () => void;
|
|
30
|
+
retrying: boolean;
|
|
31
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
/**
|
|
33
|
+
* Timeout screen shown when the gateway doesn't respond in time.
|
|
34
|
+
*/
|
|
35
|
+
export declare function LoginTimeoutScreen({ gatewayUrl, isLovable, onRetry, onFallback, onBack, retrying, }: {
|
|
36
|
+
gatewayUrl: string | null;
|
|
37
|
+
isLovable: boolean;
|
|
38
|
+
onRetry: () => void;
|
|
39
|
+
onFallback: () => void;
|
|
40
|
+
onBack: () => void;
|
|
41
|
+
retrying: boolean;
|
|
42
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
14
43
|
/**
|
|
15
44
|
* Main User Selector Component
|
|
16
45
|
*/
|
|
@@ -6,8 +6,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* Uses Brasa Design System tokens for consistent styling.
|
|
7
7
|
*/
|
|
8
8
|
import { useState } from 'react';
|
|
9
|
-
import { Crown, Ban, Eye, User, Users, Sparkles, Wrench, Trash2, Plus, ChevronRight, Loader2, AlertCircle, Clock, ArrowLeft, RefreshCw, ShieldAlert, } from '@nsxbet/admin-ui';
|
|
9
|
+
import { Crown, Ban, Eye, User, Users, Sparkles, Wrench, Trash2, Plus, ChevronRight, Loader2, AlertCircle, Clock, ArrowLeft, RefreshCw, ShieldAlert, Info, Copy, Check, ChevronDown, } from '@nsxbet/admin-ui';
|
|
10
|
+
import { ExternalLink } from 'lucide-react';
|
|
10
11
|
import { GatewayTimeoutError } from '../client/in-memory';
|
|
12
|
+
import { isLovableContext, logPrivateNetworkGuidance } from '../client/private-network-guidance';
|
|
11
13
|
/**
|
|
12
14
|
* Get user icon based on roles
|
|
13
15
|
*/
|
|
@@ -88,9 +90,31 @@ function CustomUserForm({ onSubmit, onCancel, }) {
|
|
|
88
90
|
/**
|
|
89
91
|
* Loading screen shown while fetching a BFF token.
|
|
90
92
|
*/
|
|
91
|
-
function LoginLoadingScreen({ userName }) {
|
|
93
|
+
export function LoginLoadingScreen({ userName }) {
|
|
92
94
|
return (_jsx("div", { className: "min-h-screen bg-background flex items-center justify-center p-4", children: _jsxs("div", { className: "w-full max-w-md text-center", children: [_jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 mb-6", children: _jsx(Loader2, { className: "h-8 w-8 text-primary animate-spin" }) }), _jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "Authenticating\u2026" }), _jsxs("p", { className: "text-muted-foreground", children: ["Fetching token for ", _jsx("span", { className: "font-medium text-foreground", children: userName })] })] }) }));
|
|
93
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Guidance shown on error/timeout screens when running inside a Lovable preview iframe.
|
|
98
|
+
* Explains why Chrome's Private Network Access policy blocks the gateway request
|
|
99
|
+
* and offers two resolution paths.
|
|
100
|
+
*/
|
|
101
|
+
function LovableNetworkGuidance() {
|
|
102
|
+
const [showAlternative, setShowAlternative] = useState(false);
|
|
103
|
+
const [copied, setCopied] = useState(false);
|
|
104
|
+
const previewUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
105
|
+
const flagsUrl = "chrome://flags/#block-insecure-private-network-requests";
|
|
106
|
+
const handleCopy = async () => {
|
|
107
|
+
try {
|
|
108
|
+
await navigator.clipboard.writeText(flagsUrl);
|
|
109
|
+
setCopied(true);
|
|
110
|
+
setTimeout(() => setCopied(false), 2000);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// clipboard API may not be available in all contexts
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
return (_jsx("div", { "data-testid": "lovable-network-guidance", className: "w-full mt-4 rounded-xl border border-info/30 bg-info/5 p-4 text-left", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Info, { className: "h-5 w-5 text-info flex-shrink-0 mt-0.5" }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-foreground", children: "Lovable Preview \u2014 Private Network Access" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Chrome blocks requests from Lovable's iframe to local/private network APIs. Open this preview directly in a new tab to grant permission." })] }), _jsxs("a", { href: previewUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-info text-info-foreground font-medium text-sm hover:bg-info/90 transition-colors", children: [_jsx(ExternalLink, { className: "h-4 w-4" }), "Open preview directly"] }), _jsxs("div", { children: [_jsxs("button", { onClick: () => setShowAlternative(!showAlternative), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors", children: [_jsx(ChevronDown, { className: `h-3.5 w-3.5 transition-transform ${showAlternative ? "rotate-180" : ""}` }), "Alternative: disable Chrome flag"] }), showAlternative && (_jsxs("div", { className: "mt-2 space-y-2", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Disable the Private Network Access check in Chrome flags (less secure, but works globally):" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("code", { className: "flex-1 text-xs px-3 py-1.5 rounded-md bg-muted text-foreground font-mono break-all", children: flagsUrl }), _jsx("button", { onClick: handleCopy, className: "flex-shrink-0 p-1.5 rounded-md hover:bg-muted transition-colors", title: "Copy to clipboard", children: copied ? (_jsx(Check, { className: "h-3.5 w-3.5 text-success" })) : (_jsx(Copy, { className: "h-3.5 w-3.5 text-muted-foreground" })) })] }), _jsxs("p", { className: "text-xs text-muted-foreground/70", children: ["Set \u201CBlock insecure private network requests\u201D to ", _jsx("strong", { children: "Disabled" }), ", then relaunch Chrome."] })] }))] })] })] }) }));
|
|
117
|
+
}
|
|
94
118
|
/**
|
|
95
119
|
* Shared action bar for error / timeout screens.
|
|
96
120
|
*/
|
|
@@ -100,14 +124,14 @@ function LoginRecoveryActions({ onRetry, onFallback, onBack, retrying, }) {
|
|
|
100
124
|
/**
|
|
101
125
|
* Error screen shown when the gateway returns an error or is unreachable.
|
|
102
126
|
*/
|
|
103
|
-
function LoginErrorScreen({ errorMessage, gatewayUrl, onRetry, onFallback, onBack, retrying, }) {
|
|
104
|
-
return (_jsx("div", { className: "min-h-screen bg-background flex items-center justify-center p-4", children: _jsxs("div", { className: "w-full max-w-md flex flex-col items-center text-center", children: [_jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-destructive/10 mb-6", children: _jsx(AlertCircle, { className: "h-8 w-8 text-destructive" }) }), _jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "Authentication Failed" }), _jsx("p", { className: "text-muted-foreground mb-1", children: errorMessage }), gatewayUrl && (_jsx("p", { className: "text-xs text-muted-foreground/70 font-mono break-all", children: gatewayUrl })), _jsx(LoginRecoveryActions, { onRetry: onRetry, onFallback: onFallback, onBack: onBack, retrying: retrying })] }) }));
|
|
127
|
+
export function LoginErrorScreen({ errorMessage, gatewayUrl, isLovable, onRetry, onFallback, onBack, retrying, }) {
|
|
128
|
+
return (_jsx("div", { className: "min-h-screen bg-background flex items-center justify-center p-4", children: _jsxs("div", { className: "w-full max-w-md flex flex-col items-center text-center", children: [_jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-destructive/10 mb-6", children: _jsx(AlertCircle, { className: "h-8 w-8 text-destructive" }) }), _jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "Authentication Failed" }), _jsx("p", { className: "text-muted-foreground mb-1", children: errorMessage }), gatewayUrl && (_jsx("p", { className: "text-xs text-muted-foreground/70 font-mono break-all", children: gatewayUrl })), isLovable && _jsx(LovableNetworkGuidance, {}), _jsx(LoginRecoveryActions, { onRetry: onRetry, onFallback: onFallback, onBack: onBack, retrying: retrying })] }) }));
|
|
105
129
|
}
|
|
106
130
|
/**
|
|
107
131
|
* Timeout screen shown when the gateway doesn't respond in time.
|
|
108
132
|
*/
|
|
109
|
-
function LoginTimeoutScreen({ gatewayUrl, onRetry, onFallback, onBack, retrying, }) {
|
|
110
|
-
return (_jsx("div", { className: "min-h-screen bg-background flex items-center justify-center p-4", children: _jsxs("div", { className: "w-full max-w-md flex flex-col items-center text-center", children: [_jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-amber-500/10 mb-6", children: _jsx(Clock, { className: "h-8 w-8 text-amber-500" }) }), _jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "Gateway Timeout" }), _jsx("p", { className: "text-muted-foreground mb-1", children: "The gateway did not respond in time." }), gatewayUrl && (_jsx("p", { className: "text-xs text-muted-foreground/70 font-mono break-all", children: gatewayUrl })), _jsx(LoginRecoveryActions, { onRetry: onRetry, onFallback: onFallback, onBack: onBack, retrying: retrying })] }) }));
|
|
133
|
+
export function LoginTimeoutScreen({ gatewayUrl, isLovable, onRetry, onFallback, onBack, retrying, }) {
|
|
134
|
+
return (_jsx("div", { className: "min-h-screen bg-background flex items-center justify-center p-4", children: _jsxs("div", { className: "w-full max-w-md flex flex-col items-center text-center", children: [_jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-amber-500/10 mb-6", children: _jsx(Clock, { className: "h-8 w-8 text-amber-500" }) }), _jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "Gateway Timeout" }), _jsx("p", { className: "text-muted-foreground mb-1", children: "The gateway did not respond in time." }), gatewayUrl && (_jsx("p", { className: "text-xs text-muted-foreground/70 font-mono break-all", children: gatewayUrl })), isLovable && _jsx(LovableNetworkGuidance, {}), _jsx(LoginRecoveryActions, { onRetry: onRetry, onFallback: onFallback, onBack: onBack, retrying: retrying })] }) }));
|
|
111
135
|
}
|
|
112
136
|
/**
|
|
113
137
|
* Main User Selector Component
|
|
@@ -117,6 +141,7 @@ export function UserSelector({ authClient, onUserSelected }) {
|
|
|
117
141
|
const [loginState, setLoginState] = useState({ status: 'idle' });
|
|
118
142
|
const [, forceUpdate] = useState(0);
|
|
119
143
|
const users = authClient.getAvailableUsers();
|
|
144
|
+
const isLovable = isLovableContext();
|
|
120
145
|
const findUserName = (userId) => users.find((u) => u.id === userId)?.displayName ?? userId;
|
|
121
146
|
const handleSelectUser = async (userId) => {
|
|
122
147
|
if (!authClient.gatewayUrl) {
|
|
@@ -130,6 +155,9 @@ export function UserSelector({ authClient, onUserSelected }) {
|
|
|
130
155
|
onUserSelected?.();
|
|
131
156
|
}
|
|
132
157
|
catch (error) {
|
|
158
|
+
if (isLovable) {
|
|
159
|
+
logPrivateNetworkGuidance(window.location.href);
|
|
160
|
+
}
|
|
133
161
|
if (error instanceof GatewayTimeoutError) {
|
|
134
162
|
setLoginState({ status: 'timeout', userId });
|
|
135
163
|
}
|
|
@@ -181,10 +209,10 @@ export function UserSelector({ authClient, onUserSelected }) {
|
|
|
181
209
|
return _jsx(LoginLoadingScreen, { userName: findUserName(loginState.userId) });
|
|
182
210
|
}
|
|
183
211
|
if (loginState.status === 'error') {
|
|
184
|
-
return (_jsx(LoginErrorScreen, { errorMessage: loginState.error, gatewayUrl: authClient.gatewayUrl, onRetry: handleRetry, onFallback: handleFallbackToMock, onBack: handleBack, retrying: false }));
|
|
212
|
+
return (_jsx(LoginErrorScreen, { errorMessage: loginState.error, gatewayUrl: authClient.gatewayUrl, isLovable: isLovable, onRetry: handleRetry, onFallback: handleFallbackToMock, onBack: handleBack, retrying: false }));
|
|
185
213
|
}
|
|
186
214
|
if (loginState.status === 'timeout') {
|
|
187
|
-
return (_jsx(LoginTimeoutScreen, { gatewayUrl: authClient.gatewayUrl, onRetry: handleRetry, onFallback: handleFallbackToMock, onBack: handleBack, retrying: false }));
|
|
215
|
+
return (_jsx(LoginTimeoutScreen, { gatewayUrl: authClient.gatewayUrl, isLovable: isLovable, onRetry: handleRetry, onFallback: handleFallbackToMock, onBack: handleBack, retrying: false }));
|
|
188
216
|
}
|
|
189
217
|
return (_jsx("div", { className: "min-h-screen bg-background flex items-center justify-center p-4", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "text-center mb-8", children: [_jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary mb-4 shadow-lg shadow-primary/25", children: _jsx(Users, { className: "h-8 w-8 text-primary-foreground" }) }), _jsx("h1", { className: "text-2xl font-bold text-foreground mb-2", children: "Select User" }), _jsx("p", { className: "text-muted-foreground", children: "Choose a mock user to continue in development mode" })] }), _jsx("div", { className: "space-y-3 mb-6", children: users.map((user) => {
|
|
190
218
|
const isCustom = authClient.isCustomUser(user.id);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
declare const meta: Meta;
|
|
3
|
+
export default meta;
|
|
4
|
+
export declare const LoginErrorDefault: StoryObj;
|
|
5
|
+
export declare const LoginErrorLovable: StoryObj;
|
|
6
|
+
export declare const LoginTimeoutDefault: StoryObj;
|
|
7
|
+
export declare const LoginTimeoutLovable: StoryObj;
|
|
8
|
+
export declare const Loading: StoryObj;
|
|
9
|
+
export declare const Idle: StoryObj;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { LoginErrorScreen, LoginTimeoutScreen, LoginLoadingScreen, UserSelector } from "./UserSelector";
|
|
3
|
+
const noop = () => { };
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Auth/UserSelector",
|
|
6
|
+
parameters: {
|
|
7
|
+
layout: "fullscreen",
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export default meta;
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// LoginErrorScreen
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export const LoginErrorDefault = {
|
|
15
|
+
name: "LoginError — Default",
|
|
16
|
+
render: () => (_jsx(LoginErrorScreen, { errorMessage: "Failed to fetch: NetworkError when attempting to fetch resource.", gatewayUrl: "https://admin-bff-stg.nsx.dev", isLovable: false, onRetry: noop, onFallback: noop, onBack: noop, retrying: false })),
|
|
17
|
+
};
|
|
18
|
+
export const LoginErrorLovable = {
|
|
19
|
+
name: "LoginError — Lovable Context",
|
|
20
|
+
render: () => (_jsx(LoginErrorScreen, { errorMessage: "Failed to fetch: NetworkError when attempting to fetch resource.", gatewayUrl: "https://admin-bff-stg.nsx.dev", isLovable: true, onRetry: noop, onFallback: noop, onBack: noop, retrying: false })),
|
|
21
|
+
};
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// LoginTimeoutScreen
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const LoginTimeoutDefault = {
|
|
26
|
+
name: "LoginTimeout — Default",
|
|
27
|
+
render: () => (_jsx(LoginTimeoutScreen, { gatewayUrl: "https://admin-bff-stg.nsx.dev", isLovable: false, onRetry: noop, onFallback: noop, onBack: noop, retrying: false })),
|
|
28
|
+
};
|
|
29
|
+
export const LoginTimeoutLovable = {
|
|
30
|
+
name: "LoginTimeout — Lovable Context",
|
|
31
|
+
render: () => (_jsx(LoginTimeoutScreen, { gatewayUrl: "https://admin-bff-stg.nsx.dev", isLovable: true, onRetry: noop, onFallback: noop, onBack: noop, retrying: false })),
|
|
32
|
+
};
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// LoginLoadingScreen
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
export const Loading = {
|
|
37
|
+
name: "UserSelector — Loading",
|
|
38
|
+
render: () => _jsx(LoginLoadingScreen, { userName: "Admin User" }),
|
|
39
|
+
};
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// UserSelector — Idle (full component)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
const mockUsers = [
|
|
44
|
+
{ id: "admin", displayName: "Admin User", email: "admin@nsx.bet", roles: ["admin"] },
|
|
45
|
+
{ id: "editor", displayName: "Editor User", email: "editor@nsx.bet", roles: ["admin.tasks.edit", "admin.users.view"] },
|
|
46
|
+
{ id: "viewer", displayName: "Viewer User", email: "viewer@nsx.bet", roles: ["admin.tasks.view", "admin.users.view"] },
|
|
47
|
+
{ id: "no-access", displayName: "No Access", email: "noaccess@nsx.bet", roles: [] },
|
|
48
|
+
];
|
|
49
|
+
function createStoryClient() {
|
|
50
|
+
return {
|
|
51
|
+
type: "in-memory",
|
|
52
|
+
gatewayUrl: null,
|
|
53
|
+
getAvailableUsers: () => mockUsers,
|
|
54
|
+
isCustomUser: () => false,
|
|
55
|
+
login: async () => { },
|
|
56
|
+
logout: async () => { },
|
|
57
|
+
initialize: async () => false,
|
|
58
|
+
isAuthenticated: () => false,
|
|
59
|
+
hasPermission: () => false,
|
|
60
|
+
getUser: () => null,
|
|
61
|
+
getAccessToken: async () => "mock-token",
|
|
62
|
+
subscribe: () => () => { },
|
|
63
|
+
createCustomUser: async (user) => ({ id: "custom", ...user }),
|
|
64
|
+
deleteCustomUser: () => false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export const Idle = {
|
|
68
|
+
name: "UserSelector — Idle",
|
|
69
|
+
render: () => _jsx(UserSelector, { authClient: createStoryClient() }),
|
|
70
|
+
};
|
|
@@ -5,7 +5,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
* Works with both in-memory (mock) and Keycloak auth clients.
|
|
6
6
|
* Shows user selection screen when in-memory client has no selected user.
|
|
7
7
|
*/
|
|
8
|
-
import { createContext, useContext, useEffect, useState, useCallback } from 'react';
|
|
8
|
+
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react';
|
|
9
|
+
import { useCallbackRef } from '../hooks/useCallbackRef';
|
|
9
10
|
import { UserSelector } from '../auth/components/UserSelector';
|
|
10
11
|
const AuthContext = createContext(null);
|
|
11
12
|
/**
|
|
@@ -80,15 +81,17 @@ export function AuthProvider({ children, authClient }) {
|
|
|
80
81
|
});
|
|
81
82
|
setNeedsUserSelection(false);
|
|
82
83
|
}, [authClient]);
|
|
83
|
-
|
|
84
|
-
const
|
|
84
|
+
const getAccessToken = useCallbackRef(() => authClient.getAccessToken());
|
|
85
|
+
const hasPermission = useCallbackRef((permission) => authClient.hasPermission(permission));
|
|
86
|
+
const logout = useCallbackRef(() => authClient.logout());
|
|
87
|
+
const contextValue = useMemo(() => ({
|
|
85
88
|
isAuthenticated: authState.isAuthenticated,
|
|
86
89
|
user: authState.user,
|
|
87
|
-
getAccessToken
|
|
88
|
-
hasPermission
|
|
89
|
-
logout
|
|
90
|
+
getAccessToken,
|
|
91
|
+
hasPermission,
|
|
92
|
+
logout,
|
|
90
93
|
authClient,
|
|
91
|
-
};
|
|
94
|
+
}), [authState.isAuthenticated, authState.user, authClient, getAccessToken, hasPermission, logout]);
|
|
92
95
|
// Show loading during initialization
|
|
93
96
|
if (isInitializing) {
|
|
94
97
|
return _jsx(LoadingScreen, {});
|
package/dist/hooks/useAuth.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified authentication hook that works in shell, standalone, and mock modes
|
|
3
3
|
*/
|
|
4
|
+
import { useMemo } from 'react';
|
|
4
5
|
import { usePlatformAPI } from './usePlatformAPI';
|
|
6
|
+
import { useCallbackRef } from './useCallbackRef';
|
|
5
7
|
import { useOptionalAuthContext } from '../components/AuthProvider';
|
|
6
8
|
/**
|
|
7
9
|
* useAuth provides authentication methods that work seamlessly across:
|
|
@@ -11,24 +13,27 @@ import { useOptionalAuthContext } from '../components/AuthProvider';
|
|
|
11
13
|
export function useAuth() {
|
|
12
14
|
const { isShellMode, api } = usePlatformAPI();
|
|
13
15
|
const authContext = useOptionalAuthContext();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
const standaloneGetUser = useCallbackRef(authContext
|
|
17
|
+
? () => authContext.user || { id: '', email: '', displayName: '', roles: [] }
|
|
18
|
+
: undefined);
|
|
19
|
+
return useMemo(() => {
|
|
20
|
+
if (isShellMode && api) {
|
|
21
|
+
return {
|
|
22
|
+
getAccessToken: api.auth.getAccessToken,
|
|
23
|
+
hasPermission: api.auth.hasPermission,
|
|
24
|
+
getUser: api.auth.getUser,
|
|
25
|
+
logout: api.auth.logout,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (authContext) {
|
|
29
|
+
return {
|
|
30
|
+
getAccessToken: authContext.getAccessToken,
|
|
31
|
+
hasPermission: authContext.hasPermission,
|
|
32
|
+
getUser: standaloneGetUser,
|
|
33
|
+
logout: authContext.logout,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
throw new Error('useAuth must be used within AuthProvider. ' +
|
|
37
|
+
'Wrap your app with <AuthProvider authClient={...}> from @nsxbet/admin-sdk');
|
|
38
|
+
}, [isShellMode, api, authContext, standaloneGetUser]);
|
|
34
39
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a stable function reference that always invokes the latest callback.
|
|
3
|
+
*
|
|
4
|
+
* Follows the Radix UI / "Latest Ref" pattern: the returned function identity
|
|
5
|
+
* never changes, but calling it always executes the most recent `callback`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useCallbackRef<T extends (...args: any[]) => any>(callback: T | undefined): T;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useRef, useEffect, useMemo } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a stable function reference that always invokes the latest callback.
|
|
4
|
+
*
|
|
5
|
+
* Follows the Radix UI / "Latest Ref" pattern: the returned function identity
|
|
6
|
+
* never changes, but calling it always executes the most recent `callback`.
|
|
7
|
+
*/
|
|
8
|
+
export function useCallbackRef(callback) {
|
|
9
|
+
const callbackRef = useRef(callback);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
callbackRef.current = callback;
|
|
12
|
+
});
|
|
13
|
+
return useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
|
|
14
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to access the platform API
|
|
3
|
+
*/
|
|
4
|
+
import { useMemo } from 'react';
|
|
1
5
|
/**
|
|
2
6
|
* Returns the platform API and whether the module is running in shell mode
|
|
3
7
|
*/
|
|
4
8
|
export function usePlatformAPI() {
|
|
5
9
|
const isShellMode = typeof window !== 'undefined' && window.__ADMIN_PLATFORM_API__ !== undefined;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
api: isShellMode ? window.__ADMIN_PLATFORM_API__ : undefined,
|
|
9
|
-
};
|
|
10
|
+
const api = isShellMode ? window.__ADMIN_PLATFORM_API__ : undefined;
|
|
11
|
+
return useMemo(() => ({ isShellMode, api }), [isShellMode, api]);
|
|
10
12
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { useFetch } from './hooks/useFetch';
|
|
|
13
13
|
export { useTelemetry } from './hooks/useTelemetry';
|
|
14
14
|
export { useI18n } from './hooks/useI18n';
|
|
15
15
|
export { useTimestamp } from './hooks/useTimestamp';
|
|
16
|
+
export { useCallbackRef } from './hooks/useCallbackRef';
|
|
16
17
|
export { createInMemoryAuthClient, clearInMemoryAuth, createMockUsersFromRoles, createKeycloakAuthClient, UserSelector, } from './auth';
|
|
17
18
|
export type { AuthClient, InMemoryAuthClient, MockUser, MockUserRoles, AuthState, AuthStateCallback, InMemoryAuthClientOptions, KeycloakAuthClientOptions, } from './auth';
|
|
18
19
|
export type { PlatformAPI, Breadcrumb, User, TimezoneMode, TimestampFormat } from './types/platform';
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export { useFetch } from './hooks/useFetch';
|
|
|
14
14
|
export { useTelemetry } from './hooks/useTelemetry';
|
|
15
15
|
export { useI18n } from './hooks/useI18n';
|
|
16
16
|
export { useTimestamp } from './hooks/useTimestamp';
|
|
17
|
+
export { useCallbackRef } from './hooks/useCallbackRef';
|
|
17
18
|
// Auth Client
|
|
18
19
|
export { createInMemoryAuthClient, clearInMemoryAuth, createMockUsersFromRoles, createKeycloakAuthClient, UserSelector, } from './auth';
|
|
19
20
|
// i18n
|
|
@@ -26,6 +26,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
import { createContext, useContext, useState, useEffect, useCallback, useMemo, } from 'react';
|
|
29
|
+
import { useCallbackRef } from '../hooks/useCallbackRef';
|
|
29
30
|
const RegistryContext = createContext(null);
|
|
30
31
|
/**
|
|
31
32
|
* Registry provider component
|
|
@@ -51,14 +52,14 @@ export function AdminShellRegistry({ store, children, onModulesChange, }) {
|
|
|
51
52
|
const [modules, setModules] = useState([]);
|
|
52
53
|
const [isLoading, setIsLoading] = useState(true);
|
|
53
54
|
const [error, setError] = useState(null);
|
|
54
|
-
|
|
55
|
+
const stableOnModulesChange = useCallbackRef(onModulesChange);
|
|
55
56
|
const reload = useCallback(async () => {
|
|
56
57
|
setIsLoading(true);
|
|
57
58
|
setError(null);
|
|
58
59
|
try {
|
|
59
60
|
const result = await store.modules.findAll();
|
|
60
61
|
setModules(result);
|
|
61
|
-
|
|
62
|
+
stableOnModulesChange(result);
|
|
62
63
|
}
|
|
63
64
|
catch (err) {
|
|
64
65
|
setError(err instanceof Error ? err : new Error('Failed to load modules'));
|
|
@@ -67,7 +68,7 @@ export function AdminShellRegistry({ store, children, onModulesChange, }) {
|
|
|
67
68
|
finally {
|
|
68
69
|
setIsLoading(false);
|
|
69
70
|
}
|
|
70
|
-
}, [store,
|
|
71
|
+
}, [store, stableOnModulesChange]);
|
|
71
72
|
// Initial load
|
|
72
73
|
useEffect(() => {
|
|
73
74
|
reload();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
3
3
|
const initialState = {
|
|
4
4
|
theme: "system",
|
|
5
5
|
setTheme: () => null,
|
|
@@ -24,13 +24,11 @@ storageKey = "admin-ui-theme", ...props }) {
|
|
|
24
24
|
}
|
|
25
25
|
root.classList.add(theme);
|
|
26
26
|
}, [theme]);
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
setTheme
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
};
|
|
27
|
+
const setThemeStable = useCallback((newTheme) => {
|
|
28
|
+
localStorage.setItem(storageKey, newTheme);
|
|
29
|
+
setTheme(newTheme);
|
|
30
|
+
}, [storageKey]);
|
|
31
|
+
const value = useMemo(() => ({ theme, setTheme: setThemeStable }), [theme, setThemeStable]);
|
|
34
32
|
return (_jsx(ThemeProviderContext.Provider, { ...props, value: value, children: children }));
|
|
35
33
|
}
|
|
36
34
|
// eslint-disable-next-line react-refresh/only-export-components
|
package/dist/vite/config.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Plugin } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Default admin gateway URL (staging environment).
|
|
4
|
+
* Used by the env injection plugin when no explicit value is configured.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ADMIN_GATEWAY_STAGING_URL = "https://admin-bff-stg.nsx.dev";
|
|
2
7
|
/**
|
|
3
8
|
* Shared dependencies that are externalized in module builds.
|
|
4
9
|
* These are provided by the shell via import maps.
|
|
@@ -28,6 +33,15 @@ export interface AdminModuleOptions {
|
|
|
28
33
|
* - "always": Always apply during `vite build`. Used by defineModuleConfig().
|
|
29
34
|
*/
|
|
30
35
|
buildMode?: "auto" | "always";
|
|
36
|
+
/**
|
|
37
|
+
* Admin gateway URL for BFF token integration (InMemory auth).
|
|
38
|
+
* - `undefined` (default): auto-inject staging URL (`https://admin-bff-stg.nsx.dev`)
|
|
39
|
+
* - `string`: inject the provided URL
|
|
40
|
+
* - `null`: disable automatic injection entirely
|
|
41
|
+
*
|
|
42
|
+
* Explicit env vars (`.env` files, shell env) always take precedence over this option.
|
|
43
|
+
*/
|
|
44
|
+
gatewayUrl?: string | null;
|
|
31
45
|
}
|
|
32
46
|
export interface ModuleConfigOptions extends AdminModuleOptions {
|
|
33
47
|
/**
|
package/dist/vite/config.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { loadEnv } from "vite";
|
|
1
2
|
import { generateModuleManifestPlugin, serveDistPlugin } from "./plugins.js";
|
|
2
3
|
import { adminModuleI18nPlugin } from "./i18n-plugin.js";
|
|
3
4
|
import { getSharedExternals } from "./AdminShellSharedDeps.js";
|
|
5
|
+
const ENV_KEY = "VITE_ADMIN_GATEWAY_URL";
|
|
6
|
+
/**
|
|
7
|
+
* Default admin gateway URL (staging environment).
|
|
8
|
+
* Used by the env injection plugin when no explicit value is configured.
|
|
9
|
+
*/
|
|
10
|
+
export const ADMIN_GATEWAY_STAGING_URL = "https://admin-bff-stg.nsx.dev";
|
|
4
11
|
/**
|
|
5
12
|
* Shared dependencies that are externalized in module builds.
|
|
6
13
|
* These are provided by the shell via import maps.
|
|
@@ -40,13 +47,33 @@ export const SHARED_EXTERNALS = getSharedExternals();
|
|
|
40
47
|
* ```
|
|
41
48
|
*/
|
|
42
49
|
export function adminModule(options = {}) {
|
|
43
|
-
const { entry = "./src/spa.tsx", outDir = "dist", additionalExternals = [], buildMode = "auto", } = options;
|
|
50
|
+
const { entry = "./src/spa.tsx", outDir = "dist", additionalExternals = [], buildMode = "auto", gatewayUrl, } = options;
|
|
44
51
|
const externals = [...SHARED_EXTERNALS, ...additionalExternals];
|
|
45
52
|
function isModuleBuild() {
|
|
46
53
|
if (buildMode === "always")
|
|
47
54
|
return true;
|
|
48
55
|
return process.env.ADMIN_MODULE === "true";
|
|
49
56
|
}
|
|
57
|
+
const envPlugin = {
|
|
58
|
+
name: "admin-module-env",
|
|
59
|
+
config(_config, { mode }) {
|
|
60
|
+
if (gatewayUrl === null)
|
|
61
|
+
return;
|
|
62
|
+
const env = loadEnv(mode, process.cwd(), "VITE_");
|
|
63
|
+
const fromEnv = process.env[ENV_KEY] || env[ENV_KEY];
|
|
64
|
+
if (fromEnv) {
|
|
65
|
+
console.log(`[admin-module-env] ${ENV_KEY} → ${fromEnv} (from env)`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const url = gatewayUrl ?? ADMIN_GATEWAY_STAGING_URL;
|
|
69
|
+
console.log(`[admin-module-env] ${ENV_KEY} → ${url} (auto-injected)`);
|
|
70
|
+
return {
|
|
71
|
+
define: {
|
|
72
|
+
[`import.meta.env.${ENV_KEY}`]: JSON.stringify(url),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
50
77
|
const buildConfigPlugin = {
|
|
51
78
|
name: "admin-module-build-config",
|
|
52
79
|
config(_config, { command }) {
|
|
@@ -73,6 +100,7 @@ export function adminModule(options = {}) {
|
|
|
73
100
|
},
|
|
74
101
|
};
|
|
75
102
|
return [
|
|
103
|
+
envPlugin,
|
|
76
104
|
adminModuleI18nPlugin(),
|
|
77
105
|
serveDistPlugin({ distDir: outDir }),
|
|
78
106
|
generateModuleManifestPlugin(),
|
package/dist/vite/index.d.ts
CHANGED
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export { generateModuleManifestPlugin, serveDistPlugin, type GenerateModuleManifestPluginOptions, type ServeDistPluginOptions, } from "./plugins.js";
|
|
17
17
|
export { adminModuleI18nPlugin, type AdminModuleI18nPluginOptions, } from "./i18n-plugin.js";
|
|
18
|
-
export { adminModule, defineModuleConfig, SHARED_EXTERNALS, type AdminModuleOptions, type ModuleConfigOptions, } from "./config.js";
|
|
18
|
+
export { adminModule, defineModuleConfig, SHARED_EXTERNALS, ADMIN_GATEWAY_STAGING_URL, type AdminModuleOptions, type ModuleConfigOptions, } from "./config.js";
|
|
19
19
|
export { AdminShellSharedDeps, SHARED_DEPS_CONFIG, getSharedExternals, type SharedDepConfig, } from "./AdminShellSharedDeps.js";
|
package/dist/vite/index.js
CHANGED
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export { generateModuleManifestPlugin, serveDistPlugin, } from "./plugins.js";
|
|
17
17
|
export { adminModuleI18nPlugin, } from "./i18n-plugin.js";
|
|
18
|
-
export { adminModule, defineModuleConfig, SHARED_EXTERNALS, } from "./config.js";
|
|
18
|
+
export { adminModule, defineModuleConfig, SHARED_EXTERNALS, ADMIN_GATEWAY_STAGING_URL, } from "./config.js";
|
|
19
19
|
export { AdminShellSharedDeps, SHARED_DEPS_CONFIG, getSharedExternals, } from "./AdminShellSharedDeps.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nsxbet/admin-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "SDK for building NSX Admin modules with integrated shell",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"build": "tsc",
|
|
38
38
|
"dev": "tsc --watch --preserveWatchOutput",
|
|
39
39
|
"type-check": "tsc --noEmit",
|
|
40
|
-
"test": "vitest run --passWithNoTests"
|
|
40
|
+
"test": "vitest run --passWithNoTests",
|
|
41
|
+
"storybook": "storybook dev -p 6007 --host 0.0.0.0",
|
|
42
|
+
"build-storybook": "storybook build -o storybook-static"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
45
|
"@vitejs/plugin-react": "^4.0.0",
|
|
@@ -66,14 +68,20 @@
|
|
|
66
68
|
},
|
|
67
69
|
"devDependencies": {
|
|
68
70
|
"@nsxbet/admin-ui": "workspace:*",
|
|
71
|
+
"@storybook/react-vite": "^10.3.0",
|
|
69
72
|
"@testing-library/jest-dom": "^6.9.1",
|
|
70
73
|
"@testing-library/react": "^16.3.2",
|
|
71
74
|
"@types/react": "^18.2.0",
|
|
72
75
|
"@types/react-dom": "^18.2.0",
|
|
73
76
|
"@vitejs/plugin-react": "^4.3.4",
|
|
77
|
+
"autoprefixer": "^10.4.27",
|
|
74
78
|
"i18next": "^25.7.4",
|
|
79
|
+
"postcss": "^8.5.8",
|
|
75
80
|
"react-i18next": "^16.5.3",
|
|
76
81
|
"react-router-dom": "^6.20.1",
|
|
82
|
+
"storybook": "^10.3.0",
|
|
83
|
+
"tailwindcss": "^3.4.0",
|
|
84
|
+
"tailwindcss-animate": "^1.0.7",
|
|
77
85
|
"typescript": "^5.7.0",
|
|
78
86
|
"vite": "^6.0.0",
|
|
79
87
|
"vitest": "^1.0.0"
|