@alepha/ui 0.13.2 → 0.13.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 (122) hide show
  1. package/dist/admin/{AdminFiles-BjofP3OC.js → AdminFiles-8CC9mVsc.js} +3 -3
  2. package/dist/admin/{AdminFiles-BjofP3OC.js.map → AdminFiles-8CC9mVsc.js.map} +1 -1
  3. package/dist/admin/AdminFiles-BRLMP_7y.js +3 -0
  4. package/dist/admin/AdminLayout-Cm-Y4YTQ.js +396 -0
  5. package/dist/admin/AdminLayout-Cm-Y4YTQ.js.map +1 -0
  6. package/dist/admin/AdminLayout-D5M9kSiV.js +3 -0
  7. package/dist/admin/AdminNotifications-DxBKi2RO.js +3 -0
  8. package/dist/admin/AdminNotifications-d-gw5Uie.js +154 -0
  9. package/dist/admin/AdminNotifications-d-gw5Uie.js.map +1 -0
  10. package/dist/admin/{AdminSessions-CmDVneE2.js → AdminSessions-CpVusqmd.js} +37 -10
  11. package/dist/admin/AdminSessions-CpVusqmd.js.map +1 -0
  12. package/dist/admin/AdminSessions-DA285-5Q.js +3 -0
  13. package/dist/admin/AdminUserCreate-CQIrSslj.js +3 -0
  14. package/dist/admin/AdminUserCreate-DH7u_yJj.js +103 -0
  15. package/dist/admin/AdminUserCreate-DH7u_yJj.js.map +1 -0
  16. package/dist/admin/AdminUserDetails-DVmFCDsU.js +221 -0
  17. package/dist/admin/AdminUserDetails-DVmFCDsU.js.map +1 -0
  18. package/dist/admin/AdminUserDetails-T3nkXSdz.js +3 -0
  19. package/dist/admin/AdminUserLayout-DdtZGX8n.js +3 -0
  20. package/dist/admin/AdminUserLayout-gpOyn0Y7.js +153 -0
  21. package/dist/admin/AdminUserLayout-gpOyn0Y7.js.map +1 -0
  22. package/dist/admin/AdminUserSessions-CWYzjB3D.js +3 -0
  23. package/dist/admin/AdminUserSessions-CdVwoM-h.js +129 -0
  24. package/dist/admin/AdminUserSessions-CdVwoM-h.js.map +1 -0
  25. package/dist/admin/AdminUserSettings-S7gZvvjO.js +164 -0
  26. package/dist/admin/AdminUserSettings-S7gZvvjO.js.map +1 -0
  27. package/dist/admin/AdminUserSettings-jCzVYw_2.js +3 -0
  28. package/dist/admin/{AdminUsers-88De5pev.js → AdminUsers-9qEzxqAL.js} +33 -15
  29. package/dist/admin/AdminUsers-9qEzxqAL.js.map +1 -0
  30. package/dist/admin/AdminUsers-BcSUxV01.js +3 -0
  31. package/dist/admin/index.d.ts +5568 -418
  32. package/dist/admin/index.js +302 -42
  33. package/dist/admin/index.js.map +1 -1
  34. package/dist/auth/AuthLayout-BSL8ZHgr.js +19 -0
  35. package/dist/auth/AuthLayout-BSL8ZHgr.js.map +1 -0
  36. package/dist/auth/{Login-OCrvjs9U.js → Login-AlVPPqQp.js} +6 -5
  37. package/dist/auth/Login-AlVPPqQp.js.map +1 -0
  38. package/dist/auth/Login-otdWVvVU.js +4 -0
  39. package/dist/auth/{Register-Ei34GSba.js → Register-BxJmOqpF.js} +9 -6
  40. package/dist/auth/Register-BxJmOqpF.js.map +1 -0
  41. package/dist/auth/Register-D10MnlQc.js +4 -0
  42. package/dist/auth/{ResetPassword-tO0oMzfo.js → ResetPassword-BhyZ9ek4.js} +3 -3
  43. package/dist/auth/ResetPassword-BhyZ9ek4.js.map +1 -0
  44. package/dist/auth/ResetPassword-llBG-STp.js +3 -0
  45. package/dist/auth/VerifyEmail-BvOG-IUC.js +3 -0
  46. package/dist/auth/VerifyEmail-DeLct3oQ.js +131 -0
  47. package/dist/auth/VerifyEmail-DeLct3oQ.js.map +1 -0
  48. package/dist/auth/index.d.ts +2412 -2254
  49. package/dist/auth/index.js +97 -20
  50. package/dist/auth/index.js.map +1 -1
  51. package/dist/core/index.d.ts +280 -95
  52. package/dist/core/index.js +1375 -394
  53. package/dist/core/index.js.map +1 -1
  54. package/package.json +7 -6
  55. package/src/admin/AdminRouter.ts +116 -29
  56. package/src/admin/AdminSidebar.ts +31 -0
  57. package/src/admin/MainRouter.ts +23 -0
  58. package/src/admin/components/AdminLayout.tsx +66 -104
  59. package/src/admin/components/AdminNotifications.tsx +196 -12
  60. package/src/admin/components/AdminSessions.tsx +43 -7
  61. package/src/admin/components/AdminUserCreate.tsx +84 -0
  62. package/src/admin/components/AdminUserDetails.tsx +180 -0
  63. package/src/admin/components/AdminUserLayout.tsx +172 -0
  64. package/src/admin/components/AdminUserSessions.tsx +158 -0
  65. package/src/admin/components/AdminUserSettings.tsx +165 -0
  66. package/src/admin/components/AdminUsers.tsx +29 -9
  67. package/src/admin/index.ts +15 -3
  68. package/src/auth/AuthI18n.ts +22 -0
  69. package/src/auth/AuthRouter.ts +82 -8
  70. package/src/auth/components/AuthLayout.tsx +12 -0
  71. package/src/auth/components/Login.tsx +14 -12
  72. package/src/auth/components/Register.tsx +6 -5
  73. package/src/auth/components/ResetPassword.tsx +1 -1
  74. package/src/auth/components/VerifyEmail.tsx +102 -0
  75. package/src/auth/components/buttons/UserButton.tsx +12 -2
  76. package/src/auth/index.ts +1 -0
  77. package/src/core/components/buttons/ActionButton.tsx +12 -4
  78. package/src/core/components/buttons/DarkModeButton.tsx +1 -1
  79. package/src/core/components/buttons/ThemeButton.tsx +31 -0
  80. package/src/core/components/layout/AdminShell.tsx +4 -2
  81. package/src/core/components/layout/AlephaMantineProvider.tsx +10 -4
  82. package/src/core/components/layout/Omnibar.tsx +27 -15
  83. package/src/core/components/layout/Sidebar.tsx +33 -17
  84. package/src/core/components/table/DataTable.tsx +9 -5
  85. package/src/core/hooks/useTheme.ts +25 -0
  86. package/src/core/index.ts +8 -3
  87. package/src/core/providers/ThemeProvider.ts +90 -0
  88. package/src/core/themes/aurora.ts +107 -0
  89. package/src/core/themes/crystal.ts +107 -0
  90. package/src/core/themes/default.ts +7 -0
  91. package/src/core/themes/ember.ts +107 -0
  92. package/src/core/themes/index.ts +7 -0
  93. package/src/core/themes/midnight.ts +98 -0
  94. package/src/core/themes/remoraid.ts +278 -0
  95. package/src/core/themes/slate.ts +81 -0
  96. package/styles.css +84 -0
  97. package/dist/admin/AdminFiles-DldZB7oo.js +0 -3
  98. package/dist/admin/AdminJobs-BOq6AZOW.js +0 -3
  99. package/dist/admin/AdminJobs-CDnVxEv6.js +0 -125
  100. package/dist/admin/AdminJobs-CDnVxEv6.js.map +0 -1
  101. package/dist/admin/AdminLayout-Bgx25J8m.js +0 -3
  102. package/dist/admin/AdminLayout-CervL8LV.js +0 -88
  103. package/dist/admin/AdminLayout-CervL8LV.js.map +0 -1
  104. package/dist/admin/AdminNotifications-BDQXt3-e.js +0 -3
  105. package/dist/admin/AdminNotifications-DvI2989x.js +0 -40
  106. package/dist/admin/AdminNotifications-DvI2989x.js.map +0 -1
  107. package/dist/admin/AdminParameters-D_v0GAvI.js +0 -3
  108. package/dist/admin/AdminParameters-P1LB6ZI1.js +0 -40
  109. package/dist/admin/AdminParameters-P1LB6ZI1.js.map +0 -1
  110. package/dist/admin/AdminSessions-CmDVneE2.js.map +0 -1
  111. package/dist/admin/AdminSessions-Dkk_fzWK.js +0 -3
  112. package/dist/admin/AdminUsers-88De5pev.js.map +0 -1
  113. package/dist/admin/AdminUsers-oyAXqZ5l.js +0 -3
  114. package/dist/admin/AdminVerifications-D93TKymL.js +0 -3
  115. package/dist/admin/AdminVerifications-DBVEoqJe.js +0 -40
  116. package/dist/admin/AdminVerifications-DBVEoqJe.js.map +0 -1
  117. package/dist/auth/Login-BC2jTczq.js +0 -4
  118. package/dist/auth/Login-OCrvjs9U.js.map +0 -1
  119. package/dist/auth/Register-Dh0lsQmI.js +0 -4
  120. package/dist/auth/Register-Ei34GSba.js.map +0 -1
  121. package/dist/auth/ResetPassword-BnlAQAOE.js +0 -3
  122. package/dist/auth/ResetPassword-tO0oMzfo.js.map +0 -1
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "mantine"
8
8
  ],
