@alepha/ui 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +2 -30
  2. package/dist/admin/AdminFiles-BM6_7_5A.cjs +4 -0
  3. package/dist/admin/AdminFiles-BaCIMeNt.js +4 -0
  4. package/dist/admin/AdminFiles-CllAxb1B.js +117 -0
  5. package/dist/admin/AdminFiles-CllAxb1B.js.map +1 -0
  6. package/dist/admin/AdminFiles-DC3T8uWZ.cjs +122 -0
  7. package/dist/admin/AdminFiles-DC3T8uWZ.cjs.map +1 -0
  8. package/dist/admin/AdminJobs-BXkFtlVo.js +125 -0
  9. package/dist/admin/AdminJobs-BXkFtlVo.js.map +1 -0
  10. package/dist/admin/AdminJobs-C428qrNQ.cjs +130 -0
  11. package/dist/admin/AdminJobs-C428qrNQ.cjs.map +1 -0
  12. package/dist/admin/AdminJobs-DCPPaJ4i.cjs +4 -0
  13. package/dist/admin/AdminJobs-yC6DarGO.js +4 -0
  14. package/dist/admin/AdminLayout-Bqo4cd33.cjs +4 -0
  15. package/dist/admin/AdminLayout-CQpxfko6.js +4 -0
  16. package/dist/admin/AdminLayout-CiLlywAQ.cjs +93 -0
  17. package/dist/admin/AdminLayout-CiLlywAQ.cjs.map +1 -0
  18. package/dist/admin/AdminLayout-CtkVYk-u.js +88 -0
  19. package/dist/admin/AdminLayout-CtkVYk-u.js.map +1 -0
  20. package/dist/admin/AdminNotifications-DNUeJ-PW.cjs +44 -0
  21. package/dist/admin/AdminNotifications-DNUeJ-PW.cjs.map +1 -0
  22. package/dist/admin/AdminNotifications-DaMu1AQ4.js +4 -0
  23. package/dist/admin/AdminNotifications-DnnulNNV.js +40 -0
  24. package/dist/admin/AdminNotifications-DnnulNNV.js.map +1 -0
  25. package/dist/admin/AdminNotifications-ihgbKVCx.cjs +4 -0
  26. package/dist/admin/AdminParameters-B3hvpLpu.js +40 -0
  27. package/dist/admin/AdminParameters-B3hvpLpu.js.map +1 -0
  28. package/dist/admin/AdminParameters-U4lU1rUF.cjs +4 -0
  29. package/dist/admin/AdminParameters-gdf7036N.cjs +44 -0
  30. package/dist/admin/AdminParameters-gdf7036N.cjs.map +1 -0
  31. package/dist/admin/AdminParameters-prMcCgxf.js +4 -0
  32. package/dist/admin/AdminSessions-BF_P4lHs.cjs +128 -0
  33. package/dist/admin/AdminSessions-BF_P4lHs.cjs.map +1 -0
  34. package/dist/admin/AdminSessions-CATIU61I.cjs +4 -0
  35. package/dist/admin/AdminSessions-DqOXOpYR.js +4 -0
  36. package/dist/admin/AdminSessions-Pjdz-iZx.js +123 -0
  37. package/dist/admin/AdminSessions-Pjdz-iZx.js.map +1 -0
  38. package/dist/admin/AdminUsers-BgTL-zSY.js +4 -0
  39. package/dist/admin/AdminUsers-C1HsrRxn.js +104 -0
  40. package/dist/admin/AdminUsers-C1HsrRxn.js.map +1 -0
  41. package/dist/admin/AdminUsers-HqvxwNGZ.cjs +4 -0
  42. package/dist/admin/AdminUsers-M2uEQbp5.cjs +109 -0
  43. package/dist/admin/AdminUsers-M2uEQbp5.cjs.map +1 -0
  44. package/dist/admin/AdminVerifications-BVssbtfU.cjs +44 -0
  45. package/dist/admin/AdminVerifications-BVssbtfU.cjs.map +1 -0
  46. package/dist/admin/AdminVerifications-Df6DRgNo.js +4 -0
  47. package/dist/admin/AdminVerifications-DxAtcYUR.cjs +4 -0
  48. package/dist/admin/AdminVerifications-VMpm30mS.js +40 -0
  49. package/dist/admin/AdminVerifications-VMpm30mS.js.map +1 -0
  50. package/dist/admin/core-CzO6aavT.js +2507 -0
  51. package/dist/admin/core-CzO6aavT.js.map +1 -0
  52. package/dist/{index.cjs → admin/core-aFtK4l9I.cjs} +287 -204
  53. package/dist/admin/core-aFtK4l9I.cjs.map +1 -0
  54. package/dist/admin/index.cjs +87 -0
  55. package/dist/admin/index.cjs.map +1 -0
  56. package/dist/admin/index.d.cts +1739 -0
  57. package/dist/admin/index.d.ts +1745 -0
  58. package/dist/admin/index.js +78 -0
  59. package/dist/admin/index.js.map +1 -0
  60. package/dist/auth/IconGoogle-B17BTQyD.cjs +69 -0
  61. package/dist/auth/IconGoogle-B17BTQyD.cjs.map +1 -0
  62. package/dist/auth/IconGoogle-Bfmuv9Rv.js +58 -0
  63. package/dist/auth/IconGoogle-Bfmuv9Rv.js.map +1 -0
  64. package/dist/auth/Login-BTBmbnWl.cjs +181 -0
  65. package/dist/auth/Login-BTBmbnWl.cjs.map +1 -0
  66. package/dist/auth/Login-BcQOtG3v.js +5 -0
  67. package/dist/auth/Login-Btmd70Um.cjs +5 -0
  68. package/dist/auth/Login-JeXFsUf5.js +176 -0
  69. package/dist/auth/Login-JeXFsUf5.js.map +1 -0
  70. package/dist/auth/Register-CPQnvXCZ.js +318 -0
  71. package/dist/auth/Register-CPQnvXCZ.js.map +1 -0
  72. package/dist/auth/Register-CbesZal3.cjs +5 -0
  73. package/dist/auth/Register-DpI_JdyO.js +5 -0
  74. package/dist/auth/Register-HP3rP71B.cjs +323 -0
  75. package/dist/auth/Register-HP3rP71B.cjs.map +1 -0
  76. package/dist/auth/ResetPassword-B-tkzV7g.cjs +248 -0
  77. package/dist/auth/ResetPassword-B-tkzV7g.cjs.map +1 -0
  78. package/dist/auth/ResetPassword-BlK3xEpU.js +4 -0
  79. package/dist/auth/ResetPassword-BzUjGG_-.js +243 -0
  80. package/dist/auth/ResetPassword-BzUjGG_-.js.map +1 -0
  81. package/dist/auth/ResetPassword-W3xjOnWy.cjs +4 -0
  82. package/dist/auth/chunk-DhGyd7sr.js +28 -0
  83. package/dist/auth/core-D1MHij1j.js +1795 -0
  84. package/dist/auth/core-D1MHij1j.js.map +1 -0
  85. package/dist/auth/core-rDZ9d92K.cjs +1824 -0
  86. package/dist/auth/core-rDZ9d92K.cjs.map +1 -0
  87. package/dist/auth/index.cjs +211 -0
  88. package/dist/auth/index.cjs.map +1 -0
  89. package/dist/auth/index.d.cts +6265 -0
  90. package/dist/auth/index.d.ts +6274 -0
  91. package/dist/auth/index.js +206 -0
  92. package/dist/auth/index.js.map +1 -0
  93. package/dist/core/index.cjs +2620 -0
  94. package/dist/core/index.cjs.map +1 -0
  95. package/dist/core/index.d.cts +2737 -0
  96. package/dist/core/index.d.ts +2743 -0
  97. package/dist/{index.js → core/index.js} +298 -126
  98. package/dist/core/index.js.map +1 -0
  99. package/package.json +32 -14
  100. package/src/admin/AdminRouter.ts +58 -0
  101. package/src/admin/components/AdminFiles.tsx +117 -0
  102. package/src/admin/components/AdminJobs.tsx +158 -0
  103. package/src/admin/components/AdminLayout.tsx +114 -0
  104. package/src/admin/components/AdminNotifications.tsx +20 -0
  105. package/src/admin/components/AdminParameters.tsx +24 -0
  106. package/src/admin/components/AdminSessions.tsx +159 -0
  107. package/src/admin/components/AdminUsers.tsx +137 -0
  108. package/src/admin/components/AdminVerifications.tsx +25 -0
  109. package/src/admin/index.ts +29 -0
  110. package/src/auth/AuthI18n.ts +118 -0
  111. package/src/auth/AuthRouter.ts +53 -0
  112. package/src/auth/components/Login.tsx +193 -0
  113. package/src/auth/components/Register.tsx +421 -0
  114. package/src/auth/components/ResetPassword.tsx +259 -0
  115. package/src/auth/components/buttons/UserButton.tsx +118 -0
  116. package/src/auth/components/icons/IconGithub.tsx +21 -0
  117. package/src/auth/components/icons/IconGoogle.tsx +30 -0
  118. package/src/auth/index.ts +27 -0
  119. package/src/{RootRouter.ts → core/RootRouter.ts} +2 -1
  120. package/src/{components → core/components}/buttons/ActionButton.tsx +49 -6
  121. package/src/core/components/buttons/ClipboardButton.tsx +56 -0
  122. package/src/{components → core/components}/buttons/DarkModeButton.tsx +7 -8
  123. package/src/{components → core/components}/buttons/LanguageButton.tsx +2 -2
  124. package/src/{components → core/components}/buttons/OmnibarButton.tsx +1 -1
  125. package/src/{components → core/components}/dialogs/AlertDialog.tsx +1 -1
  126. package/src/{components → core/components}/dialogs/ConfirmDialog.tsx +1 -1
  127. package/src/{components → core/components}/dialogs/PromptDialog.tsx +1 -1
  128. package/src/{components → core/components}/form/Control.tsx +1 -0
  129. package/src/{components → core/components}/layout/AdminShell.tsx +38 -7
  130. package/src/{components → core/components}/layout/AlephaMantineProvider.tsx +12 -8
  131. package/src/{components → core/components}/layout/AppBar.tsx +1 -1
  132. package/src/{components → core/components}/layout/Omnibar.tsx +1 -1
  133. package/src/{components → core/components}/layout/Sidebar.tsx +29 -26
  134. package/src/{components → core/components}/table/DataTable.tsx +1 -1
  135. package/src/{constants → core/constants}/ui.ts +9 -0
  136. package/src/{index.ts → core/index.ts} +3 -0
  137. package/src/{services → core/services}/DialogService.tsx +3 -3
  138. package/src/{services → core/services}/ToastService.tsx +3 -1
  139. package/src/{utils → core/utils}/extractSchemaFields.ts +2 -8
  140. package/src/{utils → core/utils}/icons.tsx +5 -15
  141. package/src/{utils → core/utils}/parseInput.ts +34 -26
  142. package/dist/AlephaMantineProvider-CGpgWDt8.cjs +0 -3
  143. package/dist/AlephaMantineProvider-D8cHYAge.js +0 -152
  144. package/dist/AlephaMantineProvider-D8cHYAge.js.map +0 -1
  145. package/dist/AlephaMantineProvider-DuvZFAuk.cjs +0 -175
  146. package/dist/AlephaMantineProvider-DuvZFAuk.cjs.map +0 -1
  147. package/dist/AlephaMantineProvider-twBqV4IO.js +0 -3
  148. package/dist/index.cjs.map +0 -1
  149. package/dist/index.d.cts +0 -821
  150. package/dist/index.d.cts.map +0 -1
  151. package/dist/index.d.ts +0 -821
  152. package/dist/index.d.ts.map +0 -1
  153. package/dist/index.js.map +0 -1
  154. /package/src/{components → core/components}/buttons/BurgerButton.tsx +0 -0
  155. /package/src/{components → core/components}/buttons/ToggleSidebarButton.tsx +0 -0
  156. /package/src/{components → core/components}/data/JsonViewer.tsx +0 -0
  157. /package/src/{components → core/components}/form/ControlDate.tsx +0 -0
  158. /package/src/{components → core/components}/form/ControlNumber.tsx +0 -0
  159. /package/src/{components → core/components}/form/ControlQueryBuilder.tsx +0 -0
  160. /package/src/{components → core/components}/form/ControlSelect.tsx +0 -0
  161. /package/src/{components → core/components}/form/TypeForm.tsx +0 -0
  162. /package/src/{hooks → core/hooks}/useDialog.ts +0 -0
  163. /package/src/{hooks → core/hooks}/useToast.ts +0 -0
  164. /package/src/{utils → core/utils}/string.ts +0 -0
