@open-mercato/core 0.6.8-develop.7101.1.bd9356828a → 0.7.1-develop.7102.1.b41f7e3e51

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 (159) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/login.js +19 -0
  3. package/dist/modules/auth/api/login.js.map +2 -2
  4. package/dist/modules/auth/api/routeError.js +11 -0
  5. package/dist/modules/auth/api/routeError.js.map +7 -0
  6. package/dist/modules/auth/api/users/acl/route.js +23 -7
  7. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  8. package/dist/modules/business_rules/backend/rules/[id]/page.meta.js +6 -1
  9. package/dist/modules/business_rules/backend/rules/[id]/page.meta.js.map +2 -2
  10. package/dist/modules/business_rules/backend/rules/create/page.meta.js +5 -1
  11. package/dist/modules/business_rules/backend/rules/create/page.meta.js.map +2 -2
  12. package/dist/modules/business_rules/backend/sets/[id]/page.meta.js +6 -1
  13. package/dist/modules/business_rules/backend/sets/[id]/page.meta.js.map +2 -2
  14. package/dist/modules/business_rules/backend/sets/create/page.meta.js +5 -1
  15. package/dist/modules/business_rules/backend/sets/create/page.meta.js.map +2 -2
  16. package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js +84 -0
  17. package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js.map +7 -0
  18. package/dist/modules/customer_accounts/backend/customer_accounts/settings/page.js +7 -81
  19. package/dist/modules/customer_accounts/backend/customer_accounts/settings/page.js.map +2 -2
  20. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +525 -0
  21. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +7 -0
  22. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js +731 -0
  23. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js.map +7 -0
  24. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +7 -727
  25. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  26. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +7 -521
  27. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  28. package/dist/modules/customer_accounts/extension-points.js +1 -1
  29. package/dist/modules/customer_accounts/extension-points.js.map +2 -2
  30. package/dist/modules/customer_accounts/lib/portalUrl.js +69 -0
  31. package/dist/modules/customer_accounts/lib/portalUrl.js.map +7 -0
  32. package/dist/modules/customers/backend/customers/companies/page.js +2 -2
  33. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  34. package/dist/modules/customers/backend/customers/deals/page.js +2 -2
  35. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  36. package/dist/modules/customers/backend/customers/people/page.js +2 -2
  37. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  38. package/dist/modules/customers/components/detail/AssignRoleDialog.js +33 -28
  39. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  40. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js +3 -1
  41. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js.map +2 -2
  42. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js +1 -0
  43. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js.map +2 -2
  44. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js +4 -1
  45. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js.map +2 -2
  46. package/dist/modules/eudr/backend/eudr/page.meta.js +2 -2
  47. package/dist/modules/eudr/backend/eudr/page.meta.js.map +2 -2
  48. package/dist/modules/eudr/backend/eudr/plots/[id]/page.js +8 -2
  49. package/dist/modules/eudr/backend/eudr/plots/[id]/page.js.map +2 -2
  50. package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js +3 -1
  51. package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js.map +2 -2
  52. package/dist/modules/eudr/backend/eudr/plots/create/page.js +8 -2
  53. package/dist/modules/eudr/backend/eudr/plots/create/page.js.map +2 -2
  54. package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js +1 -0
  55. package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js.map +2 -2
  56. package/dist/modules/eudr/backend/eudr/plots/page.meta.js +4 -1
  57. package/dist/modules/eudr/backend/eudr/plots/page.meta.js.map +2 -2
  58. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js +3 -1
  59. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js.map +2 -2
  60. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js +1 -0
  61. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js.map +2 -2
  62. package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js +4 -1
  63. package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js.map +2 -2
  64. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js +3 -1
  65. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js.map +2 -2
  66. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js +1 -0
  67. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js.map +2 -2
  68. package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js +4 -1
  69. package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js.map +2 -2
  70. package/dist/modules/eudr/backend/eudr/statements/[id]/page.js +2 -0
  71. package/dist/modules/eudr/backend/eudr/statements/[id]/page.js.map +2 -2
  72. package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js +3 -1
  73. package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js.map +2 -2
  74. package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js +1 -0
  75. package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js.map +2 -2
  76. package/dist/modules/eudr/backend/eudr/statements/page.js +30 -25
  77. package/dist/modules/eudr/backend/eudr/statements/page.js.map +2 -2
  78. package/dist/modules/eudr/backend/eudr/statements/page.meta.js +4 -1
  79. package/dist/modules/eudr/backend/eudr/statements/page.meta.js.map +2 -2
  80. package/dist/modules/eudr/commands/statements.js +5 -3
  81. package/dist/modules/eudr/commands/statements.js.map +2 -2
  82. package/dist/modules/eudr/components/crudErrorI18n.js +21 -1
  83. package/dist/modules/eudr/components/crudErrorI18n.js.map +2 -2
  84. package/dist/modules/eudr/components/formConfig.js +6 -2
  85. package/dist/modules/eudr/components/formConfig.js.map +2 -2
  86. package/dist/modules/eudr/lib/statement-lifecycle.js +9 -1
  87. package/dist/modules/eudr/lib/statement-lifecycle.js.map +2 -2
  88. package/dist/modules/sales/components/documents/LineItemDialog.js +1 -1
  89. package/dist/modules/sales/components/documents/LineItemDialog.js.map +1 -1
  90. package/dist/modules/sales/widgets/injection/document-history/widget.client.js +44 -2
  91. package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
  92. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +20 -16
  93. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
  94. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +10 -6
  95. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
  96. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +11 -7
  97. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +2 -2
  98. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +10 -6
  99. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
  100. package/package.json +8 -8
  101. package/src/modules/auth/api/login.ts +23 -0
  102. package/src/modules/auth/api/routeError.ts +30 -0
  103. package/src/modules/auth/api/users/acl/route.ts +49 -9
  104. package/src/modules/business_rules/backend/rules/[id]/page.meta.ts +5 -0
  105. package/src/modules/business_rules/backend/rules/create/page.meta.ts +5 -1
  106. package/src/modules/business_rules/backend/sets/[id]/page.meta.ts +5 -0
  107. package/src/modules/business_rules/backend/sets/create/page.meta.ts +5 -1
  108. package/src/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.tsx +138 -0
  109. package/src/modules/customer_accounts/backend/customer_accounts/settings/page.tsx +8 -132
  110. package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +601 -0
  111. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.tsx +838 -0
  112. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +6 -838
  113. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -590
  114. package/src/modules/customer_accounts/extension-points.ts +1 -1
  115. package/src/modules/customer_accounts/lib/portalUrl.ts +112 -0
  116. package/src/modules/customers/backend/customers/companies/page.tsx +2 -2
  117. package/src/modules/customers/backend/customers/deals/page.tsx +2 -2
  118. package/src/modules/customers/backend/customers/people/page.tsx +2 -2
  119. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +12 -7
  120. package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.ts +2 -0
  121. package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.ts +1 -0
  122. package/src/modules/eudr/backend/eudr/evidence-submissions/page.meta.ts +4 -1
  123. package/src/modules/eudr/backend/eudr/page.meta.ts +2 -2
  124. package/src/modules/eudr/backend/eudr/plots/[id]/page.meta.ts +2 -0
  125. package/src/modules/eudr/backend/eudr/plots/[id]/page.tsx +8 -2
  126. package/src/modules/eudr/backend/eudr/plots/create/page.meta.ts +1 -0
  127. package/src/modules/eudr/backend/eudr/plots/create/page.tsx +8 -2
  128. package/src/modules/eudr/backend/eudr/plots/page.meta.ts +4 -1
  129. package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.ts +2 -0
  130. package/src/modules/eudr/backend/eudr/product-mappings/create/page.meta.ts +1 -0
  131. package/src/modules/eudr/backend/eudr/product-mappings/page.meta.ts +4 -1
  132. package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.ts +2 -0
  133. package/src/modules/eudr/backend/eudr/risk-assessments/create/page.meta.ts +1 -0
  134. package/src/modules/eudr/backend/eudr/risk-assessments/page.meta.ts +4 -1
  135. package/src/modules/eudr/backend/eudr/statements/[id]/page.meta.ts +2 -0
  136. package/src/modules/eudr/backend/eudr/statements/[id]/page.tsx +2 -0
  137. package/src/modules/eudr/backend/eudr/statements/create/page.meta.ts +1 -0
  138. package/src/modules/eudr/backend/eudr/statements/page.meta.ts +4 -1
  139. package/src/modules/eudr/backend/eudr/statements/page.tsx +32 -26
  140. package/src/modules/eudr/commands/statements.ts +4 -2
  141. package/src/modules/eudr/components/crudErrorI18n.ts +31 -0
  142. package/src/modules/eudr/components/formConfig.tsx +9 -1
  143. package/src/modules/eudr/i18n/de.json +3 -2
  144. package/src/modules/eudr/i18n/en.json +3 -2
  145. package/src/modules/eudr/i18n/es.json +3 -2
  146. package/src/modules/eudr/i18n/ko.json +3 -2
  147. package/src/modules/eudr/i18n/pl.json +3 -2
  148. package/src/modules/eudr/lib/statement-lifecycle.ts +8 -0
  149. package/src/modules/sales/components/documents/LineItemDialog.tsx +1 -1
  150. package/src/modules/sales/i18n/de.json +37 -0
  151. package/src/modules/sales/i18n/en.json +37 -0
  152. package/src/modules/sales/i18n/es.json +37 -0
  153. package/src/modules/sales/i18n/ko.json +37 -0
  154. package/src/modules/sales/i18n/pl.json +37 -0
  155. package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +42 -2
  156. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +30 -26
  157. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +21 -17
  158. package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +17 -13
  159. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +16 -12
