@nsxbet/admin-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +680 -0
- package/dist/auth/client/in-memory.d.ts +27 -0
- package/dist/auth/client/in-memory.d.ts.map +1 -0
- package/dist/auth/client/in-memory.js +242 -0
- package/dist/auth/client/index.d.ts +7 -0
- package/dist/auth/client/index.d.ts.map +1 -0
- package/dist/auth/client/index.js +7 -0
- package/dist/auth/client/interface.d.ts +115 -0
- package/dist/auth/client/interface.d.ts.map +1 -0
- package/dist/auth/client/interface.js +7 -0
- package/dist/auth/client/keycloak.d.ts +19 -0
- package/dist/auth/client/keycloak.d.ts.map +1 -0
- package/dist/auth/client/keycloak.js +126 -0
- package/dist/auth/components/UserSelector.d.ts +19 -0
- package/dist/auth/components/UserSelector.d.ts.map +1 -0
- package/dist/auth/components/UserSelector.js +100 -0
- package/dist/auth/components/index.d.ts +5 -0
- package/dist/auth/components/index.d.ts.map +1 -0
- package/dist/auth/components/index.js +4 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +7 -0
- package/dist/components/AuthProvider.d.ts +48 -0
- package/dist/components/AuthProvider.d.ts.map +1 -0
- package/dist/components/AuthProvider.js +117 -0
- package/dist/hooks/useAuth.d.ts +21 -0
- package/dist/hooks/useAuth.d.ts.map +1 -0
- package/dist/hooks/useAuth.js +34 -0
- package/dist/hooks/useFetch.d.ts +8 -0
- package/dist/hooks/useFetch.d.ts.map +1 -0
- package/dist/hooks/useFetch.js +31 -0
- package/dist/hooks/useI18n.d.ts +46 -0
- package/dist/hooks/useI18n.d.ts.map +1 -0
- package/dist/hooks/useI18n.js +95 -0
- package/dist/hooks/usePlatformAPI.d.ts +12 -0
- package/dist/hooks/usePlatformAPI.d.ts.map +1 -0
- package/dist/hooks/usePlatformAPI.js +10 -0
- package/dist/hooks/useTelemetry.d.ts +17 -0
- package/dist/hooks/useTelemetry.d.ts.map +1 -0
- package/dist/hooks/useTelemetry.js +36 -0
- package/dist/i18n/config.d.ts +26 -0
- package/dist/i18n/config.d.ts.map +1 -0
- package/dist/i18n/config.js +92 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/locales/en-US.json +144 -0
- package/dist/i18n/locales/es.json +144 -0
- package/dist/i18n/locales/pt-BR.json +144 -0
- package/dist/i18n/locales/ro.json +144 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/registry/AdminShellRegistry.d.ts +140 -0
- package/dist/registry/AdminShellRegistry.d.ts.map +1 -0
- package/dist/registry/AdminShellRegistry.js +237 -0
- package/dist/registry/client/http.d.ts +21 -0
- package/dist/registry/client/http.d.ts.map +1 -0
- package/dist/registry/client/http.js +107 -0
- package/dist/registry/client/in-memory.d.ts +36 -0
- package/dist/registry/client/in-memory.d.ts.map +1 -0
- package/dist/registry/client/in-memory.js +242 -0
- package/dist/registry/client/index.d.ts +7 -0
- package/dist/registry/client/index.d.ts.map +1 -0
- package/dist/registry/client/index.js +5 -0
- package/dist/registry/client/interface.d.ts +96 -0
- package/dist/registry/client/interface.d.ts.map +1 -0
- package/dist/registry/client/interface.js +7 -0
- package/dist/registry/index.d.ts +12 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/types/index.d.ts +9 -0
- package/dist/registry/types/index.d.ts.map +1 -0
- package/dist/registry/types/index.js +6 -0
- package/dist/registry/types/manifest.d.ts +98 -0
- package/dist/registry/types/manifest.d.ts.map +1 -0
- package/dist/registry/types/manifest.js +81 -0
- package/dist/registry/types/module.d.ts +115 -0
- package/dist/registry/types/module.d.ts.map +1 -0
- package/dist/registry/types/module.js +6 -0
- package/dist/router/DynamicModule.d.ts +50 -0
- package/dist/router/DynamicModule.d.ts.map +1 -0
- package/dist/router/DynamicModule.js +141 -0
- package/dist/router/index.d.ts +2 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +1 -0
- package/dist/shell/AdminShell.d.ts +38 -0
- package/dist/shell/AdminShell.d.ts.map +1 -0
- package/dist/shell/AdminShell.js +299 -0
- package/dist/shell/BackofficeShell.d.ts +38 -0
- package/dist/shell/BackofficeShell.d.ts.map +1 -0
- package/dist/shell/BackofficeShell.js +299 -0
- package/dist/shell/components/CommandPalette.d.ts +8 -0
- package/dist/shell/components/CommandPalette.d.ts.map +1 -0
- package/dist/shell/components/CommandPalette.js +197 -0
- package/dist/shell/components/HomePage.d.ts +2 -0
- package/dist/shell/components/HomePage.d.ts.map +1 -0
- package/dist/shell/components/HomePage.js +32 -0
- package/dist/shell/components/LeftNav.d.ts +7 -0
- package/dist/shell/components/LeftNav.d.ts.map +1 -0
- package/dist/shell/components/LeftNav.js +247 -0
- package/dist/shell/components/MainContent.d.ts +9 -0
- package/dist/shell/components/MainContent.d.ts.map +1 -0
- package/dist/shell/components/MainContent.js +88 -0
- package/dist/shell/components/ModuleOverview.d.ts +7 -0
- package/dist/shell/components/ModuleOverview.d.ts.map +1 -0
- package/dist/shell/components/ModuleOverview.js +40 -0
- package/dist/shell/components/ProfilePage.d.ts +2 -0
- package/dist/shell/components/ProfilePage.d.ts.map +1 -0
- package/dist/shell/components/ProfilePage.js +30 -0
- package/dist/shell/components/RegistryPage.d.ts +8 -0
- package/dist/shell/components/RegistryPage.d.ts.map +1 -0
- package/dist/shell/components/RegistryPage.js +129 -0
- package/dist/shell/components/SettingsPage.d.ts +2 -0
- package/dist/shell/components/SettingsPage.d.ts.map +1 -0
- package/dist/shell/components/SettingsPage.js +60 -0
- package/dist/shell/components/TopBar.d.ts +8 -0
- package/dist/shell/components/TopBar.d.ts.map +1 -0
- package/dist/shell/components/TopBar.js +61 -0
- package/dist/shell/components/index.d.ts +10 -0
- package/dist/shell/components/index.d.ts.map +1 -0
- package/dist/shell/components/index.js +7 -0
- package/dist/shell/components/theme-provider.d.ts +15 -0
- package/dist/shell/components/theme-provider.d.ts.map +1 -0
- package/dist/shell/components/theme-provider.js +39 -0
- package/dist/shell/index.d.ts +9 -0
- package/dist/shell/index.d.ts.map +1 -0
- package/dist/shell/index.js +8 -0
- package/dist/shell/search/fuzzy.d.ts +18 -0
- package/dist/shell/search/fuzzy.d.ts.map +1 -0
- package/dist/shell/search/fuzzy.js +121 -0
- package/dist/shell/search/index.d.ts +3 -0
- package/dist/shell/search/index.d.ts.map +1 -0
- package/dist/shell/search/index.js +1 -0
- package/dist/shell/telemetry.d.ts +7 -0
- package/dist/shell/telemetry.d.ts.map +1 -0
- package/dist/shell/telemetry.js +25 -0
- package/dist/shell/types.d.ts +110 -0
- package/dist/shell/types.d.ts.map +1 -0
- package/dist/shell/types.js +4 -0
- package/dist/tailwind/index.d.ts +20 -0
- package/dist/tailwind/index.d.ts.map +1 -0
- package/dist/tailwind/index.js +42 -0
- package/dist/types/keycloak.d.ts +26 -0
- package/dist/types/keycloak.d.ts.map +1 -0
- package/dist/types/keycloak.js +1 -0
- package/dist/types/platform.d.ts +83 -0
- package/dist/types/platform.d.ts.map +1 -0
- package/dist/types/platform.js +5 -0
- package/dist/vite/config.d.ts +71 -0
- package/dist/vite/config.d.ts.map +1 -0
- package/dist/vite/config.js +87 -0
- package/dist/vite/index.d.ts +18 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +17 -0
- package/dist/vite/plugins.d.ts +44 -0
- package/dist/vite/plugins.d.ts.map +1 -0
- package/dist/vite/plugins.js +74 -0
- package/package.json +86 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Auth Client
|
|
3
|
+
*
|
|
4
|
+
* Provides fake authentication for development and testing.
|
|
5
|
+
* Users can be selected from a predefined list or created custom.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Default mock users
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_MOCK_USERS = [
|
|
11
|
+
{
|
|
12
|
+
id: 'admin-user',
|
|
13
|
+
email: 'admin@example.com',
|
|
14
|
+
displayName: 'Admin User',
|
|
15
|
+
roles: [
|
|
16
|
+
'admin',
|
|
17
|
+
// Tasks - full access
|
|
18
|
+
'admin.tasks.view',
|
|
19
|
+
'admin.tasks.edit',
|
|
20
|
+
'admin.tasks.delete',
|
|
21
|
+
// Users - full access
|
|
22
|
+
'admin.users.view',
|
|
23
|
+
'admin.users.edit',
|
|
24
|
+
'admin.users.delete',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'editor-user',
|
|
29
|
+
email: 'editor@example.com',
|
|
30
|
+
displayName: 'Editor User',
|
|
31
|
+
roles: [
|
|
32
|
+
// Tasks - can create/edit, no delete
|
|
33
|
+
'admin.tasks.view',
|
|
34
|
+
'admin.tasks.edit',
|
|
35
|
+
// Users - view only
|
|
36
|
+
'admin.users.view',
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'viewer-user',
|
|
41
|
+
email: 'viewer@example.com',
|
|
42
|
+
displayName: 'Viewer User',
|
|
43
|
+
roles: [
|
|
44
|
+
// View only - no edit or delete
|
|
45
|
+
'admin.tasks.view',
|
|
46
|
+
'admin.users.view',
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'no-access-user',
|
|
51
|
+
email: 'noaccess@example.com',
|
|
52
|
+
displayName: 'No Access User',
|
|
53
|
+
roles: [],
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
const DEFAULT_STORAGE_KEY = '@nsxbet/auth';
|
|
57
|
+
/**
|
|
58
|
+
* Create an in-memory auth client for development/testing
|
|
59
|
+
*/
|
|
60
|
+
export function createInMemoryAuthClient(options = {}) {
|
|
61
|
+
const { users: customUsers = [], replaceDefaults = false, storageKey = DEFAULT_STORAGE_KEY, } = options;
|
|
62
|
+
// Merge users
|
|
63
|
+
const predefinedUsers = replaceDefaults ? customUsers : [...DEFAULT_MOCK_USERS, ...customUsers];
|
|
64
|
+
// State
|
|
65
|
+
let selectedUser = null;
|
|
66
|
+
const subscribers = new Set();
|
|
67
|
+
/**
|
|
68
|
+
* Load state from localStorage
|
|
69
|
+
*/
|
|
70
|
+
function loadStorage() {
|
|
71
|
+
try {
|
|
72
|
+
const data = localStorage.getItem(storageKey);
|
|
73
|
+
if (data) {
|
|
74
|
+
return JSON.parse(data);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore parse errors
|
|
79
|
+
}
|
|
80
|
+
return { selectedUserId: null, customUsers: [] };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Save state to localStorage
|
|
84
|
+
*/
|
|
85
|
+
function saveStorage(data) {
|
|
86
|
+
localStorage.setItem(storageKey, JSON.stringify(data));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get all available users (predefined + custom)
|
|
90
|
+
*/
|
|
91
|
+
function getAllUsers() {
|
|
92
|
+
const storage = loadStorage();
|
|
93
|
+
return [...predefinedUsers, ...storage.customUsers];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Find user by ID
|
|
97
|
+
*/
|
|
98
|
+
function findUser(userId) {
|
|
99
|
+
return getAllUsers().find((u) => u.id === userId);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Notify subscribers of state change
|
|
103
|
+
*/
|
|
104
|
+
function notifySubscribers() {
|
|
105
|
+
const state = {
|
|
106
|
+
isAuthenticated: selectedUser !== null,
|
|
107
|
+
user: selectedUser ? {
|
|
108
|
+
id: selectedUser.id,
|
|
109
|
+
email: selectedUser.email,
|
|
110
|
+
displayName: selectedUser.displayName,
|
|
111
|
+
} : null,
|
|
112
|
+
};
|
|
113
|
+
subscribers.forEach((callback) => callback(state));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Convert MockUser to User
|
|
117
|
+
*/
|
|
118
|
+
function toUser(mockUser) {
|
|
119
|
+
return {
|
|
120
|
+
id: mockUser.id,
|
|
121
|
+
email: mockUser.email,
|
|
122
|
+
displayName: mockUser.displayName,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Public API
|
|
126
|
+
const client = {
|
|
127
|
+
type: 'in-memory',
|
|
128
|
+
async initialize() {
|
|
129
|
+
const storage = loadStorage();
|
|
130
|
+
if (storage.selectedUserId) {
|
|
131
|
+
const user = findUser(storage.selectedUserId);
|
|
132
|
+
if (user) {
|
|
133
|
+
selectedUser = user;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
},
|
|
139
|
+
isAuthenticated() {
|
|
140
|
+
return selectedUser !== null;
|
|
141
|
+
},
|
|
142
|
+
getUser() {
|
|
143
|
+
return selectedUser ? toUser(selectedUser) : null;
|
|
144
|
+
},
|
|
145
|
+
async getAccessToken() {
|
|
146
|
+
if (!selectedUser) {
|
|
147
|
+
throw new Error('Not authenticated');
|
|
148
|
+
}
|
|
149
|
+
return `mock-token-${selectedUser.id}-${Date.now()}`;
|
|
150
|
+
},
|
|
151
|
+
hasPermission(permission) {
|
|
152
|
+
if (!selectedUser) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
// '*' means all permissions
|
|
156
|
+
if (selectedUser.roles.includes('*')) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
// Check exact match or wildcard
|
|
160
|
+
return selectedUser.roles.some((role) => {
|
|
161
|
+
if (role === permission)
|
|
162
|
+
return true;
|
|
163
|
+
// Support wildcard like 'admin.*'
|
|
164
|
+
if (role.endsWith('.*')) {
|
|
165
|
+
const prefix = role.slice(0, -2);
|
|
166
|
+
return permission.startsWith(prefix);
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
logout() {
|
|
172
|
+
selectedUser = null;
|
|
173
|
+
const storage = loadStorage();
|
|
174
|
+
storage.selectedUserId = null;
|
|
175
|
+
saveStorage(storage);
|
|
176
|
+
notifySubscribers();
|
|
177
|
+
},
|
|
178
|
+
subscribe(callback) {
|
|
179
|
+
subscribers.add(callback);
|
|
180
|
+
return () => {
|
|
181
|
+
subscribers.delete(callback);
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
getAvailableUsers() {
|
|
185
|
+
return getAllUsers();
|
|
186
|
+
},
|
|
187
|
+
login(userId) {
|
|
188
|
+
const user = findUser(userId);
|
|
189
|
+
if (!user) {
|
|
190
|
+
throw new Error(`User not found: ${userId}`);
|
|
191
|
+
}
|
|
192
|
+
selectedUser = user;
|
|
193
|
+
const storage = loadStorage();
|
|
194
|
+
storage.selectedUserId = userId;
|
|
195
|
+
saveStorage(storage);
|
|
196
|
+
notifySubscribers();
|
|
197
|
+
},
|
|
198
|
+
createCustomUser(userData) {
|
|
199
|
+
const id = `custom-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
200
|
+
const newUser = { ...userData, id };
|
|
201
|
+
// Save to storage
|
|
202
|
+
const storage = loadStorage();
|
|
203
|
+
storage.customUsers.push(newUser);
|
|
204
|
+
saveStorage(storage);
|
|
205
|
+
// Login as the new user
|
|
206
|
+
client.login(id);
|
|
207
|
+
return newUser;
|
|
208
|
+
},
|
|
209
|
+
isCustomUser(userId) {
|
|
210
|
+
// Custom users have IDs starting with 'custom-'
|
|
211
|
+
return userId.startsWith('custom-');
|
|
212
|
+
},
|
|
213
|
+
deleteCustomUser(userId) {
|
|
214
|
+
// Cannot delete predefined users
|
|
215
|
+
if (!client.isCustomUser(userId)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
const storage = loadStorage();
|
|
219
|
+
const index = storage.customUsers.findIndex((u) => u.id === userId);
|
|
220
|
+
if (index === -1) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
// Remove from storage
|
|
224
|
+
storage.customUsers.splice(index, 1);
|
|
225
|
+
// If deleted user was logged in, logout
|
|
226
|
+
if (storage.selectedUserId === userId) {
|
|
227
|
+
storage.selectedUserId = null;
|
|
228
|
+
selectedUser = null;
|
|
229
|
+
}
|
|
230
|
+
saveStorage(storage);
|
|
231
|
+
notifySubscribers();
|
|
232
|
+
return true;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
return client;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Clear in-memory auth storage (useful for tests)
|
|
239
|
+
*/
|
|
240
|
+
export function clearInMemoryAuth(storageKey = DEFAULT_STORAGE_KEY) {
|
|
241
|
+
localStorage.removeItem(storageKey);
|
|
242
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Client exports
|
|
3
|
+
*/
|
|
4
|
+
export type { AuthClient, InMemoryAuthClient, MockUser, AuthState, AuthStateCallback, } from './interface';
|
|
5
|
+
export { createInMemoryAuthClient, clearInMemoryAuth, type InMemoryAuthClientOptions, } from './in-memory';
|
|
6
|
+
export { createKeycloakAuthClient, type KeycloakAuthClientOptions, } from './keycloak';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/client/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,KAAK,yBAAyB,GAC/B,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,wBAAwB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Client Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for authentication operations.
|
|
5
|
+
* Can be implemented by in-memory (mock) or Keycloak clients.
|
|
6
|
+
*/
|
|
7
|
+
import type { User } from '../../types/platform';
|
|
8
|
+
/**
|
|
9
|
+
* Mock user for in-memory auth client
|
|
10
|
+
*/
|
|
11
|
+
export interface MockUser {
|
|
12
|
+
id: string;
|
|
13
|
+
email: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
roles: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Auth client state
|
|
19
|
+
*/
|
|
20
|
+
export interface AuthState {
|
|
21
|
+
isAuthenticated: boolean;
|
|
22
|
+
user: User | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Callback for auth state changes
|
|
26
|
+
*/
|
|
27
|
+
export type AuthStateCallback = (state: AuthState) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Auth Client Interface
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // Create client
|
|
34
|
+
* const authClient = createInMemoryAuthClient();
|
|
35
|
+
*
|
|
36
|
+
* // Initialize (returns true if already authenticated)
|
|
37
|
+
* const isReady = await authClient.initialize();
|
|
38
|
+
*
|
|
39
|
+
* // Check auth state
|
|
40
|
+
* if (authClient.isAuthenticated()) {
|
|
41
|
+
* const user = authClient.getUser();
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* // Subscribe to changes
|
|
45
|
+
* const unsubscribe = authClient.subscribe((state) => {
|
|
46
|
+
* console.log('Auth state:', state);
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* // Logout
|
|
50
|
+
* authClient.logout();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export interface AuthClient {
|
|
54
|
+
/**
|
|
55
|
+
* Client type identifier
|
|
56
|
+
*/
|
|
57
|
+
readonly type: 'in-memory' | 'keycloak';
|
|
58
|
+
/**
|
|
59
|
+
* Initialize the auth client
|
|
60
|
+
* @returns true if user is authenticated, false if login/selection is needed
|
|
61
|
+
*/
|
|
62
|
+
initialize(): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if user is currently authenticated
|
|
65
|
+
*/
|
|
66
|
+
isAuthenticated(): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get the current user (null if not authenticated)
|
|
69
|
+
*/
|
|
70
|
+
getUser(): User | null;
|
|
71
|
+
/**
|
|
72
|
+
* Get access token for API calls
|
|
73
|
+
*/
|
|
74
|
+
getAccessToken(): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Check if user has a specific permission/role
|
|
77
|
+
*/
|
|
78
|
+
hasPermission(permission: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Log out the current user
|
|
81
|
+
*/
|
|
82
|
+
logout(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Subscribe to auth state changes
|
|
85
|
+
* @returns Unsubscribe function
|
|
86
|
+
*/
|
|
87
|
+
subscribe(callback: AuthStateCallback): () => void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extended interface for in-memory auth client
|
|
91
|
+
*/
|
|
92
|
+
export interface InMemoryAuthClient extends AuthClient {
|
|
93
|
+
readonly type: 'in-memory';
|
|
94
|
+
/**
|
|
95
|
+
* Get all available mock users
|
|
96
|
+
*/
|
|
97
|
+
getAvailableUsers(): MockUser[];
|
|
98
|
+
/**
|
|
99
|
+
* Check if a user is a custom user (can be deleted)
|
|
100
|
+
*/
|
|
101
|
+
isCustomUser(userId: string): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Login as a specific user
|
|
104
|
+
*/
|
|
105
|
+
login(userId: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Create and login as a custom user
|
|
108
|
+
*/
|
|
109
|
+
createCustomUser(user: Omit<MockUser, 'id'>): MockUser;
|
|
110
|
+
/**
|
|
111
|
+
* Delete a custom user (predefined users cannot be deleted)
|
|
112
|
+
*/
|
|
113
|
+
deleteCustomUser(userId: string): boolean;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/auth/client/interface.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;IAExC;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/B;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC;IAE3B;;OAEG;IACH,OAAO,IAAI,IAAI,GAAG,IAAI,CAAC;IAEvB;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAElC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAE3C;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;IAEf;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;CACpD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAE3B;;OAEG;IACH,iBAAiB,IAAI,QAAQ,EAAE,CAAC;IAEhC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAEtC;;OAEG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;IAEvD;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3C"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keycloak Auth Client
|
|
3
|
+
*
|
|
4
|
+
* Provides real authentication via Keycloak for production environments.
|
|
5
|
+
*/
|
|
6
|
+
import type { KeycloakConfig } from '../../types/keycloak';
|
|
7
|
+
import type { AuthClient } from './interface';
|
|
8
|
+
/**
|
|
9
|
+
* Options for creating a Keycloak auth client
|
|
10
|
+
*/
|
|
11
|
+
export interface KeycloakAuthClientOptions {
|
|
12
|
+
/** Keycloak configuration */
|
|
13
|
+
config: KeycloakConfig;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a Keycloak auth client for production
|
|
17
|
+
*/
|
|
18
|
+
export declare function createKeycloakAuthClient(options: KeycloakAuthClientOptions): AuthClient;
|
|
19
|
+
//# sourceMappingURL=keycloak.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.d.ts","sourceRoot":"","sources":["../../../src/auth/client/keycloak.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAgC,MAAM,aAAa,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,6BAA6B;IAC7B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU,CAwIvF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keycloak Auth Client
|
|
3
|
+
*
|
|
4
|
+
* Provides real authentication via Keycloak for production environments.
|
|
5
|
+
*/
|
|
6
|
+
import Keycloak from 'keycloak-js';
|
|
7
|
+
/**
|
|
8
|
+
* Create a Keycloak auth client for production
|
|
9
|
+
*/
|
|
10
|
+
export function createKeycloakAuthClient(options) {
|
|
11
|
+
const { config } = options;
|
|
12
|
+
let keycloak = null;
|
|
13
|
+
let currentUser = null;
|
|
14
|
+
let isInitialized = false;
|
|
15
|
+
const subscribers = new Set();
|
|
16
|
+
/**
|
|
17
|
+
* Parse user from Keycloak token
|
|
18
|
+
*/
|
|
19
|
+
function parseUser(tokenParsed) {
|
|
20
|
+
return {
|
|
21
|
+
id: tokenParsed.sub || '',
|
|
22
|
+
email: tokenParsed.email || '',
|
|
23
|
+
displayName: tokenParsed.name || tokenParsed.preferred_username || '',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Notify subscribers of state change
|
|
28
|
+
*/
|
|
29
|
+
function notifySubscribers() {
|
|
30
|
+
const state = {
|
|
31
|
+
isAuthenticated: currentUser !== null,
|
|
32
|
+
user: currentUser,
|
|
33
|
+
};
|
|
34
|
+
subscribers.forEach((callback) => callback(state));
|
|
35
|
+
}
|
|
36
|
+
const client = {
|
|
37
|
+
type: 'keycloak',
|
|
38
|
+
async initialize() {
|
|
39
|
+
if (keycloak) {
|
|
40
|
+
return keycloak.authenticated ?? false;
|
|
41
|
+
}
|
|
42
|
+
keycloak = new Keycloak({
|
|
43
|
+
url: config.url || 'http://localhost:8080',
|
|
44
|
+
realm: config.realm || 'admin',
|
|
45
|
+
clientId: config.clientId,
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const authenticated = await keycloak.init({
|
|
49
|
+
onLoad: 'login-required',
|
|
50
|
+
checkLoginIframe: false,
|
|
51
|
+
pkceMethod: 'S256',
|
|
52
|
+
});
|
|
53
|
+
if (authenticated && keycloak.tokenParsed) {
|
|
54
|
+
currentUser = parseUser(keycloak.tokenParsed);
|
|
55
|
+
}
|
|
56
|
+
isInitialized = true;
|
|
57
|
+
// Notify subscribers that auth is ready - this triggers React re-render
|
|
58
|
+
// which will re-evaluate permissions with the now-available token
|
|
59
|
+
notifySubscribers();
|
|
60
|
+
// Setup token refresh
|
|
61
|
+
keycloak.onTokenExpired = () => {
|
|
62
|
+
keycloak?.updateToken(30).catch(() => {
|
|
63
|
+
console.error('[AuthClient] Failed to refresh token');
|
|
64
|
+
client.logout();
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
// Setup auth state changes
|
|
68
|
+
keycloak.onAuthLogout = () => {
|
|
69
|
+
currentUser = null;
|
|
70
|
+
notifySubscribers();
|
|
71
|
+
};
|
|
72
|
+
keycloak.onAuthRefreshError = () => {
|
|
73
|
+
currentUser = null;
|
|
74
|
+
notifySubscribers();
|
|
75
|
+
};
|
|
76
|
+
return authenticated;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('[AuthClient] Keycloak initialization failed:', error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
isAuthenticated() {
|
|
84
|
+
return keycloak?.authenticated ?? false;
|
|
85
|
+
},
|
|
86
|
+
getUser() {
|
|
87
|
+
return currentUser;
|
|
88
|
+
},
|
|
89
|
+
async getAccessToken() {
|
|
90
|
+
if (!keycloak) {
|
|
91
|
+
throw new Error('Keycloak not initialized');
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await keycloak.updateToken(5);
|
|
95
|
+
return keycloak.token || '';
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error('[AuthClient] Failed to refresh token');
|
|
99
|
+
client.logout();
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
hasPermission(permission) {
|
|
104
|
+
// If not initialized yet, return true to avoid hiding modules during init
|
|
105
|
+
// The UI will re-render after authentication completes
|
|
106
|
+
if (!isInitialized || !keycloak) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return keycloak.hasRealmRole(permission);
|
|
110
|
+
},
|
|
111
|
+
logout() {
|
|
112
|
+
currentUser = null;
|
|
113
|
+
if (keycloak) {
|
|
114
|
+
keycloak.logout();
|
|
115
|
+
}
|
|
116
|
+
notifySubscribers();
|
|
117
|
+
},
|
|
118
|
+
subscribe(callback) {
|
|
119
|
+
subscribers.add(callback);
|
|
120
|
+
return () => {
|
|
121
|
+
subscribers.delete(callback);
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
return client;
|
|
126
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Selector Component
|
|
3
|
+
*
|
|
4
|
+
* Displays available mock users for selection in development mode.
|
|
5
|
+
* Uses Brasa Design System tokens for consistent styling.
|
|
6
|
+
*/
|
|
7
|
+
import type { InMemoryAuthClient } from '../client/interface';
|
|
8
|
+
interface UserSelectorProps {
|
|
9
|
+
/** The in-memory auth client */
|
|
10
|
+
authClient: InMemoryAuthClient;
|
|
11
|
+
/** Callback when user is selected */
|
|
12
|
+
onUserSelected?: () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Main User Selector Component
|
|
16
|
+
*/
|
|
17
|
+
export declare function UserSelector({ authClient, onUserSelected }: UserSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=UserSelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserSelector.d.ts","sourceRoot":"","sources":["../../../src/auth/components/UserSelector.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAY,MAAM,qBAAqB,CAAC;AAExE,UAAU,iBAAiB;IACzB,gCAAgC;IAChC,UAAU,EAAE,kBAAkB,CAAC;IAC/B,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AA2ND;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,EAAE,iBAAiB,2CAqF7E"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* User Selector Component
|
|
4
|
+
*
|
|
5
|
+
* Displays available mock users for selection in development mode.
|
|
6
|
+
* Uses Brasa Design System tokens for consistent styling.
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { Crown, Ban, Eye, User, Users, Sparkles, Wrench, Trash2, Plus, ChevronRight } from '@nsxbet/admin-ui';
|
|
10
|
+
/**
|
|
11
|
+
* Get user icon based on roles
|
|
12
|
+
*/
|
|
13
|
+
function getUserIcon(roles) {
|
|
14
|
+
const iconClass = "h-7 w-7";
|
|
15
|
+
if (roles.includes('admin') || roles.includes('*')) {
|
|
16
|
+
return _jsx(Crown, { className: `${iconClass} text-amber-500` });
|
|
17
|
+
}
|
|
18
|
+
if (roles.length === 0) {
|
|
19
|
+
return _jsx(Ban, { className: `${iconClass} text-destructive` });
|
|
20
|
+
}
|
|
21
|
+
if (roles.some((r) => r.includes('view') && !r.includes('edit'))) {
|
|
22
|
+
return _jsx(Eye, { className: `${iconClass} text-info` });
|
|
23
|
+
}
|
|
24
|
+
return _jsx(User, { className: `${iconClass} text-muted-foreground` });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get role badge color using Brasa Design System tokens
|
|
28
|
+
*/
|
|
29
|
+
function getRoleBadgeColor(role) {
|
|
30
|
+
if (role === 'admin' || role === '*') {
|
|
31
|
+
// Admin uses accent-odd (gold) - prominent highlight
|
|
32
|
+
return 'bg-accent-odd/20 text-accent-odd border-accent-odd/30';
|
|
33
|
+
}
|
|
34
|
+
if (role.includes('edit')) {
|
|
35
|
+
// Edit permissions use success (green)
|
|
36
|
+
return 'bg-success/20 text-success border-success/30';
|
|
37
|
+
}
|
|
38
|
+
if (role.includes('view')) {
|
|
39
|
+
// View permissions use info (blue)
|
|
40
|
+
return 'bg-info/20 text-info border-info/30';
|
|
41
|
+
}
|
|
42
|
+
// Default uses muted
|
|
43
|
+
return 'bg-muted text-muted-foreground border-border';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* User Card Component
|
|
47
|
+
*/
|
|
48
|
+
function UserCard({ user, isCustom, onClick, onDelete, }) {
|
|
49
|
+
const icon = getUserIcon(user.roles);
|
|
50
|
+
const handleDelete = (e) => {
|
|
51
|
+
e.stopPropagation();
|
|
52
|
+
if (onDelete && confirm(`Delete user "${user.displayName}"?`)) {
|
|
53
|
+
onDelete();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return (_jsxs("div", { className: "relative group", children: [_jsx("button", { onClick: onClick, className: "w-full p-4 rounded-xl border border-border bg-card hover:bg-muted/50 hover:border-muted-foreground/30 transition-all duration-200 text-left", children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex-shrink-0", children: icon }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "font-semibold text-foreground group-hover:text-foreground transition-colors", children: user.displayName }), isCustom && (_jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-primary/20 text-primary border border-primary/30", children: "custom" }))] }), _jsx("p", { className: "text-sm text-muted-foreground truncate", children: user.email }), _jsxs("div", { className: "flex flex-wrap gap-1.5 mt-2", children: [user.roles.length === 0 ? (_jsx("span", { className: "text-xs px-2 py-0.5 rounded-full border bg-destructive/20 text-destructive border-destructive/30", children: "no roles" })) : (user.roles.slice(0, 4).map((role) => (_jsx("span", { className: `text-xs px-2 py-0.5 rounded-full border ${getRoleBadgeColor(role)}`, children: role }, role)))), user.roles.length > 4 && (_jsxs("span", { className: "text-xs px-2 py-0.5 rounded-full border bg-muted text-muted-foreground border-border", children: ["+", user.roles.length - 4, " more"] }))] })] }), _jsx("div", { className: "text-muted-foreground group-hover:text-foreground transition-colors", children: _jsx(ChevronRight, { className: "h-5 w-5" }) })] }) }), isCustom && onDelete && (_jsx("button", { onClick: handleDelete, className: "absolute top-2 right-2 p-1.5 rounded-lg bg-destructive/10 text-destructive opacity-0 group-hover:opacity-100 hover:bg-destructive/20 transition-all", title: "Delete user", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Custom User Form Component
|
|
60
|
+
*/
|
|
61
|
+
function CustomUserForm({ onSubmit, onCancel, }) {
|
|
62
|
+
const [displayName, setDisplayName] = useState('');
|
|
63
|
+
const [email, setEmail] = useState('');
|
|
64
|
+
const [rolesInput, setRolesInput] = useState('');
|
|
65
|
+
const handleSubmit = (e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
const roles = rolesInput
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((r) => r.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
onSubmit({ displayName, email, roles });
|
|
72
|
+
};
|
|
73
|
+
const isValid = displayName.trim() && email.trim();
|
|
74
|
+
return (_jsxs("form", { onSubmit: handleSubmit, className: "p-4 rounded-xl border border-border bg-card", children: [_jsxs("h3", { className: "font-semibold text-foreground mb-4 flex items-center gap-2", children: [_jsx(Sparkles, { className: "h-5 w-5 text-amber-500" }), "Create Custom User"] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-muted-foreground mb-1", children: "Display Name" }), _jsx("input", { type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "John Doe", className: "w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-muted-foreground mb-1", children: "Email" }), _jsx("input", { type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "john@example.com", className: "w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent" })] }), _jsxs("div", { children: [_jsxs("label", { className: "block text-sm font-medium text-muted-foreground mb-1", children: ["Roles ", _jsx("span", { className: "text-muted-foreground/60", children: "(comma-separated)" })] }), _jsx("input", { type: "text", value: rolesInput, onChange: (e) => setRolesInput(e.target.value), placeholder: "admin, admin.users.view, admin.tasks.edit", className: "w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent" }), _jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: ["Use ", _jsx("code", { className: "px-1 py-0.5 rounded bg-muted", children: "*" }), " for all permissions"] })] })] }), _jsxs("div", { className: "flex gap-2 mt-4", children: [_jsx("button", { type: "button", onClick: onCancel, className: "flex-1 px-4 py-2 rounded-lg border border-border text-foreground hover:bg-muted/50 transition-colors", children: "Cancel" }), _jsx("button", { type: "submit", disabled: !isValid, className: "flex-1 px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors", children: "Create & Login" })] })] }));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Main User Selector Component
|
|
78
|
+
*/
|
|
79
|
+
export function UserSelector({ authClient, onUserSelected }) {
|
|
80
|
+
const [showCustomForm, setShowCustomForm] = useState(false);
|
|
81
|
+
const [, forceUpdate] = useState(0);
|
|
82
|
+
const users = authClient.getAvailableUsers();
|
|
83
|
+
const handleSelectUser = (userId) => {
|
|
84
|
+
authClient.login(userId);
|
|
85
|
+
onUserSelected?.();
|
|
86
|
+
};
|
|
87
|
+
const handleCreateCustomUser = (userData) => {
|
|
88
|
+
authClient.createCustomUser(userData);
|
|
89
|
+
onUserSelected?.();
|
|
90
|
+
};
|
|
91
|
+
const handleDeleteUser = (userId) => {
|
|
92
|
+
authClient.deleteCustomUser(userId);
|
|
93
|
+
// Force re-render to update the list
|
|
94
|
+
forceUpdate((n) => n + 1);
|
|
95
|
+
};
|
|
96
|
+
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) => {
|
|
97
|
+
const isCustom = authClient.isCustomUser(user.id);
|
|
98
|
+
return (_jsx(UserCard, { user: user, isCustom: isCustom, onClick: () => handleSelectUser(user.id), onDelete: isCustom ? () => handleDeleteUser(user.id) : undefined }, user.id));
|
|
99
|
+
}) }), _jsxs("div", { className: "relative my-6", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("div", { className: "w-full border-t border-border" }) }), _jsx("div", { className: "relative flex justify-center text-sm", children: _jsx("span", { className: "px-3 bg-background text-muted-foreground", children: "or" }) })] }), showCustomForm ? (_jsx(CustomUserForm, { onSubmit: handleCreateCustomUser, onCancel: () => setShowCustomForm(false) })) : (_jsxs("button", { onClick: () => setShowCustomForm(true), className: "w-full p-4 rounded-xl border border-dashed border-border text-muted-foreground hover:text-foreground hover:border-muted-foreground transition-colors flex items-center justify-center gap-2", children: [_jsx(Plus, { className: "h-5 w-5" }), "Create Custom User"] })), _jsxs("p", { className: "text-center text-xs text-muted-foreground mt-8 flex items-center justify-center gap-1", children: [_jsx(Wrench, { className: "h-3 w-3" }), "Development Mode \u2022 Data stored in localStorage"] })] }) }));
|
|
100
|
+
}
|