@alepha/ui 0.14.2 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/admin/AdminAudits-DIrCCPk3.js.map +1 -1
  2. package/dist/admin/AdminNotifications-cIbywWKi.js.map +1 -1
  3. package/dist/admin/AdminParameters-D-q3Qmhv.js.map +1 -1
  4. package/dist/admin/AdminSessions-vOgkrQ2U.js.map +1 -1
  5. package/dist/admin/AdminUserAudits-CSsN1fIC.js.map +1 -1
  6. package/dist/admin/AdminUserCreate-B72nu-3W.js.map +1 -1
  7. package/dist/admin/AdminUserDetails-CKM2IEMr.js +475 -0
  8. package/dist/admin/AdminUserDetails-CKM2IEMr.js.map +1 -0
  9. package/dist/admin/{AdminUserDetails-z1y8kJeB.js → AdminUserDetails-Zib_B6Al.js} +1 -1
  10. package/dist/admin/{AdminUserLayout-DyQYacQQ.js → AdminUserLayout-BNBOEiAO.js} +1 -1
  11. package/dist/admin/AdminUserLayout-D7En9UBq.js +334 -0
  12. package/dist/admin/AdminUserLayout-D7En9UBq.js.map +1 -0
  13. package/dist/admin/AdminUserSessions-DEaGu6n6.js.map +1 -1
  14. package/dist/admin/{AdminUserSettings-CR7MxX_R.js → AdminUserSettings-Di73D7g2.js} +6 -5
  15. package/dist/admin/AdminUserSettings-Di73D7g2.js.map +1 -0
  16. package/dist/admin/AdminUserSettings-yI-JECf5.js +3 -0
  17. package/dist/admin/AdminUsers-BnGIRvmV.js.map +1 -1
  18. package/dist/admin/index.d.ts +10 -10
  19. package/dist/admin/index.d.ts.map +1 -1
  20. package/dist/admin/index.js +18 -18
  21. package/dist/admin/index.js.map +1 -1
  22. package/dist/auth/index.js +4 -4
  23. package/dist/auth/index.js.map +1 -1
  24. package/dist/core/index.d.ts.map +1 -1
  25. package/dist/core/index.js +6 -5
  26. package/dist/core/index.js.map +1 -1
  27. package/package.json +11 -11
  28. package/src/admin/AdminRouter.ts +23 -20
  29. package/src/admin/MainRouter.ts +1 -1
  30. package/src/admin/components/audits/AdminAudits.tsx +2 -2
  31. package/src/admin/components/jobs/AdminJobs.tsx +2 -2
  32. package/src/admin/components/notifications/AdminNotifications.tsx +2 -2
  33. package/src/admin/components/parameters/AdminParameters.tsx +2 -2
  34. package/src/admin/components/sessions/AdminSessions.tsx +2 -2
  35. package/src/admin/components/shared/AdminResourceHeader.tsx +281 -0
  36. package/src/admin/components/shared/AdminResourceTabs.tsx +94 -0
  37. package/src/admin/components/shared/index.ts +10 -0
  38. package/src/admin/components/users/AdminUserAudits.tsx +2 -2
  39. package/src/admin/components/users/AdminUserCreate.tsx +2 -2
  40. package/src/admin/components/users/AdminUserDetails.tsx +337 -85
  41. package/src/admin/components/users/AdminUserLayout.tsx +164 -108
  42. package/src/admin/components/users/AdminUserSessions.tsx +2 -2
  43. package/src/admin/components/users/AdminUserSettings.tsx +10 -5
  44. package/src/admin/components/users/AdminUsers.tsx +6 -2
  45. package/src/auth/AuthRouter.ts +4 -4
  46. package/src/core/components/form/TypeForm.tsx +3 -2
  47. package/src/core/components/layout/AlephaMantineProvider.tsx +5 -1
  48. package/src/core/components/layout/Sidebar.tsx +9 -6
  49. package/dist/admin/AdminUserDetails-BCt8Su-4.js +0 -222
  50. package/dist/admin/AdminUserDetails-BCt8Su-4.js.map +0 -1
  51. package/dist/admin/AdminUserLayout-Ck0GLRE5.js +0 -151
  52. package/dist/admin/AdminUserLayout-Ck0GLRE5.js.map +0 -1
  53. package/dist/admin/AdminUserSettings-CE66UTIP.js +0 -3
  54. package/dist/admin/AdminUserSettings-CR7MxX_R.js.map +0 -1
