@mesob/auth-react 0.3.4 → 0.4.0

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 (142) hide show
  1. package/dist/components/auth/auth-layout.d.ts +1 -1
  2. package/dist/components/auth/auth-layout.js +10 -2
  3. package/dist/components/auth/auth-layout.js.map +1 -1
  4. package/dist/components/auth/countdown.js +8 -6
  5. package/dist/components/auth/countdown.js.map +1 -1
  6. package/dist/components/auth/forgot-password.js +21 -19
  7. package/dist/components/auth/forgot-password.js.map +1 -1
  8. package/dist/components/auth/reset-password-form.js +22 -21
  9. package/dist/components/auth/reset-password-form.js.map +1 -1
  10. package/dist/components/auth/set-password.d.ts +9 -0
  11. package/dist/components/auth/set-password.js +527 -0
  12. package/dist/components/auth/set-password.js.map +1 -0
  13. package/dist/components/auth/sign-in.js +45 -26
  14. package/dist/components/auth/sign-in.js.map +1 -1
  15. package/dist/components/auth/sign-up.js +25 -29
  16. package/dist/components/auth/sign-up.js.map +1 -1
  17. package/dist/components/auth/verification-form.js +24 -27
  18. package/dist/components/auth/verification-form.js.map +1 -1
  19. package/dist/components/auth/verify-email.js +40 -31
  20. package/dist/components/auth/verify-email.js.map +1 -1
  21. package/dist/components/auth/verify-phone.js +40 -31
  22. package/dist/components/auth/verify-phone.js.map +1 -1
  23. package/dist/components/authorization/deny.d.ts +11 -0
  24. package/dist/components/authorization/deny.js +52 -0
  25. package/dist/components/authorization/deny.js.map +1 -0
  26. package/dist/components/authorization/grant.d.ts +12 -0
  27. package/dist/components/authorization/grant.js +57 -0
  28. package/dist/components/authorization/grant.js.map +1 -0
  29. package/dist/components/iam/permission-selector.d.ts +19 -0
  30. package/dist/components/iam/permission-selector.js +122 -0
  31. package/dist/components/iam/permission-selector.js.map +1 -0
  32. package/dist/components/iam/permissions.js +21 -33
  33. package/dist/components/iam/permissions.js.map +1 -1
  34. package/dist/components/iam/role-detail-layout.d.ts +11 -0
  35. package/dist/components/iam/role-detail-layout.js +137 -0
  36. package/dist/components/iam/role-detail-layout.js.map +1 -0
  37. package/dist/components/iam/role-detail-page.d.ts +9 -0
  38. package/dist/components/iam/role-detail-page.js +229 -0
  39. package/dist/components/iam/role-detail-page.js.map +1 -0
  40. package/dist/components/iam/role-permissions-page.d.ts +8 -0
  41. package/dist/components/iam/role-permissions-page.js +397 -0
  42. package/dist/components/iam/role-permissions-page.js.map +1 -0
  43. package/dist/components/iam/roles.js +20 -10
  44. package/dist/components/iam/roles.js.map +1 -1
  45. package/dist/components/iam/tenants.js +9 -2
  46. package/dist/components/iam/tenants.js.map +1 -1
  47. package/dist/components/iam/users.js +10 -9
  48. package/dist/components/iam/users.js.map +1 -1
  49. package/dist/components/profile/account.js +110 -19
  50. package/dist/components/profile/account.js.map +1 -1
  51. package/dist/components/profile/change-email-form.js +26 -29
  52. package/dist/components/profile/change-email-form.js.map +1 -1
  53. package/dist/components/profile/change-phone-form.js +26 -29
  54. package/dist/components/profile/change-phone-form.js.map +1 -1
  55. package/dist/components/profile/change-profile.d.ts +2 -1
  56. package/dist/components/profile/change-profile.js +16 -8
  57. package/dist/components/profile/change-profile.js.map +1 -1
  58. package/dist/components/profile/otp-verification-modal.js +24 -27
  59. package/dist/components/profile/otp-verification-modal.js.map +1 -1
  60. package/dist/components/profile/security.js +88 -57
  61. package/dist/components/profile/security.js.map +1 -1
  62. package/dist/components/profile/verify-change-email-form.js +24 -27
  63. package/dist/components/profile/verify-change-email-form.js.map +1 -1
  64. package/dist/components/profile/verify-change-phone-form.js +24 -27
  65. package/dist/components/profile/verify-change-phone-form.js.map +1 -1
  66. package/dist/index.d.ts +9 -1
  67. package/dist/index.js +1897 -821
  68. package/dist/index.js.map +1 -1
  69. package/dist/pages/auth/forgot-password.d.ts +7 -0
  70. package/dist/pages/auth/forgot-password.js +784 -0
  71. package/dist/pages/auth/forgot-password.js.map +1 -0
  72. package/dist/pages/auth/layout.d.ts +8 -0
  73. package/dist/pages/auth/layout.js +562 -0
  74. package/dist/pages/auth/layout.js.map +1 -0
  75. package/dist/pages/auth/reset-password.d.ts +10 -0
  76. package/dist/pages/auth/reset-password.js +913 -0
  77. package/dist/pages/auth/reset-password.js.map +1 -0
  78. package/dist/pages/auth/set-password.d.ts +10 -0
  79. package/dist/pages/auth/set-password.js +946 -0
  80. package/dist/pages/auth/set-password.js.map +1 -0
  81. package/dist/pages/auth/sign-in.d.ts +10 -0
  82. package/dist/pages/auth/sign-in.js +984 -0
  83. package/dist/pages/auth/sign-in.js.map +1 -0
  84. package/dist/pages/auth/sign-up.d.ts +10 -0
  85. package/dist/pages/auth/sign-up.js +940 -0
  86. package/dist/pages/auth/sign-up.js.map +1 -0
  87. package/dist/pages/auth/verify-email.d.ts +10 -0
  88. package/dist/pages/auth/verify-email.js +950 -0
  89. package/dist/pages/auth/verify-email.js.map +1 -0
  90. package/dist/pages/auth/verify-phone.d.ts +10 -0
  91. package/dist/pages/auth/verify-phone.js +964 -0
  92. package/dist/pages/auth/verify-phone.js.map +1 -0
  93. package/dist/pages/iam/permissions.d.ts +5 -0
  94. package/dist/pages/iam/permissions.js +308 -0
  95. package/dist/pages/iam/permissions.js.map +1 -0
  96. package/dist/pages/iam/role-detail-layout.d.ts +12 -0
  97. package/dist/pages/iam/role-detail-layout.js +145 -0
  98. package/dist/pages/iam/role-detail-layout.js.map +1 -0
  99. package/dist/pages/iam/role-detail.d.ts +12 -0
  100. package/dist/pages/iam/role-detail.js +241 -0
  101. package/dist/pages/iam/role-detail.js.map +1 -0
  102. package/dist/pages/iam/role-permissions.d.ts +12 -0
  103. package/dist/pages/iam/role-permissions.js +409 -0
  104. package/dist/pages/iam/role-permissions.js.map +1 -0
  105. package/dist/pages/iam/role-users.d.ts +12 -0
  106. package/dist/pages/iam/role-users.js +825 -0
  107. package/dist/pages/iam/role-users.js.map +1 -0
  108. package/dist/pages/iam/roles.d.ts +5 -0
  109. package/dist/pages/iam/roles.js +684 -0
  110. package/dist/pages/iam/roles.js.map +1 -0
  111. package/dist/pages/iam/sessions.d.ts +5 -0
  112. package/dist/pages/iam/sessions.js +315 -0
  113. package/dist/pages/iam/sessions.js.map +1 -0
  114. package/dist/pages/iam/tenant-detail.d.ts +10 -0
  115. package/dist/pages/iam/tenant-detail.js +186 -0
  116. package/dist/pages/iam/tenant-detail.js.map +1 -0
  117. package/dist/pages/iam/tenants.d.ts +5 -0
  118. package/dist/pages/iam/tenants.js +610 -0
  119. package/dist/pages/iam/tenants.js.map +1 -0
  120. package/dist/pages/iam/user-activity.d.ts +10 -0
  121. package/dist/pages/iam/user-activity.js +850 -0
  122. package/dist/pages/iam/user-activity.js.map +1 -0
  123. package/dist/pages/iam/user-detail-layout.d.ts +12 -0
  124. package/dist/pages/iam/user-detail-layout.js +106 -0
  125. package/dist/pages/iam/user-detail-layout.js.map +1 -0
  126. package/dist/pages/iam/user-detail.d.ts +10 -0
  127. package/dist/pages/iam/user-detail.js +102 -0
  128. package/dist/pages/iam/user-detail.js.map +1 -0
  129. package/dist/pages/iam/users.d.ts +5 -0
  130. package/dist/pages/iam/users.js +1275 -0
  131. package/dist/pages/iam/users.js.map +1 -0
  132. package/dist/pages/profile/account.d.ts +5 -0
  133. package/dist/pages/profile/account.js +182 -0
  134. package/dist/pages/profile/account.js.map +1 -0
  135. package/dist/pages/profile/layout.d.ts +8 -0
  136. package/dist/pages/profile/layout.js +133 -0
  137. package/dist/pages/profile/layout.js.map +1 -0
  138. package/dist/pages/profile/security.d.ts +5 -0
  139. package/dist/pages/profile/security.js +1539 -0
  140. package/dist/pages/profile/security.js.map +1 -0
  141. package/dist/{types-vcfvnAzQ.d.ts → types-g9QcNRxT.d.ts} +13 -7
  142. package/package.json +102 -3
