@alepha/ui 0.13.2 → 0.13.3

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 (116) hide show
  1. package/dist/admin/AdminLayout-JakF7ESb.js +388 -0
  2. package/dist/admin/AdminLayout-JakF7ESb.js.map +1 -0
  3. package/dist/admin/AdminLayout-qNsIyl30.js +3 -0
  4. package/dist/admin/AdminNotifications-BPrxALdS.js +154 -0
  5. package/dist/admin/AdminNotifications-BPrxALdS.js.map +1 -0
  6. package/dist/admin/AdminNotifications-DV-35Fi3.js +3 -0
  7. package/dist/admin/{AdminSessions-CmDVneE2.js → AdminSessions-CMmBtbSw.js} +36 -9
  8. package/dist/admin/AdminSessions-CMmBtbSw.js.map +1 -0
  9. package/dist/admin/AdminSessions-Df2VYzlE.js +3 -0
  10. package/dist/admin/AdminUserCreate-Coa_yi6m.js +103 -0
  11. package/dist/admin/AdminUserCreate-Coa_yi6m.js.map +1 -0
  12. package/dist/admin/AdminUserCreate-DjiCcAk0.js +3 -0
  13. package/dist/admin/AdminUserDetails-BCFwOm9w.js +221 -0
  14. package/dist/admin/AdminUserDetails-BCFwOm9w.js.map +1 -0
  15. package/dist/admin/AdminUserDetails-C5yeJNa3.js +3 -0
  16. package/dist/admin/AdminUserLayout-B8ga5QvP.js +3 -0
  17. package/dist/admin/AdminUserLayout-CR2OqV9Z.js +153 -0
  18. package/dist/admin/AdminUserLayout-CR2OqV9Z.js.map +1 -0
  19. package/dist/admin/AdminUserSessions-A_5KkqTY.js +3 -0
  20. package/dist/admin/AdminUserSessions-Bcf6-rjG.js +129 -0
  21. package/dist/admin/AdminUserSessions-Bcf6-rjG.js.map +1 -0
  22. package/dist/admin/AdminUserSettings-DAsAhFjX.js +3 -0
  23. package/dist/admin/AdminUserSettings-DRYVdW6S.js +164 -0
  24. package/dist/admin/AdminUserSettings-DRYVdW6S.js.map +1 -0
  25. package/dist/admin/AdminUsers-Dd9a5UqO.js +3 -0
  26. package/dist/admin/{AdminUsers-88De5pev.js → AdminUsers-IN_2yHKt.js} +32 -14
  27. package/dist/admin/AdminUsers-IN_2yHKt.js.map +1 -0
  28. package/dist/admin/index.d.ts +5560 -416
  29. package/dist/admin/index.js +299 -41
  30. package/dist/admin/index.js.map +1 -1
  31. package/dist/auth/AuthLayout-BSL8ZHgr.js +19 -0
  32. package/dist/auth/AuthLayout-BSL8ZHgr.js.map +1 -0
  33. package/dist/auth/Login-DDsyCNAA.js +4 -0
  34. package/dist/auth/{Login-OCrvjs9U.js → Login-kBfaRgKG.js} +5 -4
  35. package/dist/auth/Login-kBfaRgKG.js.map +1 -0
  36. package/dist/auth/{Register-Ei34GSba.js → Register-BxJmOqpF.js} +9 -6
  37. package/dist/auth/Register-BxJmOqpF.js.map +1 -0
  38. package/dist/auth/Register-D10MnlQc.js +4 -0
  39. package/dist/auth/{ResetPassword-tO0oMzfo.js → ResetPassword-BhyZ9ek4.js} +3 -3
  40. package/dist/auth/ResetPassword-BhyZ9ek4.js.map +1 -0
  41. package/dist/auth/ResetPassword-llBG-STp.js +3 -0
  42. package/dist/auth/VerifyEmail-BvOG-IUC.js +3 -0
  43. package/dist/auth/VerifyEmail-DeLct3oQ.js +131 -0
  44. package/dist/auth/VerifyEmail-DeLct3oQ.js.map +1 -0
  45. package/dist/auth/index.d.ts +2412 -2254
  46. package/dist/auth/index.js +96 -20
  47. package/dist/auth/index.js.map +1 -1
  48. package/dist/core/index.d.ts +280 -95
  49. package/dist/core/index.js +1381 -392
  50. package/dist/core/index.js.map +1 -1
  51. package/package.json +5 -5
  52. package/src/admin/AdminRouter.ts +116 -29
  53. package/src/admin/MainRouter.ts +23 -0
  54. package/src/admin/components/AdminLayout.tsx +86 -103
  55. package/src/admin/components/AdminNotifications.tsx +196 -12
  56. package/src/admin/components/AdminSessions.tsx +43 -7
  57. package/src/admin/components/AdminUserCreate.tsx +84 -0
  58. package/src/admin/components/AdminUserDetails.tsx +180 -0
  59. package/src/admin/components/AdminUserLayout.tsx +172 -0
  60. package/src/admin/components/AdminUserSessions.tsx +158 -0
  61. package/src/admin/components/AdminUserSettings.tsx +165 -0
  62. package/src/admin/components/AdminUsers.tsx +29 -9
  63. package/src/admin/index.ts +12 -3
  64. package/src/auth/AuthI18n.ts +22 -0
  65. package/src/auth/AuthRouter.ts +82 -8
  66. package/src/auth/components/AuthLayout.tsx +12 -0
  67. package/src/auth/components/Login.tsx +13 -11
  68. package/src/auth/components/Register.tsx +6 -5
  69. package/src/auth/components/ResetPassword.tsx +1 -1
  70. package/src/auth/components/VerifyEmail.tsx +102 -0
  71. package/src/auth/components/buttons/UserButton.tsx +6 -2
  72. package/src/auth/index.ts +1 -0
  73. package/src/core/components/buttons/ActionButton.tsx +11 -4
  74. package/src/core/components/buttons/DarkModeButton.tsx +1 -1
  75. package/src/core/components/buttons/ThemeButton.tsx +31 -0
  76. package/src/core/components/layout/AdminShell.tsx +4 -2
  77. package/src/core/components/layout/AlephaMantineProvider.tsx +10 -4
  78. package/src/core/components/layout/Omnibar.tsx +27 -15
  79. package/src/core/components/layout/Sidebar.tsx +33 -15
  80. package/src/core/components/table/DataTable.tsx +9 -5
  81. package/src/core/hooks/useTheme.ts +25 -0
  82. package/src/core/index.ts +8 -3
  83. package/src/core/providers/ThemeProvider.ts +87 -0
  84. package/src/core/themes/aurora.ts +107 -0
  85. package/src/core/themes/crystal.ts +107 -0
  86. package/src/core/themes/default.ts +7 -0
  87. package/src/core/themes/ember.ts +107 -0
  88. package/src/core/themes/index.ts +7 -0
  89. package/src/core/themes/midnight.ts +104 -0
  90. package/src/core/themes/remoraid.ts +278 -0
  91. package/src/core/themes/slate.ts +81 -0
  92. package/dist/admin/AdminJobs-BOq6AZOW.js +0 -3
  93. package/dist/admin/AdminJobs-CDnVxEv6.js +0 -125
  94. package/dist/admin/AdminJobs-CDnVxEv6.js.map +0 -1
  95. package/dist/admin/AdminLayout-Bgx25J8m.js +0 -3
  96. package/dist/admin/AdminLayout-CervL8LV.js +0 -88
  97. package/dist/admin/AdminLayout-CervL8LV.js.map +0 -1
  98. package/dist/admin/AdminNotifications-BDQXt3-e.js +0 -3
  99. package/dist/admin/AdminNotifications-DvI2989x.js +0 -40
  100. package/dist/admin/AdminNotifications-DvI2989x.js.map +0 -1
  101. package/dist/admin/AdminParameters-D_v0GAvI.js +0 -3
  102. package/dist/admin/AdminParameters-P1LB6ZI1.js +0 -40
  103. package/dist/admin/AdminParameters-P1LB6ZI1.js.map +0 -1
  104. package/dist/admin/AdminSessions-CmDVneE2.js.map +0 -1
  105. package/dist/admin/AdminSessions-Dkk_fzWK.js +0 -3
  106. package/dist/admin/AdminUsers-88De5pev.js.map +0 -1
  107. package/dist/admin/AdminUsers-oyAXqZ5l.js +0 -3
  108. package/dist/admin/AdminVerifications-D93TKymL.js +0 -3
  109. package/dist/admin/AdminVerifications-DBVEoqJe.js +0 -40
  110. package/dist/admin/AdminVerifications-DBVEoqJe.js.map +0 -1
  111. package/dist/auth/Login-BC2jTczq.js +0 -4
  112. package/dist/auth/Login-OCrvjs9U.js.map +0 -1
  113. package/dist/auth/Register-Dh0lsQmI.js +0 -4
  114. package/dist/auth/Register-Ei34GSba.js.map +0 -1
  115. package/dist/auth/ResetPassword-BnlAQAOE.js +0 -3
  116. 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.3",