@@ -0,0 +1,731 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useRouter } from "next/navigation";
6
+ import { FormHeader } from "@open-mercato/ui/backend/forms";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { Input } from "@open-mercato/ui/primitives/input";
9
+ import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
10
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
11
+ import { SwitchField } from "@open-mercato/ui/primitives/switch-field";
12
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
13
+ import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
14
+ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
15
+ import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
16
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
17
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
18
+ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
19
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
20
+ import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
21
+ import { buildPortalUrlPattern } from "../../../../lib/portalUrl.js";
22
+ function formatDate(value, fallback) {
23
+ if (!value) return fallback;
24
+ const date = new Date(value);
25
+ if (Number.isNaN(date.getTime())) return fallback;
26
+ return date.toLocaleString();
27
+ }
28
+ function ResetPasswordDialog({
29
+ open,
30
+ onOpenChange,
31
+ userId,
32
+ onRunMutation
33
+ }) {
34
+ const t = useT();
35
+ const [newPassword, setNewPassword] = React.useState("");
36
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
37
+ const handleSubmit = React.useCallback(async (event) => {
38
+ event.preventDefault();
39
+ if (!newPassword.trim() || newPassword.length < 8) {
40
+ flash(t("customer_accounts.admin.detail.resetPassword.error.minLength", "Password must be at least 8 characters"), "error");
41
+ return;
42
+ }
43
+ setIsSubmitting(true);
44
+ try {
45
+ await onRunMutation(async () => {
46
+ const call = await apiCall(
47
+ `/api/customer_accounts/admin/users/${encodeURIComponent(userId)}/reset-password`,
48
+ {
49
+ method: "POST",
50
+ headers: { "content-type": "application/json" },
51
+ body: JSON.stringify({ newPassword })
52
+ }
53
+ );
54
+ if (!call.ok) {
55
+ flash(call.result?.error || t("customer_accounts.admin.detail.resetPassword.error.save", "Failed to reset password"), "error");
56
+ return;
57
+ }
58
+ flash(t("customer_accounts.admin.detail.resetPassword.flash.success", "Password reset successfully"), "success");
59
+ setNewPassword("");
60
+ onOpenChange(false);
61
+ });
62
+ } catch (err) {
63
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.resetPassword.error.save", "Failed to reset password");
64
+ flash(message, "error");
65
+ } finally {
66
+ setIsSubmitting(false);
67
+ }
68
+ }, [newPassword, onOpenChange, onRunMutation, t, userId]);
69
+ const handleKeyDown = React.useCallback((event) => {
70
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
71
+ event.preventDefault();
72
+ const form = event.target.closest("form");
73
+ if (form) form.requestSubmit();
74
+ }
75
+ }, []);
76
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => {
77
+ if (!next) setNewPassword("");
78
+ onOpenChange(next);
79
+ }, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
80
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("customer_accounts.admin.detail.resetPassword.title", "Reset Password") }) }),
81
+ /* @__PURE__ */ jsxs("form", { onSubmit: (event) => {
82
+ void handleSubmit(event);
83
+ }, onKeyDown: handleKeyDown, className: "space-y-4", children: [
84
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
85
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", htmlFor: "reset-password", children: t("customer_accounts.admin.detail.resetPassword.fields.newPassword", "New Password") }),
86
+ /* @__PURE__ */ jsx(
87
+ PasswordInput,
88
+ {
89
+ id: "reset-password",
90
+ required: true,
91
+ minLength: 8,
92
+ value: newPassword,
93
+ onChange: (event) => setNewPassword(event.target.value),
94
+ placeholder: t("customer_accounts.admin.detail.resetPassword.fields.placeholder", "Min. 8 characters"),
95
+ autoComplete: "new-password"
96
+ }
97
+ )
98
+ ] }),
99
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
100
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
101
+ setNewPassword("");
102
+ onOpenChange(false);
103
+ }, children: t("customer_accounts.admin.detail.resetPassword.actions.cancel", "Cancel") }),
104
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? t("customer_accounts.admin.detail.resetPassword.actions.resetting", "Resetting...") : t("customer_accounts.admin.detail.resetPassword.actions.reset", "Reset Password") })
105
+ ] })
106
+ ] })
107
+ ] }) });
108
+ }
109
+ function PortalUserDetailPageClient({ params, portalOrigin }) {
110
+ const id = params?.id;
111
+ const t = useT();
112
+ const router = useRouter();
113
+ const { confirm, ConfirmDialogElement } = useConfirmDialog();
114
+ const [data, setData] = React.useState(null);
115
+ const [isLoading, setIsLoading] = React.useState(true);
116
+ const [error, setError] = React.useState(null);
117
+ const [isNotFound, setIsNotFound] = React.useState(false);
118
+ const [isSaving, setIsSaving] = React.useState(false);
119
+ const [editActive, setEditActive] = React.useState(null);
120
+ const [editDisplayName, setEditDisplayName] = React.useState("");
121
+ const [availableRoles, setAvailableRoles] = React.useState([]);
122
+ const [selectedRoleIds, setSelectedRoleIds] = React.useState([]);
123
+ const [resetPasswordOpen, setResetPasswordOpen] = React.useState(false);
124
+ const [isVerifying, setIsVerifying] = React.useState(false);
125
+ const [editPersonEntityId, setEditPersonEntityId] = React.useState(null);
126
+ const [editCustomerEntityId, setEditCustomerEntityId] = React.useState(null);
127
+ const [personSearchQuery, setPersonSearchQuery] = React.useState("");
128
+ const [companySearchQuery, setCompanySearchQuery] = React.useState("");
129
+ const [personResults, setPersonResults] = React.useState([]);
130
+ const [companyResults, setCompanyResults] = React.useState([]);
131
+ const [personName, setPersonName] = React.useState(null);
132
+ const [companyName, setCompanyName] = React.useState(null);
133
+ const [isSendingResetLink, setIsSendingResetLink] = React.useState(false);
134
+ const [resetLinkUrl, setResetLinkUrl] = React.useState(null);
135
+ const mutationContextId = `customer_accounts:user:${id ?? "pending"}`;
136
+ const { runMutation, retryLastMutation } = useGuardedMutation({
137
+ contextId: mutationContextId
138
+ });
139
+ const runMutationWithContext = React.useCallback(
140
+ async (operation, mutationPayload) => {
141
+ return runMutation({
142
+ operation,
143
+ mutationPayload,
144
+ context: { entityType: "customer_accounts:user", entityId: id }
145
+ });
146
+ },
147
+ [id, runMutation]
148
+ );
149
+ React.useEffect(() => {
150
+ if (!id) {
151
+ setIsNotFound(true);
152
+ setIsLoading(false);
153
+ return;
154
+ }
155
+ let cancelled = false;
156
+ async function load() {
157
+ setIsLoading(true);
158
+ setError(null);
159
+ setIsNotFound(false);
160
+ try {
161
+ const payload = await readApiResultOrThrow(
162
+ `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,
163
+ void 0,
164
+ { errorMessage: t("customer_accounts.admin.detail.error.load", "Failed to load user") }
165
+ );
166
+ if (cancelled) return;
167
+ setData(payload);
168
+ setEditActive(payload.isActive);
169
+ setEditDisplayName(payload.displayName);
170
+ setSelectedRoleIds(payload.roles.map((role) => role.id));
171
+ setEditPersonEntityId(payload.personEntityId);
172
+ setEditCustomerEntityId(payload.customerEntityId);
173
+ } catch (err) {
174
+ if (cancelled) return;
175
+ if (err.status === 404) {
176
+ setIsNotFound(true);
177
+ } else {
178
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.error.load", "Failed to load user");
179
+ setError(message);
180
+ }
181
+ } finally {
182
+ if (!cancelled) setIsLoading(false);
183
+ }
184
+ }
185
+ load();
186
+ return () => {
187
+ cancelled = true;
188
+ };
189
+ }, [id, t]);
190
+ React.useEffect(() => {
191
+ let cancelled = false;
192
+ async function loadRoles() {
193
+ try {
194
+ const call = await apiCall(
195
+ "/api/customer_accounts/admin/roles?pageSize=100"
196
+ );
197
+ if (cancelled || !call.ok) return;
198
+ const items = Array.isArray(call.result?.items) ? call.result.items : [];
199
+ setAvailableRoles(
200
+ items.filter((item) => typeof item?.id === "string" && typeof item?.name === "string")
201
+ );
202
+ } catch {
203
+ }
204
+ }
205
+ loadRoles();
206
+ return () => {
207
+ cancelled = true;
208
+ };
209
+ }, []);
210
+ React.useEffect(() => {
211
+ if (!data) return;
212
+ let cancelled = false;
213
+ async function loadPersonName() {
214
+ if (!data.personEntityId) return;
215
+ try {
216
+ const call = await apiCall(`/api/customers/people/${encodeURIComponent(data.personEntityId)}`);
217
+ if (!cancelled && call.ok && call.result) {
218
+ setPersonName([call.result.firstName, call.result.lastName].filter(Boolean).join(" ") || call.result.id || null);
219
+ }
220
+ } catch {
221
+ }
222
+ }
223
+ async function loadCompanyName() {
224
+ if (!data.customerEntityId) return;
225
+ try {
226
+ const call = await apiCall(`/api/customers/${encodeURIComponent(data.customerEntityId)}`);
227
+ if (!cancelled && call.ok && call.result) {
228
+ setCompanyName(call.result.name || call.result.id || null);
229
+ }
230
+ } catch {
231
+ }
232
+ }
233
+ void Promise.all([loadPersonName(), loadCompanyName()]);
234
+ return () => {
235
+ cancelled = true;
236
+ };
237
+ }, [data]);
238
+ const handleSearchPeople = React.useCallback(async (query) => {
239
+ setPersonSearchQuery(query);
240
+ if (query.trim().length < 2) {
241
+ setPersonResults([]);
242
+ return;
243
+ }
244
+ try {
245
+ const call = await apiCall(
246
+ `/api/customers/people?search=${encodeURIComponent(query.trim())}&pageSize=10`
247
+ );
248
+ if (call.ok && Array.isArray(call.result?.items)) {
249
+ setPersonResults(call.result.items.map((person) => ({
250
+ id: person.id,
251
+ label: [person.firstName, person.lastName].filter(Boolean).join(" ") || person.email || person.id
252
+ })));
253
+ }
254
+ } catch {
255
+ }
256
+ }, []);
257
+ const handleSearchCompanies = React.useCallback(async (query) => {
258
+ setCompanySearchQuery(query);
259
+ if (query.trim().length < 2) {
260
+ setCompanyResults([]);
261
+ return;
262
+ }
263
+ try {
264
+ const call = await apiCall(
265
+ `/api/customers?search=${encodeURIComponent(query.trim())}&pageSize=10`
266
+ );
267
+ if (call.ok && Array.isArray(call.result?.items)) {
268
+ setCompanyResults(call.result.items.map((company) => ({
269
+ id: company.id,
270
+ label: company.name || company.id
271
+ })));
272
+ }
273
+ } catch {
274
+ }
275
+ }, []);
276
+ const handleSendResetLink = React.useCallback(async () => {
277
+ if (!id) return;
278
+ setIsSendingResetLink(true);
279
+ try {
280
+ await runMutationWithContext(async () => {
281
+ const call = await apiCall(
282
+ `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/send-reset-link`,
283
+ { method: "POST" }
284
+ );
285
+ if (!call.ok || !call.result?.resetLink) {
286
+ flash(call.result?.error || t("customer_accounts.admin.detail.sendResetLink.error", "Failed to generate reset link"), "error");
287
+ return;
288
+ }
289
+ setResetLinkUrl(call.result.resetLink);
290
+ flash(t("customer_accounts.admin.detail.sendResetLink.flash.success", "Reset link generated"), "success");
291
+ }, { id });
292
+ } catch (err) {
293
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.sendResetLink.error", "Failed to generate reset link");
294
+ flash(message, "error");
295
+ } finally {
296
+ setIsSendingResetLink(false);
297
+ }
298
+ }, [id, runMutationWithContext, t]);
299
+ const handleSave = React.useCallback(async () => {
300
+ if (!data || !id) return;
301
+ setIsSaving(true);
302
+ try {
303
+ await runMutationWithContext(async () => {
304
+ const call = await withScopedApiRequestHeaders(
305
+ buildOptimisticLockHeader(data.updatedAt),
306
+ () => apiCall(
307
+ `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,
308
+ {
309
+ method: "PUT",
310
+ headers: { "content-type": "application/json" },
311
+ body: JSON.stringify({
312
+ displayName: editDisplayName.trim() || void 0,
313
+ isActive: editActive,
314
+ roleIds: selectedRoleIds,
315
+ personEntityId: editPersonEntityId,
316
+ customerEntityId: editCustomerEntityId
317
+ })
318
+ }
319
+ )
320
+ );
321
+ if (!call.ok) {
322
+ if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return;
323
+ flash(call.result?.error || t("customer_accounts.admin.detail.error.save", "Failed to save user"), "error");
324
+ return;
325
+ }
326
+ flash(t("customer_accounts.admin.detail.flash.saved", "User updated"), "success");
327
+ setData((prev) => prev ? {
328
+ ...prev,
329
+ isActive: editActive ?? prev.isActive,
330
+ displayName: editDisplayName.trim() || prev.displayName,
331
+ personEntityId: editPersonEntityId,
332
+ customerEntityId: editCustomerEntityId,
333
+ updatedAt: call.result?.updatedAt ?? prev.updatedAt
334
+ } : prev);
335
+ }, { displayName: editDisplayName, isActive: editActive, roleIds: selectedRoleIds, personEntityId: editPersonEntityId, customerEntityId: editCustomerEntityId });
336
+ } catch (err) {
337
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.error.save", "Failed to save user");
338
+ flash(message, "error");
339
+ } finally {
340
+ setIsSaving(false);
341
+ }
342
+ }, [data, editActive, editCustomerEntityId, editDisplayName, editPersonEntityId, id, runMutationWithContext, selectedRoleIds, t]);
343
+ const handleDelete = React.useCallback(async () => {
344
+ if (!data || !id) return;
345
+ const confirmed = await confirm({
346
+ title: t("customer_accounts.admin.confirm.delete", 'Delete user "{{name}}"?', {
347
+ name: data.displayName || data.email
348
+ }),
349
+ variant: "destructive"
350
+ });
351
+ if (!confirmed) return;
352
+ try {
353
+ await runMutationWithContext(async () => {
354
+ const call = await withScopedApiRequestHeaders(
355
+ buildOptimisticLockHeader(data.updatedAt),
356
+ () => apiCall(
357
+ `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,
358
+ { method: "DELETE" }
359
+ )
360
+ );
361
+ if (!call.ok) {
362
+ if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return;
363
+ flash(t("customer_accounts.admin.error.delete", "Failed to delete user"), "error");
364
+ return;
365
+ }
366
+ flash(t("customer_accounts.admin.flash.deleted", "User deleted"), "success");
367
+ router.push("/backend/customer_accounts/users");
368
+ }, { id });
369
+ } catch (err) {
370
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.error.delete", "Failed to delete user");
371
+ flash(message, "error");
372
+ }
373
+ }, [confirm, data, id, router, runMutationWithContext, t]);
374
+ const handleVerifyEmail = React.useCallback(async () => {
375
+ if (!data || !id) return;
376
+ const confirmed = await confirm({
377
+ title: t("customer_accounts.admin.detail.verifyEmail.confirm", 'Mark email as verified for "{{name}}"?', {
378
+ name: data.displayName || data.email
379
+ })
380
+ });
381
+ if (!confirmed) return;
382
+ setIsVerifying(true);
383
+ try {
384
+ await runMutationWithContext(async () => {
385
+ const call = await apiCall(
386
+ `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/verify-email`,
387
+ { method: "POST" }
388
+ );
389
+ if (!call.ok) {
390
+ flash(call.result?.error || t("customer_accounts.admin.detail.verifyEmail.error", "Failed to verify email"), "error");
391
+ return;
392
+ }
393
+ flash(t("customer_accounts.admin.detail.verifyEmail.flash.success", "Email marked as verified"), "success");
394
+ setData((prev) => prev ? { ...prev, emailVerifiedAt: (/* @__PURE__ */ new Date()).toISOString() } : prev);
395
+ }, { id });
396
+ } catch (err) {
397
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.verifyEmail.error", "Failed to verify email");
398
+ flash(message, "error");
399
+ } finally {
400
+ setIsVerifying(false);
401
+ }
402
+ }, [confirm, data, id, runMutationWithContext, t]);
403
+ const handleRevokeSession = React.useCallback(async (sessionId) => {
404
+ if (!id) return;
405
+ try {
406
+ await runMutationWithContext(async () => {
407
+ const call = await apiCall(
408
+ `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/sessions/${encodeURIComponent(sessionId)}`,
409
+ { method: "DELETE" }
410
+ );
411
+ if (!call.ok) {
412
+ flash(t("customer_accounts.admin.detail.error.revokeSession", "Failed to revoke session"), "error");
413
+ return;
414
+ }
415
+ flash(t("customer_accounts.admin.detail.flash.sessionRevoked", "Session revoked"), "success");
416
+ setData((prev) => {
417
+ if (!prev) return prev;
418
+ return { ...prev, sessions: prev.sessions.filter((session) => session.id !== sessionId) };
419
+ });
420
+ }, { sessionId });
421
+ } catch (err) {
422
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.error.revokeSession", "Failed to revoke session");
423
+ flash(message, "error");
424
+ }
425
+ }, [id, runMutationWithContext, t]);
426
+ const handleRoleToggle = React.useCallback((roleId) => {
427
+ setSelectedRoleIds(
428
+ (prev) => prev.includes(roleId) ? prev.filter((existingId) => existingId !== roleId) : [...prev, roleId]
429
+ );
430
+ }, []);
431
+ if (isLoading) {
432
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground", children: [
433
+ /* @__PURE__ */ jsx(Spinner, { className: "h-6 w-6" }),
434
+ /* @__PURE__ */ jsx("span", { children: t("customer_accounts.admin.detail.loading", "Loading user...") })
435
+ ] });
436
+ }
437
+ if (isNotFound) {
438
+ return /* @__PURE__ */ jsx(
439
+ RecordNotFoundState,
440
+ {
441
+ label: t("customer_accounts.admin.detail.error.notFound", "User not found"),
442
+ backHref: "/backend/customer_accounts/users",
443
+ backLabel: t("customer_accounts.admin.detail.actions.backToList", "Back to list")
444
+ }
445
+ );
446
+ }
447
+ if (error || !data) {
448
+ return /* @__PURE__ */ jsx(
449
+ ErrorMessage,
450
+ {
451
+ label: error ?? t("customer_accounts.admin.detail.error.notFound", "User not found"),
452
+ action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer_accounts/users", children: t("customer_accounts.admin.detail.actions.backToList", "Back to list") }) })
453
+ }
454
+ );
455
+ }
456
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
457
+ /* @__PURE__ */ jsx(
458
+ FormHeader,
459
+ {
460
+ mode: "detail",
461
+ backHref: "/backend/customer_accounts/users",
462
+ backLabel: t("customer_accounts.admin.detail.actions.backToList", "Back to list"),
463
+ title: data.displayName,
464
+ subtitle: data.email,
465
+ onDelete: () => {
466
+ void handleDelete();
467
+ },
468
+ deleteLabel: t("customer_accounts.admin.detail.actions.delete", "Delete")
469
+ }
470
+ ),
471
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-status-info-border bg-status-info-bg p-4", children: /* @__PURE__ */ jsx("div", { className: "flex items-start gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
472
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-status-info-text", children: t("customer_accounts.admin.detail.portalAccess.title", "Customer Portal Access") }),
473
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-status-info-text", children: t("customer_accounts.admin.detail.portalAccess.description", "This user can access the customer portal at the URL below. The portal provides self-service access to orders, invoices, quotes, and account management.") }),
474
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-status-info-text", children: t("customer_accounts.admin.detail.portalAccess.url", "Portal URL: {url}", {
475
+ url: buildPortalUrlPattern(portalOrigin)
476
+ }) })
477
+ ] }) }) }),
478
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [
479
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [
480
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t("customer_accounts.admin.detail.sections.info", "User Information") }),
481
+ /* @__PURE__ */ jsxs("dl", { className: "space-y-2 text-sm", children: [
482
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
483
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: t("customer_accounts.admin.detail.fields.email", "Email") }),
484
+ /* @__PURE__ */ jsx("dd", { children: data.email })
485
+ ] }),
486
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
487
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: t("customer_accounts.admin.detail.fields.emailVerified", "Email Verified") }),
488
+ /* @__PURE__ */ jsxs("dd", { className: "flex items-center gap-2", children: [
489
+ /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${data.emailVerifiedAt ? "bg-status-success-bg text-status-success-text" : "bg-status-warning-bg text-status-warning-text"}`, children: data.emailVerifiedAt ? t("customer_accounts.admin.verified", "Yes") : t("customer_accounts.admin.unverified", "No") }),
490
+ !data.emailVerifiedAt && /* @__PURE__ */ jsx(
491
+ Button,
492
+ {
493
+ variant: "outline",
494
+ size: "sm",
495
+ onClick: () => {
496
+ void handleVerifyEmail();
497
+ },
498
+ disabled: isVerifying,
499
+ children: isVerifying ? t("customer_accounts.admin.detail.verifyEmail.actions.verifying", "Verifying...") : t("customer_accounts.admin.detail.verifyEmail.actions.verify", "Mark Verified")
500
+ }
501
+ )
502
+ ] })
503
+ ] }),
504
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
505
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: t("customer_accounts.admin.detail.fields.lastLogin", "Last Login") }),
506
+ /* @__PURE__ */ jsx("dd", { children: formatDate(data.lastLoginAt, "-") })
507
+ ] }),
508
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
509
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: t("customer_accounts.admin.detail.fields.createdAt", "Created") }),
510
+ /* @__PURE__ */ jsx("dd", { children: formatDate(data.createdAt, "-") })
511
+ ] })
512
+ ] })
513
+ ] }),
514
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [
515
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t("customer_accounts.admin.detail.sections.crmLinks", "CRM Links") }),
516
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 text-sm", children: [
517
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
518
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("customer_accounts.admin.detail.fields.personEntity", "Linked Person") }),
519
+ editPersonEntityId ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
520
+ /* @__PURE__ */ jsx(Link, { href: `/backend/customers/people/${editPersonEntityId}`, className: "text-primary hover:underline", children: personName || editPersonEntityId }),
521
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
522
+ setEditPersonEntityId(null);
523
+ setPersonName(null);
524
+ }, children: t("customer_accounts.admin.detail.actions.unlink", "Unlink") })
525
+ ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
526
+ /* @__PURE__ */ jsx(
527
+ Input,
528
+ {
529
+ type: "text",
530
+ value: personSearchQuery,
531
+ onChange: (event) => {
532
+ void handleSearchPeople(event.target.value);
533
+ },
534
+ placeholder: t("customer_accounts.admin.detail.fields.searchPerson", "Search people by name...")
535
+ }
536
+ ),
537
+ personResults.length > 0 && /* @__PURE__ */ jsx("div", { className: "absolute z-10 mt-1 w-full rounded-md border bg-background shadow-lg max-h-40 overflow-y-auto", children: personResults.map((person) => /* @__PURE__ */ jsx(
538
+ "button",
539
+ {
540
+ type: "button",
541
+ className: "w-full px-3 py-2 text-left text-sm hover:bg-muted",
542
+ onClick: () => {
543
+ setEditPersonEntityId(person.id);
544
+ setPersonName(person.label);
545
+ setPersonSearchQuery("");
546
+ setPersonResults([]);
547
+ },
548
+ children: person.label
549
+ },
550
+ person.id
551
+ )) })
552
+ ] }) })
553
+ ] }),
554
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
555
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("customer_accounts.admin.detail.fields.customerEntity", "Linked Company") }),
556
+ editCustomerEntityId ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
557
+ /* @__PURE__ */ jsx(Link, { href: `/backend/customers/companies/${editCustomerEntityId}`, className: "text-primary hover:underline", children: companyName || editCustomerEntityId }),
558
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
559
+ setEditCustomerEntityId(null);
560
+ setCompanyName(null);
561
+ }, children: t("customer_accounts.admin.detail.actions.unlink", "Unlink") })
562
+ ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
563
+ /* @__PURE__ */ jsx(
564
+ Input,
565
+ {
566
+ type: "text",
567
+ value: companySearchQuery,
568
+ onChange: (event) => {
569
+ void handleSearchCompanies(event.target.value);
570
+ },
571
+ placeholder: t("customer_accounts.admin.detail.fields.searchCompany", "Search companies by name...")
572
+ }
573
+ ),
574
+ companyResults.length > 0 && /* @__PURE__ */ jsx("div", { className: "absolute z-10 mt-1 w-full rounded-md border bg-background shadow-lg max-h-40 overflow-y-auto", children: companyResults.map((company) => /* @__PURE__ */ jsx(
575
+ "button",
576
+ {
577
+ type: "button",
578
+ className: "w-full px-3 py-2 text-left text-sm hover:bg-muted",
579
+ onClick: () => {
580
+ setEditCustomerEntityId(company.id);
581
+ setCompanyName(company.label);
582
+ setCompanySearchQuery("");
583
+ setCompanyResults([]);
584
+ },
585
+ children: company.label
586
+ },
587
+ company.id
588
+ )) })
589
+ ] }) })
590
+ ] }),
591
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customer_accounts.admin.detail.crmLinks.hint", "Changes to CRM links are saved when you click Save Changes below.") })
592
+ ] })
593
+ ] })
594
+ ] }),
595
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-4", children: [
596
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t("customer_accounts.admin.detail.sections.settings", "Account Settings") }),
597
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
598
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
599
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
600
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", htmlFor: "user-display-name", children: t("customer_accounts.admin.detail.fields.displayName", "Display Name") }),
601
+ /* @__PURE__ */ jsx(
602
+ Input,
603
+ {
604
+ id: "user-display-name",
605
+ type: "text",
606
+ value: editDisplayName,
607
+ onChange: (event) => setEditDisplayName(event.target.value)
608
+ }
609
+ )
610
+ ] }),
611
+ /* @__PURE__ */ jsx(
612
+ SwitchField,
613
+ {
614
+ id: "user-active-toggle",
615
+ label: t("customer_accounts.admin.detail.fields.isActive", "Active"),
616
+ checked: editActive ?? data.isActive,
617
+ onCheckedChange: (next) => setEditActive(next)
618
+ }
619
+ )
620
+ ] }),
621
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
622
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("customer_accounts.admin.detail.fields.roles", "Roles") }),
623
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
624
+ availableRoles.map((role) => {
625
+ const isSelected = selectedRoleIds.includes(role.id);
626
+ return /* @__PURE__ */ jsx(
627
+ Button,
628
+ {
629
+ type: "button",
630
+ variant: "outline",
631
+ size: "sm",
632
+ onClick: () => handleRoleToggle(role.id),
633
+ className: `rounded-full ${isSelected ? "border-primary bg-primary/10 text-primary" : ""}`,
634
+ children: role.name
635
+ },
636
+ role.id
637
+ );
638
+ }),
639
+ availableRoles.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("customer_accounts.admin.detail.noRolesAvailable", "No roles available") })
640
+ ] })
641
+ ] })
642
+ ] }),
643
+ /* @__PURE__ */ jsx("div", { className: "pt-2", children: /* @__PURE__ */ jsx(Button, { onClick: () => {
644
+ void handleSave();
645
+ }, disabled: isSaving, children: isSaving ? t("customer_accounts.admin.detail.actions.saving", "Saving...") : t("customer_accounts.admin.detail.actions.save", "Save Changes") }) })
646
+ ] }),
647
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [
648
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t("customer_accounts.admin.detail.sections.security", "Security") }) }),
649
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
650
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setResetPasswordOpen(true), children: t("customer_accounts.admin.detail.resetPassword.actions.open", "Reset Password") }),
651
+ /* @__PURE__ */ jsx(
652
+ Button,
653
+ {
654
+ variant: "outline",
655
+ onClick: () => {
656
+ void handleSendResetLink();
657
+ },
658
+ disabled: isSendingResetLink,
659
+ children: isSendingResetLink ? t("customer_accounts.admin.detail.sendResetLink.actions.sending", "Generating...") : t("customer_accounts.admin.detail.sendResetLink.actions.send", "Send Reset Link")
660
+ }
661
+ )
662
+ ] }),
663
+ resetLinkUrl && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-status-info-border bg-status-info-bg p-3", children: [
664
+ /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-sm font-medium text-status-info-text", children: t("customer_accounts.admin.detail.sendResetLink.linkLabel", "Password reset link (valid for 60 minutes):") }),
665
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
666
+ /* @__PURE__ */ jsx("code", { className: "flex-1 break-all rounded bg-status-info-bg px-2 py-1 text-xs text-status-info-text", children: resetLinkUrl }),
667
+ /* @__PURE__ */ jsx(
668
+ Button,
669
+ {
670
+ type: "button",
671
+ variant: "outline",
672
+ size: "sm",
673
+ onClick: () => {
674
+ void navigator.clipboard.writeText(resetLinkUrl);
675
+ flash(t("customer_accounts.admin.detail.sendResetLink.flash.copied", "Link copied to clipboard"), "success");
676
+ },
677
+ children: t("customer_accounts.admin.detail.sendResetLink.actions.copy", "Copy")
678
+ }
679
+ )
680
+ ] }),
681
+ /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-status-info-text", children: t("customer_accounts.admin.detail.sendResetLink.hint", "Share this link with the customer to let them set a new password.") })
682
+ ] })
683
+ ] }),
684
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [
685
+ /* @__PURE__ */ jsxs("h2", { className: "text-sm font-semibold", children: [
686
+ t("customer_accounts.admin.detail.sections.sessions", "Active Sessions"),
687
+ data.sessions.length > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: [
688
+ "(",
689
+ data.sessions.length,
690
+ ")"
691
+ ] })
692
+ ] }),
693
+ data.sessions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customer_accounts.admin.detail.noSessions", "No active sessions") }) : /* @__PURE__ */ jsx("div", { className: "divide-y", children: data.sessions.map((session) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-2 text-sm", children: [
694
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
695
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: session.ipAddress || t("customer_accounts.admin.detail.unknownIp", "Unknown IP") }),
696
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate max-w-xs", children: session.userAgent || t("customer_accounts.admin.detail.unknownDevice", "Unknown device") }),
697
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
698
+ t("customer_accounts.admin.detail.fields.lastUsed", "Last used"),
699
+ ": ",
700
+ formatDate(session.lastUsedAt, "-")
701
+ ] })
702
+ ] }),
703
+ /* @__PURE__ */ jsx(
704
+ Button,
705
+ {
706
+ variant: "outline",
707
+ size: "sm",
708
+ onClick: () => {
709
+ void handleRevokeSession(session.id);
710
+ },
711
+ children: t("customer_accounts.admin.detail.actions.revoke", "Revoke")
712
+ }
713
+ )
714
+ ] }, session.id)) })
715
+ ] }),
716
+ /* @__PURE__ */ jsx(
717
+ ResetPasswordDialog,
718
+ {
719
+ open: resetPasswordOpen,
720
+ onOpenChange: setResetPasswordOpen,
721
+ userId: id,
722
+ onRunMutation: runMutationWithContext
723
+ }
724
+ ),
725
+ ConfirmDialogElement
726
+ ] });
727
+ }
728
+ export {
729
+ PortalUserDetailPageClient
730
+ };
731
+ //# sourceMappingURL=PortalUserDetailPageClient.js.map