@open-kingdom/shared-frontend-feature-user-management 0.0.2-16 → 0.0.2-17

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 (30) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +598 -358
  4. package/dist/lib/components/invitations/AcceptInvitation.component.d.ts.map +1 -1
  5. package/dist/lib/components/invitations/InvitationList.component.d.ts.map +1 -1
  6. package/dist/lib/components/invitations/InviteUserModal.component.d.ts.map +1 -1
  7. package/dist/lib/components/roles/RoleList.component.d.ts +2 -0
  8. package/dist/lib/components/roles/RoleList.component.d.ts.map +1 -0
  9. package/dist/lib/components/roles/RolePermissionsModal.component.d.ts +11 -0
  10. package/dist/lib/components/roles/RolePermissionsModal.component.d.ts.map +1 -0
  11. package/dist/lib/components/roles/index.d.ts +3 -0
  12. package/dist/lib/components/roles/index.d.ts.map +1 -0
  13. package/dist/lib/components/shared/ConfirmDialog.component.d.ts.map +1 -1
  14. package/dist/lib/components/shared/FormField.component.d.ts.map +1 -1
  15. package/dist/lib/components/shared/RoleBadge.component.d.ts +1 -2
  16. package/dist/lib/components/shared/RoleBadge.component.d.ts.map +1 -1
  17. package/dist/lib/components/shared/StatusBadge.component.d.ts.map +1 -1
  18. package/dist/lib/components/shared/StatusCard.component.d.ts.map +1 -1
  19. package/dist/lib/components/users/RoleChangeModal.component.d.ts +12 -0
  20. package/dist/lib/components/users/RoleChangeModal.component.d.ts.map +1 -0
  21. package/dist/lib/components/users/UserList.component.d.ts.map +1 -1
  22. package/dist/lib/hooks/useHasPermission.d.ts +2 -0
  23. package/dist/lib/hooks/useHasPermission.d.ts.map +1 -0
  24. package/dist/lib/types.d.ts +2 -2
  25. package/dist/lib/types.d.ts.map +1 -1
  26. package/package.json +19 -1
  27. package/dist/lib/components/shared/ModalOverlay.component.d.ts +0 -9
  28. package/dist/lib/components/shared/ModalOverlay.component.d.ts.map +0 -1
  29. package/dist/lib/styles.d.ts +0 -9
  30. package/dist/lib/styles.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,411 +1,356 @@
1
- import { jsx as e, jsxs as n } from "react/jsx-runtime";
2
- import { useCallback as L, useState as k, useMemo as P } from "react";
1
+ import { jsx as e, jsxs as i } from "react/jsx-runtime";
2
+ import { useState as N, useMemo as R, useCallback as W } from "react";
3
3
  import { useDispatch as I } from "react-redux";
4
- import { useInvitationsControllerInviteMutation as O, useInvitationsControllerValidateQuery as Q, useInvitationsControllerAcceptMutation as Y, useInvitationsControllerFindAllQuery as J, useInvitationsControllerCancelMutation as K, useUsersControllerFindAllQuery as H, useUsersControllerDeleteMutation as W } from "@open-kingdom/shared-frontend-data-access-api-client";
5
- import { showSuccessNotification as D } from "@open-kingdom/shared-frontend-data-access-notifications";
4
+ import { useInvitationsControllerInviteMutation as X, useRolesControllerFindAllQuery as F, useInvitationsControllerValidateQuery as Z, useInvitationsControllerAcceptMutation as _, useInvitationsControllerFindAllQuery as ee, useInvitationsControllerCancelMutation as te, useUserRolesControllerSetRolesMutation as ne, useAuthControllerGetProfileQuery as ie, useUsersControllerFindAllQuery as ae, useUsersControllerDeleteMutation as se, usePermissionsControllerFindAllQuery as re, useRolesControllerGetPermissionsQuery as le, useRolesControllerSetPermissionsMutation as oe, useRolesControllerDeleteMutation as de } from "@open-kingdom/shared-frontend-data-access-api-client";
5
+ import { showSuccessNotification as A } from "@open-kingdom/shared-frontend-data-access-notifications";
6
6
  import { DataGrid as U } from "@open-kingdom/shared-frontend-ui-datagrid";
