@alepha/ui 0.13.6 → 0.13.7

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 (157) hide show
  1. package/dist/admin/AdminAudits-CwvH8e8c.js +215 -0
  2. package/dist/admin/AdminAudits-CwvH8e8c.js.map +1 -0
  3. package/dist/admin/AdminAudits-Dv8Vk_6r.js +3 -0
  4. package/dist/admin/AdminFiles-5CPA3lQk.js +3 -0
  5. package/dist/admin/{AdminFiles-B_jfB_Py.js → AdminFiles-C_w1tb_x.js} +4 -3
  6. package/dist/admin/AdminFiles-C_w1tb_x.js.map +1 -0
  7. package/dist/admin/AdminLayout-BnSmtA4x.js +3 -0
  8. package/dist/admin/AdminLayout-XiSivwWH.js +39 -0
  9. package/dist/admin/AdminLayout-XiSivwWH.js.map +1 -0
  10. package/dist/admin/AdminNotifications-DLjmZWtf.js +3 -0
  11. package/dist/admin/{AdminNotifications-BFEjqpqx.js → AdminNotifications-DuYy74AN.js} +3 -3
  12. package/dist/admin/AdminNotifications-DuYy74AN.js.map +1 -0
  13. package/dist/admin/AdminParameters-DYg48Jwe.js +3 -0
  14. package/dist/admin/AdminParameters-YagqWTG3.js +575 -0
  15. package/dist/admin/AdminParameters-YagqWTG3.js.map +1 -0
  16. package/dist/admin/{AdminSessions-D7DESfWK.js → AdminSessions-BCjgJ-93.js} +4 -4
  17. package/dist/admin/AdminSessions-BCjgJ-93.js.map +1 -0
  18. package/dist/admin/AdminSessions-DEh2uN-4.js +3 -0
  19. package/dist/admin/AdminUserAudits-B_PUXCKC.js +177 -0
  20. package/dist/admin/AdminUserAudits-B_PUXCKC.js.map +1 -0
  21. package/dist/admin/AdminUserAudits-D7cTcElL.js +3 -0
  22. package/dist/admin/{AdminUserCreate-Bhxsn92l.js → AdminUserCreate-DzfRbGZ4.js} +4 -4
  23. package/dist/admin/AdminUserCreate-DzfRbGZ4.js.map +1 -0
  24. package/dist/admin/{AdminUserCreate-CYI_xW5T.js → AdminUserCreate-oUA1KDIl.js} +1 -1
  25. package/dist/admin/{AdminUserDetails-C2y1Ig4n.js → AdminUserDetails-DeTrJm-t.js} +5 -5
  26. package/dist/admin/AdminUserDetails-DeTrJm-t.js.map +1 -0
  27. package/dist/admin/{AdminUserDetails-Cmzx9HxH.js → AdminUserDetails-y1H5DW8Y.js} +1 -1
  28. package/dist/admin/{AdminUserLayout-sW6cjZL0.js → AdminUserLayout-CsfrrZkD.js} +4 -7
  29. package/dist/admin/AdminUserLayout-CsfrrZkD.js.map +1 -0
  30. package/dist/admin/{AdminUserLayout-DGSf612u.js → AdminUserLayout-Dejnz13m.js} +1 -1
  31. package/dist/admin/AdminUserSessions-Bbhcpz4k.js +3 -0
  32. package/dist/admin/{AdminUserSessions-CvN15wPe.js → AdminUserSessions-DO9H85O-.js} +4 -4
  33. package/dist/admin/AdminUserSessions-DO9H85O-.js.map +1 -0
  34. package/dist/admin/{AdminUserSettings-DvaaxgcV.js → AdminUserSettings-B3jA8g3p.js} +4 -4
  35. package/dist/admin/AdminUserSettings-B3jA8g3p.js.map +1 -0
  36. package/dist/admin/AdminUserSettings-CE0xpbQc.js +3 -0
  37. package/dist/admin/AdminUsers-CegGZDhW.js +3 -0
  38. package/dist/admin/{AdminUsers-BR3C-jrg.js → AdminUsers-ebbrJBT0.js} +13 -17
  39. package/dist/admin/AdminUsers-ebbrJBT0.js.map +1 -0
  40. package/dist/admin/index.d.ts +2044 -1044
  41. package/dist/admin/index.js +65 -62
  42. package/dist/admin/index.js.map +1 -1
  43. package/dist/auth/AuthLayout-BAZJHzDG.js +23 -0
  44. package/dist/auth/AuthLayout-BAZJHzDG.js.map +1 -0
  45. package/dist/auth/{Login-7HlBjDeV.js → Login-CeNZZjrr.js} +80 -44
  46. package/dist/auth/Login-CeNZZjrr.js.map +1 -0
  47. package/dist/auth/Login-hQcu1nlu.js +4 -0
  48. package/dist/auth/Register-B6HBNVHS.js +4 -0
  49. package/dist/auth/{Register-CuQr3kgi.js → Register-s4ENeyiE.js} +131 -91
  50. package/dist/auth/Register-s4ENeyiE.js.map +1 -0
  51. package/dist/auth/ResetPassword-Cjd-W-Nu.js +3 -0
  52. package/dist/auth/ResetPassword-GLIFkJT7.js +278 -0
  53. package/dist/auth/ResetPassword-GLIFkJT7.js.map +1 -0
  54. package/dist/auth/index.d.ts +471 -426
  55. package/dist/auth/index.js +26 -18
  56. package/dist/auth/index.js.map +1 -1
  57. package/dist/core/index.d.ts +400 -130
  58. package/dist/core/index.js +1751 -1369
  59. package/dist/core/index.js.map +1 -1
  60. package/package.json +15 -11
  61. package/src/admin/AdminRouter.ts +70 -16
  62. package/src/admin/components/AdminLayout.tsx +41 -61
  63. package/src/admin/components/audits/AdminAudits.tsx +240 -0
  64. package/src/admin/components/{AdminFiles.tsx → files/AdminFiles.tsx} +1 -1
  65. package/src/admin/components/{AdminJobs.tsx → jobs/AdminJobs.tsx} +1 -1
  66. package/src/admin/components/parameters/AdminParameters.tsx +137 -0
  67. package/src/admin/components/parameters/ParameterDetails.tsx +228 -0
  68. package/src/admin/components/parameters/ParameterHistory.tsx +146 -0
  69. package/src/admin/components/parameters/ParameterTree.tsx +146 -0
  70. package/src/admin/components/parameters/types.ts +35 -0
  71. package/src/admin/components/{AdminSessions.tsx → sessions/AdminSessions.tsx} +1 -1
  72. package/src/admin/components/users/AdminUserAudits.tsx +183 -0
  73. package/src/admin/components/{AdminUserCreate.tsx → users/AdminUserCreate.tsx} +1 -1
  74. package/src/admin/components/{AdminUserLayout.tsx → users/AdminUserLayout.tsx} +1 -4
  75. package/src/admin/components/{AdminUserSettings.tsx → users/AdminUserSettings.tsx} +1 -1
  76. package/src/admin/components/{AdminUsers.tsx → users/AdminUsers.tsx} +10 -12
  77. package/src/admin/index.ts +24 -16
  78. package/src/auth/AuthRouter.ts +23 -17
  79. package/src/auth/components/AuthLayout.tsx +6 -3
  80. package/src/auth/components/Login.tsx +109 -47
  81. package/src/auth/components/Register.tsx +158 -94
  82. package/src/auth/components/ResetPassword.tsx +51 -5
  83. package/src/auth/components/buttons/UserButton.tsx +2 -0
  84. package/src/core/atoms/alephaThemeAtom.ts +13 -0
  85. package/src/core/atoms/alephaThemeListAtom.ts +10 -0
  86. package/src/core/atoms/themes/default.ts +6 -0
  87. package/src/core/{themes → atoms/themes}/midnight.ts +3 -5
  88. package/src/core/components/buttons/ActionButton.tsx +33 -26
  89. package/src/core/components/buttons/DarkModeButton.tsx +0 -1
  90. package/src/core/components/buttons/ThemeButton.tsx +10 -7
  91. package/src/core/components/buttons/ToggleSidebarButton.tsx +19 -16
  92. package/src/core/components/data/ErrorViewer.tsx +171 -0
  93. package/src/core/components/data/JsonViewer.tsx +147 -138
  94. package/src/core/components/form/Control.tsx +95 -18
  95. package/src/core/components/form/ControlArray.tsx +377 -0
  96. package/src/core/components/form/ControlObject.tsx +127 -0
  97. package/src/core/components/form/TypeForm.tsx +99 -37
  98. package/src/core/components/layout/AdminShell.tsx +14 -1
  99. package/src/core/components/layout/AlephaMantineProvider.tsx +7 -3
  100. package/src/core/components/layout/Omnibar.tsx +1 -1
  101. package/src/core/components/layout/Sidebar.tsx +47 -14
  102. package/src/core/components/table/ColumnPicker.tsx +126 -0
  103. package/src/core/components/table/DataTable.tsx +354 -181
  104. package/src/core/components/table/DataTableFilters.tsx +64 -0
  105. package/src/core/components/table/DataTablePagination.tsx +59 -0
  106. package/src/core/components/table/DataTableToolbar.tsx +126 -0
  107. package/src/core/components/table/FilterPicker.tsx +138 -0
  108. package/src/core/components/table/types.ts +199 -0
  109. package/src/core/helpers/isComponentType.ts +9 -0
  110. package/src/core/helpers/renderIcon.tsx +13 -0
  111. package/src/core/hooks/useTheme.ts +24 -18
  112. package/src/core/index.ts +24 -3
  113. package/src/core/interfaces/AlephaTheme.ts +8 -0
  114. package/src/core/providers/ThemeProvider.ts +44 -62
  115. package/src/core/services/DialogService.tsx +24 -0
  116. package/src/core/utils/parseInput.ts +2 -2
  117. package/styles.css +1 -1
  118. package/dist/admin/AdminFiles-B-0UcHVV.js +0 -3
  119. package/dist/admin/AdminFiles-B_jfB_Py.js.map +0 -1
  120. package/dist/admin/AdminLayout-BMtiXAzS.js +0 -396
  121. package/dist/admin/AdminLayout-BMtiXAzS.js.map +0 -1
  122. package/dist/admin/AdminLayout-BNo3GoHR.js +0 -3
  123. package/dist/admin/AdminNotifications-BFEjqpqx.js.map +0 -1
  124. package/dist/admin/AdminNotifications-DJs2ZjNj.js +0 -3
  125. package/dist/admin/AdminSessions-D7DESfWK.js.map +0 -1
  126. package/dist/admin/AdminSessions-PS2M8iXi.js +0 -3
  127. package/dist/admin/AdminUserCreate-Bhxsn92l.js.map +0 -1
  128. package/dist/admin/AdminUserDetails-C2y1Ig4n.js.map +0 -1
  129. package/dist/admin/AdminUserLayout-sW6cjZL0.js.map +0 -1
  130. package/dist/admin/AdminUserSessions-CvN15wPe.js.map +0 -1
  131. package/dist/admin/AdminUserSessions-D-aOcZgV.js +0 -3
  132. package/dist/admin/AdminUserSettings-CEMhIYrI.js +0 -3
  133. package/dist/admin/AdminUserSettings-DvaaxgcV.js.map +0 -1
  134. package/dist/admin/AdminUsers-BR3C-jrg.js.map +0 -1
  135. package/dist/admin/AdminUsers-CMW9vN09.js +0 -3
  136. package/dist/auth/AuthLayout-CzwUKD9y.js +0 -19
  137. package/dist/auth/AuthLayout-CzwUKD9y.js.map +0 -1
  138. package/dist/auth/Login-7HlBjDeV.js.map +0 -1
  139. package/dist/auth/Login-C-e27DGb.js +0 -4
  140. package/dist/auth/Register-CuQr3kgi.js.map +0 -1
  141. package/dist/auth/Register-DbvXwgbG.js +0 -4
  142. package/dist/auth/ResetPassword-BzU-cdd4.js +0 -243
  143. package/dist/auth/ResetPassword-BzU-cdd4.js.map +0 -1
  144. package/dist/auth/ResetPassword-DSvrdpaA.js +0 -3
  145. package/src/admin/AdminSidebar.ts +0 -31
  146. package/src/admin/components/AdminParameters.tsx +0 -24
  147. package/src/core/themes/aurora.ts +0 -107
  148. package/src/core/themes/crystal.ts +0 -107
  149. package/src/core/themes/default.ts +0 -7
  150. package/src/core/themes/ember.ts +0 -107
  151. package/src/core/themes/index.ts +0 -7
  152. package/src/core/themes/remoraid.ts +0 -278
  153. package/src/core/themes/slate.ts +0 -81
  154. /package/src/admin/components/{AdminNotifications.tsx → notifications/AdminNotifications.tsx} +0 -0
  155. /package/src/admin/components/{AdminUserDetails.tsx → users/AdminUserDetails.tsx} +0 -0
  156. /package/src/admin/components/{AdminUserSessions.tsx → users/AdminUserSessions.tsx} +0 -0
  157. /package/src/admin/components/{AdminVerifications.tsx → verifications/AdminVerifications.tsx} +0 -0