@@ -0,0 +1,825 @@
1
+ // src/pages/iam/roles/users/_components/role-users-page.tsx
2
+ import {
3
+ Avatar,
4
+ AvatarFallback,
5
+ AvatarImage,
6
+ Badge as Badge3,
7
+ Button as Button2,
8
+ DataTablePagination,
9
+ DeleteConfirmButton,
10
+ DisplayTable,
11
+ EntityEmptyState,
12
+ EntityFilter,
13
+ EntityHeader,
14
+ EntityLoadingState,
15
+ EntitySearch,
16
+ EntitySort,
17
+ EntityViewToggle,
18
+ PageBody,
19
+ Tbody,
20
+ Td,
21
+ Th,
22
+ Thead,
23
+ Tr,
24
+ useEntityPagination,
25
+ useEntityParams
26
+ } from "@mesob/ui/components";
27
+ import { IconPlus, IconUser, IconUsers as IconUsers2 } from "@tabler/icons-react";
28
+ import { useQueryClient as useQueryClient2 } from "@tanstack/react-query";
29
+ import { useMemo as useMemo2 } from "react";
30
+ import { toast } from "sonner";
31
+
32
+ // src/provider.tsx
33
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
34
+ import { deepmerge } from "deepmerge-ts";
35
+ import createFetchClient from "openapi-fetch";
36
+ import createClient from "openapi-react-query";
37
+ import { createContext, useContext, useMemo, useState } from "react";
38
+
39
+ // src/utils/cookie.ts
40
+ var isProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
41
+
42
+ // src/provider.tsx
43
+ import { jsx } from "react/jsx-runtime";
44
+ var SessionContext = createContext(null);
45
+ var ApiContext = createContext(null);
46
+ var ConfigContext = createContext(null);
47
+ var queryClient = new QueryClient({
48
+ defaultOptions: {
49
+ queries: {
50
+ refetchOnWindowFocus: false
51
+ }
52
+ }
53
+ });
54
+ function useApi() {
55
+ const context = useContext(ApiContext);
56
+ if (!context) {
57
+ throw new Error("useApi must be used within MesobAuthProvider");
58
+ }
59
+ return context;
60
+ }
61
+
62
+ // src/pages/iam/shared/navigation.tsx
63
+ import { useMesob } from "@mesob/ui/providers";
64
+ import { jsx as jsx2 } from "react/jsx-runtime";
65
+ function AppLink({ href, children, ...props }) {
66
+ const mesob = useMesob();
67
+ const Link2 = mesob?.linkComponent ?? mesob?.navigation?.Link;
68
+ const locale = mesob?.locale;
69
+ if (Link2) {
70
+ return /* @__PURE__ */ jsx2(Link2, { href, ...locale ? { locale } : {}, ...props, children });
71
+ }
72
+ return /* @__PURE__ */ jsx2("a", { href, ...props, children });
73
+ }
74
+
75
+ // src/lib/query-options.ts
76
+ import { keepPreviousData } from "@tanstack/react-query";
77
+ var defaultEntityQueryOptions = {
78
+ refetchOnMount: false,
79
+ refetchOnWindowFocus: false,
80
+ refetchOnReconnect: false,
81
+ staleTime: 60 * 1e3,
82
+ gcTime: 5 * 60 * 1e3,
83
+ placeholderData: keepPreviousData,
84
+ retry: 1
85
+ };
86
+
87
+ // src/pages/iam/shared/page-helpers.tsx
88
+ import { jsx as jsx3 } from "react/jsx-runtime";
89
+ var authApi$ = {
90
+ useQuery(...args) {
91
+ const { hooks } = useApi();
92
+ return hooks.useQuery(...args);
93
+ },
94
+ useMutation(...args) {
95
+ const { hooks } = useApi();
96
+ return hooks.useMutation(...args);
97
+ }
98
+ };
99
+ function Link(props) {
100
+ return /* @__PURE__ */ jsx3(AppLink, { ...props });
101
+ }
102
+
103
+ // src/pages/iam/users/_components/user-selector.tsx
104
+ import {
105
+ Badge as Badge2,
106
+ EntitySelector,
107
+ useEntitySectionState
108
+ } from "@mesob/ui/components";
109
+ import { cn } from "@mesob/ui/lib/utils";
110
+ import { IconCalendar, IconUsers } from "@tabler/icons-react";
111
+
112
+ // src/pages/iam/users/_components/user-card.tsx
113
+ import {
114
+ Badge,
115
+ Button,
116
+ Card,
117
+ CardContent,
118
+ CardHeader,
119
+ DropdownMenu,
120
+ DropdownMenuContent,
121
+ DropdownMenuItem,
122
+ DropdownMenuPortal,
123
+ DropdownMenuTrigger
124
+ } from "@mesob/ui/components";
125
+ import { IconDots, IconPencil } from "@tabler/icons-react";
126
+ import { useState as useState2 } from "react";
127
+
128
+ // src/pages/iam/users/_components/user-form.tsx
129
+ import { zodResolver } from "@hookform/resolvers/zod";
130
+ import {
131
+ EntityDrawer,
132
+ EntityFormActions,
133
+ Input,
134
+ Label,
135
+ Skeleton
136
+ } from "@mesob/ui/components";
137
+ import { useQueryClient } from "@tanstack/react-query";
138
+ import { useEffect } from "react";
139
+ import { useForm } from "react-hook-form";
140
+ import { z } from "zod";
141
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
142
+ var schema = z.object({
143
+ fullName: z.string().min(1, "Name is required"),
144
+ email: z.string().email().optional().or(z.literal("")),
145
+ phone: z.string().optional()
146
+ });
147
+ var defaults = {
148
+ fullName: "",
149
+ email: "",
150
+ phone: ""
151
+ };
152
+ function UserForm({
153
+ mode,
154
+ userId,
155
+ open,
156
+ onClose,
157
+ onSuccess
158
+ }) {
159
+ const qc = useQueryClient();
160
+ const { data, isLoading } = authApi$.useQuery(
161
+ "get",
162
+ "/users/{id}",
163
+ { params: { path: { id: userId ?? "" } } },
164
+ { enabled: mode === "edit" && !!userId }
165
+ );
166
+ const create = authApi$.useMutation("post", "/users", {
167
+ onSuccess: () => {
168
+ qc.invalidateQueries({ queryKey: ["get", "/users"] });
169
+ }
170
+ });
171
+ const update = authApi$.useMutation("put", "/users/{id}", {
172
+ onSuccess: () => {
173
+ qc.invalidateQueries({ queryKey: ["get", "/users"] });
174
+ if (userId) {
175
+ qc.invalidateQueries({ queryKey: ["get", "/users/{id}"] });
176
+ }
177
+ }
178
+ });
179
+ const del = authApi$.useMutation("delete", "/users/{id}", {
180
+ onSuccess: () => {
181
+ qc.invalidateQueries({ queryKey: ["get", "/users"] });
182
+ }
183
+ });
184
+ const form = useForm({
185
+ resolver: zodResolver(schema),
186
+ defaultValues: defaults
187
+ });
188
+ const { reset, formState } = form;
189
+ useEffect(() => {
190
+ if (!open) {
191
+ return;
192
+ }
193
+ if (mode === "edit" && data?.user && !isLoading) {
194
+ const u = data.user;
195
+ reset({
196
+ fullName: u.fullName,
197
+ email: u.email ?? "",
198
+ phone: u.phone ?? ""
199
+ });
200
+ } else {
201
+ reset(defaults);
202
+ }
203
+ }, [mode, data, open, isLoading, reset]);
204
+ const onSubmit = form.handleSubmit(async (d) => {
205
+ const payload = {
206
+ fullName: d.fullName,
207
+ email: d.email || void 0,
208
+ phone: d.phone || void 0
209
+ };
210
+ if (mode === "new") {
211
+ await create.mutateAsync({ body: payload });
212
+ } else if (userId) {
213
+ await update.mutateAsync({
214
+ params: { path: { id: userId } },
215
+ body: payload
216
+ });
217
+ }
218
+ onSuccess?.();
219
+ onClose();
220
+ });
221
+ const onDelete = async () => {
222
+ if (!userId) {
223
+ return;
224
+ }
225
+ await del.mutateAsync({ params: { path: { id: userId } } });
226
+ onSuccess?.();
227
+ onClose();
228
+ };
229
+ const isSubmitting = create.isPending || update.isPending;
230
+ return /* @__PURE__ */ jsx4(
231
+ EntityDrawer,
232
+ {
233
+ title: mode === "new" ? "New user" : "Edit user",
234
+ open,
235
+ onClose,
236
+ isDirty: formState.isDirty,
237
+ form: isLoading ? /* @__PURE__ */ jsx4(FormSkeleton, {}) : /* @__PURE__ */ jsxs("form", { onSubmit, className: "space-y-4", children: [
238
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
239
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "fullName", children: [
240
+ "Full name ",
241
+ /* @__PURE__ */ jsx4("span", { className: "text-destructive", children: "*" })
242
+ ] }),
243
+ /* @__PURE__ */ jsx4(
244
+ Input,
245
+ {
246
+ id: "fullName",
247
+ placeholder: "Full name",
248
+ ...form.register("fullName")
249
+ }
250
+ ),
251
+ formState.errors.fullName && /* @__PURE__ */ jsx4("p", { className: "text-sm text-destructive", children: formState.errors.fullName.message })
252
+ ] }),
253
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
254
+ /* @__PURE__ */ jsx4(Label, { htmlFor: "email", children: "Email" }),
255
+ /* @__PURE__ */ jsx4(
256
+ Input,
257
+ {
258
+ id: "email",
259
+ type: "email",
260
+ placeholder: "Email",
261
+ ...form.register("email")
262
+ }
263
+ ),
264
+ formState.errors.email && /* @__PURE__ */ jsx4("p", { className: "text-sm text-destructive", children: formState.errors.email.message })
265
+ ] }),
266
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
267
+ /* @__PURE__ */ jsx4(Label, { htmlFor: "phone", children: "Phone" }),
268
+ /* @__PURE__ */ jsx4(
269
+ Input,
270
+ {
271
+ id: "phone",
272
+ placeholder: "Phone",
273
+ ...form.register("phone")
274
+ }
275
+ )
276
+ ] })
277
+ ] }),
278
+ actions: /* @__PURE__ */ jsx4(
279
+ EntityFormActions,
280
+ {
281
+ mode,
282
+ onSubmit,
283
+ onReset: mode === "new" ? () => reset(defaults) : void 0,
284
+ onDelete: mode === "edit" ? onDelete : void 0,
285
+ isSubmitting,
286
+ isDeleting: del.isPending,
287
+ disabled: isLoading,
288
+ itemName: "user"
289
+ }
290
+ )
291
+ }
292
+ );
293
+ }
294
+ function FormSkeleton() {
295
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
296
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
297
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-4 w-20" }),
298
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-10 w-full" })
299
+ ] }),
300
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
301
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-4 w-16" }),
302
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-10 w-full" })
303
+ ] }),
304
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
305
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-4 w-14" }),
306
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-10 w-full" })
307
+ ] }),
308
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
309
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-4 w-16" }),
310
+ /* @__PURE__ */ jsx4(Skeleton, { className: "h-10 w-full" })
311
+ ] })
312
+ ] });
313
+ }
314
+
315
+ // src/pages/iam/users/_components/user-card.tsx
316
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
317
+ function UserCard({ user }) {
318
+ const [editOpen, setEditOpen] = useState2(false);
319
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
320
+ /* @__PURE__ */ jsxs2(Card, { className: "group hover:shadow-md transition-shadow", children: [
321
+ /* @__PURE__ */ jsx5(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between", children: [
322
+ /* @__PURE__ */ jsx5(
323
+ Link,
324
+ {
325
+ href: `/iam/users/${user.id}`,
326
+ className: "text-left font-semibold hover:text-primary hover:underline",
327
+ children: user.fullName
328
+ }
329
+ ),
330
+ /* @__PURE__ */ jsxs2(DropdownMenu, { children: [
331
+ /* @__PURE__ */ jsx5(
332
+ DropdownMenuTrigger,
333
+ {
334
+ render: /* @__PURE__ */ jsx5(
335
+ Button,
336
+ {
337
+ variant: "ghost",
338
+ size: "icon",
339
+ className: "h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
340
+ }
341
+ ),
342
+ children: /* @__PURE__ */ jsx5(IconDots, { className: "h-4 w-4" })
343
+ }
344
+ ),
345
+ /* @__PURE__ */ jsx5(DropdownMenuPortal, { children: /* @__PURE__ */ jsx5(DropdownMenuContent, { children: /* @__PURE__ */ jsxs2(DropdownMenuItem, { onClick: () => setEditOpen(true), children: [
346
+ /* @__PURE__ */ jsx5(IconPencil, { className: "mr-2 h-4 w-4" }),
347
+ "Edit"
348
+ ] }) }) })
349
+ ] })
350
+ ] }) }),
351
+ /* @__PURE__ */ jsxs2(CardContent, { className: "space-y-2", children: [
352
+ user.email && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
353
+ /* @__PURE__ */ jsx5("span", { className: "text-sm", children: user.email }),
354
+ user.emailVerified && /* @__PURE__ */ jsx5(Badge, { variant: "outline", className: "text-xs", children: "Verified" })
355
+ ] }),
356
+ /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
357
+ "Last sign in",
358
+ " ",
359
+ user.lastSignInAt ? new Date(user.lastSignInAt).toLocaleDateString() : "never"
360
+ ] })
361
+ ] })
362
+ ] }),
363
+ editOpen && /* @__PURE__ */ jsx5(
364
+ UserForm,
365
+ {
366
+ mode: "edit",
367
+ userId: user.id,
368
+ open: editOpen,
369
+ onClose: () => setEditOpen(false)
370
+ }
371
+ )
372
+ ] });
373
+ }
374
+
375
+ // src/pages/iam/users/_components/user-selector.tsx
376
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
377
+ function SelectableUserCard({
378
+ user,
379
+ selected,
380
+ onToggle
381
+ }) {
382
+ return (
383
+ // biome-ignore lint/a11y/useSemanticElements: div needed to avoid nested buttons from UserCard
384
+ /* @__PURE__ */ jsx6(
385
+ "div",
386
+ {
387
+ role: "button",
388
+ tabIndex: 0,
389
+ onClick: onToggle,
390
+ onKeyDown: (e) => {
391
+ if (e.key === "Enter") {
392
+ onToggle();
393
+ }
394
+ },
395
+ className: cn(
396
+ "cursor-pointer rounded-lg transition-shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
397
+ selected && "ring-primary ring-2"
398
+ ),
399
+ children: /* @__PURE__ */ jsx6(UserCard, { user })
400
+ }
401
+ )
402
+ );
403
+ }
404
+ var userColumns = [
405
+ {
406
+ key: "name",
407
+ header: "Name",
408
+ cell: (user) => /* @__PURE__ */ jsx6("p", { className: "font-medium", children: user.fullName })
409
+ },
410
+ {
411
+ key: "contact",
412
+ header: "Contact",
413
+ cell: (user) => /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
414
+ user.email && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
415
+ /* @__PURE__ */ jsx6("span", { className: "text-sm", children: user.email }),
416
+ user.emailVerified && /* @__PURE__ */ jsx6(Badge2, { variant: "outline", className: "text-xs", children: "Verified" })
417
+ ] }),
418
+ user.phone && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
419
+ /* @__PURE__ */ jsx6("span", { className: "text-sm", children: user.phone }),
420
+ user.phoneVerified && /* @__PURE__ */ jsx6(Badge2, { variant: "outline", className: "text-xs", children: "Verified" })
421
+ ] }),
422
+ !(user.email || user.phone) && /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: "\u2014" })
423
+ ] })
424
+ },
425
+ {
426
+ key: "lastSignIn",
427
+ header: "Last sign in",
428
+ cell: (user) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 text-muted-foreground", children: [
429
+ /* @__PURE__ */ jsx6(IconCalendar, { className: "h-4 w-4" }),
430
+ user.lastSignInAt ? new Date(user.lastSignInAt).toLocaleDateString() : "Never"
431
+ ] })
432
+ }
433
+ ];
434
+ var EMPTY_EXCLUDE_IDS = [];
435
+ function UserSelector({
436
+ trigger,
437
+ multiple = false,
438
+ onSelect,
439
+ excludeIds = EMPTY_EXCLUDE_IDS,
440
+ modalSize = "xl",
441
+ contentClassName
442
+ }) {
443
+ const state = useEntitySectionState({
444
+ defaultSort: "createdAt",
445
+ defaultOrder: "desc",
446
+ defaultPageSize: 10,
447
+ searchParamName: "handle"
448
+ });
449
+ const usersQuery = state.queryConfig;
450
+ const { data, isPending, isFetching } = authApi$.useQuery(
451
+ "get",
452
+ "/users",
453
+ usersQuery,
454
+ defaultEntityQueryOptions
455
+ );
456
+ const users = (data?.users ?? []).filter((user) => {
457
+ return !excludeIds.includes(user.id);
458
+ });
459
+ const config = {
460
+ title: "Select user(s)",
461
+ modalSize,
462
+ contentClassName,
463
+ multiple,
464
+ entityName: "user",
465
+ entityIcon: IconUsers,
466
+ columns: userColumns,
467
+ columnCount: 4,
468
+ getItemLabel: (user) => user.fullName,
469
+ searchPlaceholder: "Search users...",
470
+ filterOptions: [
471
+ { label: "All", value: "" },
472
+ { label: "Email Verified", value: "emailVerified" },
473
+ { label: "Phone Verified", value: "phoneVerified" },
474
+ { label: "Not Verified", value: "notVerified" }
475
+ ],
476
+ sortOptions: [
477
+ { label: "Created", value: "createdAt" },
478
+ { label: "Updated", value: "updatedAt" },
479
+ { label: "Name", value: "fullName" },
480
+ { label: "Last Sign In", value: "lastSignInAt" }
481
+ ],
482
+ showViewToggle: false,
483
+ renderCard: (user, selected, onToggle) => /* @__PURE__ */ jsx6(SelectableUserCard, { user, selected, onToggle })
484
+ };
485
+ return /* @__PURE__ */ jsx6(
486
+ EntitySelector,
487
+ {
488
+ trigger,
489
+ config,
490
+ onSelect,
491
+ items: users,
492
+ total: data?.total,
493
+ isLoading: isPending || isFetching,
494
+ state
495
+ }
496
+ );
497
+ }
498
+
499
+ // src/pages/iam/roles/users/_components/role-users-page.tsx
500
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
501
+ var TABLE_COLUMN_COUNT = 4;
502
+ function initials(user) {
503
+ return user.fullName.split(" ").filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
504
+ }
505
+ function UserIdentity({ user }) {
506
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
507
+ /* @__PURE__ */ jsxs4(Avatar, { className: "h-9 w-9", children: [
508
+ /* @__PURE__ */ jsx7(AvatarImage, { src: user.image ?? void 0 }),
509
+ /* @__PURE__ */ jsx7(AvatarFallback, { children: initials(user) })
510
+ ] }),
511
+ /* @__PURE__ */ jsx7("div", { children: /* @__PURE__ */ jsx7("p", { className: "font-medium", children: user.fullName }) })
512
+ ] });
513
+ }
514
+ function UserContact({ user }) {
515
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
516
+ user.email ? /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
517
+ /* @__PURE__ */ jsx7("span", { children: user.email }),
518
+ user.emailVerified ? /* @__PURE__ */ jsx7(Badge3, { variant: "outline", children: "Verified" }) : null
519
+ ] }) : null,
520
+ user.phone ? /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
521
+ /* @__PURE__ */ jsx7("span", { children: user.phone }),
522
+ user.phoneVerified ? /* @__PURE__ */ jsx7(Badge3, { variant: "outline", children: "Verified" }) : null
523
+ ] }) : null,
524
+ user.email || user.phone ? null : /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "\u2014" })
525
+ ] });
526
+ }
527
+ function UserRemoveButton({
528
+ roleId,
529
+ userId,
530
+ onRemove
531
+ }) {
532
+ return /* @__PURE__ */ jsx7(
533
+ DeleteConfirmButton,
534
+ {
535
+ entityName: "user",
536
+ onConfirm: () => onRemove(roleId, userId),
537
+ triggerClassName: "size-8 text-destructive hover:text-destructive"
538
+ }
539
+ );
540
+ }
541
+ function RoleUsersTable({
542
+ roleId,
543
+ users,
544
+ onRemove
545
+ }) {
546
+ return /* @__PURE__ */ jsxs4(DisplayTable, { withTableBorder: true, children: [
547
+ /* @__PURE__ */ jsx7(Thead, { children: /* @__PURE__ */ jsxs4(Tr, { children: [
548
+ /* @__PURE__ */ jsx7(Th, { children: "User" }),
549
+ /* @__PURE__ */ jsx7(Th, { children: "Contact" }),
550
+ /* @__PURE__ */ jsx7(Th, { children: "Last sign in" }),
551
+ /* @__PURE__ */ jsx7(Th, { className: "w-[60px]" })
552
+ ] }) }),
553
+ /* @__PURE__ */ jsx7(Tbody, { children: users.map((user) => /* @__PURE__ */ jsxs4(Tr, { children: [
554
+ /* @__PURE__ */ jsx7(Td, { children: /* @__PURE__ */ jsx7(UserIdentity, { user }) }),
555
+ /* @__PURE__ */ jsx7(Td, { children: /* @__PURE__ */ jsx7(UserContact, { user }) }),
556
+ /* @__PURE__ */ jsx7(Td, { children: user.lastSignInAt ? new Date(user.lastSignInAt).toLocaleDateString() : "Never" }),
557
+ /* @__PURE__ */ jsx7(Td, { children: /* @__PURE__ */ jsx7(
558
+ UserRemoveButton,
559
+ {
560
+ roleId,
561
+ userId: user.id,
562
+ onRemove
563
+ }
564
+ ) })
565
+ ] }, user.id)) })
566
+ ] });
567
+ }
568
+ function RoleUsersCards({
569
+ roleId,
570
+ users,
571
+ onRemove
572
+ }) {
573
+ return /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3", children: users.map((user) => /* @__PURE__ */ jsx7(
574
+ "div",
575
+ {
576
+ className: "rounded-xl border border-border/60 bg-card p-4 shadow-sm",
577
+ children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-3", children: [
578
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
579
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
580
+ /* @__PURE__ */ jsxs4(Avatar, { className: "h-10 w-10", children: [
581
+ /* @__PURE__ */ jsx7(AvatarImage, { src: user.image ?? void 0 }),
582
+ /* @__PURE__ */ jsx7(AvatarFallback, { children: initials(user) })
583
+ ] }),
584
+ /* @__PURE__ */ jsx7("div", { children: /* @__PURE__ */ jsx7("p", { className: "font-semibold", children: user.fullName }) })
585
+ ] }),
586
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1 text-sm text-muted-foreground", children: [
587
+ /* @__PURE__ */ jsx7("p", { children: user.email ?? user.phone ?? "No contact info" }),
588
+ /* @__PURE__ */ jsxs4("p", { children: [
589
+ "Last sign in",
590
+ " ",
591
+ user.lastSignInAt ? new Date(user.lastSignInAt).toLocaleDateString() : "never"
592
+ ] })
593
+ ] })
594
+ ] }),
595
+ /* @__PURE__ */ jsx7(
596
+ UserRemoveButton,
597
+ {
598
+ roleId,
599
+ userId: user.id,
600
+ onRemove
601
+ }
602
+ )
603
+ ] })
604
+ },
605
+ user.id
606
+ )) });
607
+ }
608
+ function RoleUsersPage({ roleId }) {
609
+ const qc = useQueryClient2();
610
+ const { queryConfig, params, setParams } = useEntityParams({
611
+ searchKey: "search",
612
+ defaultSort: "fullName",
613
+ defaultOrder: "asc"
614
+ });
615
+ const usersQuery = useMemo2(
616
+ () => ({
617
+ params: {
618
+ path: { id: roleId },
619
+ query: queryConfig.params.query
620
+ }
621
+ }),
622
+ [queryConfig, roleId]
623
+ );
624
+ const { data, isPending, isFetching } = authApi$.useQuery(
625
+ "get",
626
+ "/roles/{id}/users",
627
+ usersQuery,
628
+ { enabled: !!roleId }
629
+ );
630
+ const assignUsers = authApi$.useMutation("post", "/roles/{id}/users", {
631
+ onSuccess: (result) => {
632
+ qc.invalidateQueries({ queryKey: ["get", "/roles"] });
633
+ qc.invalidateQueries({ queryKey: ["get", "/roles/{id}"] });
634
+ qc.invalidateQueries({ queryKey: ["get", "/roles/{id}/users"] });
635
+ toast.success(
636
+ result?.created ? `${result.created} user(s) added` : "No changes"
637
+ );
638
+ },
639
+ onError: () => {
640
+ toast.error("Failed to assign users");
641
+ }
642
+ });
643
+ const revokeUser = authApi$.useMutation(
644
+ "delete",
645
+ "/roles/{id}/users/{userId}",
646
+ {
647
+ onSuccess: () => {
648
+ qc.invalidateQueries({ queryKey: ["get", "/roles"] });
649
+ qc.invalidateQueries({ queryKey: ["get", "/roles/{id}"] });
650
+ qc.invalidateQueries({ queryKey: ["get", "/roles/{id}/users"] });
651
+ toast.success("User removed");
652
+ },
653
+ onError: () => {
654
+ toast.error("Failed to remove user");
655
+ }
656
+ }
657
+ );
658
+ const users = data?.users ?? [];
659
+ const { total, pageCount } = useEntityPagination({
660
+ items: users,
661
+ total: data?.total,
662
+ pageSize: params.pageSize
663
+ });
664
+ const isLoading = isPending || isFetching;
665
+ const currentView = params.view || "table";
666
+ const handleRemoveUser = (targetRoleId, userId) => {
667
+ revokeUser.mutate({
668
+ params: {
669
+ path: { id: targetRoleId, userId }
670
+ }
671
+ });
672
+ };
673
+ if (!roleId) {
674
+ return null;
675
+ }
676
+ let content;
677
+ if (isLoading) {
678
+ content = /* @__PURE__ */ jsx7(
679
+ EntityLoadingState,
680
+ {
681
+ view: currentView,
682
+ rowCount: params.pageSize,
683
+ columnCount: TABLE_COLUMN_COUNT,
684
+ cardCount: params.pageSize
685
+ }
686
+ );
687
+ } else if (total === 0) {
688
+ content = /* @__PURE__ */ jsx7(
689
+ EntityEmptyState,
690
+ {
691
+ icon: IconUsers2,
692
+ entityName: "user",
693
+ title: "No users assigned",
694
+ description: "Assign users from the selector to add them to this role."
695
+ }
696
+ );
697
+ } else if (currentView === "table") {
698
+ content = /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
699
+ /* @__PURE__ */ jsx7(
700
+ RoleUsersTable,
701
+ {
702
+ roleId,
703
+ users,
704
+ onRemove: handleRemoveUser
705
+ }
706
+ ),
707
+ /* @__PURE__ */ jsx7(
708
+ DataTablePagination,
709
+ {
710
+ pageIndex: params.page - 1,
711
+ pageSize: params.pageSize,
712
+ pageCount,
713
+ totalRows: total,
714
+ onPageChange: (page) => setParams({ page: page + 1 }),
715
+ onPageSizeChange: (pageSize) => setParams({ pageSize, page: 1 })
716
+ }
717
+ )
718
+ ] });
719
+ } else {
720
+ content = /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
721
+ /* @__PURE__ */ jsx7(
722
+ RoleUsersCards,
723
+ {
724
+ roleId,
725
+ users,
726
+ onRemove: handleRemoveUser
727
+ }
728
+ ),
729
+ /* @__PURE__ */ jsx7(
730
+ DataTablePagination,
731
+ {
732
+ pageIndex: params.page - 1,
733
+ pageSize: params.pageSize,
734
+ pageCount,
735
+ totalRows: total,
736
+ onPageChange: (page) => setParams({ page: page + 1 }),
737
+ onPageSizeChange: (pageSize) => setParams({ pageSize, page: 1 })
738
+ }
739
+ )
740
+ ] });
741
+ }
742
+ return /* @__PURE__ */ jsxs4(PageBody, { className: "px-0 pb-6", children: [
743
+ /* @__PURE__ */ jsx7(
744
+ EntityHeader,
745
+ {
746
+ icon: /* @__PURE__ */ jsx7(IconUser, { className: "h-5 w-5" }),
747
+ title: "Role users",
748
+ actions: /* @__PURE__ */ jsx7(
749
+ UserSelector,
750
+ {
751
+ trigger: /* @__PURE__ */ jsxs4(Button2, { size: "sm", loading: assignUsers.isPending, children: [
752
+ /* @__PURE__ */ jsx7(IconPlus, { className: "h-4 w-4" }),
753
+ "Add users"
754
+ ] }),
755
+ multiple: true,
756
+ excludeIds: users.map((user) => user.id),
757
+ onSelect: (selectedUsers) => {
758
+ if (!selectedUsers.length) {
759
+ return;
760
+ }
761
+ assignUsers.mutate({
762
+ params: { path: { id: roleId } },
763
+ body: {
764
+ userIds: selectedUsers.map((user) => user.id)
765
+ }
766
+ });
767
+ }
768
+ }
769
+ ),
770
+ search: /* @__PURE__ */ jsx7(
771
+ EntitySearch,
772
+ {
773
+ paramKey: "search",
774
+ placeholder: "Search assigned users..."
775
+ }
776
+ ),
777
+ filter: /* @__PURE__ */ jsx7(
778
+ EntityFilter,
779
+ {
780
+ options: [
781
+ { label: "All", value: "" },
782
+ { label: "Name", value: "fullName" },
783
+ { label: "Email", value: "email" },
784
+ { label: "Phone", value: "phone" }
785
+ ],
786
+ placeholder: "Search field"
787
+ }
788
+ ),
789
+ sort: /* @__PURE__ */ jsx7(
790
+ EntitySort,
791
+ {
792
+ defaultSort: "fullName",
793
+ defaultOrder: "asc",
794
+ options: [
795
+ { label: "Created", value: "createdAt" },
796
+ { label: "Updated", value: "updatedAt" },
797
+ { label: "Name", value: "fullName" },
798
+ { label: "Last Sign In", value: "lastSignInAt" }
799
+ ]
800
+ }
801
+ ),
802
+ view: /* @__PURE__ */ jsx7(EntityViewToggle, { views: ["table", "card"] })
803
+ }
804
+ ),
805
+ content
806
+ ] });
807
+ }
808
+
809
+ // src/pages/iam/role-users.tsx
810
+ import { jsx as jsx8 } from "react/jsx-runtime";
811
+ var metadata = {
812
+ title: "Role Users",
813
+ description: "Manage users assigned to a role."
814
+ };
815
+ async function RoleUsersRoutePage({
816
+ params
817
+ }) {
818
+ const { id } = await params;
819
+ return /* @__PURE__ */ jsx8(RoleUsersPage, { roleId: id });
820
+ }
821
+ export {
822
+ RoleUsersRoutePage as default,
823
+ metadata
824
+ };
825
+ //# sourceMappingURL=role-users.js.map