@@ -0,0 +1,117 @@
1
+ import { useClient } from "@alepha/react";
2
+ import { useI18n } from "@alepha/react/i18n";
3
+ import { DataTable, Flex, Text } from "@alepha/ui";
4
+ import { Badge } from "@mantine/core";
5
+ import { type Page, t } from "alepha";
6
+ import { type FileController, type FileEntity, files } from "alepha/api/files";
7
+
8
+ const AdminFiles = () => {
9
+ const client = useClient<FileController>();
10
+ const { l } = useI18n();
11
+
12
+ const filters = t.object({
13
+ bucket: t.optional(t.string()),
14
+ name: t.optional(
15
+ t.string({
16
+ $control: {
17
+ query: t.pick(files.schema, ["name", "bucket", "mimeType"]),
18
+ },
19
+ }),
20
+ ),
21
+ });
22
+
23
+ const formatFileSize = (bytes: number) => {
24
+ if (bytes === 0) return "0 B";
25
+ const k = 1024;
26
+ const sizes = ["B", "KB", "MB", "GB"];
27
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
28
+ return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
29
+ };
30
+
31
+ return (
32
+ <Flex flex={1}>
33
+ <DataTable<FileEntity, typeof filters>
34
+ submitOnInit
35
+ defaultSize={10}
36
+ typeFormProps={{
37
+ skipSubmitButton: true,
38
+ columns: 3,
39
+ }}
40
+ tableProps={{
41
+ horizontalSpacing: "xs",
42
+ verticalSpacing: "xs",
43
+ }}
44
+ onFilterChange={(key, _value, form) => {
45
+ if (key === "name" || key === "bucket") {
46
+ return form.submit();
47
+ }
48
+ }}
49
+ filters={filters}
50
+ items={async (filters) => {
51
+ const response = await client.findFiles({
52
+ query: filters,
53
+ });
54
+
55
+ return response as Page<FileEntity>;
56
+ }}
57
+ columns={{
58
+ name: {
59
+ label: "Name",
60
+ value: (item) => (
61
+ <Text size="sm" fw={500} lineClamp={1}>
62
+ {item.name}
63
+ </Text>
64
+ ),
65
+ },
66
+ bucket: {
67
+ label: "Bucket",
68
+ fit: true,
69
+ value: (item) => (
70
+ <Badge size="sm" variant="light" color="blue">
71
+ {item.bucket}
72
+ </Badge>
73
+ ),
74
+ },
75
+ mimeType: {
76
+ label: "Type",
77
+ fit: true,
78
+ value: (item) => (
79
+ <Text size="xs" c="dimmed">
80
+ {item.mimeType}
81
+ </Text>
82
+ ),
83
+ },
84
+ size: {
85
+ label: "Size",
86
+ fit: true,
87
+ value: (item) => (
88
+ <Text size="xs" c="dimmed">
89
+ {formatFileSize(item.size)}
90
+ </Text>
91
+ ),
92
+ },
93
+ creatorName: {
94
+ label: "Creator",
95
+ fit: true,
96
+ value: (item) => (
97
+ <Text size="xs" c="dimmed">
98
+ {item.creatorName || "-"}
99
+ </Text>
100
+ ),
101
+ },
102
+ createdAt: {
103
+ label: "Created",
104
+ fit: true,
105
+ value: (item) => (
106
+ <Text size="xs" c="dimmed">
107
+ {l(item.createdAt, { date: "fromNow" })}
108
+ </Text>
109
+ ),
110
+ },
111
+ }}
112
+ />
113
+ </Flex>
114
+ );
115
+ };
116
+
117
+ export default AdminFiles;
@@ -0,0 +1,158 @@
1
+ import { useClient } from "@alepha/react";
2
+ import { useI18n } from "@alepha/react/i18n";
3
+ import { DataTable, Flex, Text } from "@alepha/ui";
4
+ import { Badge } from "@mantine/core";
5
+ import {
6
+ IconCheck,
7
+ IconClock,
8
+ IconPlayerPlay,
9
+ IconX,
10
+ } from "@tabler/icons-react";
11
+ import { type Page, t } from "alepha";
12
+ import {
13
+ type JobController,
14
+ type JobExecutionEntity,
15
+ jobExecutions,
16
+ } from "alepha/api/jobs";
17
+
18
+ const AdminJobs = () => {
19
+ const client = useClient<JobController>();
20
+ const { l } = useI18n();
21
+
22
+ const filters = t.object({
23
+ job: t.optional(
24
+ t.string({
25
+ $control: {
26
+ query: t.pick(jobExecutions.schema, ["job"]),
27
+ },
28
+ }),
29
+ ),
30
+ status: t.optional(t.enum(["STARTED", "FAILED", "COMPLETED"])),
31
+ });
32
+
33
+ const getStatusColor = (status: string) => {
34
+ switch (status) {
35
+ case "COMPLETED":
36
+ return "green";
37
+ case "FAILED":
38
+ return "red";
39
+ case "STARTED":
40
+ return "blue";
41
+ default:
42
+ return "gray";
43
+ }
44
+ };
45
+
46
+ const getStatusIcon = (status: string) => {
47
+ switch (status) {
48
+ case "COMPLETED":
49
+ return <IconCheck size={12} />;
50
+ case "FAILED":
51
+ return <IconX size={12} />;
52
+ case "STARTED":
53
+ return <IconPlayerPlay size={12} />;
54
+ default:
55
+ return <IconClock size={12} />;
56
+ }
57
+ };
58
+
59
+ const formatDuration = (
60
+ start: Date | string,
61
+ end?: Date | string | null,
62
+ ): string => {
63
+ const startTime = new Date(start).getTime();
64
+ const endTime = end ? new Date(end).getTime() : Date.now();
65
+ const duration = endTime - startTime;
66
+
67
+ if (duration < 1000) return `${duration}ms`;
68
+ if (duration < 60000) return `${(duration / 1000).toFixed(1)}s`;
69
+ return `${Math.floor(duration / 60000)}m ${Math.floor((duration % 60000) / 1000)}s`;
70
+ };
71
+
72
+ return (
73
+ <Flex flex={1}>
74
+ <DataTable<JobExecutionEntity, typeof filters>
75
+ submitOnInit
76
+ defaultSize={10}
77
+ typeFormProps={{
78
+ skipSubmitButton: true,
79
+ columns: 3,
80
+ }}
81
+ tableProps={{
82
+ horizontalSpacing: "xs",
83
+ verticalSpacing: "xs",
84
+ }}
85
+ onFilterChange={(key, _value, form) => {
86
+ if (key === "job" || key === "status") {
87
+ return form.submit();
88
+ }
89
+ }}
90
+ filters={filters}
91
+ items={async (filters) => {
92
+ const response = await client.getJobExecutions({
93
+ query: filters,
94
+ });
95
+
96
+ return response as Page<JobExecutionEntity>;
97
+ }}
98
+ columns={{
99
+ job: {
100
+ label: "Job",
101
+ value: (item) => (
102
+ <Text size="sm" fw={500}>
103
+ {item.job}
104
+ </Text>
105
+ ),
106
+ },
107
+ status: {
108
+ label: "Status",
109
+ fit: true,
110
+ value: (item) => (
111
+ <Badge
112
+ size="sm"
113
+ variant="light"
114
+ color={getStatusColor(item.status)}
115
+ leftSection={getStatusIcon(item.status)}
116
+ >
117
+ {item.status}
118
+ </Badge>
119
+ ),
120
+ },
121
+ duration: {
122
+ label: "Duration",
123
+ fit: true,
124
+ value: (item) => (
125
+ <Text size="xs" c="dimmed" ff="monospace">
126
+ {formatDuration(item.createdAt, item.finishedAt)}
127
+ </Text>
128
+ ),
129
+ },
130
+ error: {
131
+ label: "Error",
132
+ value: (item) =>
133
+ item.error ? (
134
+ <Text size="xs" c="red" lineClamp={1}>
135
+ {item.error}
136
+ </Text>
137
+ ) : (
138
+ <Text size="xs" c="dimmed">
139
+ -
140
+ </Text>
141
+ ),
142
+ },
143
+ createdAt: {
144
+ label: "Started",
145
+ fit: true,
146
+ value: (item) => (
147
+ <Text size="xs" c="dimmed">
148
+ {l(item.createdAt, { date: "fromNow" })}
149
+ </Text>
150
+ ),
151
+ },
152
+ }}
153
+ />
154
+ </Flex>
155
+ );
156
+ };
157
+
158
+ export default AdminJobs;
@@ -0,0 +1,114 @@
1
+ import { NestedView, useRouter } from "@alepha/react";
2
+ import { AdminShell, AlephaMantineProvider } from "@alepha/ui";
3
+ import { Flex } from "@mantine/core";
4
+ import {
5
+ IconChecklist,
6
+ IconFileDatabase,
7
+ IconJumpRope,
8
+ IconMail,
9
+ IconSettings,
10
+ IconShield,
11
+ IconTruckDelivery,
12
+ IconUser,
13
+ } from "@tabler/icons-react";
14
+ import type { AdminRouter } from "../AdminRouter.ts";
15
+
16
+ const AdminLayout = () => {
17
+ const router = useRouter<AdminRouter>();
18
+ return (
19
+ <AlephaMantineProvider>
20
+ <AdminShell
21
+ appShellMainProps={{
22
+ bg: "var(--alepha-background)",
23
+ }}
24
+ appShellHeaderProps={{
25
+ bg: "var(--alepha-background)",
26
+ }}
27
+ appShellNavbarProps={{
28
+ bg: "var(--alepha-background)",
29
+ }}
30
+ appShellProps={{
31
+ withBorder: false,
32
+ }}
33
+ appBarProps={{
34
+ items: [
35
+ {
36
+ type: "search",
37
+ position: "center",
38
+ },
39
+ {
40
+ type: "dark",
41
+ position: "right",
42
+ },
43
+ ],
44
+ }}
45
+ sidebarProps={{
46
+ menu: [
47
+ {
48
+ type: "section",
49
+ label: "Management",
50
+ },
51
+ {
52
+ icon: IconUser,
53
+ label: "Users",
54
+ href: router.path("adminUsers"),
55
+ },
56
+ {
57
+ icon: IconMail,
58
+ label: "Notifications",
59
+ href: router.path("adminNotifications"),
60
+ },
61
+ {
62
+ icon: IconShield,
63
+ label: "Sessions",
64
+ href: router.path("adminSessions"),
65
+ },
66
+ {
67
+ icon: IconChecklist,
68
+ label: "Verifications",
69
+ href: router.path("adminVerifications"),
70
+ },
71
+ {
72
+ type: "section",
73
+ label: "System",
74
+ },
75
+ {
76
+ label: "Jobs",
77
+ href: router.path("adminJobs"),
78
+ icon: IconTruckDelivery,
79
+ },
80
+ {
81
+ label: "Workflows",
82
+ href: router.path("adminWorkflows"),
83
+ icon: IconJumpRope,
84
+ },
85
+ {
86
+ label: "Parameters",
87
+ href: router.path("adminParameters"),
88
+ icon: IconSettings,
89
+ },
90
+ {
91
+ label: "Files",
92
+ href: router.path("adminFiles"),
93
+ icon: IconFileDatabase,
94
+ },
95
+ ],
96
+ }}
97
+ >
98
+ <Flex
99
+ flex={1}
100
+ p={"lg"}
101
+ mt={-16}
102
+ ml={-16}
103
+ bg={"var(--alepha-surface)"}
104
+ bdrs={"lg"}
105
+ bd={"1px solid var(--alepha-border)"}
106
+ >
107
+ <NestedView />
108
+ </Flex>
109
+ </AdminShell>
110
+ </AlephaMantineProvider>
111
+ );
112
+ };
113
+
114
+ export default AdminLayout;
@@ -0,0 +1,20 @@
1
+ import { Flex, Text } from "@alepha/ui";
2
+ import { Stack } from "@mantine/core";
3
+ import { IconBell } from "@tabler/icons-react";
4
+
5
+ const AdminNotifications = () => {
6
+ return (
7
+ <Flex flex={1} justify="center" align="center">
8
+ <Stack align="center" gap="xs">
9
+ <IconBell size={48} stroke={1.5} color="var(--mantine-color-dimmed)" />
10
+ <Text c="dimmed">Notification Center</Text>
11
+ <Text size="xs" c="dimmed" ta="center" maw={400}>
12
+ Notifications are processed through the queue system. Email and SMS
13
+ notifications are sent asynchronously using the configured providers.
14
+ </Text>
15
+ </Stack>
16
+ </Flex>
17
+ );
18
+ };
19
+
20
+ export default AdminNotifications;
@@ -0,0 +1,24 @@
1
+ import { Flex, Text } from "@alepha/ui";
2
+ import { Stack } from "@mantine/core";
3
+ import { IconSettings } from "@tabler/icons-react";
4
+
5
+ const AdminParameters = () => {
6
+ return (
7
+ <Flex flex={1} justify="center" align="center">
8
+ <Stack align="center" gap="xs">
9
+ <IconSettings
10
+ size={48}
11
+ stroke={1.5}
12
+ color="var(--mantine-color-dimmed)"
13
+ />
14
+ <Text c="dimmed">Parameter Management</Text>
15
+ <Text size="xs" c="dimmed" ta="center" maw={400}>
16
+ Application parameters and configuration settings. Define parameters
17
+ using the $config descriptor to manage dynamic application settings.
18
+ </Text>
19
+ </Stack>
20
+ </Flex>
21
+ );
22
+ };
23
+
24
+ export default AdminParameters;
@@ -0,0 +1,159 @@
1
+ import { useClient } from "@alepha/react";
2
+ import { useI18n } from "@alepha/react/i18n";
3
+ import { DataTable, Flex, Text } from "@alepha/ui";
4
+ import { Badge, Group } from "@mantine/core";
5
+ import {
6
+ IconDeviceDesktop,
7
+ IconDeviceMobile,
8
+ IconDeviceTablet,
9
+ } from "@tabler/icons-react";
10
+ import { type Page, t } from "alepha";
11
+ import {
12
+ type SessionController,
13
+ type SessionEntity,
14
+ sessions,
15
+ } from "alepha/api/users";
16
+
17
+ export interface AdminSessionsProps {
18
+ userRealmName?: string;
19
+ }
20
+
21
+ const AdminSessions = (props: AdminSessionsProps) => {
22
+ const client = useClient<SessionController>();
23
+ const { l } = useI18n();
24
+
25
+ const filters = t.object({
26
+ userId: t.optional(
27
+ t.uuid({
28
+ $control: {
29
+ query: t.pick(sessions.schema, ["userId"]),
30
+ },
31
+ }),
32
+ ),
33
+ });
34
+
35
+ const getDeviceIcon = (device?: string) => {
36
+ switch (device) {
37
+ case "MOBILE":
38
+ return <IconDeviceMobile size={14} />;
39
+ case "TABLET":
40
+ return <IconDeviceTablet size={14} />;
41
+ default:
42
+ return <IconDeviceDesktop size={14} />;
43
+ }
44
+ };
45
+
46
+ const isExpired = (expiresAt: Date | string) => {
47
+ return new Date(expiresAt) < new Date();
48
+ };
49
+
50
+ return (
51
+ <Flex flex={1}>
52
+ <DataTable<SessionEntity, typeof filters>
53
+ submitOnInit
54
+ defaultSize={10}
55
+ typeFormProps={{
56
+ skipSubmitButton: true,
57
+ columns: 3,
58
+ }}
59
+ tableProps={{
60
+ horizontalSpacing: "xs",
61
+ verticalSpacing: "xs",
62
+ }}
63
+ onFilterChange={(key, _value, form) => {
64
+ if (key === "userId") {
65
+ return form.submit();
66
+ }
67
+ }}
68
+ filters={filters}
69
+ tableTrProps={(item) => {
70
+ if (isExpired(item.expiresAt)) {
71
+ return {
72
+ opacity: 0.5,
73
+ };
74
+ }
75
+ return {};
76
+ }}
77
+ items={async (filters) => {
78
+ const response = await client.findSessions({
79
+ query: {
80
+ ...filters,
81
+ userRealmName: props.userRealmName,
82
+ },
83
+ });
84
+
85
+ return response as Page<SessionEntity>;
86
+ }}
87
+ columns={{
88
+ userId: {
89
+ label: "User ID",
90
+ value: (item) => (
91
+ <Text size="xs" ff="monospace">
92
+ {item.userId.slice(0, 8)}...
93
+ </Text>
94
+ ),
95
+ },
96
+ userAgent: {
97
+ label: "Device",
98
+ fit: true,
99
+ value: (item) => (
100
+ <Group gap={4}>
101
+ {item.userAgent ? (
102
+ <>
103
+ <Badge
104
+ size="xs"
105
+ variant="light"
106
+ leftSection={getDeviceIcon(item.userAgent.device)}
107
+ >
108
+ {item.userAgent.device}
109
+ </Badge>
110
+ <Text size="xs" c="dimmed">
111
+ {item.userAgent.browser} / {item.userAgent.os}
112
+ </Text>
113
+ </>
114
+ ) : (
115
+ <Text size="xs" c="dimmed">
116
+ -
117
+ </Text>
118
+ )}
119
+ </Group>
120
+ ),
121
+ },
122
+ ip: {
123
+ label: "IP",
124
+ fit: true,
125
+ value: (item) => (
126
+ <Text size="xs" ff="monospace" c="dimmed">
127
+ {item.ip || "-"}
128
+ </Text>
129
+ ),
130
+ },
131
+ expiresAt: {
132
+ label: "Status",
133
+ fit: true,
134
+ value: (item) => (
135
+ <Badge
136
+ size="sm"
137
+ variant="light"
138
+ color={isExpired(item.expiresAt) ? "red" : "green"}
139
+ >
140
+ {isExpired(item.expiresAt) ? "Expired" : "Active"}
141
+ </Badge>
142
+ ),
143
+ },
144
+ createdAt: {
145
+ label: "Created",
146
+ fit: true,
147
+ value: (item) => (
148
+ <Text size="xs" c="dimmed">
149
+ {l(item.createdAt, { date: "fromNow" })}
150
+ </Text>
151
+ ),
152
+ },
153
+ }}
154
+ />
155
+ </Flex>
156
+ );
157
+ };
158
+
159
+ export default AdminSessions;