@@ -3,7 +3,17 @@ import { useAuth } from "@alepha/react/auth";
3
3
  import { useForm } from "@alepha/react/form";
4
4
  import { useI18n } from "@alepha/react/i18n";
5
5
  import { ActionButton, Control, capitalize } from "@alepha/ui";
6
- import { Alert, Card, Flex, Group, PinInput, Stack, Text } from "@mantine/core";
6
+ import {
7
+ Alert,
8
+ Card,
9
+ Flex,
10
+ Group,
11
+ Image,
12
+ PinInput,
13
+ Stack,
14
+ Text,
15
+ Title,
16
+ } from "@mantine/core";
7
17
  import {
8
18
  IconAlertCircle,
9
19
  IconLock,
@@ -57,7 +67,7 @@ const Register = (props: RegisterProps) => {
57
67
  );
58
68
  const [isSubmitting, setIsSubmitting] = useState(false);
59
69
 
60
- const hasUsernamePassword = props.realmConfig.authenticationMethods.find(
70
+ const credentialsProvider = props.realmConfig.authenticationMethods.find(
61
71
  (it) => it.type === "CREDENTIALS",
62
72
  );
63
73
 
@@ -97,6 +107,7 @@ const Register = (props: RegisterProps) => {
97
107
 
98
108
  // Phase 1: Create registration intent
99
109
  const intent = await userCtrl.createRegistrationIntent({
110
+ query: { userRealmName: props.realmConfig.realmName },
100
111
  body: {
101
112
  username: data.username,
102
113
  email: data.email,
@@ -130,10 +141,11 @@ const Register = (props: RegisterProps) => {
130
141
  });
131
142
 
132
143
  // Auto-login after registration
133
- if (identifier) {
134
- await auth.login("credentials", {
144
+ if (identifier && credentialsProvider) {
145
+ await auth.login(credentialsProvider.name, {
135
146
  username: identifier,
136
147
  password: data.password,
148
+ realm: props.realmConfig.realmName,
137
149
  });
138
150
  }
139
151
 
@@ -162,10 +174,11 @@ const Register = (props: RegisterProps) => {
162
174
  });
163
175
 
164
176
  // Auto-login after registration
165
- if (registrationState.credentials) {
166
- await auth.login("credentials", {
177
+ if (registrationState.credentials && credentialsProvider) {
178
+ await auth.login(credentialsProvider.name, {
167
179
  username: registrationState.credentials.identifier,
168
180
  password: registrationState.credentials.password,
181
+ realm: props.realmConfig.realmName,
169
182
  });
170
183
  }
171
184
 
@@ -280,12 +293,46 @@ const Register = (props: RegisterProps) => {
280
293
  );
281
294
  }
282
295
 
296
+ // External login methods
297
+ const externalMethods = props.realmConfig.authenticationMethods.filter(
298
+ (method) => method.type !== "CREDENTIALS",
299
+ );
300
+
301
+ const showOrDivider = credentialsProvider && externalMethods.length > 0;
302
+
283
303
  // Registration form phase UI
284
304
  return (
285
305
  <Flex flex={1} justify={"center"} align={"center"}>
286
306
  <Stack gap={"sm"} w={360}>
287
307
  <Card withBorder p={"lg"} bg={"var(--alepha-elevated)"}>
288
308
  <Stack gap={"md"}>
309
+ {/* Realm branding */}
310
+ {(settings.logoUrl ||
311
+ settings.displayName ||
312
+ settings.description) && (
313
+ <Stack gap={"xs"} align="center" mb="xs">
314
+ {settings.logoUrl && (
315
+ <Image
316
+ src={settings.logoUrl}
317
+ alt={settings.displayName || props.realmConfig.realmName}
318
+ h={48}
319
+ w="auto"
320
+ fit="contain"
321
+ />
322
+ )}
323
+ {settings.displayName && (
324
+ <Title order={4} ta="center">
325
+ {settings.displayName}
326
+ </Title>
327
+ )}
328
+ {settings.description && (
329
+ <Text size="sm" c="dimmed" ta="center">
330
+ {settings.description}
331
+ </Text>
332
+ )}
333
+ </Stack>
334
+ )}
335
+
289
336
  {!isRegistrationAllowed ? (
290
337
  <>
291
338
  <Alert
@@ -295,108 +342,125 @@ const Register = (props: RegisterProps) => {
295
342
  >
296
343
  <Text size="sm">{tr("registerDisabled")}</Text>
297
344
  </Alert>
298
- <ActionButton href={router.path("login")}>
345
+ <ActionButton
346
+ href={router.path("login", {
347
+ query: { realm: props.realmConfig.realmName },
348
+ })}
349
+ >
299
350
  {tr("registerBackToSignIn")}
300
351
  </ActionButton>
301
352
  </>
302
- ) : hasUsernamePassword ? (
353
+ ) : (
303
354
  <>
304
- <form {...form.props}>
305
- <Stack flex={1} gap={"md"}>
306
- {settings.usernameEnabled !== false &&
307
- form.input.username && (
355
+ {/* Credentials registration form */}
356
+ {credentialsProvider && (
357
+ <form {...form.props}>
358
+ <Stack flex={1} gap={"md"}>
359
+ {settings.usernameEnabled !== false &&
360
+ form.input.username && (
361
+ <Control
362
+ title={tr("registerUsername")}
363
+ input={form.input.username}
364
+ icon={<IconUser />}
365
+ text={{
366
+ autoComplete: "username",
367
+ }}
368
+ />
369
+ )}
370
+ {settings.emailEnabled !== false && form.input.email && (
308
371
  <Control
309
- title={tr("registerUsername")}
310
- input={form.input.username}
311
- icon={<IconUser />}
372
+ title={tr("registerEmail")}
373
+ input={form.input.email}
374
+ icon={<IconMail />}
312
375
  text={{
313
- autoComplete: "username",
376
+ autoComplete: "email",
314
377
  }}
315
378
  />
316
379
  )}
317
- {settings.emailEnabled !== false && form.input.email && (
380
+ {settings.phoneEnabled === true &&
381
+ form.input.phoneNumber && (
382
+ <Control
383
+ title={tr("registerPhone")}
384
+ input={form.input.phoneNumber}
385
+ icon={<IconPhone />}
386
+ text={{
387
+ autoComplete: "tel",
388
+ }}
389
+ />
390
+ )}
318
391
  <Control
319
- title={tr("registerEmail")}
320
- input={form.input.email}
321
- icon={<IconMail />}
322
- text={{
323
- autoComplete: "email",
392
+ title={tr("registerPassword")}
393
+ input={form.input.password}
394
+ icon={<IconLock />}
395
+ password={{
396
+ autoComplete: "new-password",
324
397
  }}
325
398
  />
326
- )}
327
- {settings.phoneEnabled === true &&
328
- form.input.phoneNumber && (
329
- <Control
330
- title={tr("registerPhone")}
331
- input={form.input.phoneNumber}
332
- icon={<IconPhone />}
333
- text={{
334
- autoComplete: "tel",
335
- }}
336
- />
337
- )}
338
- <Control
339
- title={tr("registerPassword")}
340
- input={form.input.password}
341
- icon={<IconLock />}
342
- password={{
343
- autoComplete: "new-password",
344
- }}
345
- />
346
- <Control
347
- title={tr("registerConfirmPassword")}
348
- input={form.input.confirmPassword}
349
- icon={<IconLock />}
350
- password={{
351
- autoComplete: "new-password",
352
- }}
353
- />
354
- <ActionButton form={form} color={"blue"} variant={"filled"}>
355
- {tr("registerCreateAccount")}
356
- </ActionButton>
399
+ <Control
400
+ title={tr("registerConfirmPassword")}
401
+ input={form.input.confirmPassword}
402
+ icon={<IconLock />}
403
+ password={{
404
+ autoComplete: "new-password",
405
+ }}
406
+ />
407
+ <ActionButton
408
+ form={form}
409
+ color={"blue"}
410
+ variant={"filled"}
411
+ >
412
+ {tr("registerCreateAccount")}
413
+ </ActionButton>
414
+ </Stack>
415
+ </form>
416
+ )}
417
+
418
+ {/* OR divider - only when both credentials AND external methods exist */}
419
+ {showOrDivider && (
420
+ <Group align="center" justify="center" gap={"md"}>
421
+ <Flex flex={1} h={"1px"} bg={"var(--alepha-border)"} />
422
+ <Text size="xs" c="dimmed">
423
+ {tr("registerOr")}
424
+ </Text>
425
+ <Flex flex={1} h={"1px"} bg={"var(--alepha-border)"} />
426
+ </Group>
427
+ )}
428
+
429
+ {/* External login methods */}
430
+ {externalMethods.length > 0 && (
431
+ <Stack gap={"sm"}>
432
+ {externalMethods.map((method) => (
433
+ <ActionButton
434
+ variant={"default"}
435
+ key={method.type}
436
+ leftSection={leftSection(method.name.toLowerCase())}
437
+ onClick={() =>
438
+ auth.login(method.name, {
439
+ redirect,
440
+ realm: props.realmConfig.realmName,
441
+ })
442
+ }
443
+ >
444
+ {tr("registerContinueWith", {
445
+ args: [capitalize(method.name)],
446
+ })}
447
+ </ActionButton>
448
+ ))}
357
449
  </Stack>
358
- </form>
359
- <Group align="center" justify="center" gap={"md"}>
360
- <Flex flex={1} h={"1px"} bg={"var(--alepha-text-muted)"} />
361
- <Text size="xs">{tr("registerOr")}</Text>
362
- <Flex flex={1} h={"1px"} bg={"var(--alepha-text-muted)"} />
363
- </Group>
364
- </>
365
- ) : null}
366
- {isRegistrationAllowed && (
367
- <>
368
- <Stack gap={"sm"}>
369
- {props.realmConfig.authenticationMethods.map(
370
- (method) =>
371
- method.type !== "CREDENTIALS" && (
372
- <ActionButton
373
- variant={"default"}
374
- key={method.type}
375
- leftSection={leftSection(method.name.toLowerCase())}
376
- onClick={() =>
377
- auth.login(method.name, {
378
- redirect,
379
- })
380
- }
381
- >
382
- {tr("registerContinueWith", {
383
- args: [capitalize(method.name)],
384
- })}
385
- </ActionButton>
386
- ),
387
- )}
388
- </Stack>
389
- {props.realmConfig.authenticationMethods.length > 0 && (
390
- <Text size="sm" ta="center">
391
- {tr("registerHaveAccount")}{" "}
392
- <ActionButton
393
- href={router.path("login")}
394
- anchorProps={{ inherit: true }}
395
- >
396
- {tr("registerSignIn")}
397
- </ActionButton>
398
- </Text>
399
450
  )}
451
+
452
+ {/* Sign in link */}
453
+ <Text size="sm" ta="center">
454
+ {tr("registerHaveAccount")}{" "}
455
+ <ActionButton
456
+ href={router.path("login", {
457
+ query: { realm: props.realmConfig.realmName },
458
+ })}
459
+ anchorProps={{ inherit: true }}
460
+ >
461
+ {tr("registerSignIn")}
462
+ </ActionButton>
463
+ </Text>
400
464
  </>
401
465
  )}
402
466
  </Stack>
@@ -2,7 +2,16 @@ import { useClient, useRouter } from "@alepha/react";
2
2
  import { useForm } from "@alepha/react/form";
3
3
  import { useI18n } from "@alepha/react/i18n";
4
4
  import { ActionButton, Control } from "@alepha/ui";
5
- import { Alert, Card, Flex, PinInput, Stack, Text } from "@mantine/core";
5
+ import {
6
+ Alert,
7
+ Card,
8
+ Flex,
9
+ Image,
10
+ PinInput,
11
+ Stack,
12
+ Text,
13
+ Title,
14
+ } from "@mantine/core";
6
15
  import {
7
16
  IconAlertCircle,
8
17
  IconCheck,
@@ -43,8 +52,8 @@ const ResetPassword = (props: ResetPasswordProps) => {
43
52
  const [isSubmitting, setIsSubmitting] = useState(false);
44
53
  const redirect = router.query.r || "/";
45
54
 
46
- const isResetPasswordAllowed =
47
- props.realmConfig.settings?.resetPasswordAllowed !== false;
55
+ const settings = props.realmConfig.settings;
56
+ const isResetPasswordAllowed = settings?.resetPasswordAllowed !== false;
48
57
 
49
58
  // Phase 1: Request password reset intent
50
59
  const emailForm = useForm({
@@ -52,6 +61,7 @@ const ResetPassword = (props: ResetPasswordProps) => {
52
61
  handler: async (data) => {
53
62
  setError(null);
54
63
  const intent = await userCtrl.createPasswordResetIntent({
64
+ query: { userRealmName: props.realmConfig.realmName },
55
65
  body: { email: data.email },
56
66
  });
57
67
 
@@ -111,6 +121,7 @@ const ResetPassword = (props: ResetPasswordProps) => {
111
121
 
112
122
  try {
113
123
  const intent = await userCtrl.createPasswordResetIntent({
124
+ query: { userRealmName: props.realmConfig.realmName },
114
125
  body: { email: resetState.email },
115
126
  });
116
127
 
@@ -130,6 +141,33 @@ const ResetPassword = (props: ResetPasswordProps) => {
130
141
  <Stack gap={"sm"} w={360}>
131
142
  <Card withBorder p={"lg"} bg={"var(--alepha-elevated)"}>
132
143
  <Stack gap={"md"}>
144
+ {/* Realm branding */}
145
+ {(settings.logoUrl ||
146
+ settings.displayName ||
147
+ settings.description) && (
148
+ <Stack gap={"xs"} align="center" mb="xs">
149
+ {settings.logoUrl && (
150
+ <Image
151
+ src={settings.logoUrl}
152
+ alt={settings.displayName || props.realmConfig.realmName}
153
+ h={48}
154
+ w="auto"
155
+ fit="contain"
156
+ />
157
+ )}
158
+ {settings.displayName && (
159
+ <Title order={4} ta="center">
160
+ {settings.displayName}
161
+ </Title>
162
+ )}
163
+ {settings.description && (
164
+ <Text size="sm" c="dimmed" ta="center">
165
+ {settings.description}
166
+ </Text>
167
+ )}
168
+ </Stack>
169
+ )}
170
+
133
171
  {error && (
134
172
  <Alert variant="light" color="red" icon={<IconAlertCircle />}>
135
173
  <Text size="sm">{error}</Text>
@@ -145,7 +183,11 @@ const ResetPassword = (props: ResetPasswordProps) => {
145
183
  >
146
184
  <Text size="sm">{tr("resetPasswordDisabled")}</Text>
147
185
  </Alert>
148
- <ActionButton href={router.path("login")}>
186
+ <ActionButton
187
+ href={router.path("login", {
188
+ query: { realm: props.realmConfig.realmName },
189
+ })}
190
+ >
149
191
  {tr("resetPasswordBackToSignIn")}
150
192
  </ActionButton>
151
193
  </>
@@ -241,7 +283,11 @@ const ResetPassword = (props: ResetPasswordProps) => {
241
283
  <Alert variant="light" color="green" icon={<IconCheck />}>
242
284
  <Text size="sm">{tr("resetPasswordSuccess")}</Text>
243
285
  </Alert>
244
- <ActionButton href={router.path("login")}>
286
+ <ActionButton
287
+ href={router.path("login", {
288
+ query: { realm: props.realmConfig.realmName },
289
+ })}
290
+ >
245
291
  {tr("resetPasswordBackToSignIn")}
246
292
  </ActionButton>
247
293
  </>
@@ -51,6 +51,8 @@ const UserButton = (props: UserButtonProps) => {
51
51
  ...buttonProps
52
52
  } = props;
53
53
 
54
+ buttonProps.variant ??= "subtle";
55
+
54
56
  const auth = useAuth<{
55
57
  username?: string;
56
58
  email?: string;
@@ -0,0 +1,13 @@
1
+ import { $atom, type Static, t } from "alepha";
2
+
3
+ export const alephaThemeAtom = $atom({
4
+ name: "alepha.ui.theme",
5
+ schema: t.object({
6
+ index: t.integer(),
7
+ }),
8
+ default: {
9
+ index: 0,
10
+ },
11
+ });
12
+
13
+ export type CurrentAlephaTheme = Static<typeof alephaThemeAtom.schema>;
@@ -0,0 +1,10 @@
1
+ import { $atom, t } from "alepha";
2
+ import type { AlephaTheme } from "../interfaces/AlephaTheme.ts";
3
+ import { defaultTheme } from "./themes/default.ts";
4
+ import { midnightTheme } from "./themes/midnight.ts";
5
+
6
+ export const alephaThemeListAtom = $atom({
7
+ name: "alepha.ui.themeList",
8
+ schema: t.array(t.json<AlephaTheme>()), // TODO: translate to proper schema
9
+ default: [defaultTheme, midnightTheme],
10
+ });
@@ -0,0 +1,6 @@
1
+ import type { AlephaTheme } from "../../interfaces/AlephaTheme.ts";
2
+
3
+ export const defaultTheme: AlephaTheme = {
4
+ name: "Default",
5
+ description: "Default Alepha Theme",
6
+ };
@@ -1,10 +1,9 @@
1
- import type { AlephaTheme } from "../providers/ThemeProvider.ts";
1
+ import type { AlephaTheme } from "../../interfaces/AlephaTheme.ts";
2
2
 
3
3
  export const midnightTheme: AlephaTheme = {
4
- id: "midnight",
5
- label: "Midnight",
4
+ name: "Midnight",
6
5
  description: "Clean, developer-focused design",
7
- primaryColor: "gray",
6
+ primaryColor: "pink",
8
7
  primaryShade: { light: 7, dark: 8 },
9
8
  fontFamily:
10
9
  '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
@@ -24,7 +23,6 @@ export const midnightTheme: AlephaTheme = {
24
23
  h6: { fontSize: "0.75rem", lineHeight: "1.5" },
25
24
  },
26
25
  },
27
-
28
26
  radius: {
29
27
  xs: "3px",
30
28
  sm: "6px",
@@ -21,16 +21,17 @@ import {
21
21
  type ThemeIconProps,
22
22
  Tooltip,
23
23
  type TooltipProps,
24
+ useMantineTheme,
24
25
  } from "@mantine/core";
25
26
  import { IconCheck, IconChevronRight } from "@tabler/icons-react";
26
27
  import {
27
28
  type ButtonHTMLAttributes,
28
29
  Children,
29
30
  type ComponentType,
30
- isValidElement,
31
31
  type ReactNode,
32
32
  } from "react";
33
33
  import { ui } from "../../constants/ui.ts";
34
+ import { isComponentType } from "../../helpers/isComponentType.ts";
34
35
 
35
36
  export interface ActionMenuItem {
36
37
  /**
@@ -119,7 +120,7 @@ export interface ActionCommonProps extends ButtonProps {
119
120
  * Tooltip to display on hover. Can be a string for simple tooltips
120
121
  * or a TooltipProps object for advanced configuration.
121
122
  */
122
- tooltip?: string | TooltipProps;
123
+ tooltip?: string | number | TooltipProps;
123
124
 
124
125
  /**
125
126
  * Menu configuration. When provided, the action will display a dropdown menu.
@@ -148,7 +149,7 @@ export interface ActionCommonProps extends ButtonProps {
148
149
  /**
149
150
  * Visual intent of the action button.
150
151
  */
151
- intent?: "primary" | "success" | "danger" | "warning" | "info";
152
+ intent?: "primary" | "success" | "danger" | "warning" | "info" | "none";
152
153
  }
153
154
 
154
155
  export type ActionProps = ActionCommonProps &
@@ -239,12 +240,36 @@ const ActionMenuItem = (props: {
239
240
  };
240
241
 
241
242
  const ActionButton = (_props: ActionProps) => {
242
- const props = { variant: "subtle", ..._props };
243
+ const theme = useMantineTheme();
244
+ const props = { ..._props };
243
245
  const { tooltip, menu, icon, ...restProps } = props;
244
246
 
245
- // set default color to gray (not colored)
246
- restProps.color ??= "gray";
247
- restProps.c ??= "var(--mantine-color-text)";
247
+ if (props.variant === "subtle") {
248
+ restProps.c ??= "var(--mantine-color-text)";
249
+ restProps.color ??= "gray";
250
+ }
251
+
252
+ if (props.intent) {
253
+ if (props.intent === "none") {
254
+ restProps.c ??= "var(--mantine-color-text)";
255
+ restProps.color ??= "gray";
256
+ } else if (props.intent === "primary") {
257
+ restProps.c ??= "white";
258
+ restProps.color ??= theme.primaryColor;
259
+ } else if (props.intent === "success") {
260
+ restProps.c ??= "white";
261
+ restProps.color ??= "green";
262
+ } else if (props.intent === "danger") {
263
+ restProps.c ??= "white";
264
+ restProps.color ??= "red";
265
+ } else if (props.intent === "warning") {
266
+ restProps.c ??= "var(--mantine-color-text)";
267
+ restProps.color ??= "yellow";
268
+ } else if (props.intent === "info") {
269
+ restProps.c ??= "white";
270
+ restProps.color ??= "blue";
271
+ }
272
+ }
248
273
 
249
274
  if (props.icon) {
250
275
  const icon = isComponentType(props.icon) ? (
@@ -379,7 +404,7 @@ const ActionButton = (_props: ActionProps) => {
379
404
  openDelay: 1000,
380
405
  };
381
406
  const tooltipProps: TooltipProps =
382
- typeof tooltip === "string"
407
+ typeof tooltip === "string" || typeof tooltip === "number"
383
408
  ? {
384
409
  ...defaultTooltipProps,
385
410
  label: tooltip,
@@ -599,21 +624,3 @@ const ActionHrefButton = (props: ActionNavigationButtonProps) => {
599
624
  };
600
625
 
601
626
  // ---------------------------------------------------------------------------------------------------------------------
602
-
603
- export function isComponentType(param: any): param is ComponentType<any> {
604
- if (isValidElement(param)) return false;
605
- return (
606
- typeof param === "function" ||
607
- (typeof param === "object" && param !== null && "$$typeof" in param)
608
- );
609
- }
610
-
611
- export const renderIcon = (icon: ReactNode | ComponentType): ReactNode => {
612
- if (!icon) return null;
613
- if (isValidElement(icon)) return icon;
614
- if (isComponentType(icon)) {
615
- const IconComponent = icon;
616
- return <IconComponent size={ui.sizes.icon.md} />;
617
- }
618
- return icon as ReactNode;
619
- };
@@ -77,7 +77,6 @@ const DarkModeButton = (props: DarkModeButtonProps) => {
77
77
  size={props.size ?? "sm"}
78
78
  aria-label="Toggle color scheme"
79
79
  px={"xs"}
80
- c={colorScheme !== "default" ? undefined : "transparent"}
81
80
  fullWidth={props.fullWidth ?? false}
82
81
  icon={
83
82
  colorScheme === "dark"
@@ -1,7 +1,7 @@
1
- import { useInject } from "@alepha/react";
1
+ import { useStore } from "@alepha/react";
2
2
  import { IconPalette } from "@tabler/icons-react";
3
+ import { alephaThemeListAtom } from "../../atoms/alephaThemeListAtom.ts";
3
4
  import { useTheme } from "../../hooks/useTheme.ts";
4
- import { ThemeProvider } from "../../providers/ThemeProvider.ts";
5
5
  import ActionButton, { type ActionProps } from "./ActionButton.tsx";
6
6
 
7
7
  export interface ThemeButtonProps {
@@ -10,17 +10,20 @@ export interface ThemeButtonProps {
10
10
 
11
11
  const ThemeButton = (props: ThemeButtonProps) => {
12
12
  const [theme, setTheme] = useTheme();
13
- const themes = useInject(ThemeProvider).themes;
13
+ const themeList = useStore(alephaThemeListAtom)[0];
14
14
 
15
15
  return (
16
16
  <ActionButton
17
17
  variant="subtle"
18
18
  icon={IconPalette}
19
19
  menu={{
20
- items: themes.map((it) => ({
21
- label: it.label,
22
- onClick: () => setTheme(it),
23
- active: theme.id === it.id,
20
+ items: themeList.map((it, index) => ({
21
+ label: it.name,
22
+ onClick: () =>
23
+ setTheme({
24
+ index,
25
+ }),
26
+ active: theme.name === it.name,
24
27
  })),
25
28
  }}
26
29
  {...props.actionProps}