9
9
  "author": "Nicolas Foures",
10
- "version": "0.13.2",
10
+ "version": "0.13.4",
11
11
  "type": "module",
12
12
  "engines": {
13
13
  "node": ">=22.0.0"
@@ -18,10 +18,11 @@
18
18
  "style": "./styles.css",
19
19
  "files": [
20
20
  "dist",
21
- "src"
21
+ "src",
22
+ "styles.css"
22
23
  ],
23
24
  "dependencies": {
24
- "@alepha/react": "0.13.2",
25
+ "@alepha/react": "0.13.4",
25
26
  "@mantine/core": "^8.3.9",
26
27
  "@mantine/dates": "^8.3.9",
27
28
  "@mantine/hooks": "^8.3.9",
@@ -30,12 +31,12 @@
30
31
  "@mantine/nprogress": "^8.3.9",
31
32
  "@mantine/spotlight": "^8.3.9",
32
33
  "@tabler/icons-react": "^3.35.0",
33
- "alepha": "0.13.2",
34
+ "alepha": "0.13.4",
34
35
  "dayjs": "^1.11.19"
35
36
  },
36
37
  "devDependencies": {
37
- "react": "^19.2.0",
38
- "react-dom": "^19.2.0"
38
+ "react": "^19.2.1",
39
+ "react-dom": "^19.2.1"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "react": "*",
@@ -1,58 +1,145 @@
1
- import { $page } from "@alepha/react";
1
+ import { $page, ReactRouter, Redirection } from "@alepha/react";
2
+ import { ReactAuth } from "@alepha/react/auth";
3
+ import { AuthRouter } from "@alepha/ui/auth";
4
+ import {
5
+ IconBell,
6
+ IconDevices,
7
+ IconFile,
8
+ IconPlus,
9
+ IconUser,
10
+ IconUsers,
11
+ } from "@tabler/icons-react";
12
+ import { $inject } from "alepha";
13
+ import type { FileController } from "alepha/api/files";
14
+ import type { NotificationController } from "alepha/api/notifications";
15
+ import type { SessionController, UserController } from "alepha/api/users";
16
+ import { $client } from "alepha/server/links";
2
17
 
3
18
  export class AdminRouter {
4
- admin = $page({
19
+ protected readonly router = $inject(ReactRouter);
20
+ protected readonly authRouter = $inject(AuthRouter);
21
+ protected readonly auth = $inject(ReactAuth);
22
+ protected readonly userCtrl = $client<UserController>();
23
+ protected readonly sessionCtrl = $client<SessionController>();
24
+ protected readonly notificationCtrl = $client<NotificationController>();
25
+ protected readonly fileCtrl = $client<FileController>();
26
+
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+ // Layout
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+
31
+ public readonly layout = $page({
32
+ name: "AdminLayout",
5
33
  path: "/admin",
6
34
  label: "Admin",
7
35
  lazy: () => import("./components/AdminLayout.tsx"),
36
+ resolve: ({ user, url }) => {
37
+ if (!user) {
38
+ throw new Redirection(
39
+ this.router.path(this.authRouter.login.name, {
40
+ query: {
41
+ r: url.pathname,
42
+ },
43
+ }),
44
+ );
45
+ }
46
+ return {};
47
+ },
8
48
  });
9
49
 
10
- adminUsers = $page({
11
- parent: this.admin,
50
+ // ─────────────────────────────────────────────────────────────────────────────
51
+ // Users
52
+ // ─────────────────────────────────────────────────────────────────────────────
53
+
54
+ public readonly adminUsers = $page({
55
+ icon: IconUsers,
56
+ parent: this.layout,
12
57
  path: "/users",
13
58
  label: "Users",
59
+ description: "Manage application users and their roles.",
14
60
  lazy: () => import("./components/AdminUsers.tsx"),
61
+ can: () => this.userCtrl.findUsers.can(),
15
62
  });
16
63
 
17
- adminNotifications = $page({
18
- parent: this.admin,
19
- path: "/notifications",
20
- label: "Notifications",
21
- lazy: () => import("./components/AdminNotifications.tsx"),
64
+ public readonly adminUserCreate = $page({
65
+ icon: IconPlus,
66
+ parent: this.layout,
67
+ path: "/users/create",
68
+ label: "Create User",
69
+ description: "Create a new user account.",
70
+ lazy: () => import("./components/AdminUserCreate.tsx"),
71
+ can: () => this.userCtrl.createUser.can(),
72
+ });
73
+
74
+ public readonly adminUserLayout = $page({
75
+ icon: IconUser,
76
+ parent: this.layout,
77
+ path: "/users/:userId",
78
+ label: "User",
79
+ lazy: () => import("./components/AdminUserLayout.tsx"),
80
+ can: () => this.userCtrl.getUser.can(),
81
+ });
82
+
83
+ public readonly adminUserDetails = $page({
84
+ parent: this.adminUserLayout,
85
+ path: "/details",
86
+ label: "Details",
87
+ lazy: () => import("./components/AdminUserDetails.tsx"),
22
88
  });
23
89
 
24
- adminSessions = $page({
25
- parent: this.admin,
90
+ public readonly adminUserSessions = $page({
91
+ parent: this.adminUserLayout,
26
92
  path: "/sessions",
27
93
  label: "Sessions",
28
- lazy: () => import("./components/AdminSessions.tsx"),
94
+ lazy: () => import("./components/AdminUserSessions.tsx"),
29
95
  });
30
96
 
31
- adminVerifications = $page({
32
- parent: this.admin,
33
- path: "/verifications",
34
- label: "Verifications",
35
- lazy: () => import("./components/AdminVerifications.tsx"),
97
+ public readonly adminUserSettings = $page({
98
+ parent: this.adminUserLayout,
99
+ path: "/settings",
100
+ label: "Settings",
101
+ lazy: () => import("./components/AdminUserSettings.tsx"),
36
102
  });
37
103
 
38
- adminJobs = $page({
39
- parent: this.admin,
40
- path: "/jobs",
41
- label: "Jobs",
42
- lazy: () => import("./components/AdminJobs.tsx"),
104
+ // ─────────────────────────────────────────────────────────────────────────────
105
+ // Sessions
106
+ // ─────────────────────────────────────────────────────────────────────────────
107
+
108
+ public readonly adminSessions = $page({
109
+ icon: IconDevices,
110
+ parent: this.layout,
111
+ path: "/sessions",
112
+ label: "Sessions",
113
+ description: "View and manage all active sessions.",
114
+ lazy: () => import("./components/AdminSessions.tsx"),
115
+ can: () => this.sessionCtrl.findSessions.can(),
43
116
  });
44
117
 
45
- adminParameters = $page({
46
- parent: this.admin,
47
- path: "/parameters",
48
- label: "Parameters",
49
- lazy: () => import("./components/AdminParameters.tsx"),
118
+ // ─────────────────────────────────────────────────────────────────────────────
119
+ // Notifications
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+
122
+ public readonly adminNotifications = $page({
123
+ icon: IconBell,
124
+ parent: this.layout,
125
+ path: "/notifications",
126
+ label: "Notifications",
127
+ description: "View notification history and status.",
128
+ lazy: () => import("./components/AdminNotifications.tsx"),
129
+ can: () => this.notificationCtrl.findNotifications.can(),
50
130
  });
51
131
 
52
- adminFiles = $page({
53
- parent: this.admin,
132
+ // ─────────────────────────────────────────────────────────────────────────────
133
+ // Files
134
+ // ─────────────────────────────────────────────────────────────────────────────
135
+
136
+ public readonly adminFiles = $page({
137
+ icon: IconFile,
138
+ parent: this.layout,
54
139
  path: "/files",
55
140
  label: "Files",
141
+ description: "Manage uploaded files and storage.",
56
142
  lazy: () => import("./components/AdminFiles.tsx"),
143
+ can: () => this.fileCtrl.findFiles.can(),
57
144
  });
58
145
  }
@@ -0,0 +1,31 @@
1
+ import type { ReactRouter } from "@alepha/react";
2
+ import type { SidebarNode } from "@alepha/ui";
3
+ import { createElement } from "react";
4
+ import ToggleSidebarButton from "../core/components/buttons/ToggleSidebarButton.tsx";
5
+
6
+ export class AdminSidebar {
7
+ public menu = (router: ReactRouter<any>): SidebarNode[] => [
8
+ {
9
+ element: createElement(ToggleSidebarButton),
10
+ },
11
+ {
12
+ type: "spacer",
13
+ },
14
+ {
15
+ ...router.node("adminUsers"),
16
+ description: undefined,
17
+ },
18
+ {
19
+ ...router.node("adminSessions"),
20
+ description: undefined,
21
+ },
22
+ {
23
+ ...router.node("adminNotifications"),
24
+ description: undefined,
25
+ },
26
+ {
27
+ ...router.node("adminFiles"),
28
+ description: undefined,
29
+ },
30
+ ];
31
+ }
@@ -0,0 +1,23 @@
1
+ import { $page } from "@alepha/react";
2
+ import { AlephaMantineProvider } from "@alepha/ui";
3
+ import { AuthRouter } from "@alepha/ui/auth";
4
+ import { $inject } from "alepha";
5
+ import { AdminRouter } from "./AdminRouter.ts";
6
+
7
+ /**
8
+ * Main application router that combines Auth and Admin routers.
9
+ *
10
+ * We assume that the main application router will always have Admin and Auth routers.
11
+ *
12
+ * This is basically a convenience class to avoid having to inject these routers everywhere.
13
+ * Code is lightweight enough that we can just copy it if needed.
14
+ */
15
+ export class MainRouter {
16
+ auth = $inject(AuthRouter);
17
+ admin = $inject(AdminRouter);
18
+
19
+ layout = $page({
20
+ component: AlephaMantineProvider,
21
+ children: () => [this.auth.layout, this.admin.layout],
22
+ });
23
+ }
@@ -1,113 +1,75 @@
1
- import { NestedView, useRouter } from "@alepha/react";
2
- import { AdminShell, AlephaMantineProvider } from "@alepha/ui";
3
- import { Flex } from "@mantine/core";
1
+ import { NestedView, useInject, useRouter } from "@alepha/react";
4
2
  import {
5
- IconChecklist,
6
- IconFileDatabase,
7
- IconJumpRope,
8
- IconMail,
9
- IconSettings,
10
- IconShield,
11
- IconTruckDelivery,
12
- IconUser,
13
- } from "@tabler/icons-react";
3
+ ActionButton,
4
+ AdminShell,
5
+ OmnibarButton,
6
+ ThemeButton,
7
+ } from "@alepha/ui";
8
+ import { UserButton } from "@alepha/ui/auth";
9
+ import { Flex } from "@mantine/core";
10
+ import { IconArrowLeft } from "@tabler/icons-react";
14
11
  import type { AdminRouter } from "../AdminRouter.ts";
12
+ import { AdminSidebar } from "../AdminSidebar.ts";
15
13
 
16
14
  const AdminLayout = () => {
17
15
  const router = useRouter<AdminRouter>();
16
+ const sidebar = useInject(AdminSidebar);
17
+
18
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>
19
+ <AdminShell
20
+ appShellMainProps={{
21
+ bg: "var(--alepha-surface)",
22
+ }}
23
+ appShellHeaderProps={{
24
+ bg: "var(--alepha-background)",
25
+ }}
26
+ appShellNavbarProps={
27
+ {
28
+ // bg: "var(--alepha-background)",
29
+ }
30
+ }
31
+ appShellProps={
32
+ {
33
+ // withBorder: false,
34
+ }
35
+ }
36
+ appShellFooterProps={{
37
+ bg: "var(--alepha-background)",
38
+ }}
39
+ footer={<Flex h={12} />}
40
+ appBarProps={{
41
+ items: [
42
+ {
43
+ element: <ActionButton icon={IconArrowLeft} href={"/"} />,
44
+ position: "left",
45
+ },
46
+ {
47
+ element: <OmnibarButton actionProps={{ variant: "outline" }} />,
48
+ position: "right",
49
+ },
50
+ {
51
+ element: <UserButton />,
52
+ position: "right",
53
+ },
54
+ {
55
+ element: <ThemeButton />,
56
+ position: "right",
57
+ },
58
+ {
59
+ type: "dark",
60
+ position: "right",
61
+ },
62
+ ],
63
+ }}
64
+ sidebarProps={{
65
+ gap: "xs",
66
+ menu: sidebar.menu(router),
67
+ }}
68
+ >
69
+ <Flex flex={1} bg={"var(--alepha-surface)"}>
70
+ <NestedView />
71
+ </Flex>
72
+ </AdminShell>
111
73
  );
112
74
  };
113
75
 
@@ -1,18 +1,202 @@
1
- import { Flex, Text } from "@alepha/ui";
2
- import { Stack } from "@mantine/core";
3
- import { IconBell } from "@tabler/icons-react";
1
+ import { useClient } from "@alepha/react";
2
+ import { useI18n } from "@alepha/react/i18n";
3
+ import { DataTable, Flex, Text } from "@alepha/ui";
4
+ import { Badge, Tooltip } from "@mantine/core";
5
+ import {
6
+ IconAlertCircle,
7
+ IconCheck,
8
+ IconClock,
9
+ IconMail,
10
+ IconMessage,
11
+ } from "@tabler/icons-react";
12
+ import { type Page, t } from "alepha";
13
+ import type {
14
+ NotificationController,
15
+ NotificationEntity,
16
+ } from "alepha/api/notifications";
4
17
 
5
18
  const AdminNotifications = () => {
19
+ const client = useClient<NotificationController>();
20
+ const { l } = useI18n();
21
+
22
+ const filters = t.object({
23
+ type: t.optional(
24
+ t.enum(["email", "sms"], {
25
+ title: "Type",
26
+ }),
27
+ ),
28
+ status: t.optional(
29
+ t.enum(["pending", "sent", "failed"], {
30
+ title: "Status",
31
+ }),
32
+ ),
33
+ template: t.optional(
34
+ t.string({
35
+ title: "Template",
36
+ }),
37
+ ),
38
+ contact: t.optional(
39
+ t.string({
40
+ title: "Contact",
41
+ }),
42
+ ),
43
+ });
44
+
45
+ const getStatus = (item: NotificationEntity) => {
46
+ if (item.error) return "failed";
47
+ if (item.sentAt) return "sent";
48
+ return "pending";
49
+ };
50
+
51
+ const getStatusBadge = (item: NotificationEntity) => {
52
+ const status = getStatus(item);
53
+ switch (status) {
54
+ case "sent":
55
+ return (
56
+ <Badge
57
+ size="sm"
58
+ variant="light"
59
+ color="green"
60
+ leftSection={<IconCheck size={12} />}
61
+ >
62
+ Sent
63
+ </Badge>
64
+ );
65
+ case "failed":
66
+ return (
67
+ <Tooltip label={item.error?.message} multiline maw={300}>
68
+ <Badge
69
+ size="sm"
70
+ variant="light"
71
+ color="red"
72
+ leftSection={<IconAlertCircle size={12} />}
73
+ >
74
+ Failed
75
+ </Badge>
76
+ </Tooltip>
77
+ );
78
+ default:
79
+ return (
80
+ <Badge
81
+ size="sm"
82
+ variant="light"
83
+ color="yellow"
84
+ leftSection={<IconClock size={12} />}
85
+ >
86
+ Pending
87
+ </Badge>
88
+ );
89
+ }
90
+ };
91
+
6
92
  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>
93
+ <Flex flex={1} direction="column">
94
+ <DataTable<NotificationEntity, typeof filters>
95
+ submitOnInit
96
+ defaultSize={10}
97
+ typeFormProps={{
98
+ skipSubmitButton: true,
99
+ columns: 4,
100
+ }}
101
+ tableProps={{
102
+ horizontalSpacing: "xs",
103
+ verticalSpacing: "xs",
104
+ }}
105
+ onFilterChange={(_key, _value, form) => {
106
+ return form.submit();
107
+ }}
108
+ filters={filters}
109
+ tableTrProps={(item) => {
110
+ const status = getStatus(item);
111
+ if (status === "failed") {
112
+ return {
113
+ bg: "var(--mantine-color-red-light)",
114
+ };
115
+ }
116
+ return {};
117
+ }}
118
+ items={async (filters) => {
119
+ const response = await client.findNotifications({
120
+ query: filters,
121
+ });
122
+
123
+ return response as Page<NotificationEntity>;
124
+ }}
125
+ columns={{
126
+ type: {
127
+ label: "Type",
128
+ fit: true,
129
+ value: (item) => (
130
+ <Badge
131
+ size="sm"
132
+ variant="outline"
133
+ leftSection={
134
+ item.type === "email" ? (
135
+ <IconMail size={12} />
136
+ ) : (
137
+ <IconMessage size={12} />
138
+ )
139
+ }
140
+ >
141
+ {item.type.toUpperCase()}
142
+ </Badge>
143
+ ),
144
+ },
145
+ template: {
146
+ label: "Template",
147
+ value: (item) => (
148
+ <Text size="sm" fw={500}>
149
+ {item.template}
150
+ </Text>
151
+ ),
152
+ },
153
+ contact: {
154
+ label: "Contact",
155
+ value: (item) => (
156
+ <Text size="sm" ff="monospace">
157
+ {item.contact}
158
+ </Text>
159
+ ),
160
+ },
161
+ category: {
162
+ label: "Category",
163
+ fit: true,
164
+ value: (item) =>
165
+ item.category ? (
166
+ <Badge size="xs" variant="light">
167
+ {item.category}
168
+ </Badge>
169
+ ) : (
170
+ <Text size="xs" c="dimmed">
171
+ -
172
+ </Text>
173
+ ),
174
+ },
175
+ status: {
176
+ label: "Status",
177
+ fit: true,
178
+ value: (item) => getStatusBadge(item),
179
+ },
180
+ sentAt: {
181
+ label: "Sent",
182
+ fit: true,
183
+ value: (item) => (
184
+ <Text size="xs" c="dimmed">
185
+ {item.sentAt ? l(item.sentAt, { date: "fromNow" }) : "-"}
186
+ </Text>
187
+ ),
188
+ },
189
+ createdAt: {
190
+ label: "Created",
191
+ fit: true,
192
+ value: (item) => (
193
+ <Text size="xs" c="dimmed">
194
+ {l(item.createdAt, { date: "fromNow" })}
195
+ </Text>
196
+ ),
197
+ },
198
+ }}
199
+ />
16
200
  </Flex>
17
201
  );
18
202
  };