11
11
  "type": "module",
12
12
  "engines": {
13
13
  "node": ">=22.0.0"
@@ -21,7 +21,7 @@
21
21
  "src"
22
22
  ],
23
23
  "dependencies": {
24
- "@alepha/react": "0.13.2",
24
+ "@alepha/react": "0.13.3",
25
25
  "@mantine/core": "^8.3.9",
26
26
  "@mantine/dates": "^8.3.9",
27
27
  "@mantine/hooks": "^8.3.9",
@@ -30,12 +30,12 @@
30
30
  "@mantine/nprogress": "^8.3.9",
31
31
  "@mantine/spotlight": "^8.3.9",
32
32
  "@tabler/icons-react": "^3.35.0",
33
- "alepha": "0.13.2",
33
+ "alepha": "0.13.3",
34
34
  "dayjs": "^1.11.19"
35
35
  },
36
36
  "devDependencies": {
37
- "react": "^19.2.0",
38
- "react-dom": "^19.2.0"
37
+ "react": "^19.2.1",
38
+ "react-dom": "^19.2.1"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "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,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,96 @@
1
1
  import { NestedView, useRouter } from "@alepha/react";
2
- import { AdminShell, AlephaMantineProvider } from "@alepha/ui";
3
- import { Flex } from "@mantine/core";
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";
11
+ import ToggleSidebarButton from "../../core/components/buttons/ToggleSidebarButton.tsx";
14
12
  import type { AdminRouter } from "../AdminRouter.ts";
15
13
 
16
14
  const AdminLayout = () => {
17
15
  const router = useRouter<AdminRouter>();
18
16
  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>
17
+ <AdminShell
18
+ appShellMainProps={{
19
+ bg: "var(--alepha-surface)",
20
+ }}
21
+ appShellHeaderProps={{
22
+ bg: "var(--alepha-background)",
23
+ }}
24
+ appShellNavbarProps={
25
+ {
26
+ // bg: "var(--alepha-background)",
27
+ }
28
+ }
29
+ appShellProps={
30
+ {
31
+ // withBorder: false,
32
+ }
33
+ }
34
+ appShellFooterProps={{
35
+ bg: "var(--alepha-background)",
36
+ }}
37
+ footer={<Flex h={12} />}
38
+ appBarProps={{
39
+ items: [
40
+ {
41
+ element: <ActionButton icon={IconArrowLeft} href={"/"} />,
42
+ position: "left",
43
+ },
44
+ {
45
+ element: <OmnibarButton actionProps={{ variant: "outline" }} />,
46
+ position: "right",
47
+ },
48
+ {
49
+ element: <UserButton />,
50
+ position: "right",
51
+ },
52
+ {
53
+ element: <ThemeButton />,
54
+ position: "right",
55
+ },
56
+ {
57
+ type: "dark",
58
+ position: "right",
59
+ },
60
+ ],
61
+ }}
62
+ sidebarProps={{
63
+ gap: "xs",
64
+ menu: [
65
+ {
66
+ element: <ToggleSidebarButton />,
67
+ },
68
+ {
69
+ type: "spacer",
70
+ },
71
+ {
72
+ ...router.node("adminUsers"),
73
+ description: undefined,
74
+ },
75
+ {
76
+ ...router.node("adminSessions"),
77
+ description: undefined,
78
+ },
79
+ {
80
+ ...router.node("adminNotifications"),
81
+ description: undefined,
82
+ },
83
+ {
84
+ ...router.node("adminFiles"),
85
+ description: undefined,
86
+ },
87
+ ],
88
+ }}
89
+ >
90
+ <Flex flex={1} bg={"var(--alepha-surface)"}>
91
+ <NestedView />
92
+ </Flex>
93
+ </AdminShell>
111
94
  );
112
95
  };
113
96
 
@@ -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
  };