@alepha/ui 0.13.6 → 0.13.8

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 +2700 -1178
  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 +605 -532
  55. package/dist/auth/index.js +26 -18
  56. package/dist/auth/index.js.map +1 -1
  57. package/dist/core/index.d.ts +425 -155
  58. package/dist/core/index.js +1751 -1369
  59. package/dist/core/index.js.map +1 -1
  60. package/package.json +23 -20
  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
@@ -0,0 +1,183 @@
1
+ import { useClient, useRouterState } from "@alepha/react";
2
+ import { useI18n } from "@alepha/react/i18n";
3
+ import { DataTable, Flex, Text } from "@alepha/ui";
4
+ import { Badge, Group, Tooltip } from "@mantine/core";
5
+ import {
6
+ IconAlertTriangle,
7
+ IconCheck,
8
+ IconInfoCircle,
9
+ IconX,
10
+ } from "@tabler/icons-react";
11
+ import { type Page, t } from "alepha";
12
+ import type { AuditController, AuditEntity } from "alepha/api/audits";
13
+
14
+ export interface AdminUserAuditsProps {
15
+ userRealmName?: string;
16
+ }
17
+
18
+ const getSeverityColor = (severity: string) => {
19
+ switch (severity) {
20
+ case "critical":
21
+ return "red";
22
+ case "warning":
23
+ return "yellow";
24
+ default:
25
+ return "blue";
26
+ }
27
+ };
28
+
29
+ const getSeverityIcon = (severity: string) => {
30
+ switch (severity) {
31
+ case "critical":
32
+ return <IconAlertTriangle size={12} />;
33
+ case "warning":
34
+ return <IconAlertTriangle size={12} />;
35
+ default:
36
+ return <IconInfoCircle size={12} />;
37
+ }
38
+ };
39
+
40
+ const AdminUserAudits = (_props: AdminUserAuditsProps) => {
41
+ const state = useRouterState();
42
+ const client = useClient<AuditController>();
43
+ const { l } = useI18n();
44
+ const userId = state.params.userId as string;
45
+
46
+ const filters = t.object({
47
+ type: t.optional(t.text()),
48
+ action: t.optional(t.text()),
49
+ severity: t.optional(t.enum(["info", "warning", "critical"])),
50
+ success: t.optional(t.boolean()),
51
+ from: t.optional(t.datetime()),
52
+ to: t.optional(t.datetime()),
53
+ });
54
+
55
+ return (
56
+ <Flex flex={1} direction="column">
57
+ <DataTable<AuditEntity, typeof filters>
58
+ submitOnInit
59
+ defaultSize={15}
60
+ typeFormProps={{
61
+ skipSubmitButton: true,
62
+ columns: 4,
63
+ }}
64
+ tableProps={{
65
+ horizontalSpacing: "xs",
66
+ verticalSpacing: "xs",
67
+ striped: false,
68
+ highlightOnHover: true,
69
+ }}
70
+ filters={filters}
71
+ items={async (query) => {
72
+ const response = await client.findByUser({
73
+ params: { userId },
74
+ query,
75
+ });
76
+ return response as Page<AuditEntity>;
77
+ }}
78
+ columns={{
79
+ type: {
80
+ label: "Type",
81
+ fit: true,
82
+ value: (item) => (
83
+ <Badge size="sm" variant="light" color="grape">
84
+ {item.type}
85
+ </Badge>
86
+ ),
87
+ },
88
+ action: {
89
+ label: "Action",
90
+ fit: true,
91
+ value: (item) => (
92
+ <Badge size="sm" variant="outline">
93
+ {item.action}
94
+ </Badge>
95
+ ),
96
+ },
97
+ severity: {
98
+ label: "Severity",
99
+ fit: true,
100
+ value: (item) => (
101
+ <Badge
102
+ size="sm"
103
+ variant="light"
104
+ color={getSeverityColor(item.severity)}
105
+ leftSection={getSeverityIcon(item.severity)}
106
+ >
107
+ {item.severity}
108
+ </Badge>
109
+ ),
110
+ },
111
+ description: {
112
+ label: "Description",
113
+ value: (item) => (
114
+ <Text size="sm" lineClamp={1}>
115
+ {item.description || "-"}
116
+ </Text>
117
+ ),
118
+ },
119
+ resource: {
120
+ label: "Resource",
121
+ fit: true,
122
+ value: (item) =>
123
+ item.resourceType ? (
124
+ <Tooltip label={item.resourceId || "N/A"}>
125
+ <Badge size="xs" variant="dot" color="gray">
126
+ {item.resourceType}
127
+ </Badge>
128
+ </Tooltip>
129
+ ) : (
130
+ <Text size="xs" c="dimmed">
131
+ -
132
+ </Text>
133
+ ),
134
+ },
135
+ success: {
136
+ label: "Status",
137
+ fit: true,
138
+ value: (item) =>
139
+ item.success ? (
140
+ <Group gap={4}>
141
+ <IconCheck size={14} color="var(--mantine-color-green-6)" />
142
+ <Text size="xs" c="green">
143
+ Success
144
+ </Text>
145
+ </Group>
146
+ ) : (
147
+ <Tooltip label={item.errorMessage || "Failed"}>
148
+ <Group gap={4}>
149
+ <IconX size={14} color="var(--mantine-color-red-6)" />
150
+ <Text size="xs" c="red">
151
+ Failed
152
+ </Text>
153
+ </Group>
154
+ </Tooltip>
155
+ ),
156
+ },
157
+ ipAddress: {
158
+ label: "IP",
159
+ fit: true,
160
+ value: (item) => (
161
+ <Text size="xs" c="dimmed" ff="monospace">
162
+ {item.ipAddress || "-"}
163
+ </Text>
164
+ ),
165
+ },
166
+ createdAt: {
167
+ label: "Time",
168
+ fit: true,
169
+ value: (item) => (
170
+ <Tooltip label={l(item.createdAt, { date: "medium" })}>
171
+ <Text size="xs" c="dimmed">
172
+ {l(item.createdAt, { date: "fromNow" })}
173
+ </Text>
174
+ </Tooltip>
175
+ ),
176
+ },
177
+ }}
178
+ />
179
+ </Flex>
180
+ );
181
+ };
182
+
183
+ export default AdminUserAudits;
@@ -4,7 +4,7 @@ import { ActionButton, Control, Flex } from "@alepha/ui";
4
4
  import { Card, Stack, Text } from "@mantine/core";
