@strapi-community/plugin-better-auth-dashboard 1.0.0-beta.1 → 1.0.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 (46) hide show
  1. package/README.md +0 -3
  2. package/dist/admin/Avatar-CdT7eBCh.js +39 -0
  3. package/dist/admin/Avatar-zlMTAZh3.mjs +40 -0
  4. package/dist/admin/{Root-2FMy-qQg.mjs → Root-CgVBJuoM.mjs} +12 -4
  5. package/dist/admin/{Root-DDi0mtve.js → Root-CsiOnzlB.js} +12 -4
  6. package/dist/admin/cs-C_jSgeON.js +416 -0
  7. package/dist/admin/cs-wtnknvBC.mjs +416 -0
  8. package/dist/admin/de-Cg5HyoCY.js +416 -0
  9. package/dist/admin/de-DnnVdUxk.mjs +416 -0
  10. package/dist/admin/en-B3utEgMT.mjs +416 -0
  11. package/dist/admin/en-Cz1IJEvp.js +416 -0
  12. package/dist/admin/es-CGA0Tslt.js +416 -0
  13. package/dist/admin/es-RoCEriJL.mjs +416 -0
  14. package/dist/admin/fr-Ca74t21t.js +416 -0
  15. package/dist/admin/fr-CtmuSfiQ.mjs +416 -0
  16. package/dist/admin/{index-BHfYu0uA.js → index-BCBu-0nr.js} +65 -10
  17. package/dist/admin/{index-D9CiI8Yr.mjs → index-BI2WIsds.mjs} +73 -18
  18. package/dist/admin/index-CPSoAbZt.js +2321 -0
  19. package/dist/admin/index-D9ttdrT3.js +2716 -0
  20. package/dist/admin/index-DQdn5s8q.js +1184 -0
  21. package/dist/admin/index-DvfwvCj0.mjs +1182 -0
  22. package/dist/admin/index-VyHXoQ6p.mjs +2321 -0
  23. package/dist/admin/index-gFqHlMLP.mjs +2716 -0
  24. package/dist/admin/index.js +1 -1
  25. package/dist/admin/index.mjs +1 -1
  26. package/dist/admin/it-BAB9hDdp.mjs +416 -0
  27. package/dist/admin/it-dYT2_ERV.js +416 -0
  28. package/dist/admin/nl-CVqSbUU9.js +416 -0
  29. package/dist/admin/nl-CYv26PHv.mjs +416 -0
  30. package/dist/admin/pl-Bc0cS2mD.mjs +416 -0
  31. package/dist/admin/pl-HYF4BxRv.js +416 -0
  32. package/dist/admin/pt-DDUGyLtb.js +416 -0
  33. package/dist/admin/pt-DZyyK3EG.mjs +416 -0
  34. package/dist/admin/{useModelSchema-CqOTI7P1.js → useModelSchema-BogQah8f.js} +309 -287
  35. package/dist/admin/{useModelSchema-B2tmzlwQ.mjs → useModelSchema-C6bxNjW4.mjs} +320 -298
  36. package/dist/server/index.js +15 -15
  37. package/dist/server/index.mjs +15 -15
  38. package/package.json +4 -4
  39. package/dist/admin/Avatar-Cvk89aMx.js +0 -67
  40. package/dist/admin/Avatar-DNczJU0i.mjs +0 -66
  41. package/dist/admin/index-BQi_BUi6.mjs +0 -1968
  42. package/dist/admin/index-BV-4M_Fp.js +0 -1970
  43. package/dist/admin/index-CANDPUGD.mjs +0 -1215
  44. package/dist/admin/index-CqR8WpOa.js +0 -1217
  45. package/dist/admin/index-D6ktNZcj.js +0 -1685
  46. package/dist/admin/index-DszX3ibA.mjs +0 -1683