7
- import { useTheme as R } from "@open-kingdom/shared-frontend-ui-theme";
8
- import { useKeyboardEvent as X } from "@react-hookz/web";
9
- import { useForm as T } from "react-hook-form";
10
- import { zodResolver as $ } from "@hookform/resolvers/zod";
11
- import { z as h } from "zod";
12
- import { formatDate as j } from "@open-kingdom/shared-poly-util-date";
13
- function M({
7
+ import { AlertDialog as ce, AlertDialogContent as ue, AlertDialogHeader as me, AlertDialogTitle as fe, AlertDialogDescription as he, AlertDialogFooter as pe, AlertDialogCancel as ge, AlertDialogAction as ve, Badge as V, Label as be, Dialog as j, DialogContent as E, DialogHeader as O, DialogTitle as G, Input as P, Button as w, Card as M, CardContent as T, DialogDescription as $ } from "@open-kingdom/shared-frontend-ui-primitives";
8
+ import { useForm as B } from "react-hook-form";
9
+ import { zodResolver as q } from "@hookform/resolvers/zod";
10
+ import { z as C } from "zod";
11
+ import { formatDate as z } from "@open-kingdom/shared-poly-util-date";
12
+ import { useDeepCompareEffect as H } from "@react-hookz/web";
13
+ function Q({
14
14
  isOpen: t,
15
- onClose: a,
16
- ariaLabelledBy: r,
17
- children: i
15
+ title: n,
16
+ message: s,
17
+ confirmLabel: m = "Confirm",
18
+ onConfirm: f,
19
+ onCancel: b,
20
+ isLoading: d = !1
18
21
  }) {
19
- X("Escape", () => {
20
- t && a();
21
- });
22
- const l = L((s) => {
23
- s?.focus();
24
- }, []);
25
- return t ? /* @__PURE__ */ e(
26
- "div",
22
+ return /* @__PURE__ */ e(
23
+ ce,
27
24
  {
28
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50",
29
- onClick: a,
30
- children: /* @__PURE__ */ e(
31
- "div",
32
- {
33
- ref: l,
34
- className: "w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-neutral-800",
35
- role: "dialog",
36
- "aria-modal": "true",
37
- "aria-labelledby": r,
38
- tabIndex: -1,
39
- onClick: (s) => s.stopPropagation(),
40
- children: i
41
- }
42
- )
25
+ open: t,
26
+ onOpenChange: (l) => !l && b(),
27
+ children: /* @__PURE__ */ i(ue, { children: [
28
+ /* @__PURE__ */ i(me, { children: [
29
+ /* @__PURE__ */ e(fe, { children: n }),
30
+ /* @__PURE__ */ e(he, { children: s })
31
+ ] }),
32
+ /* @__PURE__ */ i(pe, { children: [
33
+ /* @__PURE__ */ e(
34
+ ge,
35
+ {
36
+ "data-testid": "confirm-cancel-btn",
37
+ disabled: d,
38
+ onClick: b,
39
+ children: "Cancel"
40
+ }
41
+ ),
42
+ /* @__PURE__ */ e(
43
+ ve,
44
+ {
45
+ "data-testid": "confirm-btn",
46
+ disabled: d,
47
+ onClick: f,
48
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
49
+ children: m
50
+ }
51
+ )
52
+ ] })
53
+ ] })
43
54
  }
44
- ) : null;
45
- }
46
- const v = "w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-md bg-white dark:bg-neutral-700 text-neutral-900 dark:text-neutral-100", Z = "block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1", C = "max-w-md mx-auto mt-8 p-6 bg-white dark:bg-neutral-800 rounded-lg shadow-lg", S = "text-neutral-600 dark:text-neutral-400", A = "rounded-md px-4 py-2 text-sm font-medium transition-colors", F = `${A} bg-primary-500 text-white hover:bg-primary-600 disabled:opacity-50`, E = `${A} border border-neutral-300 text-neutral-700 hover:bg-neutral-50 dark:border-neutral-600 dark:text-neutral-300 dark:hover:bg-neutral-700`, _ = `${A} bg-error-500 text-white hover:bg-error-600 disabled:opacity-50`, ee = "rounded px-2 py-1 text-xs font-medium text-error-600 transition-colors hover:bg-error-50 disabled:cursor-not-allowed disabled:opacity-40 dark:text-error-400 dark:hover:bg-error-900/20";
47
- function B({
48
- isOpen: t,
49
- title: a,
50
- message: r,
51
- confirmLabel: i = "Confirm",
52
- onConfirm: l,
53
- onCancel: s,
54
- isLoading: c = !1
55
- }) {
56
- const u = "confirm-dialog-title";
57
- return /* @__PURE__ */ n(M, { isOpen: t, onClose: s, ariaLabelledBy: u, children: [
58
- /* @__PURE__ */ e(
59
- "h3",
60
- {
61
- id: u,
62
- className: "text-lg font-semibold text-neutral-900 dark:text-neutral-100",
63
- children: a
64
- }
65
- ),
66
- /* @__PURE__ */ e("p", { className: "mt-2 text-sm text-neutral-600 dark:text-neutral-400", children: r }),
67
- /* @__PURE__ */ n("div", { className: "mt-6 flex justify-end gap-3", children: [
68
- /* @__PURE__ */ e(
69
- "button",
70
- {
71
- type: "button",
72
- "data-testid": "confirm-cancel-btn",
73
- onClick: s,
74
- disabled: c,
75
- className: E,
76
- children: "Cancel"
77
- }
78
- ),
79
- /* @__PURE__ */ e(
80
- "button",
81
- {
82
- type: "button",
83
- "data-testid": "confirm-btn",
84
- onClick: l,
85
- disabled: c,
86
- className: _,
87
- children: i
88
- }
89
- )
90
- ] })
91
- ] });
55
+ );
92
56
  }
93
- const te = {
94
- admin: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300",
95
- user: "bg-primary-100 text-primary-800 dark:bg-primary-900/30 dark:text-primary-300",
96
- guest: "bg-neutral-100 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300"
57
+ const xe = {
58
+ admin: "secondary",
59
+ user: "default",
60
+ guest: "muted"
97
61
  };
98
- function G({ role: t }) {
62
+ function Y({ role: t }) {
99
63
  if (!t) return null;
100
- const a = t.charAt(0).toUpperCase() + t.slice(1);
101
- return /* @__PURE__ */ e(
102
- "span",
103
- {
104
- className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-medium ${te[t]}`,
105
- children: a
106
- }
107
- );
64
+ const n = t.charAt(0).toUpperCase() + t.slice(1);
65
+ return /* @__PURE__ */ e(V, { variant: xe[t] ?? "default", children: n });
108
66
  }
109
- function y({
67
+ function D({
110
68
  label: t,
111
- htmlFor: a,
112
- required: r,
113
- error: i,
114
- children: l
69
+ htmlFor: n,
70
+ required: s,
71
+ error: m,
72
+ children: f
115
73
  }) {
116
- return /* @__PURE__ */ n("div", { children: [
117
- /* @__PURE__ */ n("label", { htmlFor: a, className: Z, children: [
74
+ return /* @__PURE__ */ i("div", { children: [
75
+ /* @__PURE__ */ i(be, { htmlFor: n, className: "mb-1", children: [
118
76
  t,
119
77
  " ",
120
- r && /* @__PURE__ */ e("span", { className: "text-error-500", children: "*" })
78
+ s && /* @__PURE__ */ e("span", { className: "text-destructive", children: "*" })
121
79
  ] }),
122
- l,
123
- i && /* @__PURE__ */ e(
124
- "p",
125
- {
126
- "data-testid": "field-error",
127
- className: "mt-1 text-sm text-error-600 dark:text-error-400",
128
- children: i
129
- }
130
- )
80
+ f,
81
+ m && /* @__PURE__ */ e("p", { "data-testid": "field-error", className: "mt-1 text-sm text-destructive", children: m })
131
82
  ] });
132
83
  }
133
- const ae = h.object({
134
- email: h.string().email("Invalid email address"),
135
- role: h.enum(["guest", "user", "admin"])
84
+ const ye = C.object({
85
+ email: C.string().email("Invalid email address"),
86
+ role: C.string().min(1)
136
87
  });
137
- function re({ isOpen: t, onClose: a }) {
138
- const r = I(), [i, { isLoading: l }] = O(), {
139
- register: s,
140
- handleSubmit: c,
141
- reset: u,
142
- formState: { errors: o }
143
- } = T({
144
- resolver: $(ae),
88
+ function Ce({ isOpen: t, onClose: n }) {
89
+ const s = I(), [m, { isLoading: f }] = X(), { data: b } = F(), d = b ?? [], {
90
+ register: l,
91
+ handleSubmit: r,
92
+ reset: c,
93
+ formState: { errors: g }
94
+ } = B({
95
+ resolver: q(ye),
145
96
  defaultValues: { email: "", role: "guest" }
146
- }), d = "invite-modal-title", m = () => {
147
- u(), a();
97
+ }), a = () => {
98
+ c(), n();
148
99
  };
149
- return /* @__PURE__ */ n(
150
- M,
100
+ return /* @__PURE__ */ e(
101
+ j,
151
102
  {
152
- isOpen: t,
153
- onClose: m,
154
- ariaLabelledBy: d,
155
- children: [
156
- /* @__PURE__ */ e(
157
- "h3",
158
- {
159
- id: d,
160
- className: "text-lg font-semibold text-neutral-900 dark:text-neutral-100",
161
- children: "Invite User"
162
- }
163
- ),
164
- /* @__PURE__ */ n("form", { onSubmit: c(async (f) => {
103
+ open: t,
104
+ onOpenChange: (u) => !u && a(),
105
+ children: /* @__PURE__ */ i(E, { children: [
106
+ /* @__PURE__ */ e(O, { children: /* @__PURE__ */ e(G, { children: "Invite User" }) }),
107
+ /* @__PURE__ */ i("form", { onSubmit: r(async (u) => {
165
108
  try {
166
- await i({ inviteUserDto: f }).unwrap(), r(D("Invitation sent successfully")), u(), a();
109
+ await m({ inviteUserDto: u }).unwrap(), s(A("Invitation sent successfully")), c(), n();
167
110
  } catch {
168
111
  }
169
- }), className: "mt-4 space-y-4", children: [
112
+ }), className: "space-y-4", children: [
170
113
  /* @__PURE__ */ e(
171
- y,
114
+ D,
172
115
  {
173
116
  label: "Email",
174
117
  htmlFor: "invite-email",
175
118
  required: !0,
176
- error: o.email?.message,
119
+ error: g.email?.message,
177
120
  children: /* @__PURE__ */ e(
178
- "input",
121
+ P,
179
122
  {
180
123
  id: "invite-email",
181
124
  "data-testid": "invite-email-input",
182
125
  type: "email",
183
126
  placeholder: "user@example.com",
184
- className: v,
185
- ...s("email")
127
+ ...l("email")
186
128
  }
187
129
  )
188
130
  }
189
131
  ),
190
- /* @__PURE__ */ e(y, { label: "Role", htmlFor: "invite-role", children: /* @__PURE__ */ n(
132
+ /* @__PURE__ */ e(D, { label: "Role", htmlFor: "invite-role", children: /* @__PURE__ */ e(
191
133
  "select",
192
134
  {
193
135
  id: "invite-role",
194
136
  "data-testid": "invite-role-select",
195
- className: v,
196
- ...s("role"),
197
- children: [
198
- /* @__PURE__ */ e("option", { value: "guest", children: "Guest" }),
199
- /* @__PURE__ */ e("option", { value: "user", children: "User" }),
200
- /* @__PURE__ */ e("option", { value: "admin", children: "Admin" })
201
- ]
137
+ className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
138
+ ...l("role"),
139
+ children: d.map((u) => /* @__PURE__ */ e("option", { value: u.name, children: u.name.charAt(0).toUpperCase() + u.name.slice(1) }, u.id))
202
140
  }
203
141
  ) }),
204
- /* @__PURE__ */ n("div", { className: "flex justify-end gap-3", children: [
142
+ /* @__PURE__ */ i("div", { className: "flex justify-end gap-3", children: [
205
143
  /* @__PURE__ */ e(
206
- "button",
144
+ w,
207
145
  {
208
146
  type: "button",
147
+ variant: "secondary",
209
148
  "data-testid": "invite-cancel-btn",
210
- onClick: m,
211
- disabled: l,
212
- className: E,
149
+ onClick: a,
150
+ disabled: f,
213
151
  children: "Cancel"
214
152
  }
215
153
  ),
216
154
  /* @__PURE__ */ e(
217
- "button",
155
+ w,
218
156
  {
219
157
  type: "submit",
220
158
  "data-testid": "invite-submit-btn",
221
- disabled: l,
222
- className: F,
223
- children: l ? "Sending..." : "Send Invitation"
159
+ disabled: f,
160
+ children: f ? "Sending..." : "Send Invitation"
224
161
  }
225
162
  )
226
163
  ] })
227
164
  ] })
228
- ]
165
+ ] })
229
166
  }
230
167
  );
231
168
  }
232
- const ne = {
233
- error: "text-xl font-bold text-error-700 dark:text-error-400",
234
- success: "text-xl font-bold text-success-700 dark:text-success-400"
169
+ const Ne = {
170
+ error: "text-xl font-bold text-destructive",
171
+ success: "text-xl font-bold text-success"
235
172
  };
236
- function w({
173
+ function k({
237
174
  variant: t,
238
- title: a,
239
- message: r,
240
- children: i
175
+ title: n,
176
+ message: s,
177
+ children: m
241
178
  }) {
242
- return t === "loading" ? /* @__PURE__ */ e("div", { className: C, children: /* @__PURE__ */ e("p", { className: `text-center ${S}`, children: r }) }) : /* @__PURE__ */ n(
243
- "div",
179
+ return t === "loading" ? /* @__PURE__ */ e(M, { className: "max-w-md mx-auto mt-8", children: /* @__PURE__ */ e(T, { className: "pt-6", children: /* @__PURE__ */ e("p", { className: "text-center text-muted-foreground", children: s }) }) }) : /* @__PURE__ */ e(
180
+ M,
244
181
  {
245
- className: C,
182
+ className: "max-w-md mx-auto mt-8",
246
183
  role: t === "error" ? "alert" : void 0,
247
- children: [
248
- a && /* @__PURE__ */ e("h2", { "data-testid": "status-card-title", className: ne[t], children: a }),
249
- /* @__PURE__ */ e("p", { "data-testid": "status-card-message", className: `mt-2 ${S}`, children: r }),
250
- i
251
- ]
184
+ children: /* @__PURE__ */ i(T, { className: "pt-6", children: [
185
+ n && /* @__PURE__ */ e(
186
+ "h2",
187
+ {
188
+ "data-testid": "status-card-title",
189
+ className: Ne[t],
190
+ children: n
191
+ }
192
+ ),
193
+ /* @__PURE__ */ e(
194
+ "p",
195
+ {
196
+ "data-testid": "status-card-message",
197
+ className: "mt-2 text-muted-foreground",
198
+ children: s
199
+ }
200
+ ),
201
+ m
202
+ ] })
252
203
  }
253
204
  );
254
205
  }
255
- const ie = h.object({
256
- firstName: h.string().optional(),
257
- lastName: h.string().optional(),
258
- password: h.string().min(8, "Password must be at least 8 characters"),
259
- confirmPassword: h.string()
206
+ const we = C.object({
207
+ firstName: C.string().optional(),
208
+ lastName: C.string().optional(),
209
+ password: C.string().min(8, "Password must be at least 8 characters"),
210
+ confirmPassword: C.string()
260
211
  }).refine((t) => t.password === t.confirmPassword, {
261
212
  message: "Passwords do not match",
262
213
  path: ["confirmPassword"]
263
214
  });
264
- function Ae({ token: t, loginPath: a }) {
215
+ function Ye({ token: t, loginPath: n }) {
265
216
  const {
266
- data: r,
267
- isLoading: i,
268
- error: l
269
- } = Q({ token: t }), [s, { isLoading: c, isSuccess: u }] = Y(), {
270
- register: o,
271
- handleSubmit: d,
272
- formState: { errors: m }
273
- } = T({
274
- resolver: $(ie),
217
+ data: s,
218
+ isLoading: m,
219
+ error: f
220
+ } = Z({ token: t }), [b, { isLoading: d, isSuccess: l }] = _(), {
221
+ register: r,
222
+ handleSubmit: c,
223
+ formState: { errors: g }
224
+ } = B({
225
+ resolver: q(we),
275
226
  defaultValues: {
276
227
  firstName: "",
277
228
  lastName: "",
278
229
  password: "",
279
230
  confirmPassword: ""
280
231
  }
281
- }), b = r?.email ?? "", f = r?.role ?? "user", x = async (g) => {
232
+ }), a = s?.email ?? "", v = async (u) => {
282
233
  try {
283
- await s({
234
+ await b({
284
235
  acceptInvitationDto: {
285
236
  token: t,
286
- password: g.password,
287
- firstName: g.firstName || void 0,
288
- lastName: g.lastName || void 0
237
+ password: u.password,
238
+ firstName: u.firstName || void 0,
239
+ lastName: u.lastName || void 0
289
240
  }
290
241
  }).unwrap();
291
242
  } catch {
292
243
  }
293
244
  };
294
- return u ? /* @__PURE__ */ e(
295
- w,
245
+ return l ? /* @__PURE__ */ e(
246
+ k,
296
247
  {
297
248
  variant: "success",
298
249
  title: "Account Created",
299
250
  message: "Your account has been created successfully. You can now log in with your email and password.",
300
- children: a && /* @__PURE__ */ e(
251
+ children: n && /* @__PURE__ */ e(
301
252
  "a",
302
253
  {
303
- href: a,
254
+ href: n,
304
255
  "data-testid": "accept-login-link",
305
- className: "mt-4 inline-block text-sm font-medium text-primary-600 hover:underline dark:text-primary-400",
256
+ className: "mt-4 inline-block text-sm font-medium text-primary hover:underline",
306
257
  children: "Go to login"
307
258
  }
308
259
  )
309
260
  }
310
- ) : i ? /* @__PURE__ */ e(w, { variant: "loading", message: "Validating invitation..." }) : l ? /* @__PURE__ */ e(
311
- w,
261
+ ) : m ? /* @__PURE__ */ e(k, { variant: "loading", message: "Validating invitation..." }) : f ? /* @__PURE__ */ e(
262
+ k,
312
263
  {
313
264
  variant: "error",
314
265
  title: "Validation Failed",
315
266
  message: "Unable to validate this invitation. Please check your connection and try again."
316
267
  }
317
- ) : r?.valid ? /* @__PURE__ */ n("div", { className: C, children: [
268
+ ) : s?.valid ? /* @__PURE__ */ e(M, { className: "max-w-md mx-auto mt-8", children: /* @__PURE__ */ i(T, { className: "pt-6", children: [
318
269
  /* @__PURE__ */ e(
319
270
  "h2",
320
271
  {
321
272
  "data-testid": "accept-heading",
322
- className: "text-xl font-bold text-neutral-900 dark:text-neutral-100",
273
+ className: "text-xl font-bold text-foreground",
323
274
  children: "Accept Invitation"
324
275
  }
325
276
  ),
326
- /* @__PURE__ */ n("p", { className: `mt-1 text-sm ${S}`, children: [
327
- "You've been invited as ",
328
- /* @__PURE__ */ e("strong", { "data-testid": "accept-role", children: f }),
277
+ /* @__PURE__ */ i("p", { className: "mt-1 text-sm text-muted-foreground", children: [
278
+ "You've been invited with email",
329
279
  " ",
330
- "with email ",
331
- /* @__PURE__ */ e("strong", { "data-testid": "accept-email", children: b })
280
+ /* @__PURE__ */ e("strong", { "data-testid": "accept-email", children: a })
332
281
  ] }),
333
- /* @__PURE__ */ n("form", { onSubmit: d(x), className: "mt-4 space-y-4", children: [
334
- /* @__PURE__ */ e(y, { label: "First Name", htmlFor: "accept-firstName", children: /* @__PURE__ */ e(
335
- "input",
282
+ /* @__PURE__ */ i("form", { onSubmit: c(v), className: "mt-4 space-y-4", children: [
283
+ /* @__PURE__ */ e(D, { label: "First Name", htmlFor: "accept-firstName", children: /* @__PURE__ */ e(
284
+ P,
336
285
  {
337
286
  id: "accept-firstName",
338
287
  "data-testid": "accept-first-name-input",
339
288
  type: "text",
340
289
  placeholder: "John",
341
- className: v,
342
- ...o("firstName")
290
+ ...r("firstName")
343
291
  }
344
292
  ) }),
345
- /* @__PURE__ */ e(y, { label: "Last Name", htmlFor: "accept-lastName", children: /* @__PURE__ */ e(
346
- "input",
293
+ /* @__PURE__ */ e(D, { label: "Last Name", htmlFor: "accept-lastName", children: /* @__PURE__ */ e(
294
+ P,
347
295
  {
348
296
  id: "accept-lastName",
349
297
  "data-testid": "accept-last-name-input",
350
298
  type: "text",
351
299
  placeholder: "Doe",
352
- className: v,
353
- ...o("lastName")
300
+ ...r("lastName")
354
301
  }
355
302
  ) }),
356
303
  /* @__PURE__ */ e(
357
- y,
304
+ D,
358
305
  {
359
306
  label: "Password",
360
307
  htmlFor: "accept-password",
361
308
  required: !0,
362
- error: m.password?.message,
309
+ error: g.password?.message,
363
310
  children: /* @__PURE__ */ e(
364
- "input",
311
+ P,
365
312
  {
366
313
  id: "accept-password",
367
314
  "data-testid": "accept-password-input",
368
315
  type: "password",
369
316
  placeholder: "Min. 8 characters",
370
- className: v,
371
- ...o("password")
317
+ ...r("password")
372
318
  }
373
319
  )
374
320
  }
375
321
  ),
376
322
  /* @__PURE__ */ e(
377
- y,
323
+ D,
378
324
  {
379
325
  label: "Confirm Password",
380
326
  htmlFor: "accept-confirmPassword",
381
327
  required: !0,
382
- error: m.confirmPassword?.message,
328
+ error: g.confirmPassword?.message,
383
329
  children: /* @__PURE__ */ e(
384
- "input",
330
+ P,
385
331
  {
386
332
  id: "accept-confirmPassword",
387
333
  "data-testid": "accept-confirm-password-input",
388
334
  type: "password",
389
335
  placeholder: "Repeat password",
390
- className: v,
391
- ...o("confirmPassword")
336
+ ...r("confirmPassword")
392
337
  }
393
338
  )
394
339
  }
395
340
  ),
396
341
  /* @__PURE__ */ e(
397
- "button",
342
+ w,
398
343
  {
399
344
  type: "submit",
400
345
  "data-testid": "accept-submit-btn",
401
- disabled: c,
402
- className: `w-full ${F}`,
403
- children: c ? "Creating account..." : "Create Account"
346
+ disabled: d,
347
+ className: "w-full",
348
+ children: d ? "Creating account..." : "Create Account"
404
349
  }
405
350
  )
406
351
  ] })
407
- ] }) : /* @__PURE__ */ e(
408
- w,
352
+ ] }) }) : /* @__PURE__ */ e(
353
+ k,
409
354
  {
410
355
  variant: "error",
411
356
  title: "Invalid Invitation",
@@ -413,252 +358,547 @@ function Ae({ token: t, loginPath: a }) {
413
358
  }
414
359
  );
415
360
  }
416
- const le = {
417
- pending: "bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-300",
418
- accepted: "bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-300",
419
- expired: "bg-neutral-100 text-neutral-700 dark:bg-neutral-700 dark:text-neutral-300"
361
+ const De = {
362
+ pending: "warning",
363
+ accepted: "success",
364
+ expired: "muted"
420
365
  };
421
- function se({ status: t }) {
366
+ function Se({ status: t }) {
422
367
  if (!t) return null;
423
- const a = t.charAt(0).toUpperCase() + t.slice(1);
424
- return /* @__PURE__ */ e(
425
- "span",
426
- {
427
- className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-medium ${le[t]}`,
428
- children: a
429
- }
430
- );
368
+ const n = t.charAt(0).toUpperCase() + t.slice(1);
369
+ return /* @__PURE__ */ e(V, { variant: De[t], children: n });
431
370
  }
432
- const oe = {
371
+ const Re = {
433
372
  field: "email",
434
373
  headerName: "Email",
435
374
  flex: 2
436
- }, de = {
437
- field: "role",
438
- headerName: "Role",
439
- flex: 1,
440
- cellRenderer: (t) => t.data ? /* @__PURE__ */ e(G, { role: t.data.role }) : null
441
- }, ce = {
375
+ };
376
+ function Ie(t) {
377
+ return {
378
+ headerName: "Role",
379
+ flex: 1,
380
+ cellRenderer: (n) => n.data ? /* @__PURE__ */ e(Y, { role: t.get(n.data.roleId) ?? null }) : null
381
+ };
382
+ }
383
+ const Ae = {
442
384
  field: "status",
443
385
  headerName: "Status",
444
386
  flex: 1,
445
- cellRenderer: (t) => t.data ? /* @__PURE__ */ e(se, { status: t.data.status }) : null
446
- }, ue = {
387
+ cellRenderer: (t) => t.data ? /* @__PURE__ */ e(Se, { status: t.data.status }) : null
388
+ }, Pe = {
447
389
  headerName: "Invited",
448
390
  flex: 1,
449
- valueGetter: (t) => j(t.data?.invitedAt)
450
- }, me = {
391
+ valueGetter: (t) => z(t.data?.invitedAt)
392
+ }, ke = {
451
393
  headerName: "Expires",
452
394
  flex: 1,
453
- valueGetter: (t) => j(t.data?.tokenExpiry)
395
+ valueGetter: (t) => z(t.data?.tokenExpiry)
454
396
  };
455
- function fe(t) {
397
+ function Le(t) {
456
398
  return {
457
399
  headerName: "Actions",
458
400
  flex: 1,
459
401
  sortable: !1,
460
402
  filter: !1,
461
- cellRenderer: (a) => a.data ? /* @__PURE__ */ e(
403
+ cellRenderer: (n) => n.data ? /* @__PURE__ */ e(
462
404
  "button",
463
405
  {
464
- onClick: () => a.data && t(a.data),
465
- className: ee,
406
+ onClick: () => n.data && t(n.data),
407
+ className: "text-destructive text-xs font-medium rounded px-2 py-1 transition-colors hover:bg-destructive/10 disabled:cursor-not-allowed disabled:opacity-40",
466
408
  children: "Cancel"
467
409
  }
468
410
  ) : null
469
411
  };
470
412
  }
471
- const pe = [
472
- oe,
473
- de,
474
- ce,
475
- ue,
476
- me
477
- ];
478
- function he(t) {
479
- return [...pe, fe(t)];
413
+ function Fe(t, n) {
414
+ return [
415
+ Re,
416
+ Ie(n),
417
+ Ae,
418
+ Pe,
419
+ ke,
420
+ Le(t)
421
+ ];
480
422
  }
481
- function Fe() {
482
- const t = I(), { theme: a, mode: r } = R(), { data: i, isLoading: l, error: s, refetch: c } = J(), [u, { isLoading: o }] = K(), [d, m] = k(null), b = i ?? [];
483
- async function f() {
484
- if (!d) return;
485
- "error" in await u({ id: d.id }) || t(D("Invitation cancelled successfully")), m(null);
423
+ function Je() {
424
+ const t = I(), { data: n, isLoading: s, error: m, refetch: f } = ee(), [b, { isLoading: d }] = te(), { data: l } = F(), [r, c] = N(null), g = n ?? [], a = R(() => {
425
+ const y = l ?? [];
426
+ return new Map(y.map((o) => [o.id, o.name]));
427
+ }, [l]);
428
+ async function v() {
429
+ if (r)
430
+ try {
431
+ await b({ id: r.id }).unwrap(), t(A("Invitation cancelled successfully"));
432
+ } catch {
433
+ } finally {
434
+ c(null);
435
+ }
486
436
  }
487
- const x = P(() => he(m), []);
488
- return s ? /* @__PURE__ */ n(
437
+ const u = R(
438
+ () => Fe(c, a),
439
+ [a]
440
+ );
441
+ return m ? /* @__PURE__ */ i(
489
442
  "div",
490
443
  {
491
444
  "data-testid": "invitations-error",
492
- className: "rounded-lg bg-error-50 p-6 text-center dark:bg-error-900/20",
445
+ className: "rounded-lg bg-destructive/10 p-6 text-center",
493
446
  role: "alert",
494
447
  children: [
495
- /* @__PURE__ */ e("p", { className: "text-error-700 dark:text-error-300", children: "Failed to load invitations." }),
448
+ /* @__PURE__ */ e("p", { className: "text-destructive", children: "Failed to load invitations." }),
496
449
  /* @__PURE__ */ e(
497
450
  "button",
498
451
  {
499
452
  "data-testid": "invitations-retry-btn",
500
- onClick: c,
501
- className: "mt-2 text-sm font-medium text-primary-600 hover:underline dark:text-primary-400",
453
+ onClick: f,
454
+ className: "mt-2 text-sm font-medium text-primary hover:underline",
502
455
  children: "Try again"
503
456
  }
504
457
  )
505
458
  ]
506
459
  }
507
- ) : /* @__PURE__ */ n("div", { children: [
460
+ ) : /* @__PURE__ */ i("div", { children: [
508
461
  /* @__PURE__ */ e("div", { className: "mb-4 flex items-center justify-between", children: /* @__PURE__ */ e(
509
462
  "h2",
510
463
  {
511
464
  "data-testid": "invitations-heading",
512
- className: "text-xl font-semibold text-neutral-900 dark:text-neutral-100",
465
+ className: "text-xl font-semibold text-foreground",
513
466
  children: "Invitations"
514
467
  }
515
468
  ) }),
516
469
  /* @__PURE__ */ e(
517
470
  U,
518
471
  {
519
- rowData: b,
520
- columnDefs: x,
521
- mode: r,
522
- theme: a,
523
- loading: l
472
+ rowData: g,
473
+ columnDefs: u,
474
+ loading: s
524
475
  }
525
476
  ),
526
477
  /* @__PURE__ */ e(
527
- B,
478
+ Q,
528
479
  {
529
- isOpen: !!d,
480
+ isOpen: !!r,
530
481
  title: "Cancel Invitation",
531
- message: `Are you sure you want to cancel the invitation for ${d?.email}? This action cannot be undone.`,
532
- confirmLabel: o ? "Cancelling..." : "Cancel Invitation",
533
- onConfirm: f,
534
- onCancel: () => m(null),
535
- isLoading: o
482
+ message: `Are you sure you want to cancel the invitation for ${r?.email}? This action cannot be undone.`,
483
+ confirmLabel: d ? "Cancelling..." : "Cancel Invitation",
484
+ onConfirm: v,
485
+ onCancel: () => c(null),
486
+ isLoading: d
536
487
  }
537
488
  )
538
489
  ] });
539
490
  }
540
- function Le({ currentUserId: t }) {
541
- const a = I(), { theme: r, mode: i } = R(), { data: l, isLoading: s, error: c, refetch: u } = H(), [o, { isLoading: d }] = W(), [m, b] = k(!1), [f, x] = k(null), g = l ?? [], V = L(async () => {
542
- if (f)
491
+ function Me({
492
+ isOpen: t,
493
+ onClose: n,
494
+ user: s
495
+ }) {
496
+ const m = I(), { data: f } = F(), [b, { isLoading: d }] = ne(), l = R(
497
+ () => f ?? [],
498
+ [f]
499
+ ), [r, c] = N(null);
500
+ H(() => {
501
+ if (!l.length) return;
502
+ const a = l.find((v) => v.name === s.role);
503
+ c(a?.id ?? null);
504
+ }, [l, s.role]);
505
+ const g = async (a) => {
506
+ if (a.preventDefault(), r !== null)
507
+ try {
508
+ await b({
509
+ userId: s.id,
510
+ updateUserRolesDto: { roleIds: [r] }
511
+ }).unwrap(), m(A("Role updated successfully")), n();
512
+ } catch {
513
+ }
514
+ };
515
+ return /* @__PURE__ */ e(j, { open: t, onOpenChange: (a) => !a && n(), children: /* @__PURE__ */ i(E, { children: [
516
+ /* @__PURE__ */ i(O, { children: [
517
+ /* @__PURE__ */ e(G, { children: "Change Role" }),
518
+ /* @__PURE__ */ i($, { children: [
519
+ "Changing role for ",
520
+ /* @__PURE__ */ e("strong", { children: s.email })
521
+ ] })
522
+ ] }),
523
+ /* @__PURE__ */ i("form", { onSubmit: g, className: "space-y-4", children: [
524
+ /* @__PURE__ */ e(D, { label: "Role", htmlFor: "role-select", children: /* @__PURE__ */ e(
525
+ "select",
526
+ {
527
+ id: "role-select",
528
+ "data-testid": "role-select",
529
+ className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
530
+ value: r ?? "",
531
+ onChange: (a) => c(Number(a.target.value)),
532
+ children: l.map((a) => /* @__PURE__ */ e("option", { value: a.id, children: a.name.charAt(0).toUpperCase() + a.name.slice(1) }, a.id))
533
+ }
534
+ ) }),
535
+ /* @__PURE__ */ i("div", { className: "flex justify-end gap-3", children: [
536
+ /* @__PURE__ */ e(
537
+ w,
538
+ {
539
+ type: "button",
540
+ variant: "secondary",
541
+ "data-testid": "role-change-cancel-btn",
542
+ onClick: n,
543
+ disabled: d,
544
+ children: "Cancel"
545
+ }
546
+ ),
547
+ /* @__PURE__ */ e(
548
+ w,
549
+ {
550
+ type: "submit",
551
+ "data-testid": "role-change-submit-btn",
552
+ disabled: d || r === null,
553
+ children: d ? "Saving..." : "Save"
554
+ }
555
+ )
556
+ ] })
557
+ ] })
558
+ ] }) });
559
+ }
560
+ function L(t, n) {
561
+ const { data: s } = ie();
562
+ return s?.permissions ? s.permissions.includes(`${t}:${n}`) : !1;
563
+ }
564
+ function Ke({ currentUserId: t }) {
565
+ const n = I(), { data: s, isLoading: m, error: f, refetch: b } = ae(), [d, { isLoading: l }] = se(), r = L("users", "delete"), c = L("roles", "update"), [g, a] = N(!1), [v, u] = N(null), [y, o] = N(null), p = s ?? [], h = W(async () => {
566
+ if (v)
543
567
  try {
544
- await o({ id: f.id }).unwrap(), a(D("User deleted successfully"));
568
+ await d({ id: v.id }).unwrap(), n(A("User deleted successfully"));
545
569
  } catch {
546
570
  } finally {
547
- x(null);
571
+ u(null);
548
572
  }
549
- }, [f, o, a]), q = P(
573
+ }, [v, d, n]), J = R(
550
574
  () => [
551
575
  { field: "email", headerName: "Email", flex: 2 },
552
576
  {
553
577
  headerName: "Name",
554
578
  flex: 2,
555
- valueGetter: (p) => {
556
- const { firstName: N, lastName: z } = p.data ?? {};
557
- return [N, z].filter(Boolean).join(" ") || "—";
579
+ valueGetter: (x) => {
580
+ const { firstName: S, lastName: K } = x.data ?? {};
581
+ return [S, K].filter(Boolean).join(" ") || "—";
558
582
  }
559
583
  },
560
584
  {
561
585
  field: "role",
562
586
  headerName: "Role",
563
587
  flex: 1,
564
- cellRenderer: (p) => p.data ? /* @__PURE__ */ e(G, { role: p.data.role }) : null
588
+ cellRenderer: (x) => x.data ? /* @__PURE__ */ e(Y, { role: x.data.role }) : null
565
589
  },
566
590
  {
567
591
  headerName: "Actions",
568
592
  flex: 1,
569
593
  sortable: !1,
570
594
  filter: !1,
571
- cellRenderer: (p) => {
572
- if (!p.data) return null;
573
- const N = p.data.id === t;
574
- return /* @__PURE__ */ e(
575
- "button",
576
- {
577
- onClick: () => p.data && x(p.data),
578
- disabled: N,
579
- title: N ? "Cannot delete your own account" : "Delete user",
580
- className: "rounded px-2 py-1 text-xs font-medium text-error-600 transition-colors hover:bg-error-50 disabled:cursor-not-allowed disabled:opacity-40 dark:text-error-400 dark:hover:bg-error-900/20",
581
- children: "Delete"
582
- }
583
- );
595
+ cellRenderer: (x) => {
596
+ if (!x.data) return null;
597
+ const S = x.data.id === t;
598
+ return /* @__PURE__ */ i("div", { className: "flex gap-1", children: [
599
+ c && /* @__PURE__ */ e(
600
+ "button",
601
+ {
602
+ onClick: () => x.data && o(x.data),
603
+ disabled: S,
604
+ title: S ? "Cannot change your own role" : "Change user role",
605
+ className: "rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10 disabled:cursor-not-allowed disabled:opacity-40",
606
+ children: "Change Role"
607
+ }
608
+ ),
609
+ r && /* @__PURE__ */ e(
610
+ "button",
611
+ {
612
+ onClick: () => x.data && u(x.data),
613
+ disabled: S,
614
+ title: S ? "Cannot delete your own account" : "Delete user",
615
+ className: "rounded px-2 py-1 text-xs font-medium text-destructive transition-colors hover:bg-destructive/10 disabled:cursor-not-allowed disabled:opacity-40",
616
+ children: "Delete"
617
+ }
618
+ )
619
+ ] });
584
620
  }
585
621
  }
586
622
  ],
587
- [t]
623
+ [t, c, r]
588
624
  );
589
- return c ? /* @__PURE__ */ n(
625
+ return f ? /* @__PURE__ */ i(
590
626
  "div",
591
627
  {
592
628
  "data-testid": "users-error",
593
- className: "rounded-lg bg-error-50 p-6 text-center dark:bg-error-900/20",
629
+ className: "rounded-lg bg-destructive/10 p-6 text-center",
594
630
  role: "alert",
595
631
  children: [
596
- /* @__PURE__ */ e("p", { className: "text-error-700 dark:text-error-300", children: "Failed to load users." }),
632
+ /* @__PURE__ */ e("p", { className: "text-destructive", children: "Failed to load users." }),
597
633
  /* @__PURE__ */ e(
598
634
  "button",
599
635
  {
600
636
  "data-testid": "users-retry-btn",
601
- onClick: u,
602
- className: "mt-2 text-sm font-medium text-primary-600 hover:underline dark:text-primary-400",
637
+ onClick: b,
638
+ className: "mt-2 text-sm font-medium text-primary hover:underline",
603
639
  children: "Try again"
604
640
  }
605
641
  )
606
642
  ]
607
643
  }
608
- ) : /* @__PURE__ */ n("div", { children: [
609
- /* @__PURE__ */ n("div", { className: "mb-4 flex items-center justify-between", children: [
644
+ ) : /* @__PURE__ */ i("div", { children: [
645
+ /* @__PURE__ */ i("div", { className: "mb-4 flex items-center justify-between", children: [
610
646
  /* @__PURE__ */ e(
611
647
  "h2",
612
648
  {
613
649
  "data-testid": "users-heading",
614
- className: "text-xl font-semibold text-neutral-900 dark:text-neutral-100",
650
+ className: "text-xl font-semibold text-foreground",
615
651
  children: "Users"
616
652
  }
617
653
  ),
618
654
  /* @__PURE__ */ e(
619
- "button",
655
+ w,
620
656
  {
621
657
  "data-testid": "invite-user-btn",
622
- onClick: () => b(!0),
623
- className: F,
658
+ onClick: () => a(!0),
624
659
  children: "Invite User"
625
660
  }
626
661
  )
627
662
  ] }),
663
+ /* @__PURE__ */ e(U, { rowData: p, columnDefs: J, loading: m }),
628
664
  /* @__PURE__ */ e(
629
- U,
665
+ Ce,
630
666
  {
631
- rowData: g,
632
- columnDefs: q,
633
- mode: i,
634
- theme: r,
635
- loading: s
667
+ isOpen: g,
668
+ onClose: () => a(!1)
636
669
  }
637
670
  ),
638
671
  /* @__PURE__ */ e(
639
- re,
672
+ Me,
640
673
  {
641
- isOpen: m,
642
- onClose: () => b(!1)
674
+ isOpen: !!y,
675
+ onClose: () => o(null),
676
+ user: {
677
+ id: y?.id ?? 0,
678
+ email: y?.email ?? "",
679
+ role: y?.role ?? ""
680
+ }
643
681
  }
644
682
  ),
645
683
  /* @__PURE__ */ e(
646
- B,
684
+ Q,
647
685
  {
648
- isOpen: !!f,
686
+ isOpen: !!v,
649
687
  title: "Delete User",
650
- message: `Are you sure you want to delete ${f?.email}? This action cannot be undone.`,
688
+ message: `Are you sure you want to delete ${v?.email}? This action cannot be undone.`,
689
+ confirmLabel: l ? "Deleting..." : "Delete",
690
+ onConfirm: h,
691
+ onCancel: () => u(null),
692
+ isLoading: l
693
+ }
694
+ )
695
+ ] });
696
+ }
697
+ function Te({
698
+ isOpen: t,
699
+ onClose: n,
700
+ role: s
701
+ }) {
702
+ const m = I(), { data: f } = re(), { data: b } = le(
703
+ { id: s.id },
704
+ { skip: !t }
705
+ ), [d, { isLoading: l }] = oe(), [r, c] = N(/* @__PURE__ */ new Set()), g = f ?? [], a = b ?? [];
706
+ H(() => {
707
+ c(new Set(a.map((o) => o.id)));
708
+ }, [a]);
709
+ const v = R(() => {
710
+ const o = /* @__PURE__ */ new Map();
711
+ for (const p of g) {
712
+ const h = o.get(p.resource) ?? [];
713
+ h.push(p), o.set(p.resource, h);
714
+ }
715
+ return o;
716
+ }, [g]);
717
+ function u(o) {
718
+ c((p) => {
719
+ const h = new Set(p);
720
+ return h.has(o) ? h.delete(o) : h.add(o), h;
721
+ });
722
+ }
723
+ async function y(o) {
724
+ o.preventDefault();
725
+ try {
726
+ await d({
727
+ id: s.id,
728
+ setRolePermissionsDto: { permissionIds: [...r] }
729
+ }).unwrap(), m(A("Permissions updated successfully")), n();
730
+ } catch {
731
+ }
732
+ }
733
+ return /* @__PURE__ */ e(j, { open: t, onOpenChange: (o) => !o && n(), children: /* @__PURE__ */ i(E, { children: [
734
+ /* @__PURE__ */ i(O, { children: [
735
+ /* @__PURE__ */ e(G, { children: "Manage Permissions" }),
736
+ /* @__PURE__ */ i($, { children: [
737
+ "Editing permissions for ",
738
+ /* @__PURE__ */ e("strong", { children: s.name })
739
+ ] })
740
+ ] }),
741
+ /* @__PURE__ */ i("form", { onSubmit: y, className: "mt-4", children: [
742
+ /* @__PURE__ */ e("div", { className: "max-h-64 space-y-4 overflow-y-auto", children: [...v.entries()].map(([o, p]) => /* @__PURE__ */ i("fieldset", { children: [
743
+ /* @__PURE__ */ e("legend", { className: "text-sm font-medium capitalize text-foreground", children: o }),
744
+ /* @__PURE__ */ e("div", { className: "mt-1 space-y-1", children: p.map((h) => /* @__PURE__ */ i(
745
+ "label",
746
+ {
747
+ className: "flex items-center gap-2 text-sm text-muted-foreground",
748
+ children: [
749
+ /* @__PURE__ */ e(
750
+ "input",
751
+ {
752
+ type: "checkbox",
753
+ checked: r.has(h.id),
754
+ onChange: () => u(h.id),
755
+ className: "rounded border-input text-primary focus:ring-ring"
756
+ }
757
+ ),
758
+ /* @__PURE__ */ e("span", { children: h.action }),
759
+ h.description && /* @__PURE__ */ i("span", { className: "text-xs text-muted-foreground/70", children: [
760
+ "— ",
761
+ h.description
762
+ ] })
763
+ ]
764
+ },
765
+ h.id
766
+ )) })
767
+ ] }, o)) }),
768
+ /* @__PURE__ */ i("div", { className: "mt-4 flex justify-end gap-3", children: [
769
+ /* @__PURE__ */ e(
770
+ w,
771
+ {
772
+ type: "button",
773
+ variant: "secondary",
774
+ "data-testid": "permissions-cancel-btn",
775
+ onClick: n,
776
+ disabled: l,
777
+ children: "Cancel"
778
+ }
779
+ ),
780
+ /* @__PURE__ */ e(
781
+ w,
782
+ {
783
+ type: "submit",
784
+ "data-testid": "permissions-save-btn",
785
+ disabled: l,
786
+ children: l ? "Saving..." : "Save"
787
+ }
788
+ )
789
+ ] })
790
+ ] })
791
+ ] }) });
792
+ }
793
+ function We() {
794
+ const t = I(), { data: n, isLoading: s, error: m, refetch: f } = F(), [b, { isLoading: d }] = de(), l = L("roles", "delete"), r = L("roles", "update"), [c, g] = N(null), [a, v] = N(null), u = n ?? [];
795
+ async function y() {
796
+ if (c)
797
+ try {
798
+ await b({ id: c.id }).unwrap(), t(A("Role deleted successfully"));
799
+ } catch {
800
+ } finally {
801
+ g(null);
802
+ }
803
+ }
804
+ const o = R(
805
+ () => [
806
+ { field: "name", headerName: "Name", flex: 1 },
807
+ { field: "description", headerName: "Description", flex: 2 },
808
+ {
809
+ headerName: "Type",
810
+ flex: 1,
811
+ valueGetter: (p) => p.data?.isSystem ? "System" : "Custom"
812
+ },
813
+ {
814
+ headerName: "Actions",
815
+ flex: 1,
816
+ sortable: !1,
817
+ filter: !1,
818
+ cellRenderer: (p) => {
819
+ if (!p.data) return null;
820
+ const h = !!p.data.isSystem;
821
+ return /* @__PURE__ */ i("div", { className: "flex gap-1", children: [
822
+ r && /* @__PURE__ */ e(
823
+ "button",
824
+ {
825
+ onClick: () => p.data && v(p.data),
826
+ className: "rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10",
827
+ children: "Permissions"
828
+ }
829
+ ),
830
+ l && /* @__PURE__ */ e(
831
+ "button",
832
+ {
833
+ onClick: () => p.data && g(p.data),
834
+ disabled: h,
835
+ title: h ? "Cannot delete a system role" : "Delete role",
836
+ className: "rounded px-2 py-1 text-xs font-medium text-destructive transition-colors hover:bg-destructive/10 disabled:cursor-not-allowed disabled:opacity-40",
837
+ children: "Delete"
838
+ }
839
+ )
840
+ ] });
841
+ }
842
+ }
843
+ ],
844
+ [r, l]
845
+ );
846
+ return m ? /* @__PURE__ */ i(
847
+ "div",
848
+ {
849
+ "data-testid": "roles-error",
850
+ className: "rounded-lg bg-destructive/10 p-6 text-center",
851
+ role: "alert",
852
+ children: [
853
+ /* @__PURE__ */ e("p", { className: "text-destructive", children: "Failed to load roles." }),
854
+ /* @__PURE__ */ e(
855
+ "button",
856
+ {
857
+ "data-testid": "roles-retry-btn",
858
+ onClick: f,
859
+ className: "mt-2 text-sm font-medium text-primary hover:underline",
860
+ children: "Try again"
861
+ }
862
+ )
863
+ ]
864
+ }
865
+ ) : /* @__PURE__ */ i("div", { children: [
866
+ /* @__PURE__ */ e("div", { className: "mb-4 flex items-center justify-between", children: /* @__PURE__ */ e(
867
+ "h2",
868
+ {
869
+ "data-testid": "roles-heading",
870
+ className: "text-xl font-semibold text-foreground",
871
+ children: "Roles & Permissions"
872
+ }
873
+ ) }),
874
+ /* @__PURE__ */ e(U, { rowData: u, columnDefs: o, loading: s }),
875
+ /* @__PURE__ */ e(
876
+ Te,
877
+ {
878
+ isOpen: !!a,
879
+ onClose: () => v(null),
880
+ role: a ? { id: a.id, name: a.name } : { id: 0, name: "" }
881
+ }
882
+ ),
883
+ /* @__PURE__ */ e(
884
+ Q,
885
+ {
886
+ isOpen: !!c,
887
+ title: "Delete Role",
888
+ message: `Are you sure you want to delete the "${c?.name}" role? This action cannot be undone.`,
651
889
  confirmLabel: d ? "Deleting..." : "Delete",
652
- onConfirm: V,
653
- onCancel: () => x(null),
890
+ onConfirm: y,
891
+ onCancel: () => g(null),
654
892
  isLoading: d
655
893
  }
656
894
  )
657
895
  ] });
658
896
  }
659
897
  export {
660
- Ae as AcceptInvitation,
661
- Fe as InvitationList,
662
- w as StatusCard,
663
- Le as UserList
898
+ Ye as AcceptInvitation,
899
+ Je as InvitationList,
900
+ We as RoleList,
901
+ k as StatusCard,
902
+ Ke as UserList,
903
+ L as useHasPermission
664
904
  };