@@ -2,25 +2,110 @@ import { useClient } from "@alepha/react";
2
2
  import { useForm } from "@alepha/react/form";
3
3
  import { useI18n } from "@alepha/react/i18n";
4
4
  import { useRouterState } from "@alepha/react/router";
5
- import { ActionButton, Control, Flex, Text } from "@alepha/ui";
6
- import { Card, Group, Loader, Stack } from "@mantine/core";
7
- import { IconCheck, IconX } from "@tabler/icons-react";
5
+ import { ActionButton, ClipboardButton, Control } from "@alepha/ui";
6
+ import {
7
+ Badge,
8
+ Box,
9
+ Card,
10
+ Center,
11
+ Divider,
12
+ Grid,
13
+ Group,
14
+ Loader,
15
+ Paper,
16
+ SimpleGrid,
17
+ Stack,
18
+ Text,
19
+ ThemeIcon,
20
+ } from "@mantine/core";
21
+ import {
22
+ IconActivity,
23
+ IconCalendar,
24
+ IconCheck,
25
+ IconDevices,
26
+ IconKey,
27
+ IconShieldCheck,
28
+ IconUser,
29
+ IconX,
30
+ } from "@tabler/icons-react";
8
31
  import { t } from "alepha";
9
- import type { UserController, UserEntity } from "alepha/api/users";
10
- import { useEffect, useState } from "react";
32
+ import type { AdminUserController, UserEntity } from "alepha/api/users";
33
+ import { type ReactNode, useEffect, useState } from "react";
11
34
 
12
35
  export interface AdminUserDetailsProps {
13
36
  userRealmName?: string;
14
37
  }
15
38
 
39
+ interface DataRowProps {
40
+ label: string;
41
+ value: ReactNode;
42
+ copyValue?: string;
43
+ }
44
+
45
+ const DataRow = ({ label, value, copyValue }: DataRowProps) => (
46
+ <Group
47
+ justify="space-between"
48
+ py={8}
49
+ wrap="nowrap"
50
+ style={{ borderBottom: "1px solid var(--mantine-color-default-border)" }}
51
+ >
52
+ <Text size="sm" c="dimmed" style={{ flexShrink: 0 }}>
53
+ {label}
54
+ </Text>
55
+ <Group gap={6} wrap="nowrap" style={{ minWidth: 0 }}>
56
+ {typeof value === "string" ? (
57
+ <Text size="sm" fw={500} truncate style={{ maxWidth: 220 }}>
58
+ {value || "—"}
59
+ </Text>
60
+ ) : (
61
+ value
62
+ )}
63
+ {copyValue && (
64
+ <ClipboardButton
65
+ value={copyValue}
66
+ size="xs"
67
+ variant="subtle"
68
+ c="dimmed"
69
+ />
70
+ )}
71
+ </Group>
72
+ </Group>
73
+ );
74
+
75
+ interface StatCardProps {
76
+ icon: ReactNode;
77
+ label: string;
78
+ value: string | number;
79
+ color: string;
80
+ }
81
+
82
+ const StatCard = ({ icon, label, value, color }: StatCardProps) => (
83
+ <Paper p="md" radius="md" withBorder>
84
+ <Group gap="sm">
85
+ <ThemeIcon size="lg" radius="md" variant="light" color={color}>
86
+ {icon}
87
+ </ThemeIcon>
88
+ <Box>
89
+ <Text size="xl" fw={700} lh={1}>
90
+ {value}
91
+ </Text>
92
+ <Text size="xs" c="dimmed">
93
+ {label}
94
+ </Text>
95
+ </Box>
96
+ </Group>
97
+ </Paper>
98
+ );
99
+
16
100
  const AdminUserDetails = (props: AdminUserDetailsProps) => {
17
101
  const state = useRouterState();
18
- const client = useClient<UserController>();
102
+ const client = useClient<AdminUserController>();
19
103
  const { l } = useI18n();
20
104
  const userId = state.params.userId as string;
21
105
 
22
106
  const [user, setUser] = useState<UserEntity | null>(null);
23
107
  const [loading, setLoading] = useState(true);
108
+ const [editing, setEditing] = useState(false);
24
109
 
25
110
  useEffect(() => {
26
111
  const loadUser = async () => {
@@ -54,6 +139,7 @@ const AdminUserDetails = (props: AdminUserDetailsProps) => {
54
139
  body: data,
55
140
  });
56
141
  setUser(updated);
142
+ setEditing(false);
57
143
  },
58
144
  });