5
5
  import { t } from "alepha";
6
6
  import type { UserController } from "alepha/api/users";
7
- import type { AdminRouter } from "../AdminRouter.ts";
7
+ import type { AdminRouter } from "../../AdminRouter.ts";
8
8
 
9
9
  export interface AdminUserCreateProps {
10
10
  userRealmName?: string;
@@ -9,7 +9,7 @@ import { Avatar, Badge, Card, Group, Loader, Stack, Tabs } from "@mantine/core";
9
9
  import { IconDevices, IconSettings, IconUser } from "@tabler/icons-react";
10
10
  import type { UserController, UserEntity } from "alepha/api/users";
11
11
  import { useEffect, useState } from "react";
12
- import type { AdminRouter } from "../AdminRouter.ts";
12
+ import type { AdminRouter } from "../../AdminRouter.ts";
13
13
 
14
14
  export interface AdminUserLayoutProps {
15
15
  userRealmName?: string;
@@ -112,7 +112,6 @@ const AdminUserLayout = (props: AdminUserLayoutProps) => {
112
112
  <Tabs value={activeTab}>
113
113
  <Tabs.List>
114
114
  <ActionButton
115
- variant="subtle"
116
115
  href={detailsPath}
117
116
  leftSection={<IconUser size={16} />}
118
117
  c={activeTab === "details" ? undefined : "dimmed"}
@@ -128,7 +127,6 @@ const AdminUserLayout = (props: AdminUserLayoutProps) => {
128
127
  Details
129
128
  </ActionButton>
130
129
  <ActionButton
131
- variant="subtle"
132
130
  href={sessionsPath}
133
131
  leftSection={<IconDevices size={16} />}
134
132
  c={activeTab === "sessions" ? undefined : "dimmed"}
@@ -144,7 +142,6 @@ const AdminUserLayout = (props: AdminUserLayoutProps) => {
144
142
  Sessions
145
143
  </ActionButton>
146
144
  <ActionButton
147
- variant="subtle"
148
145
  href={settingsPath}
149
146
  leftSection={<IconSettings size={16} />}
150
147
  c={activeTab === "settings" ? undefined : "dimmed"}
@@ -9,7 +9,7 @@ import {
9
9
  } from "@tabler/icons-react";
10
10
  import type { UserController, UserEntity } from "alepha/api/users";
11
11
  import { useEffect, useState } from "react";
12
- import type { AdminRouter } from "../AdminRouter.ts";
12
+ import type { AdminRouter } from "../../AdminRouter.ts";
13
13
 
14
14
  export interface AdminUserSettingsProps {
15
15
  userRealmName?: string;
@@ -1,11 +1,11 @@
1
1
  import { useClient, useRouter } from "@alepha/react";
2
2
  import { useI18n } from "@alepha/react/i18n";
3
- import { ActionButton, DataTable, Text } from "@alepha/ui";
3
+ import { DataTable, Text } from "@alepha/ui";
4
4
  import { Badge, Flex, Group } from "@mantine/core";
5
- import { IconCheck, IconPlus, IconX } from "@tabler/icons-react";
5
+ import { IconCheck, IconUsersPlus, IconX } from "@tabler/icons-react";
6
6
  import { type Page, t } from "alepha";
7
7
  import { type UserController, type UserEntity, users } from "alepha/api/users";
8
- import type { AdminRouter } from "../AdminRouter.ts";
8
+ import type { AdminRouter } from "../../AdminRouter.ts";
9
9
 
10
10
  export interface AdminUsersProps {
11
11
  userRealmName?: string;
@@ -28,17 +28,15 @@ const AdminUsers = (props: AdminUsersProps) => {
28
28
 
29
29
  return (
30
30
  <Flex flex={1} direction="column">
31
- <Flex justify="flex-end" p="md" pb={0}>
32
- <ActionButton
33
- leftSection={<IconPlus size={16} />}
34
- href={router.path("adminUserCreate")}
35
- >
36
- Create User
37
- </ActionButton>
38
- </Flex>
39
-
40
31
  <DataTable<UserEntity, typeof filters>
41
32
  submitOnInit
33
+ actions={[
34
+ {
35
+ icon: IconUsersPlus,
36
+ href: router.path("adminUserCreate"),
37
+ label: "Create User",
38
+ },
39
+ ]}
42
40
  defaultSize={10}
43
41
  typeFormProps={{
44
42
  skipSubmitButton: true,
@@ -2,26 +2,35 @@ import { AlephaUI } from "@alepha/ui";
2
2
  import { AlephaUIAuth } from "@alepha/ui/auth";
3
3
  import { $module } from "alepha";
4
4
  import { AdminRouter } from "./AdminRouter.ts";
5
- import { AdminSidebar } from "./AdminSidebar.ts";
6
5
  import { MainRouter } from "./MainRouter.ts";
7
6
 
8
7
  // ---------------------------------------------------------------------------------------------------------------------
9
8
 
10
9
  export { AdminRouter } from "./AdminRouter.ts";
11
- export { AdminSidebar } from "./AdminSidebar.ts";
12
- export { default as AdminFiles } from "./components/AdminFiles.tsx";
13
- export { default as AdminJobs } from "./components/AdminJobs.tsx";
10
+ // Layout
14
11
  export { default as AdminLayout } from "./components/AdminLayout.tsx";
15
- export { default as AdminNotifications } from "./components/AdminNotifications.tsx";
16
- export { default as AdminParameters } from "./components/AdminParameters.tsx";
17
- export { default as AdminSessions } from "./components/AdminSessions.tsx";
18
- export { default as AdminUserCreate } from "./components/AdminUserCreate.tsx";
19
- export { default as AdminUserDetails } from "./components/AdminUserDetails.tsx";
20
- export { default as AdminUserLayout } from "./components/AdminUserLayout.tsx";
21
- export { default as AdminUserSessions } from "./components/AdminUserSessions.tsx";
22
- export { default as AdminUserSettings } from "./components/AdminUserSettings.tsx";
23
- export { default as AdminUsers } from "./components/AdminUsers.tsx";
24
- export { default as AdminVerifications } from "./components/AdminVerifications.tsx";
12
+ // Audits
13
+ export { default as AdminAudits } from "./components/audits/AdminAudits.tsx";
14
+ // Files
15
+ export { default as AdminFiles } from "./components/files/AdminFiles.tsx";
16
+ // Jobs
17
+ export { default as AdminJobs } from "./components/jobs/AdminJobs.tsx";
18
+ // Notifications
19
+ export { default as AdminNotifications } from "./components/notifications/AdminNotifications.tsx";
20
+ // Parameters
21
+ export { default as AdminParameters } from "./components/parameters/AdminParameters.tsx";
22
+ // Sessions
23
+ export { default as AdminSessions } from "./components/sessions/AdminSessions.tsx";
24
+ // Users
25
+ export { default as AdminUserAudits } from "./components/users/AdminUserAudits.tsx";
26
+ export { default as AdminUserCreate } from "./components/users/AdminUserCreate.tsx";
27
+ export { default as AdminUserDetails } from "./components/users/AdminUserDetails.tsx";
28
+ export { default as AdminUserLayout } from "./components/users/AdminUserLayout.tsx";
29
+ export { default as AdminUserSessions } from "./components/users/AdminUserSessions.tsx";
30
+ export { default as AdminUserSettings } from "./components/users/AdminUserSettings.tsx";
31
+ export { default as AdminUsers } from "./components/users/AdminUsers.tsx";
32
+ // Verifications
33
+ export { default as AdminVerifications } from "./components/verifications/AdminVerifications.tsx";
25
34
  export { MainRouter } from "./MainRouter.ts";
26
35
 
27
36
  // ---------------------------------------------------------------------------------------------------------------------
@@ -33,9 +42,8 @@ export { MainRouter } from "./MainRouter.ts";
33
42
  */
34
43
  export const AlephaUIAdmin = $module({
35
44
  name: "alepha.ui.admin",
36
- services: [AlephaUI, AlephaUIAuth, AdminRouter, MainRouter, AdminSidebar],
45
+ services: [AlephaUI, AlephaUIAuth, AdminRouter, MainRouter],
37
46
  register: (alepha) => {
38
47
  alepha.with(AdminRouter);
39
- alepha.with(AdminSidebar);
40
48
  },
41
49
  });
@@ -11,6 +11,16 @@ import { $inject, AlephaError, t } from "alepha";
11
11
  import type { UserRealmController } from "alepha/api/users";
12
12
  import { $client } from "alepha/server/links";
13
13
 
14
+ /**
15
+ * Schema for realm query parameter used across auth pages.
16
+ */
17
+ const realmQuerySchema = t.object({
18
+ r: t.optional(t.string({ description: "Redirect URL after authentication" })),
19
+ realm: t.optional(
20
+ t.string({ description: "User realm name for multi-tenant auth" }),
21
+ ),
22
+ });
23
+
14
24
  export class AuthRouter {
15
25
  protected readonly userRealmClient = $client<UserRealmController>();
16
26
  protected readonly auth = $inject(ReactAuth);
@@ -33,15 +43,13 @@ export class AuthRouter {
33
43
  description: "Sign in to your account",
34
44
  path: "/login",
35
45
  schema: {
36
- query: t.object({
37
- r: t.optional(t.string()),
38
- }),
46
+ query: realmQuerySchema,
39
47
  },
40
48
  can: () => !this.auth.user,
41
49
  lazy: () => import("./components/Login.tsx"),
42
- resolve: async () => {
50
+ resolve: async ({ query }) => {
43
51
  return {
44
- realmConfig: await this.loadRealmConfig(),
52
+ realmConfig: await this.loadRealmConfig(query.realm),
45
53
  };
46
54
  },
47
55
  });
@@ -52,15 +60,13 @@ export class AuthRouter {
52
60
  description: "Create a new account",
53
61
  path: "/register",
54
62
  schema: {
55
- query: t.object({
56
- r: t.optional(t.string()),
57
- }),
63
+ query: realmQuerySchema,
58
64
  },
59
65
  can: () => !this.auth.user,
60
66
  lazy: () => import("./components/Register.tsx"),
61
- resolve: async () => {
67
+ resolve: async ({ query }) => {
62
68
  return {
63
- realmConfig: await this.loadRealmConfig(),
69
+ realmConfig: await this.loadRealmConfig(query.realm),
64
70
  };
65
71
  },
66
72
  });
@@ -71,15 +77,13 @@ export class AuthRouter {
71
77
  description: "Reset your account password",
72
78
  path: "/reset-password",
73
79
  schema: {
74
- query: t.object({
75
- r: t.optional(t.string()),
76
- }),
80
+ query: realmQuerySchema,
77
81
  },
78
82
  can: () => !this.auth.user,
79
83
  lazy: () => import("./components/ResetPassword.tsx"),
80
- resolve: async () => {
84
+ resolve: async ({ query }) => {
81
85
  return {
82
- realmConfig: await this.loadRealmConfig(),
86
+ realmConfig: await this.loadRealmConfig(query.realm),
83
87
  };
84
88
  },
85
89
  });
@@ -111,9 +115,11 @@ export class AuthRouter {
111
115
  },
112
116
  });
113
117
 
114
- protected async loadRealmConfig() {
118
+ protected async loadRealmConfig(userRealmName?: string) {
115
119
  try {
116
- return await this.userRealmClient.getRealmConfig();
120
+ return await this.userRealmClient.getRealmConfig({
121
+ query: { userRealmName },
122
+ });
117
123
  } catch (e) {
118
124
  if (e instanceof AlephaError) {
119
125
  throw new AlephaError(
@@ -1,11 +1,14 @@
1
1
  import { NestedView } from "@alepha/react";
2
+ import { AlephaMantineProvider } from "@alepha/ui";
2
3
  import { Flex } from "@mantine/core";
3
4
 
4
5
  const AuthLayout = () => {
5
6
  return (
6
- <Flex flex={1} align={"center"} h={"100vh"} justify={"center"}>
7
- <NestedView />
8
- </Flex>
7
+ <AlephaMantineProvider omnibar={false}>
8
+ <Flex flex={1} align={"center"} h={"100vh"} justify={"center"}>
9
+ <NestedView />
10
+ </Flex>
11
+ </AlephaMantineProvider>
9
12
  );
10
13
  };
11
14
 
@@ -3,9 +3,9 @@ import { useAuth } from "@alepha/react/auth";
3
3
  import { FormValidationError, useForm } from "@alepha/react/form";
4
4
  import { useI18n } from "@alepha/react/i18n";
5
5
  import { ActionButton, Control, capitalize } from "@alepha/ui";
6
- import { Card, Flex, Group, Stack, Text } from "@mantine/core";
6
+ import { Card, Flex, Group, Image, Stack, Text, Title } from "@mantine/core";
7
7
  import { IconLock, IconUser } from "@tabler/icons-react";
8
- import { t } from "alepha";
8
+ import { AlephaError, t } from "alepha";
9
9
  import type { UserRealmConfig } from "alepha/api/users";
10
10
  import { HttpError } from "alepha/server";
11
11
  import { useMemo } from "react";
@@ -24,7 +24,7 @@ const Login = (props: LoginProps) => {
24
24
  const { tr } = useI18n<AuthI18n, "en">();
25
25
  const redirect = router.query.r || "/";
26
26
 
27
- const hasUsernamePassword = props.realmConfig.authenticationMethods.find(
27
+ const credentialsProvider = props.realmConfig.authenticationMethods.find(
28
28
  (it) => it.type === "CREDENTIALS",
29
29
  );
30
30
 
@@ -68,10 +68,15 @@ const Login = (props: LoginProps) => {
68
68
  }),
69
69
  }),
70
70
  handler: async (data) => {
71
+ if (!credentialsProvider) {
72
+ throw new AlephaError("Credentials provider not configured");
73
+ }
74
+
71
75
  try {
72
- await auth.login("credentials", {
76
+ await auth.login(credentialsProvider.name, {
73
77
  username: data.identifier,
74
78
  password: data.password,
79
+ realm: props.realmConfig.realmName,
75
80
  });
76
81
  await router.go(router.query.r || "/");
77
82
  } catch (error) {
@@ -89,12 +94,59 @@ const Login = (props: LoginProps) => {
89
94
  },
90
95
  });
91
96
 
97
+ const getAutoCompleteType = () => {
98
+ if (loginMethods.includes("email")) {
99
+ return "email";
100
+ }
101
+ if (loginMethods.includes("username")) {
102
+ return "username";
103
+ }
104
+ if (loginMethods.includes("phone")) {
105
+ return "tel";
106
+ }
107
+ return "username";
108
+ };
109
+
110
+ const externalLoginMethods = props.realmConfig.authenticationMethods.filter(
111
+ (method) => method.type !== "CREDENTIALS",
112
+ );
113
+
114
+ const showOrDivider = credentialsProvider && externalLoginMethods.length > 0;
115
+
92
116
  return (
93
117
  <Flex flex={1} justify={"center"} align={"center"}>
94
118
  <Stack gap={"sm"} w={360}>
95
119
  <Card withBorder p={"lg"} bg={"var(--alepha-elevated)"}>
96
120
  <Stack gap={"md"}>
97
- {hasUsernamePassword && (
121
+ {/* Realm branding */}
122
+ {(settings.logoUrl ||
123
+ settings.displayName ||
124
+ settings.description) && (
125
+ <Stack gap={"xs"} align="center" mb="xs">
126
+ {settings.logoUrl && (
127
+ <Image
128
+ src={settings.logoUrl}
129
+ alt={settings.displayName || props.realmConfig.realmName}
130
+ h={48}
131
+ w="auto"
132
+ fit="contain"
133
+ />
134
+ )}
135
+ {settings.displayName && (
136
+ <Title order={4} ta="center">
137
+ {settings.displayName}
138
+ </Title>
139
+ )}
140
+ {settings.description && (
141
+ <Text size="sm" c="dimmed" ta="center">
142
+ {settings.description}
143
+ </Text>
144
+ )}
145
+ </Stack>
146
+ )}
147
+
148
+ {/* Credentials login form */}
149
+ {credentialsProvider && (
98
150
  <>
99
151
  <form {...form.props}>
100
152
  <Stack flex={1} gap={"md"}>
@@ -103,9 +155,7 @@ const Login = (props: LoginProps) => {
103
155
  input={form.input.identifier}
104
156
  icon={IconUser}
105
157
  text={{
106
- autoComplete: loginMethods.includes("email")
107
- ? "email"
108
- : "username",
158
+ autoComplete: getAutoCompleteType(),
109
159
  }}
110
160
  />
111
161
  <Control
@@ -116,56 +166,68 @@ const Login = (props: LoginProps) => {
116
166
  autoComplete: "current-password",
117
167
  }}
118
168
  />
119
- <ActionButton color={"blue"} variant={"filled"} form={form}>
169
+ <ActionButton variant={"filled"} form={form}>
120
170
  {tr("loginSignIn")}
121
171
  </ActionButton>
122
172
  </Stack>
123
173
  </form>
124
- <Stack gap="xs">
125
- {settings.resetPasswordAllowed && (
126
- <Text size="sm" ta="center">
127
- <ActionButton
128
- href={router.path("resetPassword")}
129
- anchorProps={{ inherit: true }}
130
- >
131
- {tr("loginForgotPassword")}
132
- </ActionButton>
133
- </Text>
134
- )}
135
- <Group align="center" justify="center" gap={"md"}>
136
- <Flex flex={1} h={"1px"} bg={"var(--alepha-text-muted)"} />
137
- <Text size="xs">{tr("loginOr")}</Text>
138
- <Flex flex={1} h={"1px"} bg={"var(--alepha-text-muted)"} />
139
- </Group>
140
- </Stack>
141
- </>
142
- )}
143
- <Stack gap={"sm"}>
144
- {props.realmConfig.authenticationMethods.map(
145
- (method) =>
146
- method.type !== "CREDENTIALS" && (
174
+ {settings.resetPasswordAllowed && (
175
+ <Text size="sm" ta="center">
147
176
  <ActionButton
148
- variant={"default"}
149
- key={method.type}
150
- leftSection={leftSection(method.name.toLowerCase())}
151
- onClick={() =>
152
- auth.login(method.name, {
153
- redirect,
154
- })
155
- }
156
- >
157
- {tr("loginContinueWith", {
158
- args: [capitalize(method.name)],
177
+ href={router.path("resetPassword", {
178
+ query: { realm: props.realmConfig.realmName },
159
179
  })}
180
+ anchorProps={{ inherit: true }}
181
+ >
182
+ {tr("loginForgotPassword")}
160
183
  </ActionButton>
161
- ),
162
- )}
163
- </Stack>
184
+ </Text>
185
+ )}
186
+ </>
187
+ )}
188
+
189
+ {/* OR divider - only when both credentials AND external methods exist */}
190
+ {showOrDivider && (
191
+ <Group align="center" justify="center" gap={"md"}>
192
+ <Flex flex={1} h={"1px"} bg={"var(--alepha-border)"} />
193
+ <Text size="xs" c={"dimmed"}>
194
+ {tr("loginOr")}
195
+ </Text>
196
+ <Flex flex={1} h={"1px"} bg={"var(--alepha-border)"} />
197
+ </Group>
198
+ )}
199
+
200
+ {/* External login methods */}
201
+ {externalLoginMethods.length > 0 && (
202
+ <Stack gap={"sm"}>
203
+ {externalLoginMethods.map((method) => (
204
+ <ActionButton
205
+ variant={"default"}
206
+ key={method.type}
207
+ leftSection={leftSection(method.name.toLowerCase())}
208
+ onClick={() =>
209
+ auth.login(method.name, {
210
+ redirect,
211
+ realm: props.realmConfig.realmName,
212
+ })
213
+ }
214
+ >
215
+ {tr("loginContinueWith", {
216
+ args: [capitalize(method.name)],
217
+ })}
218
+ </ActionButton>
219
+ ))}
220
+ </Stack>
221
+ )}
222
+
223
+ {/* Registration link */}
164
224
  {settings.registrationAllowed && (
165
225
  <Text size="sm" ta="center">
166
226
  {tr("loginNoAccount")}{" "}
167
227
  <ActionButton
168
- href={router.path("register")}
228
+ href={router.path("register", {
229
+ query: { realm: props.realmConfig.realmName },
230
+ })}
169
231
  anchorProps={{ inherit: true }}
170
232
  >
171
233
  {tr("loginSignUp")}