@@ -0,0 +1,2716 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const designSystem = require("@strapi/design-system");
5
+ const icons = require("@strapi/icons");
6
+ const admin = require("@strapi/strapi/admin");
7
+ const react = require("react");
8
+ const reactIntl = require("react-intl");
9
+ const reactQuery = require("react-query");
10
+ const index = require("./index-BCBu-0nr.js");
11
+ const Avatar = require("./Avatar-CdT7eBCh.js");
12
+ const useModelSchema = require("./useModelSchema-BogQah8f.js");
13
+ function useUsers(options = {}) {
14
+ const { limit = 25, offset = 0, search } = options;
15
+ const { formatMessage } = reactIntl.useIntl();
16
+ return reactQuery.useQuery({
17
+ queryKey: ["dash-users", limit, offset, search],
18
+ queryFn: async () => {
19
+ const whereClause = search ? JSON.stringify([
20
+ {
21
+ field: "email",
22
+ operator: "contains",
23
+ value: search,
24
+ connector: "OR"
25
+ },
26
+ {
27
+ field: "name",
28
+ operator: "contains",
29
+ value: search,
30
+ connector: "OR"
31
+ }
32
+ ]) : void 0;
33
+ const result = await index.client.dash.listUsers({
34
+ query: {
35
+ limit,
36
+ offset,
37
+ sortBy: "createdAt",
38
+ sortOrder: "desc",
39
+ ...whereClause ? { where: whereClause } : {}
40
+ }
41
+ });
42
+ if (result.error) {
43
+ throw new Error(
44
+ result.error.message ?? formatMessage({
45
+ id: index.getTranslation("common.errors.loadUsers"),
46
+ defaultMessage: "Failed to load users"
47
+ })
48
+ );
49
+ }
50
+ return result.data;
51
+ },
52
+ keepPreviousData: true
53
+ });
54
+ }
55
+ function CreateUserDialog({ onClose }) {
56
+ const { formatMessage } = reactIntl.useIntl();
57
+ const qc = reactQuery.useQueryClient();
58
+ const { toggleNotification } = admin.useNotification();
59
+ const [name, setName] = react.useState("");
60
+ const [email, setEmail] = react.useState("");
61
+ const [image, setImage] = react.useState("");
62
+ const [password, setPassword] = react.useState("");
63
+ const [generatePassword, setGeneratePassword] = react.useState(false);
64
+ const [emailVerified, setEmailVerified] = react.useState(false);
65
+ const [sendVerificationEmail, setSendVerificationEmail] = react.useState(false);
66
+ const createMutation = reactQuery.useMutation({
67
+ mutationFn: async () => {
68
+ const result = await index.client.dash.createUser({
69
+ name,
70
+ email,
71
+ ...image ? { image } : {},
72
+ ...generatePassword ? { generatePassword: true } : password ? { password } : {},
73
+ emailVerified,
74
+ sendVerificationEmail
75
+ });
76
+ if (result.error)
77
+ throw new Error(
78
+ result.error.message ?? formatMessage({
79
+ id: index.getTranslation("users.create.errors.createFailed"),
80
+ defaultMessage: "Create failed"
81
+ })
82
+ );
83
+ return result.data;
84
+ },
85
+ onSuccess: () => {
86
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
87
+ qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
88
+ toggleNotification({
89
+ type: "success",
90
+ message: formatMessage({
91
+ id: index.getTranslation("users.create.notifications.success"),
92
+ defaultMessage: "User created successfully"
93
+ })
94
+ });
95
+ onClose();
96
+ },
97
+ onError: (err) => {
98
+ toggleNotification({
99
+ type: "danger",
100
+ message: err.message ?? formatMessage({
101
+ id: index.getTranslation("users.create.notifications.error"),
102
+ defaultMessage: "Failed to create user"
103
+ })
104
+ });
105
+ }
106
+ });
107
+ const footer = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
108
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: onClose, children: formatMessage({
109
+ id: index.getTranslation("users.create.cancel"),
110
+ defaultMessage: "Cancel"
111
+ }) }),
112
+ /* @__PURE__ */ jsxRuntime.jsx(
113
+ designSystem.Button,
114
+ {
115
+ loading: createMutation.isLoading,
116
+ onClick: () => createMutation.mutate(),
117
+ disabled: !name || !email,
118
+ "data-testid": "create-user-submit",
119
+ children: formatMessage({
120
+ id: index.getTranslation("users.create.submit"),
121
+ defaultMessage: "Create user"
122
+ })
123
+ }
124
+ )
125
+ ] });
126
+ return /* @__PURE__ */ jsxRuntime.jsx(
127
+ useModelSchema.SheetDialog,
128
+ {
129
+ title: formatMessage({
130
+ id: index.getTranslation("users.create.title"),
131
+ defaultMessage: "Create user"
132
+ }),
133
+ actionButtons: footer,
134
+ onClose,
135
+ "data-testid": "create-user-drawer",
136
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, children: [
137
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
138
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
139
+ id: index.getTranslation("users.create.sections.personal"),
140
+ defaultMessage: "Personal information"
141
+ }) }),
142
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, required: true, children: [
143
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
144
+ id: index.getTranslation("users.create.fields.name.label"),
145
+ defaultMessage: "Full name"
146
+ }) }),
147
+ /* @__PURE__ */ jsxRuntime.jsx(
148
+ designSystem.TextInput,
149
+ {
150
+ name: "name",
151
+ value: name,
152
+ onChange: (e) => setName(e.target.value),
153
+ placeholder: formatMessage({
154
+ id: index.getTranslation("users.create.fields.name.placeholder"),
155
+ defaultMessage: "Jane Smith"
156
+ }),
157
+ "data-testid": "new-user-name"
158
+ }
159
+ )
160
+ ] }),
161
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, required: true, children: [
162
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
163
+ id: index.getTranslation("users.create.fields.email.label"),
164
+ defaultMessage: "Email address"
165
+ }) }),
166
+ /* @__PURE__ */ jsxRuntime.jsx(
167
+ designSystem.TextInput,
168
+ {
169
+ name: "email",
170
+ type: "email",
171
+ value: email,
172
+ onChange: (e) => setEmail(e.target.value),
173
+ placeholder: formatMessage({
174
+ id: index.getTranslation("users.create.fields.email.placeholder"),
175
+ defaultMessage: "jane@example.com"
176
+ }),
177
+ "data-testid": "new-user-email"
178
+ }
179
+ )
180
+ ] }),
181
+ /* @__PURE__ */ jsxRuntime.jsx(
182
+ useModelSchema.MediaPickerField,
183
+ {
184
+ label: formatMessage({
185
+ id: index.getTranslation("users.create.fields.image.label"),
186
+ defaultMessage: "Avatar URL"
187
+ }),
188
+ name: "image",
189
+ value: image,
190
+ onChange: setImage,
191
+ placeholder: formatMessage({
192
+ id: index.getTranslation("users.create.fields.image.placeholder"),
193
+ defaultMessage: "https://example.com/avatar.png"
194
+ }),
195
+ hint: formatMessage({
196
+ id: index.getTranslation("users.create.fields.image.hint"),
197
+ defaultMessage: "Optional — publicly accessible image URL"
198
+ })
199
+ }
200
+ )
201
+ ] }),
202
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
204
+ id: index.getTranslation("users.create.sections.password"),
205
+ defaultMessage: "Password"
206
+ }) }),
207
+ /* @__PURE__ */ jsxRuntime.jsx(
208
+ designSystem.Checkbox,
209
+ {
210
+ name: "generatePassword",
211
+ checked: generatePassword,
212
+ onCheckedChange: (checked) => {
213
+ setGeneratePassword(checked);
214
+ if (checked) setPassword("");
215
+ },
216
+ children: formatMessage({
217
+ id: index.getTranslation("users.create.fields.generatePassword"),
218
+ defaultMessage: "Generate a random password"
219
+ })
220
+ }
221
+ ),
222
+ !generatePassword && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, children: [
223
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
224
+ id: index.getTranslation("users.create.fields.password.label"),
225
+ defaultMessage: "Password"
226
+ }) }),
227
+ /* @__PURE__ */ jsxRuntime.jsx(
228
+ designSystem.TextInput,
229
+ {
230
+ name: "password",
231
+ type: "password",
232
+ value: password,
233
+ onChange: (e) => setPassword(e.target.value),
234
+ placeholder: formatMessage({
235
+ id: index.getTranslation(
236
+ "users.create.fields.password.placeholder"
237
+ ),
238
+ defaultMessage: "Leave blank for a passwordless account"
239
+ })
240
+ }
241
+ )
242
+ ] })
243
+ ] }),
244
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
245
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
246
+ id: index.getTranslation("users.create.sections.emailVerification"),
247
+ defaultMessage: "Email verification"
248
+ }) }),
249
+ /* @__PURE__ */ jsxRuntime.jsx(
250
+ designSystem.Checkbox,
251
+ {
252
+ name: "emailVerified",
253
+ checked: emailVerified,
254
+ onCheckedChange: (checked) => {
255
+ setEmailVerified(checked);
256
+ if (checked) setSendVerificationEmail(false);
257
+ },
258
+ children: formatMessage({
259
+ id: index.getTranslation("users.create.fields.emailVerified"),
260
+ defaultMessage: "Mark email as already verified"
261
+ })
262
+ }
263
+ ),
264
+ !emailVerified && /* @__PURE__ */ jsxRuntime.jsx(
265
+ designSystem.Checkbox,
266
+ {
267
+ name: "sendVerificationEmail",
268
+ checked: sendVerificationEmail,
269
+ onCheckedChange: (checked) => setSendVerificationEmail(checked),
270
+ children: formatMessage({
271
+ id: index.getTranslation("users.create.fields.sendVerificationEmail"),
272
+ defaultMessage: "Send a verification email to the user"
273
+ })
274
+ }
275
+ ),
276
+ emailVerified && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "success600", children: formatMessage({
277
+ id: index.getTranslation("users.create.fields.emailVerifiedHint"),
278
+ defaultMessage: "The user will be able to sign in immediately without verifying their email."
279
+ }) }) })
280
+ ] })
281
+ ] })
282
+ }
283
+ );
284
+ }
285
+ function usePluginSettings() {
286
+ const { get } = admin.useFetchClient();
287
+ return reactQuery.useQuery({
288
+ queryKey: ["dash-plugin-settings"],
289
+ queryFn: async () => {
290
+ const { data } = await get(
291
+ "/better-auth-dashboard/settings"
292
+ );
293
+ return data;
294
+ },
295
+ staleTime: 5 * 60 * 1e3
296
+ });
297
+ }
298
+ function ReadOnlyField({ label, value }) {
299
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
300
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: label }),
301
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TextInput, { readOnly: true, value })
302
+ ] });
303
+ }
304
+ function BackupCodes({ codes }) {
305
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 2, gridCols: 2, children: codes.map((code) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
306
+ designSystem.Box,
307
+ {
308
+ width: "100%",
309
+ background: "neutral0",
310
+ borderColor: "neutral200",
311
+ borderStyle: "solid",
312
+ borderWidth: "1px",
313
+ hasRadius: true,
314
+ padding: 2,
315
+ textAlign: "center",
316
+ children: /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.Code, { variant: "omega", fontWeight: "semiBold", textColor: "neutral800", children: code })
317
+ }
318
+ ) }, code)) });
319
+ }
320
+ const STANDARD_FIELDS = /* @__PURE__ */ new Set([
321
+ "id",
322
+ "name",
323
+ "email",
324
+ "emailVerified",
325
+ "image",
326
+ "banned",
327
+ "banReason",
328
+ "banExpires",
329
+ "createdAt",
330
+ "updatedAt"
331
+ ]);
332
+ function UserDetailDrawer({
333
+ userId,
334
+ banEnabled,
335
+ emailVerificationEnabled,
336
+ twoFactorEnabled,
337
+ onClose
338
+ }) {
339
+ const { formatMessage } = reactIntl.useIntl();
340
+ const qc = reactQuery.useQueryClient();
341
+ const { toggleNotification } = admin.useNotification();
342
+ const { get, put } = admin.useFetchClient();
343
+ const schemaQuery = useModelSchema.useModelSchema("user");
344
+ const settingsQuery = usePluginSettings();
345
+ const emailCallbackUrl = settingsQuery.data?.email_callback_url ?? null;
346
+ const userQuery = reactQuery.useQuery({
347
+ queryKey: ["dash-user", userId],
348
+ queryFn: async () => {
349
+ const result = await index.client.dash.user(
350
+ {},
351
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
352
+ );
353
+ if (result.error)
354
+ throw new Error(
355
+ result.error.message ?? formatMessage({
356
+ id: index.getTranslation("userDetail.common.loadUserFailed"),
357
+ defaultMessage: "Failed to load user"
358
+ })
359
+ );
360
+ return result.data;
361
+ }
362
+ });
363
+ const orgsQuery = reactQuery.useQuery({
364
+ queryKey: ["dash-user-orgs", userId],
365
+ queryFn: async () => {
366
+ const result = await index.client.dash.userOrganizations(
367
+ {},
368
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
369
+ );
370
+ if (result.error)
371
+ throw new Error(
372
+ result.error.message ?? formatMessage({
373
+ id: index.getTranslation("userDetail.common.failed"),
374
+ defaultMessage: "Failed"
375
+ })
376
+ );
377
+ return result.data?.organizations ?? [];
378
+ }
379
+ });
380
+ const sessionsQuery = reactQuery.useQuery({
381
+ queryKey: ["dash-user-sessions", userId],
382
+ queryFn: async () => {
383
+ const { data } = await get(
384
+ `/better-auth-dashboard/db?uid=plugin::better-auth.session&filters[userId][$eq]=${userId}&sort[0]=createdAt:desc&pagination[pageSize]=50`
385
+ );
386
+ return data.results ?? [];
387
+ }
388
+ });
389
+ const strapiUserQuery = reactQuery.useQuery({
390
+ queryKey: ["dash-strapi-user", userId],
391
+ enabled: !!schemaQuery.data,
392
+ queryFn: async () => {
393
+ const relationFields = Object.entries(schemaQuery.data).filter(([, attr]) => attr.type === "relation").map(([fieldName]) => fieldName);
394
+ const populateParam = relationFields.length > 0 ? `&populate=${encodeURIComponent(relationFields.join(","))}` : "";
395
+ const { data } = await get(
396
+ `/better-auth-dashboard/db?uid=plugin::better-auth.user&filters[id][$eq]=${userId}&pagination[pageSize]=1${populateParam}`
397
+ );
398
+ return data.results?.[0] ?? null;
399
+ }
400
+ });
401
+ const [activeTab, setActiveTab] = react.useState("profile");
402
+ const [editName, setEditName] = react.useState(void 0);
403
+ const [editEmail, setEditEmail] = react.useState(void 0);
404
+ const [editEmailVerified, setEditEmailVerified] = react.useState(void 0);
405
+ const [editImage, setEditImage] = react.useState(void 0);
406
+ const [editExtra, setEditExtra] = react.useState({});
407
+ const [newPassword, setNewPassword] = react.useState("");
408
+ const [banReason, setBanReason] = react.useState("");
409
+ const [banExpiresDays, setBanExpiresDays] = react.useState("");
410
+ const [confirmRevokeAll, setConfirmRevokeAll] = react.useState(false);
411
+ const [confirmRevokeSessionId, setConfirmRevokeSessionId] = react.useState(null);
412
+ const [confirmBan, setConfirmBan] = react.useState(false);
413
+ const [confirmUnban, setConfirmUnban] = react.useState(false);
414
+ const [confirmUnlinkAccountId, setConfirmUnlinkAccountId] = react.useState(null);
415
+ const [confirmDisable2FA, setConfirmDisable2FA] = react.useState(false);
416
+ const [showTotpModal, setShowTotpModal] = react.useState(false);
417
+ const [showBackupCodesModal, setShowBackupCodesModal] = react.useState(false);
418
+ const [confirmRegenerateBackupCodes, setConfirmRegenerateBackupCodes] = react.useState(false);
419
+ const [twoFactorData, setTwoFactorData] = react.useState(null);
420
+ const [totpUriData, setTotpUriData] = react.useState(null);
421
+ const [backupCodesData, setBackupCodesData] = react.useState(null);
422
+ const user = userQuery.data;
423
+ const displayName = editName ?? user?.name ?? "";
424
+ const displayEmail = editEmail ?? user?.email ?? "";
425
+ const displayEmailVerified = editEmailVerified ?? user?.emailVerified ?? false;
426
+ const displayImage = editImage ?? user?.image ?? "";
427
+ const handleExtraChange = (name, value) => {
428
+ setEditExtra((prev) => ({ ...prev, [name]: value }));
429
+ };
430
+ const extraData = {
431
+ ...strapiUserQuery.data ?? {},
432
+ ...editExtra
433
+ };
434
+ const updateMutation = reactQuery.useMutation({
435
+ mutationFn: async () => {
436
+ const body = {};
437
+ if (editName !== void 0) body.name = editName;
438
+ if (editEmail !== void 0) body.email = editEmail;
439
+ if (editEmailVerified !== void 0)
440
+ body.emailVerified = editEmailVerified;
441
+ if (editImage !== void 0) body.image = editImage;
442
+ const documentId = strapiUserQuery.data?.documentId;
443
+ if (!documentId)
444
+ throw new Error(
445
+ formatMessage({
446
+ id: index.getTranslation("userDetail.errors.documentIdMissing"),
447
+ defaultMessage: "Could not resolve documentId for user"
448
+ })
449
+ );
450
+ const updateUser = await index.client.dash.updateUser(
451
+ { userId, ...body },
452
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
453
+ );
454
+ if (!updateUser.data)
455
+ throw new Error(
456
+ formatMessage({
457
+ id: index.getTranslation("userDetail.common.updateFailed"),
458
+ defaultMessage: "Update failed"
459
+ })
460
+ );
461
+ if (Object.keys(editExtra).length > 0) {
462
+ await put(
463
+ `/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.user`,
464
+ editExtra
465
+ );
466
+ }
467
+ },
468
+ onSuccess: async () => {
469
+ await qc.invalidateQueries({ queryKey: ["dash-strapi-user", userId] });
470
+ qc.invalidateQueries({ queryKey: ["dash-user", userId] });
471
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
472
+ setEditName(void 0);
473
+ setEditEmail(void 0);
474
+ setEditEmailVerified(void 0);
475
+ setEditImage(void 0);
476
+ setEditExtra({});
477
+ toggleNotification({
478
+ type: "success",
479
+ message: formatMessage({
480
+ id: index.getTranslation("userDetail.notifications.updated"),
481
+ defaultMessage: "User updated successfully"
482
+ })
483
+ });
484
+ onClose();
485
+ },
486
+ onError: (err) => {
487
+ toggleNotification({
488
+ type: "danger",
489
+ message: err.message ?? formatMessage({
490
+ id: index.getTranslation("userDetail.common.updateFailed"),
491
+ defaultMessage: "Update failed"
492
+ })
493
+ });
494
+ }
495
+ });
496
+ const revokeSessionMutation = reactQuery.useMutation({
497
+ mutationFn: async (sessionId) => {
498
+ const result = await index.client.dash.sessions.revoke(
499
+ {},
500
+ useModelSchema.withContext({ sessionId, userId }, index.getAuthHeaders())
501
+ );
502
+ if (result.error)
503
+ throw new Error(
504
+ result.error.message ?? formatMessage({
505
+ id: index.getTranslation("userDetail.common.revokeFailed"),
506
+ defaultMessage: "Revoke failed"
507
+ })
508
+ );
509
+ },
510
+ onSuccess: () => {
511
+ setConfirmRevokeSessionId(null);
512
+ qc.invalidateQueries({ queryKey: ["dash-user-sessions", userId] });
513
+ toggleNotification({
514
+ type: "success",
515
+ message: formatMessage({
516
+ id: index.getTranslation("userDetail.notifications.sessionRevoked"),
517
+ defaultMessage: "Session revoked"
518
+ })
519
+ });
520
+ },
521
+ onError: (err) => {
522
+ toggleNotification({
523
+ type: "danger",
524
+ message: err.message ?? formatMessage({
525
+ id: index.getTranslation("userDetail.notifications.revokeSessionError"),
526
+ defaultMessage: "Failed to revoke session"
527
+ })
528
+ });
529
+ }
530
+ });
531
+ const passwordMutation = reactQuery.useMutation({
532
+ mutationFn: async () => {
533
+ const result = await index.client.dash.setPassword(
534
+ { password: newPassword },
535
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
536
+ );
537
+ if (result.error)
538
+ throw new Error(
539
+ result.error.message ?? formatMessage({
540
+ id: index.getTranslation("userDetail.common.passwordUpdateFailed"),
541
+ defaultMessage: "Password update failed"
542
+ })
543
+ );
544
+ return result.data;
545
+ },
546
+ onSuccess: () => {
547
+ setNewPassword("");
548
+ toggleNotification({
549
+ type: "success",
550
+ message: formatMessage({
551
+ id: index.getTranslation("userDetail.notifications.passwordUpdated"),
552
+ defaultMessage: "Password updated successfully"
553
+ })
554
+ });
555
+ },
556
+ onError: (err) => {
557
+ toggleNotification({
558
+ type: "danger",
559
+ message: err.message ?? formatMessage({
560
+ id: index.getTranslation("userDetail.common.passwordUpdateFailed"),
561
+ defaultMessage: "Password update failed"
562
+ })
563
+ });
564
+ }
565
+ });
566
+ const banMutation = reactQuery.useMutation({
567
+ mutationFn: async () => {
568
+ const body = {};
569
+ if (banReason) body.banReason = banReason;
570
+ if (banExpiresDays) {
571
+ const days = parseInt(banExpiresDays, 10);
572
+ if (!Number.isNaN(days)) body.banExpires = Date.now() + days * 864e5;
573
+ }
574
+ const result = await index.client.dash.banUser(
575
+ body,
576
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
577
+ );
578
+ if (result.error)
579
+ throw new Error(
580
+ result.error.message ?? formatMessage({
581
+ id: index.getTranslation("userDetail.common.banFailed"),
582
+ defaultMessage: "Ban failed"
583
+ })
584
+ );
585
+ return result.data;
586
+ },
587
+ onSuccess: () => {
588
+ setConfirmBan(false);
589
+ qc.invalidateQueries({ queryKey: ["dash-user", userId] });
590
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
591
+ setBanReason("");
592
+ setBanExpiresDays("");
593
+ toggleNotification({
594
+ type: "success",
595
+ message: formatMessage({
596
+ id: index.getTranslation("userDetail.notifications.banned"),
597
+ defaultMessage: "User has been banned"
598
+ })
599
+ });
600
+ },
601
+ onError: (err) => {
602
+ toggleNotification({
603
+ type: "danger",
604
+ message: err.message ?? formatMessage({
605
+ id: index.getTranslation("userDetail.common.banFailed"),
606
+ defaultMessage: "Ban failed"
607
+ })
608
+ });
609
+ }
610
+ });
611
+ const unbanMutation = reactQuery.useMutation({
612
+ mutationFn: async () => {
613
+ const result = await index.client.dash.unbanUser(
614
+ {},
615
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
616
+ );
617
+ if (result.error)
618
+ throw new Error(
619
+ result.error.message ?? formatMessage({
620
+ id: index.getTranslation("userDetail.common.unbanFailed"),
621
+ defaultMessage: "Unban failed"
622
+ })
623
+ );
624
+ return result.data;
625
+ },
626
+ onSuccess: () => {
627
+ setConfirmUnban(false);
628
+ qc.invalidateQueries({ queryKey: ["dash-user", userId] });
629
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
630
+ toggleNotification({
631
+ type: "success",
632
+ message: formatMessage({
633
+ id: index.getTranslation("userDetail.notifications.unbanned"),
634
+ defaultMessage: "Ban has been lifted"
635
+ })
636
+ });
637
+ },
638
+ onError: (err) => {
639
+ toggleNotification({
640
+ type: "danger",
641
+ message: err.message ?? formatMessage({
642
+ id: index.getTranslation("userDetail.common.unbanFailed"),
643
+ defaultMessage: "Unban failed"
644
+ })
645
+ });
646
+ }
647
+ });
648
+ const unlinkAccountMutation = reactQuery.useMutation({
649
+ mutationFn: async (providerId) => {
650
+ const result = await index.client.dash.unlinkAccount(
651
+ { providerId },
652
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
653
+ );
654
+ if (result.error)
655
+ throw new Error(
656
+ result.error.message ?? formatMessage({
657
+ id: index.getTranslation("userDetail.common.unlinkFailed"),
658
+ defaultMessage: "Failed to unlink account"
659
+ })
660
+ );
661
+ },
662
+ onSuccess: () => {
663
+ setConfirmUnlinkAccountId(null);
664
+ qc.invalidateQueries({ queryKey: ["dash-user", userId] });
665
+ toggleNotification({
666
+ type: "success",
667
+ message: formatMessage({
668
+ id: index.getTranslation("userDetail.notifications.accountUnlinked"),
669
+ defaultMessage: "Account unlinked"
670
+ })
671
+ });
672
+ },
673
+ onError: (err) => {
674
+ toggleNotification({
675
+ type: "danger",
676
+ message: err.message ?? formatMessage({
677
+ id: index.getTranslation("userDetail.common.unlinkFailed"),
678
+ defaultMessage: "Failed to unlink account"
679
+ })
680
+ });
681
+ }
682
+ });
683
+ const revokeAllMutation = reactQuery.useMutation({
684
+ mutationFn: async () => {
685
+ const result = await index.client.dash.sessions.revokeAll(
686
+ { userId },
687
+ useModelSchema.withContext({}, index.getAuthHeaders())
688
+ );
689
+ if (result.error)
690
+ throw new Error(
691
+ result.error.message ?? formatMessage({
692
+ id: index.getTranslation("userDetail.common.revokeFailed"),
693
+ defaultMessage: "Revoke failed"
694
+ })
695
+ );
696
+ return result.data;
697
+ },
698
+ onSuccess: () => {
699
+ setConfirmRevokeAll(false);
700
+ toggleNotification({
701
+ type: "success",
702
+ message: formatMessage({
703
+ id: index.getTranslation("userDetail.notifications.allSessionsRevoked"),
704
+ defaultMessage: "All sessions revoked"
705
+ })
706
+ });
707
+ },
708
+ onError: (err) => {
709
+ toggleNotification({
710
+ type: "danger",
711
+ message: err.message ?? formatMessage({
712
+ id: index.getTranslation("userDetail.notifications.revokeAllError"),
713
+ defaultMessage: "Failed to revoke sessions"
714
+ })
715
+ });
716
+ }
717
+ });
718
+ const sendVerificationMutation = reactQuery.useMutation({
719
+ mutationFn: async () => {
720
+ if (!emailCallbackUrl) {
721
+ throw new Error(
722
+ formatMessage({
723
+ id: index.getTranslation("userDetail.common.callbackUrlMissing"),
724
+ defaultMessage: "Set `email_callback_url` in the better-auth-dashboard plugin config to enable this action."
725
+ })
726
+ );
727
+ }
728
+ const result = await index.client.dash.sendVerificationEmail(
729
+ { callbackUrl: emailCallbackUrl },
730
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
731
+ );
732
+ if (result.error)
733
+ throw new Error(
734
+ result.error.message ?? formatMessage({
735
+ id: index.getTranslation("userDetail.common.failed"),
736
+ defaultMessage: "Failed"
737
+ })
738
+ );
739
+ return result.data;
740
+ },
741
+ onSuccess: () => {
742
+ toggleNotification({
743
+ type: "success",
744
+ message: formatMessage({
745
+ id: index.getTranslation("userDetail.notifications.verificationSent"),
746
+ defaultMessage: "Verification email sent"
747
+ })
748
+ });
749
+ },
750
+ onError: (err) => {
751
+ toggleNotification({
752
+ type: "danger",
753
+ message: err.message ?? formatMessage({
754
+ id: index.getTranslation("userDetail.common.sendEmailFailed"),
755
+ defaultMessage: "Failed to send email"
756
+ })
757
+ });
758
+ }
759
+ });
760
+ const sendResetPasswordMutation = reactQuery.useMutation({
761
+ mutationFn: async () => {
762
+ if (!emailCallbackUrl) {
763
+ throw new Error(
764
+ formatMessage({
765
+ id: index.getTranslation("userDetail.common.callbackUrlMissing"),
766
+ defaultMessage: "Set `email_callback_url` in the better-auth-dashboard plugin config to enable this action."
767
+ })
768
+ );
769
+ }
770
+ const result = await index.client.dash.sendResetPasswordEmail(
771
+ { callbackUrl: emailCallbackUrl },
772
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
773
+ );
774
+ if (result.error)
775
+ throw new Error(
776
+ result.error.message ?? formatMessage({
777
+ id: index.getTranslation("userDetail.common.failed"),
778
+ defaultMessage: "Failed"
779
+ })
780
+ );
781
+ return result.data;
782
+ },
783
+ onSuccess: () => {
784
+ toggleNotification({
785
+ type: "success",
786
+ message: formatMessage({
787
+ id: index.getTranslation("userDetail.notifications.resetPasswordSent"),
788
+ defaultMessage: "Password reset email sent"
789
+ })
790
+ });
791
+ },
792
+ onError: (err) => {
793
+ toggleNotification({
794
+ type: "danger",
795
+ message: err.message ?? formatMessage({
796
+ id: index.getTranslation("userDetail.common.sendEmailFailed"),
797
+ defaultMessage: "Failed to send email"
798
+ })
799
+ });
800
+ }
801
+ });
802
+ const enableTwoFactorMutation = reactQuery.useMutation({
803
+ mutationFn: async () => {
804
+ const result = await index.client.dash.enableTwoFactor(
805
+ {},
806
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
807
+ );
808
+ if (result.error)
809
+ throw new Error(
810
+ result.error.message ?? formatMessage({
811
+ id: index.getTranslation("userDetail.common.enableTwoFactorFailed"),
812
+ defaultMessage: "Failed to enable 2FA"
813
+ })
814
+ );
815
+ return result.data;
816
+ },
817
+ onSuccess: (data) => {
818
+ if (data) setTwoFactorData(data);
819
+ qc.invalidateQueries({ queryKey: ["dash-user", userId] });
820
+ toggleNotification({
821
+ type: "success",
822
+ message: formatMessage({
823
+ id: index.getTranslation("userDetail.notifications.twoFactorEnabled"),
824
+ defaultMessage: "Two-factor authentication enabled"
825
+ })
826
+ });
827
+ },
828
+ onError: (err) => {
829
+ toggleNotification({
830
+ type: "danger",
831
+ message: err.message ?? formatMessage({
832
+ id: index.getTranslation("userDetail.common.enableTwoFactorFailed"),
833
+ defaultMessage: "Failed to enable 2FA"
834
+ })
835
+ });
836
+ }
837
+ });
838
+ const disableTwoFactorMutation = reactQuery.useMutation({
839
+ mutationFn: async () => {
840
+ const result = await index.client.dash.disableTwoFactor(
841
+ {},
842
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
843
+ );
844
+ if (result.error)
845
+ throw new Error(
846
+ result.error.message ?? formatMessage({
847
+ id: index.getTranslation("userDetail.common.disableTwoFactorFailed"),
848
+ defaultMessage: "Failed to disable 2FA"
849
+ })
850
+ );
851
+ return result.data;
852
+ },
853
+ onSuccess: () => {
854
+ setConfirmDisable2FA(false);
855
+ setTwoFactorData(null);
856
+ setTotpUriData(null);
857
+ setBackupCodesData(null);
858
+ qc.invalidateQueries({ queryKey: ["dash-user", userId] });
859
+ toggleNotification({
860
+ type: "success",
861
+ message: formatMessage({
862
+ id: index.getTranslation("userDetail.notifications.twoFactorDisabled"),
863
+ defaultMessage: "Two-factor authentication disabled"
864
+ })
865
+ });
866
+ },
867
+ onError: (err) => {
868
+ toggleNotification({
869
+ type: "danger",
870
+ message: err.message ?? formatMessage({
871
+ id: index.getTranslation("userDetail.common.disableTwoFactorFailed"),
872
+ defaultMessage: "Failed to disable 2FA"
873
+ })
874
+ });
875
+ }
876
+ });
877
+ const viewTotpUriMutation = reactQuery.useMutation({
878
+ mutationFn: async () => {
879
+ const result = await index.client.dash.viewTwoFactorTotpUri(
880
+ {},
881
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
882
+ );
883
+ if (result.error)
884
+ throw new Error(
885
+ result.error.message ?? formatMessage({
886
+ id: index.getTranslation("userDetail.common.loadTotpUriFailed"),
887
+ defaultMessage: "Failed to load TOTP URI"
888
+ })
889
+ );
890
+ return result.data;
891
+ },
892
+ onSuccess: (data) => {
893
+ if (data) {
894
+ setTotpUriData(data);
895
+ setShowTotpModal(true);
896
+ }
897
+ },
898
+ onError: (err) => {
899
+ toggleNotification({
900
+ type: "danger",
901
+ message: err.message ?? formatMessage({
902
+ id: index.getTranslation("userDetail.common.loadTotpUriFailed"),
903
+ defaultMessage: "Failed to load TOTP URI"
904
+ })
905
+ });
906
+ }
907
+ });
908
+ const viewBackupCodesMutation = reactQuery.useMutation({
909
+ mutationFn: async () => {
910
+ const result = await index.client.dash.viewBackupCodes(
911
+ {},
912
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
913
+ );
914
+ if (result.error)
915
+ throw new Error(
916
+ result.error.message ?? formatMessage({
917
+ id: index.getTranslation("userDetail.common.loadBackupCodesFailed"),
918
+ defaultMessage: "Failed to load backup codes"
919
+ })
920
+ );
921
+ return result.data;
922
+ },
923
+ onSuccess: (data) => {
924
+ if (data) {
925
+ setBackupCodesData(data.backupCodes);
926
+ setShowBackupCodesModal(true);
927
+ }
928
+ },
929
+ onError: (err) => {
930
+ toggleNotification({
931
+ type: "danger",
932
+ message: err.message ?? formatMessage({
933
+ id: index.getTranslation("userDetail.common.loadBackupCodesFailed"),
934
+ defaultMessage: "Failed to load backup codes"
935
+ })
936
+ });
937
+ }
938
+ });
939
+ const generateBackupCodesMutation = reactQuery.useMutation({
940
+ mutationFn: async () => {
941
+ const result = await index.client.dash.generateBackupCodes(
942
+ {},
943
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
944
+ );
945
+ if (result.error)
946
+ throw new Error(
947
+ result.error.message ?? formatMessage({
948
+ id: index.getTranslation("userDetail.common.generateBackupCodesFailed"),
949
+ defaultMessage: "Failed to generate backup codes"
950
+ })
951
+ );
952
+ return result.data;
953
+ },
954
+ onSuccess: (data) => {
955
+ setConfirmRegenerateBackupCodes(false);
956
+ if (data) {
957
+ setBackupCodesData(data.backupCodes);
958
+ setShowBackupCodesModal(true);
959
+ }
960
+ toggleNotification({
961
+ type: "success",
962
+ message: formatMessage({
963
+ id: index.getTranslation("userDetail.notifications.backupCodesGenerated"),
964
+ defaultMessage: "New backup codes generated"
965
+ })
966
+ });
967
+ },
968
+ onError: (err) => {
969
+ toggleNotification({
970
+ type: "danger",
971
+ message: err.message ?? formatMessage({
972
+ id: index.getTranslation("userDetail.common.generateBackupCodesFailed"),
973
+ defaultMessage: "Failed to generate backup codes"
974
+ })
975
+ });
976
+ }
977
+ });
978
+ const hasEdits = editName !== void 0 || editEmail !== void 0 || editEmailVerified !== void 0 || editImage !== void 0 || Object.keys(editExtra).length > 0;
979
+ const customFields = Object.entries(schemaQuery.data ?? {}).filter(([name]) => !STANDARD_FIELDS.has(name)).map(([name, attribute]) => ({ name, attribute }));
980
+ const banExpiryPreview = banExpiresDays && !Number.isNaN(parseInt(banExpiresDays, 10)) ? new Date(
981
+ Date.now() + parseInt(banExpiresDays, 10) * 864e5
982
+ ).toLocaleDateString(void 0, {
983
+ weekday: "long",
984
+ year: "numeric",
985
+ month: "long",
986
+ day: "numeric"
987
+ }) : null;
988
+ const drawerTitle = /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { tag: "span", gap: 3, alignItems: "center", children: [
989
+ /* @__PURE__ */ jsxRuntime.jsx(Avatar.Avatar, { name: user?.name ?? "?", src: user?.image, size: 32 }),
990
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { tag: "span", direction: "column", alignItems: "flex-start", children: [
991
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", textColor: "neutral800", children: user?.name ?? formatMessage({
992
+ id: index.getTranslation("userDetail.header.fallbackName"),
993
+ defaultMessage: "User"
994
+ }) }),
995
+ user?.email && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: user.email })
996
+ ] }),
997
+ user?.banned && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "danger", size: "S", children: formatMessage({
998
+ id: index.getTranslation("userDetail.header.banned"),
999
+ defaultMessage: "Banned"
1000
+ }) })
1001
+ ] });
1002
+ const profileFooter = activeTab === "profile" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1003
+ /* @__PURE__ */ jsxRuntime.jsx(
1004
+ designSystem.Button,
1005
+ {
1006
+ variant: "tertiary",
1007
+ onClick: () => {
1008
+ setEditName(void 0);
1009
+ setEditEmail(void 0);
1010
+ setEditEmailVerified(void 0);
1011
+ setEditImage(void 0);
1012
+ setEditExtra({});
1013
+ onClose();
1014
+ },
1015
+ children: formatMessage({
1016
+ id: index.getTranslation("userDetail.footer.discard"),
1017
+ defaultMessage: "Discard"
1018
+ })
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsxRuntime.jsx(
1022
+ designSystem.Button,
1023
+ {
1024
+ disabled: !hasEdits,
1025
+ loading: updateMutation.isLoading,
1026
+ onClick: () => updateMutation.mutate(),
1027
+ "data-testid": "save-user-btn",
1028
+ children: formatMessage({
1029
+ id: index.getTranslation("userDetail.footer.save"),
1030
+ defaultMessage: "Save changes"
1031
+ })
1032
+ }
1033
+ )
1034
+ ] }) : void 0;
1035
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1036
+ useModelSchema.SheetDialog,
1037
+ {
1038
+ title: drawerTitle,
1039
+ actionButtons: profileFooter,
1040
+ onClose,
1041
+ "data-testid": "user-detail-drawer",
1042
+ children: [
1043
+ userQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: formatMessage({
1044
+ id: index.getTranslation("userDetail.loadingUser"),
1045
+ defaultMessage: "Loading user…"
1046
+ }) }) }) : userQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx(
1047
+ designSystem.Alert,
1048
+ {
1049
+ closeLabel: formatMessage({
1050
+ id: index.getTranslation("userDetail.common.close"),
1051
+ defaultMessage: "Close"
1052
+ }),
1053
+ title: formatMessage({
1054
+ id: index.getTranslation("userDetail.common.loadUserFailed"),
1055
+ defaultMessage: "Failed to load user"
1056
+ }),
1057
+ variant: "danger",
1058
+ children: userQuery.error instanceof Error ? userQuery.error.message : formatMessage({
1059
+ id: index.getTranslation("userDetail.errors.generic"),
1060
+ defaultMessage: "An error occurred"
1061
+ })
1062
+ }
1063
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Root, { defaultValue: "profile", onValueChange: setActiveTab, children: [
1064
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.List, { children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "profile", children: formatMessage({
1066
+ id: index.getTranslation("userDetail.tabs.profile"),
1067
+ defaultMessage: "Profile"
1068
+ }) }),
1069
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "security", children: formatMessage({
1070
+ id: index.getTranslation("userDetail.tabs.security"),
1071
+ defaultMessage: "Security"
1072
+ }) }),
1073
+ banEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "ban", children: formatMessage({
1074
+ id: index.getTranslation("userDetail.tabs.ban"),
1075
+ defaultMessage: "Ban"
1076
+ }) }),
1077
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "sessions", children: formatMessage({
1078
+ id: index.getTranslation("userDetail.tabs.sessions"),
1079
+ defaultMessage: "Sessions"
1080
+ }) }),
1081
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "organizations", children: formatMessage({
1082
+ id: index.getTranslation("userDetail.tabs.organizations"),
1083
+ defaultMessage: "Organizations"
1084
+ }) })
1085
+ ] }),
1086
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "profile", children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.EditLayout, { children: [
1087
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, children: [
1088
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1089
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1090
+ id: index.getTranslation("userDetail.profile.personalInfo"),
1091
+ defaultMessage: "Personal information"
1092
+ }) }),
1093
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, children: [
1094
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
1095
+ id: index.getTranslation("userDetail.profile.fullName"),
1096
+ defaultMessage: "Full name"
1097
+ }) }),
1098
+ /* @__PURE__ */ jsxRuntime.jsx(
1099
+ designSystem.TextInput,
1100
+ {
1101
+ value: displayName,
1102
+ onChange: (e) => setEditName(e.target.value),
1103
+ "data-testid": "user-name-input"
1104
+ }
1105
+ )
1106
+ ] }),
1107
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, children: [
1108
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
1109
+ id: index.getTranslation("userDetail.profile.email"),
1110
+ defaultMessage: "Email address"
1111
+ }) }),
1112
+ /* @__PURE__ */ jsxRuntime.jsx(
1113
+ designSystem.TextInput,
1114
+ {
1115
+ type: "email",
1116
+ value: displayEmail,
1117
+ onChange: (e) => setEditEmail(e.target.value)
1118
+ }
1119
+ )
1120
+ ] }),
1121
+ /* @__PURE__ */ jsxRuntime.jsx(
1122
+ designSystem.Checkbox,
1123
+ {
1124
+ checked: displayEmailVerified,
1125
+ onCheckedChange: (checked) => setEditEmailVerified(checked),
1126
+ children: formatMessage({
1127
+ id: index.getTranslation("userDetail.profile.markVerified"),
1128
+ defaultMessage: "Mark email as verified"
1129
+ })
1130
+ }
1131
+ ),
1132
+ /* @__PURE__ */ jsxRuntime.jsx(
1133
+ useModelSchema.MediaPickerField,
1134
+ {
1135
+ label: formatMessage({
1136
+ id: index.getTranslation("userDetail.profile.avatar.label"),
1137
+ defaultMessage: "Avatar URL"
1138
+ }),
1139
+ name: "image",
1140
+ value: displayImage,
1141
+ onChange: setEditImage,
1142
+ placeholder: formatMessage({
1143
+ id: index.getTranslation(
1144
+ "userDetail.profile.avatar.placeholder"
1145
+ ),
1146
+ defaultMessage: "https://example.com/avatar.png"
1147
+ }),
1148
+ hint: formatMessage({
1149
+ id: index.getTranslation("userDetail.profile.avatar.hint"),
1150
+ defaultMessage: "Optional — publicly accessible image URL"
1151
+ })
1152
+ }
1153
+ )
1154
+ ] }),
1155
+ /* @__PURE__ */ jsxRuntime.jsx(
1156
+ useModelSchema.CustomFieldsSection,
1157
+ {
1158
+ fields: customFields,
1159
+ data: extraData,
1160
+ onChange: handleExtraChange
1161
+ }
1162
+ )
1163
+ ] }),
1164
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.EditSidebar, { children: [
1165
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1166
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1167
+ id: index.getTranslation("userDetail.profile.details"),
1168
+ defaultMessage: "Details"
1169
+ }) }),
1170
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 3, gridCols: 2, children: [
1171
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.MetaItem, { children: [
1172
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaKey, { children: formatMessage({
1173
+ id: index.getTranslation("userDetail.profile.userId"),
1174
+ defaultMessage: "User ID"
1175
+ }) }),
1176
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MonoChip, { children: user?.id })
1177
+ ] }) }),
1178
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.MetaItem, { children: [
1179
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaKey, { children: formatMessage({
1180
+ id: index.getTranslation("userDetail.profile.created"),
1181
+ defaultMessage: "Created"
1182
+ }) }),
1183
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaVal, { children: user?.createdAt ? new Date(user.createdAt).toLocaleString() : "—" })
1184
+ ] }) }),
1185
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.MetaItem, { children: [
1186
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaKey, { children: formatMessage({
1187
+ id: index.getTranslation(
1188
+ "userDetail.profile.lastUpdated"
1189
+ ),
1190
+ defaultMessage: "Last updated"
1191
+ }) }),
1192
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaVal, { children: user?.updatedAt ? new Date(user.updatedAt).toLocaleString() : "—" })
1193
+ ] }) })
1194
+ ] })
1195
+ ] }),
1196
+ emailVerificationEnabled && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
1197
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1198
+ id: index.getTranslation(
1199
+ "userDetail.profile.emailActions.title"
1200
+ ),
1201
+ defaultMessage: "Email actions"
1202
+ }) }),
1203
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
1204
+ /* @__PURE__ */ jsxRuntime.jsx(
1205
+ designSystem.Button,
1206
+ {
1207
+ variant: "secondary",
1208
+ size: "S",
1209
+ fullWidth: true,
1210
+ loading: sendVerificationMutation.isLoading,
1211
+ disabled: !emailCallbackUrl || user?.emailVerified,
1212
+ onClick: () => sendVerificationMutation.mutate(),
1213
+ children: formatMessage({
1214
+ id: index.getTranslation(
1215
+ "userDetail.profile.emailActions.sendVerification"
1216
+ ),
1217
+ defaultMessage: "Send verification email"
1218
+ })
1219
+ }
1220
+ ),
1221
+ /* @__PURE__ */ jsxRuntime.jsx(
1222
+ designSystem.Button,
1223
+ {
1224
+ variant: "secondary",
1225
+ size: "S",
1226
+ fullWidth: true,
1227
+ loading: sendResetPasswordMutation.isLoading,
1228
+ disabled: !emailCallbackUrl,
1229
+ onClick: () => sendResetPasswordMutation.mutate(),
1230
+ children: formatMessage({
1231
+ id: index.getTranslation(
1232
+ "userDetail.profile.emailActions.sendReset"
1233
+ ),
1234
+ defaultMessage: "Send password reset"
1235
+ })
1236
+ }
1237
+ ),
1238
+ !settingsQuery.isLoading && !emailCallbackUrl && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
1239
+ id: index.getTranslation(
1240
+ "userDetail.profile.emailActions.callbackUrlHint"
1241
+ ),
1242
+ defaultMessage: "Set `email_callback_url` in the better-auth-dashboard plugin config to enable these actions."
1243
+ }) })
1244
+ ] })
1245
+ ] }),
1246
+ /* @__PURE__ */ jsxRuntime.jsx(
1247
+ useModelSchema.EditViewSidePanels,
1248
+ {
1249
+ model: "plugin::better-auth.user",
1250
+ documentId: strapiUserQuery.data?.documentId,
1251
+ document: strapiUserQuery.data ?? void 0
1252
+ }
1253
+ )
1254
+ ] })
1255
+ ] }) }),
1256
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "security", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
1257
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1258
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1259
+ id: index.getTranslation("userDetail.security.password.title"),
1260
+ defaultMessage: "Password"
1261
+ }) }),
1262
+ /* @__PURE__ */ jsxRuntime.jsxs(
1263
+ designSystem.Field.Root,
1264
+ {
1265
+ hint: formatMessage({
1266
+ id: index.getTranslation("userDetail.security.password.hint"),
1267
+ defaultMessage: "The user will be able to sign in with this new password"
1268
+ }),
1269
+ children: [
1270
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
1271
+ id: index.getTranslation("userDetail.security.password.label"),
1272
+ defaultMessage: "New password"
1273
+ }) }),
1274
+ /* @__PURE__ */ jsxRuntime.jsx(
1275
+ designSystem.TextInput,
1276
+ {
1277
+ type: "password",
1278
+ value: newPassword,
1279
+ onChange: (e) => setNewPassword(e.target.value),
1280
+ placeholder: formatMessage({
1281
+ id: index.getTranslation(
1282
+ "userDetail.security.password.placeholder"
1283
+ ),
1284
+ defaultMessage: "Enter a strong password…"
1285
+ })
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
1289
+ ]
1290
+ }
1291
+ ),
1292
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
1293
+ designSystem.Button,
1294
+ {
1295
+ disabled: !newPassword,
1296
+ loading: passwordMutation.isLoading,
1297
+ onClick: () => passwordMutation.mutate(),
1298
+ children: formatMessage({
1299
+ id: index.getTranslation("userDetail.security.password.submit"),
1300
+ defaultMessage: "Set password"
1301
+ })
1302
+ }
1303
+ ) })
1304
+ ] }),
1305
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1306
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1307
+ id: index.getTranslation(
1308
+ "userDetail.security.linkedAccounts.title"
1309
+ ),
1310
+ defaultMessage: "Linked accounts"
1311
+ }) }),
1312
+ user?.account && user.account.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: user.account.map((acc) => /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
1313
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.ProviderBadge, { children: acc.providerId }),
1314
+ /* @__PURE__ */ jsxRuntime.jsx(
1315
+ designSystem.Button,
1316
+ {
1317
+ variant: "danger-light",
1318
+ size: "S",
1319
+ onClick: () => setConfirmUnlinkAccountId(acc.providerId),
1320
+ children: formatMessage({
1321
+ id: index.getTranslation("userDetail.common.unlink"),
1322
+ defaultMessage: "Unlink"
1323
+ })
1324
+ }
1325
+ )
1326
+ ] }, acc.id)) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1327
+ id: index.getTranslation(
1328
+ "userDetail.security.linkedAccounts.empty"
1329
+ ),
1330
+ defaultMessage: "No linked OAuth accounts."
1331
+ }) })
1332
+ ] }),
1333
+ twoFactorEnabled && /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1334
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
1335
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1336
+ id: index.getTranslation(
1337
+ "userDetail.security.twoFactor.title"
1338
+ ),
1339
+ defaultMessage: "Two-factor authentication"
1340
+ }) }),
1341
+ /* @__PURE__ */ jsxRuntime.jsx(
1342
+ designSystem.Badge,
1343
+ {
1344
+ size: "S",
1345
+ variant: user?.twoFactorEnabled ? "success" : "neutral",
1346
+ children: user?.twoFactorEnabled ? formatMessage({
1347
+ id: index.getTranslation(
1348
+ "userDetail.security.twoFactor.status.enabled"
1349
+ ),
1350
+ defaultMessage: "Enabled"
1351
+ }) : formatMessage({
1352
+ id: index.getTranslation(
1353
+ "userDetail.security.twoFactor.status.disabled"
1354
+ ),
1355
+ defaultMessage: "Not enabled"
1356
+ })
1357
+ }
1358
+ )
1359
+ ] }),
1360
+ user?.twoFactorEnabled ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
1361
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
1362
+ /* @__PURE__ */ jsxRuntime.jsxs(
1363
+ designSystem.Flex,
1364
+ {
1365
+ direction: "column",
1366
+ gap: 1,
1367
+ alignItems: "flex-start",
1368
+ children: [
1369
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: formatMessage({
1370
+ id: index.getTranslation(
1371
+ "userDetail.common.totpAuthenticator"
1372
+ ),
1373
+ defaultMessage: "TOTP authenticator"
1374
+ }) }),
1375
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1376
+ id: index.getTranslation(
1377
+ "userDetail.security.twoFactor.totp.description"
1378
+ ),
1379
+ defaultMessage: "View the URI and secret for this user's authenticator app."
1380
+ }) })
1381
+ ]
1382
+ }
1383
+ ),
1384
+ /* @__PURE__ */ jsxRuntime.jsx(
1385
+ designSystem.Button,
1386
+ {
1387
+ variant: "secondary",
1388
+ size: "S",
1389
+ loading: viewTotpUriMutation.isLoading,
1390
+ onClick: () => viewTotpUriMutation.mutate(),
1391
+ children: totpUriData ? formatMessage({
1392
+ id: index.getTranslation("userDetail.common.refresh"),
1393
+ defaultMessage: "Refresh"
1394
+ }) : formatMessage({
1395
+ id: index.getTranslation("userDetail.common.view"),
1396
+ defaultMessage: "View"
1397
+ })
1398
+ }
1399
+ )
1400
+ ] }),
1401
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
1402
+ /* @__PURE__ */ jsxRuntime.jsxs(
1403
+ designSystem.Flex,
1404
+ {
1405
+ direction: "column",
1406
+ gap: 1,
1407
+ alignItems: "flex-start",
1408
+ children: [
1409
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: formatMessage({
1410
+ id: index.getTranslation(
1411
+ "userDetail.common.backupCodes"
1412
+ ),
1413
+ defaultMessage: "Backup codes"
1414
+ }) }),
1415
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1416
+ id: index.getTranslation(
1417
+ "userDetail.security.twoFactor.backupCodes.description"
1418
+ ),
1419
+ defaultMessage: "View or regenerate the user's recovery codes."
1420
+ }) })
1421
+ ]
1422
+ }
1423
+ ),
1424
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1425
+ /* @__PURE__ */ jsxRuntime.jsx(
1426
+ designSystem.Button,
1427
+ {
1428
+ variant: "secondary",
1429
+ size: "S",
1430
+ loading: viewBackupCodesMutation.isLoading,
1431
+ onClick: () => viewBackupCodesMutation.mutate(),
1432
+ children: backupCodesData ? formatMessage({
1433
+ id: index.getTranslation(
1434
+ "userDetail.common.refresh"
1435
+ ),
1436
+ defaultMessage: "Refresh"
1437
+ }) : formatMessage({
1438
+ id: index.getTranslation("userDetail.common.view"),
1439
+ defaultMessage: "View"
1440
+ })
1441
+ }
1442
+ ),
1443
+ /* @__PURE__ */ jsxRuntime.jsx(
1444
+ designSystem.Button,
1445
+ {
1446
+ variant: "secondary",
1447
+ size: "S",
1448
+ onClick: () => setConfirmRegenerateBackupCodes(true),
1449
+ children: formatMessage({
1450
+ id: index.getTranslation(
1451
+ "userDetail.common.regenerate"
1452
+ ),
1453
+ defaultMessage: "Regenerate"
1454
+ })
1455
+ }
1456
+ )
1457
+ ] })
1458
+ ] }),
1459
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.DangerRow, { children: [
1460
+ /* @__PURE__ */ jsxRuntime.jsxs(
1461
+ designSystem.Flex,
1462
+ {
1463
+ direction: "column",
1464
+ gap: 1,
1465
+ alignItems: "flex-start",
1466
+ children: [
1467
+ /* @__PURE__ */ jsxRuntime.jsx(
1468
+ designSystem.Typography,
1469
+ {
1470
+ variant: "omega",
1471
+ fontWeight: "semiBold",
1472
+ textColor: "danger600",
1473
+ children: formatMessage({
1474
+ id: index.getTranslation(
1475
+ "userDetail.common.disableTwoFactorTitle"
1476
+ ),
1477
+ defaultMessage: "Disable two-factor authentication"
1478
+ })
1479
+ }
1480
+ ),
1481
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger500", children: formatMessage({
1482
+ id: index.getTranslation(
1483
+ "userDetail.security.twoFactor.disable.description"
1484
+ ),
1485
+ defaultMessage: "The user will no longer need a second factor to sign in."
1486
+ }) })
1487
+ ]
1488
+ }
1489
+ ),
1490
+ /* @__PURE__ */ jsxRuntime.jsx(
1491
+ designSystem.Button,
1492
+ {
1493
+ variant: "danger-light",
1494
+ size: "S",
1495
+ onClick: () => setConfirmDisable2FA(true),
1496
+ children: formatMessage({
1497
+ id: index.getTranslation(
1498
+ "userDetail.common.disableTwoFactor"
1499
+ ),
1500
+ defaultMessage: "Disable 2FA"
1501
+ })
1502
+ }
1503
+ )
1504
+ ] })
1505
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
1506
+ twoFactorData && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
1507
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
1508
+ id: index.getTranslation(
1509
+ "userDetail.security.twoFactor.setup.instructions"
1510
+ ),
1511
+ defaultMessage: "Scan the TOTP URI with an authenticator app, or enter the secret key manually."
1512
+ }) }),
1513
+ /* @__PURE__ */ jsxRuntime.jsx(
1514
+ ReadOnlyField,
1515
+ {
1516
+ label: formatMessage({
1517
+ id: index.getTranslation("userDetail.common.totpUri"),
1518
+ defaultMessage: "TOTP URI"
1519
+ }),
1520
+ value: twoFactorData.totpURI
1521
+ }
1522
+ ),
1523
+ /* @__PURE__ */ jsxRuntime.jsx(
1524
+ ReadOnlyField,
1525
+ {
1526
+ label: formatMessage({
1527
+ id: index.getTranslation("userDetail.common.secretKey"),
1528
+ defaultMessage: "Secret key"
1529
+ }),
1530
+ value: twoFactorData.secret
1531
+ }
1532
+ ),
1533
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
1534
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1535
+ id: index.getTranslation(
1536
+ "userDetail.common.backupCodes"
1537
+ ),
1538
+ defaultMessage: "Backup codes"
1539
+ }) }),
1540
+ /* @__PURE__ */ jsxRuntime.jsx(BackupCodes, { codes: twoFactorData.backupCodes })
1541
+ ] })
1542
+ ] }),
1543
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
1544
+ /* @__PURE__ */ jsxRuntime.jsxs(
1545
+ designSystem.Flex,
1546
+ {
1547
+ direction: "column",
1548
+ gap: 1,
1549
+ alignItems: "flex-start",
1550
+ children: [
1551
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: formatMessage({
1552
+ id: index.getTranslation(
1553
+ "userDetail.security.twoFactor.enable.title"
1554
+ ),
1555
+ defaultMessage: "Enable two-factor authentication"
1556
+ }) }),
1557
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1558
+ id: index.getTranslation(
1559
+ "userDetail.security.twoFactor.enable.description"
1560
+ ),
1561
+ defaultMessage: "Add an extra layer of security to this account."
1562
+ }) })
1563
+ ]
1564
+ }
1565
+ ),
1566
+ /* @__PURE__ */ jsxRuntime.jsx(
1567
+ designSystem.Button,
1568
+ {
1569
+ size: "S",
1570
+ loading: enableTwoFactorMutation.isLoading,
1571
+ onClick: () => enableTwoFactorMutation.mutate(),
1572
+ children: formatMessage({
1573
+ id: index.getTranslation(
1574
+ "userDetail.security.twoFactor.enable.submit"
1575
+ ),
1576
+ defaultMessage: "Enable 2FA"
1577
+ })
1578
+ }
1579
+ )
1580
+ ] })
1581
+ ] })
1582
+ ] })
1583
+ ] }) }),
1584
+ banEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "ban", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: user?.banned ? /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1585
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1586
+ id: index.getTranslation("userDetail.ban.status.title"),
1587
+ defaultMessage: "Ban status"
1588
+ }) }),
1589
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.WarnCard, { children: [
1590
+ /* @__PURE__ */ jsxRuntime.jsx(
1591
+ designSystem.Typography,
1592
+ {
1593
+ variant: "omega",
1594
+ fontWeight: "semiBold",
1595
+ textColor: "danger600",
1596
+ children: formatMessage({
1597
+ id: index.getTranslation("userDetail.ban.status.current"),
1598
+ defaultMessage: "This user is currently banned"
1599
+ })
1600
+ }
1601
+ ),
1602
+ user.banReason && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: formatMessage(
1603
+ {
1604
+ id: index.getTranslation(
1605
+ "userDetail.ban.status.reason"
1606
+ ),
1607
+ defaultMessage: "Reason: {reason}"
1608
+ },
1609
+ { reason: user.banReason }
1610
+ ) }),
1611
+ user.banExpires ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: formatMessage(
1612
+ {
1613
+ id: index.getTranslation(
1614
+ "userDetail.ban.status.expires"
1615
+ ),
1616
+ defaultMessage: "Expires: {date}"
1617
+ },
1618
+ {
1619
+ date: new Date(
1620
+ user.banExpires
1621
+ ).toLocaleDateString(void 0, {
1622
+ weekday: "long",
1623
+ year: "numeric",
1624
+ month: "long",
1625
+ day: "numeric"
1626
+ })
1627
+ }
1628
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: formatMessage({
1629
+ id: index.getTranslation(
1630
+ "userDetail.ban.status.permanent"
1631
+ ),
1632
+ defaultMessage: "Duration: Permanent"
1633
+ }) })
1634
+ ] }),
1635
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
1636
+ designSystem.Button,
1637
+ {
1638
+ variant: "secondary",
1639
+ onClick: () => setConfirmUnban(true),
1640
+ children: formatMessage({
1641
+ id: index.getTranslation("userDetail.common.liftBan"),
1642
+ defaultMessage: "Lift ban"
1643
+ })
1644
+ }
1645
+ ) })
1646
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1647
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1648
+ id: index.getTranslation("userDetail.ban.apply.title"),
1649
+ defaultMessage: "Apply ban"
1650
+ }) }),
1651
+ /* @__PURE__ */ jsxRuntime.jsxs(
1652
+ designSystem.Field.Root,
1653
+ {
1654
+ hint: formatMessage({
1655
+ id: index.getTranslation("userDetail.ban.apply.reason.hint"),
1656
+ defaultMessage: "Optional — will be shown to the user"
1657
+ }),
1658
+ style: { width: "100%" },
1659
+ children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
1661
+ id: index.getTranslation(
1662
+ "userDetail.ban.apply.reason.label"
1663
+ ),
1664
+ defaultMessage: "Reason"
1665
+ }) }),
1666
+ /* @__PURE__ */ jsxRuntime.jsx(
1667
+ designSystem.TextInput,
1668
+ {
1669
+ value: banReason,
1670
+ onChange: (e) => setBanReason(e.target.value),
1671
+ placeholder: formatMessage({
1672
+ id: index.getTranslation(
1673
+ "userDetail.ban.apply.reason.placeholder"
1674
+ ),
1675
+ defaultMessage: "e.g. Violated terms of service"
1676
+ })
1677
+ }
1678
+ ),
1679
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
1680
+ ]
1681
+ }
1682
+ ),
1683
+ /* @__PURE__ */ jsxRuntime.jsxs(
1684
+ designSystem.Field.Root,
1685
+ {
1686
+ hint: formatMessage({
1687
+ id: index.getTranslation(
1688
+ "userDetail.ban.apply.duration.hint"
1689
+ ),
1690
+ defaultMessage: "Leave empty for a permanent ban"
1691
+ }),
1692
+ style: { width: "100%" },
1693
+ children: [
1694
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
1695
+ id: index.getTranslation(
1696
+ "userDetail.ban.apply.duration.label"
1697
+ ),
1698
+ defaultMessage: "Duration (days)"
1699
+ }) }),
1700
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "center", children: [
1701
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
1702
+ designSystem.NumberInput,
1703
+ {
1704
+ value: banExpiresDays ? Number(banExpiresDays) : void 0,
1705
+ onValueChange: (v) => setBanExpiresDays(
1706
+ v === void 0 ? "" : String(v)
1707
+ ),
1708
+ placeholder: formatMessage({
1709
+ id: index.getTranslation(
1710
+ "userDetail.ban.apply.duration.placeholder"
1711
+ ),
1712
+ defaultMessage: "e.g. 30"
1713
+ })
1714
+ }
1715
+ ) }),
1716
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { shrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.PreviewPill, { children: banExpiryPreview ? formatMessage(
1717
+ {
1718
+ id: index.getTranslation(
1719
+ "userDetail.ban.apply.preview.expires"
1720
+ ),
1721
+ defaultMessage: "Expires {date}"
1722
+ },
1723
+ { date: banExpiryPreview }
1724
+ ) : formatMessage({
1725
+ id: index.getTranslation(
1726
+ "userDetail.ban.apply.preview.permanent"
1727
+ ),
1728
+ defaultMessage: "Permanent ban"
1729
+ }) }) })
1730
+ ] }),
1731
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
1732
+ ]
1733
+ }
1734
+ ),
1735
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
1736
+ designSystem.Button,
1737
+ {
1738
+ variant: "danger",
1739
+ onClick: () => setConfirmBan(true),
1740
+ children: formatMessage({
1741
+ id: index.getTranslation("userDetail.common.banUser"),
1742
+ defaultMessage: "Ban user"
1743
+ })
1744
+ }
1745
+ ) })
1746
+ ] }) }) }),
1747
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "sessions", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
1748
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1749
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1750
+ id: index.getTranslation("userDetail.sessions.management"),
1751
+ defaultMessage: "Session management"
1752
+ }) }),
1753
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
1754
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", children: [
1755
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: formatMessage({
1756
+ id: index.getTranslation(
1757
+ "userDetail.common.revokeAllSessions"
1758
+ ),
1759
+ defaultMessage: "Revoke all sessions"
1760
+ }) }),
1761
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1762
+ id: index.getTranslation(
1763
+ "userDetail.sessions.revokeAll.description"
1764
+ ),
1765
+ defaultMessage: "Signs the user out immediately on all devices."
1766
+ }) })
1767
+ ] }),
1768
+ /* @__PURE__ */ jsxRuntime.jsx(
1769
+ designSystem.Button,
1770
+ {
1771
+ variant: "danger-light",
1772
+ size: "S",
1773
+ onClick: () => setConfirmRevokeAll(true),
1774
+ style: { flexShrink: 0 },
1775
+ children: formatMessage({
1776
+ id: index.getTranslation("userDetail.common.revokeAll"),
1777
+ defaultMessage: "Revoke all"
1778
+ })
1779
+ }
1780
+ )
1781
+ ] })
1782
+ ] }),
1783
+ /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1784
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1785
+ id: index.getTranslation("userDetail.sessions.active"),
1786
+ defaultMessage: "Active sessions"
1787
+ }) }),
1788
+ sessionsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: formatMessage({
1789
+ id: index.getTranslation("userDetail.sessions.loading"),
1790
+ defaultMessage: "Loading sessions…"
1791
+ }) }) }) : (sessionsQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1792
+ id: index.getTranslation("userDetail.sessions.empty"),
1793
+ defaultMessage: "No active sessions."
1794
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, alignItems: "stretch", children: (sessionsQuery.data ?? []).map((session) => /* @__PURE__ */ jsxRuntime.jsxs(
1795
+ useModelSchema.AccountRow,
1796
+ {
1797
+ alignItems: "flex-start",
1798
+ children: [
1799
+ /* @__PURE__ */ jsxRuntime.jsxs(
1800
+ designSystem.Flex,
1801
+ {
1802
+ direction: "column",
1803
+ alignItems: "flex-start",
1804
+ gap: 1,
1805
+ flex: 1,
1806
+ minWidth: 0,
1807
+ children: [
1808
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1809
+ session.ipAddress && /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MonoChip, { children: session.ipAddress }),
1810
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(
1811
+ {
1812
+ id: index.getTranslation(
1813
+ "userDetail.sessions.timestamps"
1814
+ ),
1815
+ defaultMessage: "Created {createdAt} · Expires {expiresAt}"
1816
+ },
1817
+ {
1818
+ createdAt: new Date(
1819
+ session.createdAt
1820
+ ).toLocaleString(),
1821
+ expiresAt: new Date(
1822
+ session.expiresAt
1823
+ ).toLocaleString()
1824
+ }
1825
+ ) })
1826
+ ] }),
1827
+ session.userAgent && /* @__PURE__ */ jsxRuntime.jsx(
1828
+ designSystem.Typography,
1829
+ {
1830
+ variant: "pi",
1831
+ textColor: "neutral500",
1832
+ ellipsis: true,
1833
+ width: "100%",
1834
+ children: session.userAgent
1835
+ }
1836
+ )
1837
+ ]
1838
+ }
1839
+ ),
1840
+ /* @__PURE__ */ jsxRuntime.jsx(
1841
+ designSystem.IconButton,
1842
+ {
1843
+ label: formatMessage({
1844
+ id: index.getTranslation(
1845
+ "userDetail.common.revokeSession"
1846
+ ),
1847
+ defaultMessage: "Revoke session"
1848
+ }),
1849
+ onClick: () => setConfirmRevokeSessionId(String(session.id)),
1850
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1851
+ }
1852
+ )
1853
+ ]
1854
+ },
1855
+ session.documentId
1856
+ )) })
1857
+ ] })
1858
+ ] }) }),
1859
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "organizations", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
1860
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: formatMessage({
1861
+ id: index.getTranslation("userDetail.organizations.memberships"),
1862
+ defaultMessage: "Memberships"
1863
+ }) }),
1864
+ orgsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true, children: formatMessage({
1865
+ id: index.getTranslation("userDetail.organizations.loading"),
1866
+ defaultMessage: "Loading…"
1867
+ }) }) }) : (orgsQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
1868
+ id: index.getTranslation("userDetail.organizations.empty"),
1869
+ defaultMessage: "Not a member of any organizations."
1870
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: (orgsQuery.data ?? []).map(
1871
+ (org) => org ? /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.AccountRow, { children: /* @__PURE__ */ jsxRuntime.jsxs(
1872
+ designSystem.Flex,
1873
+ {
1874
+ direction: "column",
1875
+ gap: 1,
1876
+ alignItems: "flex-start",
1877
+ children: [
1878
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: org.name }),
1879
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1880
+ /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.RoleBadge, { memberRole: org.role, children: org.role }),
1881
+ org.teams.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage(
1882
+ {
1883
+ id: index.getTranslation(
1884
+ "userDetail.organizations.teams"
1885
+ ),
1886
+ defaultMessage: "{count, plural, one {# team} other {# teams}}"
1887
+ },
1888
+ { count: org.teams.length }
1889
+ ) })
1890
+ ] })
1891
+ ]
1892
+ }
1893
+ ) }, org.id) : null
1894
+ ) })
1895
+ ] }) }) })
1896
+ ] }),
1897
+ confirmRevokeAll && /* @__PURE__ */ jsxRuntime.jsx(
1898
+ useModelSchema.ConfirmDialog,
1899
+ {
1900
+ title: formatMessage({
1901
+ id: index.getTranslation("userDetail.common.revokeAllSessions"),
1902
+ defaultMessage: "Revoke all sessions"
1903
+ }),
1904
+ message: formatMessage({
1905
+ id: index.getTranslation("userDetail.confirm.revokeAll.message"),
1906
+ defaultMessage: "Are you sure you want to revoke all sessions for this user? They will be signed out on all devices."
1907
+ }),
1908
+ confirmLabel: formatMessage({
1909
+ id: index.getTranslation("userDetail.common.revokeAll"),
1910
+ defaultMessage: "Revoke all"
1911
+ }),
1912
+ loading: revokeAllMutation.isLoading,
1913
+ onConfirm: () => revokeAllMutation.mutate(),
1914
+ onCancel: () => setConfirmRevokeAll(false)
1915
+ }
1916
+ ),
1917
+ confirmRevokeSessionId && /* @__PURE__ */ jsxRuntime.jsx(
1918
+ useModelSchema.ConfirmDialog,
1919
+ {
1920
+ title: formatMessage({
1921
+ id: index.getTranslation("userDetail.common.revokeSession"),
1922
+ defaultMessage: "Revoke session"
1923
+ }),
1924
+ message: formatMessage({
1925
+ id: index.getTranslation("userDetail.confirm.revokeSession.message"),
1926
+ defaultMessage: "Are you sure you want to revoke this session? The user will be signed out on this device."
1927
+ }),
1928
+ confirmLabel: formatMessage({
1929
+ id: index.getTranslation("userDetail.confirm.revokeSession.confirm"),
1930
+ defaultMessage: "Revoke"
1931
+ }),
1932
+ loading: revokeSessionMutation.isLoading,
1933
+ onConfirm: () => revokeSessionMutation.mutate(confirmRevokeSessionId),
1934
+ onCancel: () => setConfirmRevokeSessionId(null)
1935
+ }
1936
+ ),
1937
+ confirmBan && /* @__PURE__ */ jsxRuntime.jsx(
1938
+ useModelSchema.ConfirmDialog,
1939
+ {
1940
+ title: formatMessage({
1941
+ id: index.getTranslation("userDetail.common.banUser"),
1942
+ defaultMessage: "Ban user"
1943
+ }),
1944
+ message: banReason ? formatMessage(
1945
+ {
1946
+ id: index.getTranslation(
1947
+ "userDetail.confirm.ban.messageWithReason"
1948
+ ),
1949
+ defaultMessage: 'Ban this user for the following reason: "{reason}"? They will be prevented from signing in.'
1950
+ },
1951
+ { reason: banReason }
1952
+ ) : formatMessage({
1953
+ id: index.getTranslation("userDetail.confirm.ban.message"),
1954
+ defaultMessage: "Are you sure you want to ban this user? They will be prevented from signing in."
1955
+ }),
1956
+ confirmLabel: formatMessage({
1957
+ id: index.getTranslation("userDetail.common.banUser"),
1958
+ defaultMessage: "Ban user"
1959
+ }),
1960
+ variant: "danger",
1961
+ loading: banMutation.isLoading,
1962
+ onConfirm: () => banMutation.mutate(),
1963
+ onCancel: () => setConfirmBan(false)
1964
+ }
1965
+ ),
1966
+ confirmUnban && /* @__PURE__ */ jsxRuntime.jsx(
1967
+ useModelSchema.ConfirmDialog,
1968
+ {
1969
+ title: formatMessage({
1970
+ id: index.getTranslation("userDetail.common.liftBan"),
1971
+ defaultMessage: "Lift ban"
1972
+ }),
1973
+ message: formatMessage({
1974
+ id: index.getTranslation("userDetail.confirm.unban.message"),
1975
+ defaultMessage: "Are you sure you want to lift the ban on this user? They will be able to sign in again."
1976
+ }),
1977
+ confirmLabel: formatMessage({
1978
+ id: index.getTranslation("userDetail.common.liftBan"),
1979
+ defaultMessage: "Lift ban"
1980
+ }),
1981
+ variant: "default",
1982
+ loading: unbanMutation.isLoading,
1983
+ onConfirm: () => unbanMutation.mutate(),
1984
+ onCancel: () => setConfirmUnban(false)
1985
+ }
1986
+ ),
1987
+ confirmUnlinkAccountId && /* @__PURE__ */ jsxRuntime.jsx(
1988
+ useModelSchema.ConfirmDialog,
1989
+ {
1990
+ title: formatMessage({
1991
+ id: index.getTranslation("userDetail.confirm.unlink.title"),
1992
+ defaultMessage: "Unlink account"
1993
+ }),
1994
+ message: formatMessage(
1995
+ {
1996
+ id: index.getTranslation("userDetail.confirm.unlink.message"),
1997
+ defaultMessage: 'Are you sure you want to unlink the "{provider}" provider from this user?'
1998
+ },
1999
+ { provider: confirmUnlinkAccountId }
2000
+ ),
2001
+ confirmLabel: formatMessage({
2002
+ id: index.getTranslation("userDetail.common.unlink"),
2003
+ defaultMessage: "Unlink"
2004
+ }),
2005
+ loading: unlinkAccountMutation.isLoading,
2006
+ onConfirm: () => unlinkAccountMutation.mutate(confirmUnlinkAccountId),
2007
+ onCancel: () => setConfirmUnlinkAccountId(null)
2008
+ }
2009
+ ),
2010
+ confirmDisable2FA && /* @__PURE__ */ jsxRuntime.jsx(
2011
+ useModelSchema.ConfirmDialog,
2012
+ {
2013
+ title: formatMessage({
2014
+ id: index.getTranslation("userDetail.common.disableTwoFactorTitle"),
2015
+ defaultMessage: "Disable two-factor authentication"
2016
+ }),
2017
+ message: formatMessage({
2018
+ id: index.getTranslation("userDetail.confirm.disableTwoFactor.message"),
2019
+ defaultMessage: "Are you sure you want to disable 2FA for this user? They will no longer need a second factor to sign in."
2020
+ }),
2021
+ confirmLabel: formatMessage({
2022
+ id: index.getTranslation("userDetail.common.disableTwoFactor"),
2023
+ defaultMessage: "Disable 2FA"
2024
+ }),
2025
+ loading: disableTwoFactorMutation.isLoading,
2026
+ onConfirm: () => disableTwoFactorMutation.mutate(),
2027
+ onCancel: () => setConfirmDisable2FA(false)
2028
+ }
2029
+ ),
2030
+ confirmRegenerateBackupCodes && /* @__PURE__ */ jsxRuntime.jsx(
2031
+ useModelSchema.ConfirmDialog,
2032
+ {
2033
+ title: formatMessage({
2034
+ id: index.getTranslation(
2035
+ "userDetail.confirm.regenerateBackupCodes.title"
2036
+ ),
2037
+ defaultMessage: "Regenerate backup codes"
2038
+ }),
2039
+ message: formatMessage({
2040
+ id: index.getTranslation(
2041
+ "userDetail.confirm.regenerateBackupCodes.message"
2042
+ ),
2043
+ defaultMessage: "This will invalidate all existing backup codes. New codes will be shown once after generation. Are you sure?"
2044
+ }),
2045
+ confirmLabel: formatMessage({
2046
+ id: index.getTranslation("userDetail.common.regenerate"),
2047
+ defaultMessage: "Regenerate"
2048
+ }),
2049
+ loading: generateBackupCodesMutation.isLoading,
2050
+ onConfirm: () => generateBackupCodesMutation.mutate(),
2051
+ onCancel: () => setConfirmRegenerateBackupCodes(false)
2052
+ }
2053
+ ),
2054
+ showTotpModal && totpUriData && /* @__PURE__ */ jsxRuntime.jsx(
2055
+ designSystem.Modal.Root,
2056
+ {
2057
+ defaultOpen: true,
2058
+ onOpenChange: (open) => !open && setShowTotpModal(false),
2059
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
2060
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: formatMessage({
2061
+ id: index.getTranslation("userDetail.common.totpAuthenticator"),
2062
+ defaultMessage: "TOTP authenticator"
2063
+ }) }) }),
2064
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
2065
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: formatMessage({
2066
+ id: index.getTranslation("userDetail.totpModal.description"),
2067
+ defaultMessage: "Share these details with the user to set up their authenticator app."
2068
+ }) }),
2069
+ /* @__PURE__ */ jsxRuntime.jsx(
2070
+ ReadOnlyField,
2071
+ {
2072
+ label: formatMessage({
2073
+ id: index.getTranslation("userDetail.common.totpUri"),
2074
+ defaultMessage: "TOTP URI"
2075
+ }),
2076
+ value: totpUriData.totpURI
2077
+ }
2078
+ ),
2079
+ /* @__PURE__ */ jsxRuntime.jsx(
2080
+ ReadOnlyField,
2081
+ {
2082
+ label: formatMessage({
2083
+ id: index.getTranslation("userDetail.common.secretKey"),
2084
+ defaultMessage: "Secret key"
2085
+ }),
2086
+ value: totpUriData.secret
2087
+ }
2088
+ )
2089
+ ] }) }),
2090
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Footer, { children: /* @__PURE__ */ jsxRuntime.jsx(
2091
+ designSystem.Button,
2092
+ {
2093
+ variant: "tertiary",
2094
+ onClick: () => setShowTotpModal(false),
2095
+ children: formatMessage({
2096
+ id: index.getTranslation("userDetail.common.close"),
2097
+ defaultMessage: "Close"
2098
+ })
2099
+ }
2100
+ ) })
2101
+ ] })
2102
+ }
2103
+ ),
2104
+ showBackupCodesModal && backupCodesData && /* @__PURE__ */ jsxRuntime.jsx(
2105
+ designSystem.Modal.Root,
2106
+ {
2107
+ defaultOpen: true,
2108
+ onOpenChange: (open) => !open && setShowBackupCodesModal(false),
2109
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
2110
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: formatMessage({
2111
+ id: index.getTranslation("userDetail.common.backupCodes"),
2112
+ defaultMessage: "Backup codes"
2113
+ }) }) }),
2114
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
2115
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: formatMessage({
2116
+ id: index.getTranslation(
2117
+ "userDetail.backupCodesModal.description"
2118
+ ),
2119
+ defaultMessage: "Each code can only be used once. Store them somewhere safe."
2120
+ }) }),
2121
+ /* @__PURE__ */ jsxRuntime.jsx(BackupCodes, { codes: backupCodesData })
2122
+ ] }) }),
2123
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Footer, { children: /* @__PURE__ */ jsxRuntime.jsx(
2124
+ designSystem.Button,
2125
+ {
2126
+ variant: "tertiary",
2127
+ onClick: () => setShowBackupCodesModal(false),
2128
+ children: formatMessage({
2129
+ id: index.getTranslation("userDetail.common.close"),
2130
+ defaultMessage: "Close"
2131
+ })
2132
+ }
2133
+ ) })
2134
+ ] })
2135
+ }
2136
+ )
2137
+ ]
2138
+ }
2139
+ );
2140
+ }
2141
+ const PAGE_SIZE = 25;
2142
+ function UsersPage({ config }) {
2143
+ const { formatMessage } = reactIntl.useIntl();
2144
+ const rbac = admin.useRBAC(index.PERMISSIONS.user);
2145
+ const qc = reactQuery.useQueryClient();
2146
+ const { toggleNotification } = admin.useNotification();
2147
+ const banEnabled = index.hasPlugin(config, "admin");
2148
+ const emailVerificationEnabled = config.emailVerification.sendVerificationEmailEnabled;
2149
+ const twoFactorEnabled = index.hasPlugin(config, "two-factor");
2150
+ const [page, setPage] = react.useState(1);
2151
+ const [search, setSearch] = react.useState("");
2152
+ const [searchInput, setSearchInput] = react.useState("");
2153
+ const [selected, setSelected] = react.useState(/* @__PURE__ */ new Set());
2154
+ const [showCreate, setShowCreate] = react.useState(false);
2155
+ const [detailUserId, setDetailUserId] = react.useState(null);
2156
+ const [confirmDelete, setConfirmDelete] = react.useState(null);
2157
+ const [confirmDeleteMany, setConfirmDeleteMany] = react.useState(false);
2158
+ const [confirmBanMany, setConfirmBanMany] = react.useState(false);
2159
+ const offset = (page - 1) * PAGE_SIZE;
2160
+ const { data, isLoading, isError, error } = useUsers({
2161
+ limit: PAGE_SIZE,
2162
+ offset,
2163
+ search: search || void 0
2164
+ });
2165
+ const users = data?.users ?? [];
2166
+ const total = data?.total ?? 0;
2167
+ const pageCount = Math.ceil(total / PAGE_SIZE);
2168
+ const deleteMutation = reactQuery.useMutation({
2169
+ mutationFn: async (userId) => {
2170
+ const result = await index.client.dash.deleteUser(
2171
+ {},
2172
+ useModelSchema.withContext({ userId }, index.getAuthHeaders())
2173
+ );
2174
+ if (result.error)
2175
+ throw new Error(
2176
+ result.error.message ?? formatMessage({
2177
+ id: index.getTranslation("users.list.errors.deleteFailed"),
2178
+ defaultMessage: "Delete failed"
2179
+ })
2180
+ );
2181
+ },
2182
+ onSuccess: () => {
2183
+ setConfirmDelete(null);
2184
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
2185
+ qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
2186
+ toggleNotification({
2187
+ type: "success",
2188
+ message: formatMessage({
2189
+ id: index.getTranslation("users.list.notifications.deleted"),
2190
+ defaultMessage: "User deleted"
2191
+ })
2192
+ });
2193
+ },
2194
+ onError: (err) => {
2195
+ toggleNotification({
2196
+ type: "danger",
2197
+ message: err.message ?? formatMessage({
2198
+ id: index.getTranslation("users.list.notifications.deleteError"),
2199
+ defaultMessage: "Failed to delete user"
2200
+ })
2201
+ });
2202
+ }
2203
+ });
2204
+ const deleteManyMutation = reactQuery.useMutation({
2205
+ mutationFn: async (userIds) => {
2206
+ const result = await index.client.dash.deleteManyUsers(
2207
+ {},
2208
+ useModelSchema.withContext({ userIds }, index.getAuthHeaders())
2209
+ );
2210
+ if (result.error)
2211
+ throw new Error(
2212
+ result.error.message ?? formatMessage({
2213
+ id: index.getTranslation("users.list.errors.deleteFailed"),
2214
+ defaultMessage: "Delete failed"
2215
+ })
2216
+ );
2217
+ return result.data;
2218
+ },
2219
+ onSuccess: (_data, userIds) => {
2220
+ setConfirmDeleteMany(false);
2221
+ setSelected(/* @__PURE__ */ new Set());
2222
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
2223
+ qc.invalidateQueries({ queryKey: ["dash-user-stats"] });
2224
+ toggleNotification({
2225
+ type: "success",
2226
+ message: formatMessage(
2227
+ {
2228
+ id: index.getTranslation("users.list.notifications.deletedMany"),
2229
+ defaultMessage: "{count, plural, one {# user} other {# users}} deleted"
2230
+ },
2231
+ { count: userIds.length }
2232
+ )
2233
+ });
2234
+ },
2235
+ onError: (err) => {
2236
+ toggleNotification({
2237
+ type: "danger",
2238
+ message: err.message ?? formatMessage({
2239
+ id: index.getTranslation("users.list.notifications.deleteManyError"),
2240
+ defaultMessage: "Failed to delete users"
2241
+ })
2242
+ });
2243
+ }
2244
+ });
2245
+ const banManyMutation = reactQuery.useMutation({
2246
+ mutationFn: async (userIds) => {
2247
+ const result = await index.client.dash.banManyUsers(
2248
+ {},
2249
+ useModelSchema.withContext({ userIds }, index.getAuthHeaders())
2250
+ );
2251
+ if (result.error)
2252
+ throw new Error(
2253
+ result.error.message ?? formatMessage({
2254
+ id: index.getTranslation("users.list.errors.banFailed"),
2255
+ defaultMessage: "Ban failed"
2256
+ })
2257
+ );
2258
+ return result.data;
2259
+ },
2260
+ onSuccess: (_data, userIds) => {
2261
+ setConfirmBanMany(false);
2262
+ setSelected(/* @__PURE__ */ new Set());
2263
+ qc.invalidateQueries({ queryKey: ["dash-users"] });
2264
+ toggleNotification({
2265
+ type: "success",
2266
+ message: formatMessage(
2267
+ {
2268
+ id: index.getTranslation("users.list.notifications.bannedMany"),
2269
+ defaultMessage: "{count, plural, one {# user} other {# users}} banned"
2270
+ },
2271
+ { count: userIds.length }
2272
+ )
2273
+ });
2274
+ },
2275
+ onError: (err) => {
2276
+ toggleNotification({
2277
+ type: "danger",
2278
+ message: err.message ?? formatMessage({
2279
+ id: index.getTranslation("users.list.notifications.banManyError"),
2280
+ defaultMessage: "Failed to ban users"
2281
+ })
2282
+ });
2283
+ }
2284
+ });
2285
+ const toggleSelect = (id) => {
2286
+ setSelected((prev) => {
2287
+ const next = new Set(prev);
2288
+ if (next.has(id)) next.delete(id);
2289
+ else next.add(id);
2290
+ return next;
2291
+ });
2292
+ };
2293
+ const allSelected = users.length > 0 && users.every((u) => selected.has(u.id));
2294
+ const someSelected = selected.size > 0;
2295
+ const handleSelectAll = () => {
2296
+ if (allSelected) {
2297
+ setSelected(/* @__PURE__ */ new Set());
2298
+ } else {
2299
+ setSelected(new Set(users.map((u) => u.id)));
2300
+ }
2301
+ };
2302
+ const handleSearch = (e) => {
2303
+ e.preventDefault();
2304
+ setSearch(searchInput);
2305
+ setPage(1);
2306
+ };
2307
+ const canSelect = rbac.allowedActions.canDelete || rbac.allowedActions.canUpdate;
2308
+ const colCount = canSelect ? 6 : 5;
2309
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "data-testid": "users-page", children: [
2310
+ /* @__PURE__ */ jsxRuntime.jsx(
2311
+ admin.Layouts.Header,
2312
+ {
2313
+ title: formatMessage({
2314
+ id: index.getTranslation("users.list.header.title"),
2315
+ defaultMessage: "Users"
2316
+ }),
2317
+ subtitle: data?.onlineUsers ? formatMessage(
2318
+ {
2319
+ id: index.getTranslation("users.list.header.subtitleWithOnline"),
2320
+ defaultMessage: "{total} total · {online} online"
2321
+ },
2322
+ { total: total.toLocaleString(), online: data.onlineUsers }
2323
+ ) : formatMessage(
2324
+ {
2325
+ id: index.getTranslation("users.list.header.subtitle"),
2326
+ defaultMessage: "{total} total"
2327
+ },
2328
+ { total: total.toLocaleString() }
2329
+ ),
2330
+ primaryAction: rbac.allowedActions.canCreate && /* @__PURE__ */ jsxRuntime.jsx(
2331
+ designSystem.Button,
2332
+ {
2333
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
2334
+ onClick: () => setShowCreate(true),
2335
+ "data-testid": "create-user-btn",
2336
+ children: formatMessage({
2337
+ id: index.getTranslation("users.list.header.create"),
2338
+ defaultMessage: "Create user"
2339
+ })
2340
+ }
2341
+ )
2342
+ }
2343
+ ),
2344
+ /* @__PURE__ */ jsxRuntime.jsx(
2345
+ admin.Layouts.Action,
2346
+ {
2347
+ startActions: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2348
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { onSubmit: handleSearch, children: /* @__PURE__ */ jsxRuntime.jsx(
2349
+ designSystem.Searchbar,
2350
+ {
2351
+ clearLabel: formatMessage({
2352
+ id: index.getTranslation("users.list.search.clear"),
2353
+ defaultMessage: "Clear"
2354
+ }),
2355
+ name: "search",
2356
+ placeholder: formatMessage({
2357
+ id: index.getTranslation("users.list.search.placeholder"),
2358
+ defaultMessage: "Search by email…"
2359
+ }),
2360
+ value: searchInput,
2361
+ onChange: (e) => setSearchInput(e.target.value),
2362
+ onClear: () => {
2363
+ setSearchInput("");
2364
+ setSearch("");
2365
+ setPage(1);
2366
+ },
2367
+ "data-testid": "user-search",
2368
+ children: formatMessage({
2369
+ id: index.getTranslation("users.list.search.label"),
2370
+ defaultMessage: "Search users"
2371
+ })
2372
+ }
2373
+ ) }),
2374
+ someSelected && rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(
2375
+ designSystem.Button,
2376
+ {
2377
+ variant: "danger-light",
2378
+ size: "S",
2379
+ onClick: () => setConfirmDeleteMany(true),
2380
+ "data-testid": "delete-selected-btn",
2381
+ children: formatMessage(
2382
+ {
2383
+ id: index.getTranslation("users.list.actions.deleteSelected"),
2384
+ defaultMessage: "Delete {count} selected"
2385
+ },
2386
+ { count: selected.size }
2387
+ )
2388
+ }
2389
+ ),
2390
+ someSelected && banEnabled && rbac.allowedActions.canUpdate && /* @__PURE__ */ jsxRuntime.jsx(
2391
+ designSystem.Button,
2392
+ {
2393
+ variant: "secondary",
2394
+ size: "S",
2395
+ onClick: () => setConfirmBanMany(true),
2396
+ children: formatMessage(
2397
+ {
2398
+ id: index.getTranslation("users.list.actions.banSelected"),
2399
+ defaultMessage: "Ban {count} selected"
2400
+ },
2401
+ { count: selected.size }
2402
+ )
2403
+ }
2404
+ )
2405
+ ] })
2406
+ }
2407
+ ),
2408
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
2409
+ isError && /* @__PURE__ */ jsxRuntime.jsx(
2410
+ designSystem.Alert,
2411
+ {
2412
+ closeLabel: formatMessage({
2413
+ id: index.getTranslation("users.list.alert.close"),
2414
+ defaultMessage: "Close"
2415
+ }),
2416
+ title: formatMessage({
2417
+ id: index.getTranslation("users.list.alert.error"),
2418
+ defaultMessage: "Error"
2419
+ }),
2420
+ variant: "danger",
2421
+ children: error?.message
2422
+ }
2423
+ ),
2424
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: formatMessage({
2425
+ id: index.getTranslation("users.list.loading"),
2426
+ defaultMessage: "Loading users…"
2427
+ }) }) }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount, rowCount: users.length + 1, children: [
2428
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2429
+ canSelect && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(
2430
+ designSystem.Checkbox,
2431
+ {
2432
+ checked: allSelected ? true : someSelected ? "indeterminate" : false,
2433
+ onCheckedChange: handleSelectAll,
2434
+ "aria-label": formatMessage({
2435
+ id: index.getTranslation("users.list.table.selectAll"),
2436
+ defaultMessage: "Select all"
2437
+ })
2438
+ }
2439
+ ) }),
2440
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
2441
+ id: index.getTranslation("users.list.table.headers.name"),
2442
+ defaultMessage: "Name"
2443
+ }) }) }),
2444
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
2445
+ id: index.getTranslation("users.list.table.headers.email"),
2446
+ defaultMessage: "Email"
2447
+ }) }) }),
2448
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
2449
+ id: index.getTranslation("users.list.table.headers.status"),
2450
+ defaultMessage: "Status"
2451
+ }) }) }),
2452
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
2453
+ id: index.getTranslation("users.list.table.headers.created"),
2454
+ defaultMessage: "Created"
2455
+ }) }) }),
2456
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { children: formatMessage({
2457
+ id: index.getTranslation("users.list.table.headers.actions"),
2458
+ defaultMessage: "Actions"
2459
+ }) }) })
2460
+ ] }) }),
2461
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: colCount, children: /* @__PURE__ */ jsxRuntime.jsx(
2462
+ designSystem.Flex,
2463
+ {
2464
+ justifyContent: "center",
2465
+ padding: 8,
2466
+ "data-testid": "users-empty",
2467
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: search ? formatMessage(
2468
+ {
2469
+ id: index.getTranslation("users.list.empty.search"),
2470
+ defaultMessage: 'No users matching "{search}"'
2471
+ },
2472
+ { search }
2473
+ ) : formatMessage({
2474
+ id: index.getTranslation("users.list.empty.default"),
2475
+ defaultMessage: "No users found"
2476
+ }) })
2477
+ }
2478
+ ) }) }) : users.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { "data-testid": "user-row", children: [
2479
+ canSelect && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(
2480
+ designSystem.Checkbox,
2481
+ {
2482
+ checked: selected.has(user.id),
2483
+ onCheckedChange: () => toggleSelect(user.id),
2484
+ "aria-label": formatMessage(
2485
+ {
2486
+ id: index.getTranslation(
2487
+ "users.list.table.selectRow"
2488
+ ),
2489
+ defaultMessage: "Select {name}"
2490
+ },
2491
+ { name: user.name }
2492
+ )
2493
+ }
2494
+ ) }),
2495
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
2496
+ /* @__PURE__ */ jsxRuntime.jsx(
2497
+ Avatar.Avatar,
2498
+ {
2499
+ name: user.name ?? "",
2500
+ src: user.image,
2501
+ size: 28
2502
+ }
2503
+ ),
2504
+ /* @__PURE__ */ jsxRuntime.jsx(
2505
+ designSystem.Typography,
2506
+ {
2507
+ fontWeight: "semiBold",
2508
+ textColor: "neutral800",
2509
+ children: user.name
2510
+ }
2511
+ )
2512
+ ] }) }),
2513
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: user.email }) }),
2514
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
2515
+ user.emailVerified ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "success", size: "S", children: formatMessage({
2516
+ id: index.getTranslation(
2517
+ "users.list.status.verified"
2518
+ ),
2519
+ defaultMessage: "Verified"
2520
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "neutral", size: "S", children: formatMessage({
2521
+ id: index.getTranslation(
2522
+ "users.list.status.unverified"
2523
+ ),
2524
+ defaultMessage: "Unverified"
2525
+ }) }),
2526
+ user.banned && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "danger", size: "S", children: formatMessage({
2527
+ id: index.getTranslation("users.list.status.banned"),
2528
+ defaultMessage: "Banned"
2529
+ }) })
2530
+ ] }) }),
2531
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: new Date(user.createdAt).toLocaleDateString() }) }),
2532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "flex-end", children: [
2533
+ rbac.allowedActions.canUpdate && /* @__PURE__ */ jsxRuntime.jsx(
2534
+ designSystem.IconButton,
2535
+ {
2536
+ label: formatMessage({
2537
+ id: index.getTranslation("users.list.table.editUser"),
2538
+ defaultMessage: "Edit user"
2539
+ }),
2540
+ variant: "ghost",
2541
+ onClick: () => setDetailUserId(user.id),
2542
+ "data-testid": "edit-user-btn",
2543
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
2544
+ }
2545
+ ),
2546
+ rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(
2547
+ designSystem.IconButton,
2548
+ {
2549
+ label: formatMessage({
2550
+ id: index.getTranslation(
2551
+ "users.list.table.deleteUser"
2552
+ ),
2553
+ defaultMessage: "Delete user"
2554
+ }),
2555
+ variant: "ghost",
2556
+ onClick: () => setConfirmDelete(user.id),
2557
+ "data-testid": "delete-user-btn",
2558
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
2559
+ }
2560
+ )
2561
+ ] }) })
2562
+ ] }, user.id)) })
2563
+ ] }),
2564
+ pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", gap: 2, children: [
2565
+ /* @__PURE__ */ jsxRuntime.jsx(
2566
+ designSystem.Button,
2567
+ {
2568
+ variant: "tertiary",
2569
+ size: "S",
2570
+ disabled: page === 1,
2571
+ onClick: () => setPage((p) => p - 1),
2572
+ children: formatMessage({
2573
+ id: index.getTranslation("users.list.pagination.previous"),
2574
+ defaultMessage: "Previous"
2575
+ })
2576
+ }
2577
+ ),
2578
+ /* @__PURE__ */ jsxRuntime.jsx(
2579
+ designSystem.Button,
2580
+ {
2581
+ variant: "tertiary",
2582
+ size: "S",
2583
+ disabled: page >= pageCount,
2584
+ onClick: () => setPage((p) => p + 1),
2585
+ children: formatMessage({
2586
+ id: index.getTranslation("users.list.pagination.next"),
2587
+ defaultMessage: "Next"
2588
+ })
2589
+ }
2590
+ )
2591
+ ] })
2592
+ ] }) }),
2593
+ rbac.allowedActions.canCreate && showCreate && /* @__PURE__ */ jsxRuntime.jsx(CreateUserDialog, { onClose: () => setShowCreate(false) }),
2594
+ rbac.allowedActions.canUpdate && detailUserId && /* @__PURE__ */ jsxRuntime.jsx(
2595
+ UserDetailDrawer,
2596
+ {
2597
+ userId: detailUserId,
2598
+ banEnabled,
2599
+ emailVerificationEnabled,
2600
+ twoFactorEnabled,
2601
+ onClose: () => setDetailUserId(null)
2602
+ }
2603
+ ),
2604
+ rbac.allowedActions.canDelete && confirmDelete && /* @__PURE__ */ jsxRuntime.jsx(
2605
+ useModelSchema.ConfirmDialog,
2606
+ {
2607
+ title: formatMessage({
2608
+ id: index.getTranslation("users.list.confirmDelete.title"),
2609
+ defaultMessage: "Delete user"
2610
+ }),
2611
+ message: formatMessage({
2612
+ id: index.getTranslation("users.list.confirmDelete.message"),
2613
+ defaultMessage: "Are you sure you want to delete this user? This action cannot be undone."
2614
+ }),
2615
+ confirmLabel: formatMessage({
2616
+ id: index.getTranslation("users.list.confirmDelete.confirm"),
2617
+ defaultMessage: "Delete"
2618
+ }),
2619
+ loading: deleteMutation.isLoading,
2620
+ onConfirm: () => deleteMutation.mutate(confirmDelete),
2621
+ onCancel: () => setConfirmDelete(null)
2622
+ }
2623
+ ),
2624
+ rbac.allowedActions.canDelete && confirmDeleteMany && /* @__PURE__ */ jsxRuntime.jsx(
2625
+ useModelSchema.ConfirmDialog,
2626
+ {
2627
+ title: formatMessage(
2628
+ {
2629
+ id: index.getTranslation("users.list.confirmDeleteMany.title"),
2630
+ defaultMessage: "Delete {count, plural, one {# user} other {# users}}"
2631
+ },
2632
+ { count: selected.size }
2633
+ ),
2634
+ message: formatMessage(
2635
+ {
2636
+ id: index.getTranslation("users.list.confirmDeleteMany.message"),
2637
+ defaultMessage: "Are you sure you want to delete {count, plural, one {# user} other {# users}}? This action cannot be undone."
2638
+ },
2639
+ { count: selected.size }
2640
+ ),
2641
+ confirmLabel: formatMessage({
2642
+ id: index.getTranslation("users.list.confirmDeleteMany.confirm"),
2643
+ defaultMessage: "Delete all"
2644
+ }),
2645
+ loading: deleteManyMutation.isLoading,
2646
+ onConfirm: () => deleteManyMutation.mutate([...selected]),
2647
+ onCancel: () => setConfirmDeleteMany(false)
2648
+ }
2649
+ ),
2650
+ rbac.allowedActions.canUpdate && confirmBanMany && /* @__PURE__ */ jsxRuntime.jsx(
2651
+ useModelSchema.ConfirmDialog,
2652
+ {
2653
+ title: formatMessage(
2654
+ {
2655
+ id: index.getTranslation("users.list.confirmBanMany.title"),
2656
+ defaultMessage: "Ban {count, plural, one {# user} other {# users}}"
2657
+ },
2658
+ { count: selected.size }
2659
+ ),
2660
+ message: formatMessage(
2661
+ {
2662
+ id: index.getTranslation("users.list.confirmBanMany.message"),
2663
+ defaultMessage: "Are you sure you want to ban {count, plural, one {# user} other {# users}}? They will be prevented from signing in."
2664
+ },
2665
+ { count: selected.size }
2666
+ ),
2667
+ confirmLabel: formatMessage({
2668
+ id: index.getTranslation("users.list.confirmBanMany.confirm"),
2669
+ defaultMessage: "Ban all"
2670
+ }),
2671
+ variant: "danger",
2672
+ loading: banManyMutation.isLoading,
2673
+ onConfirm: () => banManyMutation.mutate([...selected]),
2674
+ onCancel: () => setConfirmBanMany(false)
2675
+ }
2676
+ )
2677
+ ] });
2678
+ }
2679
+ function ProtectedUsersPage() {
2680
+ const { formatMessage } = reactIntl.useIntl();
2681
+ const { data: config, isError, error } = index.useDashConfig();
2682
+ if (isError) {
2683
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(
2684
+ designSystem.Alert,
2685
+ {
2686
+ closeLabel: formatMessage({
2687
+ id: index.getTranslation("users.config.close"),
2688
+ defaultMessage: "Close"
2689
+ }),
2690
+ title: formatMessage({
2691
+ id: index.getTranslation("users.config.errorTitle"),
2692
+ defaultMessage: "Error loading configuration"
2693
+ }),
2694
+ variant: "danger",
2695
+ children: error.message
2696
+ }
2697
+ ) });
2698
+ }
2699
+ if (!config) {
2700
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", alignItems: "center", padding: 12, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: formatMessage({
2701
+ id: index.getTranslation("users.config.loading"),
2702
+ defaultMessage: "Loading user configuration…"
2703
+ }) }) });
2704
+ }
2705
+ return /* @__PURE__ */ jsxRuntime.jsx(
2706
+ admin.Page.Protect,
2707
+ {
2708
+ permissions: index.PERMISSIONS.user.filter(
2709
+ (p) => p.action === `plugin::${index.PLUGIN_ID}.user.read`
2710
+ ),
2711
+ children: /* @__PURE__ */ jsxRuntime.jsx(UsersPage, { config })
2712
+ }
2713
+ );
2714
+ }
2715
+ exports.UsersPage = UsersPage;
2716
+ exports.default = ProtectedUsersPage;