59
145
 
@@ -70,111 +156,277 @@ const AdminUserDetails = (props: AdminUserDetailsProps) => {
70
156
 
71
157
  if (loading) {
72
158
  return (
73
- <Flex flex={1} justify="center" align="center">
159
+ <Center flex={1} py="xl">
74
160
  <Loader />
75
- </Flex>
161
+ </Center>
76
162
  );
77
163
  }
78
164
 
79
165
  if (!user) {
80
166
  return (
81
- <Flex flex={1} justify="center" align="center">
82
- <Text c="dimmed">User not found</Text>
83
- </Flex>
167
+ <Center flex={1} py="xl">
168
+ <Stack align="center" gap="xs">
169
+ <IconUser size={48} opacity={0.3} />
170
+ <Text c="dimmed">User not found</Text>
171
+ </Stack>
172
+ </Center>
84
173
  );
85
174
  }
86
175
 
87
- return (
88
- <Flex flex={1} direction="column" gap="md">
89
- <Card withBorder p="lg">
90
- <Stack gap="md">
91
- <Text size="lg" fw={500}>
92
- User Details
93
- </Text>
176
+ const displayName =
177
+ user.firstName && user.lastName
178
+ ? `${user.firstName} ${user.lastName}`
179
+ : user.firstName || user.lastName || null;
94
180
 
95
- <Group gap="xl">
96
- <Stack gap={4}>
97
- <Text size="xs" c="dimmed">
98
- User ID
99
- </Text>
100
- <Text size="sm" ff="monospace">
101
- {user.id}
102
- </Text>
103
- </Stack>
181
+ return (
182
+ <Stack gap="md">
183
+ {/* Stats Overview */}
184
+ <SimpleGrid cols={{ base: 2, sm: 4 }}>
185
+ <StatCard
186
+ icon={<IconDevices size={18} />}
187
+ label="Sessions"
188
+ value={0}
189
+ color="blue"
190
+ />
191
+ <StatCard
192
+ icon={<IconActivity size={18} />}
193
+ label="API Calls"
194
+ value={0}
195
+ color="green"
196
+ />
197
+ <StatCard
198
+ icon={<IconKey size={18} />}
199
+ label="Failed Logins"
200
+ value={0}
201
+ color="orange"
202
+ />
203
+ <StatCard
204
+ icon={<IconShieldCheck size={18} />}
205
+ label="Roles"
206
+ value={user.roles.length}
207
+ color="violet"
208
+ />
209
+ </SimpleGrid>
104
210
 
105
- <Stack gap={4}>
106
- <Text size="xs" c="dimmed">
107
- Username
211
+ <Grid>
212
+ {/* Left Column - Account Details */}
213
+ <Grid.Col span={{ base: 12, md: 6 }}>
214
+ <Card padding={0} radius="md" withBorder h="100%">
215
+ <Group justify="space-between" p="md" pb={0}>
216
+ <Text fw={600} size="sm">
217
+ Account Details
108
218
  </Text>
109
- <Text size="sm">{user.username || "-"}</Text>
110
- </Stack>
219
+ </Group>
220
+ <Box px="md" pb="md">
221
+ <DataRow label="User ID" value={user.id} copyValue={user.id} />
222
+ <DataRow
223
+ label="Username"
224
+ value={user.username || "—"}
225
+ copyValue={user.username}
226
+ />
227
+ <DataRow
228
+ label="Email"
229
+ value={
230
+ <Group gap={6}>
231
+ <Text size="sm" fw={500}>
232
+ {user.email || "—"}
233
+ </Text>
234
+ {user.email && (
235
+ <Badge
236
+ size="xs"
237
+ variant="light"
238
+ color={user.emailVerified ? "green" : "orange"}
239
+ >
240
+ {user.emailVerified ? "verified" : "unverified"}
241
+ </Badge>
242
+ )}
243
+ </Group>
244
+ }
245
+ copyValue={user.email}
246
+ />
247
+ <DataRow
248
+ label="Phone"
249
+ value={user.phoneNumber || "—"}
250
+ copyValue={user.phoneNumber}
251
+ />
252
+ <DataRow label="Realm" value={user.realm} />
253
+ <DataRow
254
+ label="Status"
255
+ value={
256
+ <Group gap={4}>
257
+ <ThemeIcon
258
+ size={16}
259
+ radius="xl"
260
+ color={user.enabled ? "green" : "red"}
261
+ variant="filled"
262
+ >
263
+ {user.enabled ? (
264
+ <IconCheck size={10} />
265
+ ) : (
266
+ <IconX size={10} />
267
+ )}
268
+ </ThemeIcon>
269
+ <Text size="sm" fw={500}>
270
+ {user.enabled ? "Active" : "Disabled"}
271
+ </Text>
272
+ </Group>
273
+ }
274
+ />
275
+ </Box>
276
+ </Card>
277
+ </Grid.Col>
111
278
 
112
- <Stack gap={4}>
113
- <Text size="xs" c="dimmed">
114
- Email Verified
279
+ {/* Right Column - Personal Info */}
280
+ <Grid.Col span={{ base: 12, md: 6 }}>
281
+ <Card padding={0} radius="md" withBorder h="100%">
282
+ <Group justify="space-between" p="md" pb={0}>
283
+ <Text fw={600} size="sm">
284
+ Personal Information
115
285
  </Text>
116
- {user.emailVerified ? (
117
- <Group gap={4}>
118
- <IconCheck size={14} color="var(--mantine-color-green-6)" />
119
- <Text size="sm" c="green">
120
- Verified
121
- </Text>
122
- </Group>
123
- ) : (
124
- <Group gap={4}>
125
- <IconX size={14} color="var(--mantine-color-red-6)" />
126
- <Text size="sm" c="red">
127
- Not Verified
128
- </Text>
129
- </Group>
286
+ {!editing && (
287
+ <ActionButton
288
+ variant="subtle"
289
+ size="xs"
290
+ onClick={() => setEditing(true)}
291
+ >
292
+ Edit
293
+ </ActionButton>
130
294
  )}
131
- </Stack>
295
+ </Group>
296
+
297
+ {editing ? (
298
+ <Box p="md">
299
+ <form {...form.props}>
300
+ <Stack gap="sm">
301
+ <SimpleGrid cols={2}>
302
+ <Control
303
+ title="First Name"
304
+ input={form.input.firstName}
305
+ />
306
+ <Control title="Last Name" input={form.input.lastName} />
307
+ </SimpleGrid>
308
+ <SimpleGrid cols={2}>
309
+ <Control title="Email" input={form.input.email} />
310
+ <Control title="Phone" input={form.input.phoneNumber} />
311
+ </SimpleGrid>
312
+ <Control title="Roles" input={form.input.roles} />
313
+ <Divider />
314
+ <Group justify="flex-end" gap="xs">
315
+ <ActionButton
316
+ variant="subtle"
317
+ size="xs"
318
+ onClick={() => setEditing(false)}
319
+ >
320
+ Cancel
321
+ </ActionButton>
322
+ <ActionButton size="xs" form={form}>
323
+ Save
324
+ </ActionButton>
325
+ </Group>
326
+ </Stack>
327
+ </form>
328
+ </Box>
329
+ ) : (
330
+ <Box px="md" pb="md">
331
+ <DataRow label="First Name" value={user.firstName || "—"} />
332
+ <DataRow label="Last Name" value={user.lastName || "—"} />
333
+ <DataRow label="Display Name" value={displayName || "—"} />
334
+ <DataRow
335
+ label="Roles"
336
+ value={
337
+ user.roles.length > 0 ? (
338
+ <Group gap={4}>
339
+ {user.roles.map((role) => (
340
+ <Badge key={role} size="xs" variant="light">
341
+ {role}
342
+ </Badge>
343
+ ))}
344
+ </Group>
345
+ ) : (
346
+ <Text size="sm" c="dimmed">
347
+ No roles
348
+ </Text>
349
+ )
350
+ }
351
+ />
352
+ </Box>
353
+ )}
354
+ </Card>
355
+ </Grid.Col>
356
+ </Grid>
132
357
 
133
- <Stack gap={4}>
358
+ {/* Timeline */}
359
+ <Card padding={0} radius="md" withBorder>
360
+ <Group justify="space-between" p="md" pb={0}>
361
+ <Text fw={600} size="sm">
362
+ Activity Timeline
363
+ </Text>
364
+ </Group>
365
+ <SimpleGrid cols={{ base: 2, sm: 4 }} p="md">
366
+ <Box>
367
+ <Group gap={6} mb={4}>
368
+ <IconCalendar size={14} style={{ opacity: 0.5 }} />
134
369
  <Text size="xs" c="dimmed">
135
370
  Created
136
371
  </Text>
137
- <Text size="sm">{l(user.createdAt, { date: "medium" })}</Text>
138
- </Stack>
139
-
140
- <Stack gap={4}>
372
+ </Group>
373
+ <Text size="sm" fw={500}>
374
+ {l(user.createdAt, { date: "ll" })}
375
+ </Text>
376
+ <Text size="xs" c="dimmed">
377
+ {l(user.createdAt, { date: "fromNow" })}
378
+ </Text>
379
+ </Box>
380
+ <Box>
381
+ <Group gap={6} mb={4}>
382
+ <IconCalendar size={14} style={{ opacity: 0.5 }} />
141
383
  <Text size="xs" c="dimmed">
142
384
  Updated
143
385
  </Text>
144
- <Text size="sm">{l(user.updatedAt, { date: "medium" })}</Text>
145
- </Stack>
146
- </Group>
147
- </Stack>
148
- </Card>
149
-
150
- <Card withBorder p="lg">
151
- <form {...form.props}>
152
- <Stack gap="md">
153
- <Text size="lg" fw={500}>
154
- Edit User
155
- </Text>
156
-
157
- <Group grow>
158
- <Control title="Email" input={form.input.email} />
159
- <Control title="Phone Number" input={form.input.phoneNumber} />
160
386
  </Group>
161
-
162
- <Group grow>
163
- <Control title="First Name" input={form.input.firstName} />
164
- <Control title="Last Name" input={form.input.lastName} />
387
+ <Text size="sm" fw={500}>
388
+ {l(user.updatedAt, { date: "ll" })}
389
+ </Text>
390
+ <Text size="xs" c="dimmed">
391
+ {l(user.updatedAt, { date: "fromNow" })}
392
+ </Text>
393
+ </Box>
394
+ <Box>
395
+ <Group gap={6} mb={4}>
396
+ <IconDevices size={14} style={{ opacity: 0.5 }} />
397
+ <Text size="xs" c="dimmed">
398
+ Last Login
399
+ </Text>
165
400
  </Group>
166
-
167
- <Control title="Roles" input={form.input.roles} />
168
-
169
- <Control title="Enabled" input={form.input.enabled} />
170
-
171
- <Group>
172
- <ActionButton form={form}>Save Changes</ActionButton>
401
+ <Text size="sm" c="dimmed">
402
+
403
+ </Text>
404
+ </Box>
405
+ <Box>
406
+ <Group gap={6} mb={4}>
407
+ <IconCheck size={14} style={{ opacity: 0.5 }} />
408
+ <Text size="xs" c="dimmed">
409
+ Email Verified
410
+ </Text>
173
411
  </Group>
174
- </Stack>
175
- </form>
412
+ {user.emailVerified ? (
413
+ <>
414
+ <Text size="sm" fw={500}>
415
+ {l(user.updatedAt, { date: "ll" })}
416
+ </Text>
417
+ <Text size="xs" c="dimmed">
418
+ {l(user.updatedAt, { date: "fromNow" })}
419
+ </Text>
420
+ </>
421
+ ) : (
422
+ <Text size="sm" c="dimmed">
423
+ Not verified
424
+ </Text>
425
+ )}
426
+ </Box>
427
+ </SimpleGrid>
176
428
  </Card>
177
- </Flex>
429
+ </Stack>
178
430
  );
179
431
  };
180
432