@open-mercato/core 0.8.1-develop.7241.1.fad7b2ce2a → 0.8.1-develop.7261.1.5d42353f61

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 (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/lib/storage.js +15 -1
  3. package/dist/modules/attachments/lib/storage.js.map +2 -2
  4. package/dist/modules/auth/api/reset/confirm.js +3 -2
  5. package/dist/modules/auth/api/reset/confirm.js.map +2 -2
  6. package/dist/modules/auth/api/reset.js +2 -1
  7. package/dist/modules/auth/api/reset.js.map +2 -2
  8. package/dist/modules/auth/di.js +6 -6
  9. package/dist/modules/auth/di.js.map +2 -2
  10. package/dist/modules/auth/lib/tenantHint.js +29 -0
  11. package/dist/modules/auth/lib/tenantHint.js.map +7 -0
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +11 -3
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/customer_accounts/api/admin/users.js +27 -14
  15. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  16. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +9 -0
  17. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
  18. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js +8 -8
  19. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js.map +2 -2
  20. package/dist/modules/customer_accounts/di.js +3 -3
  21. package/dist/modules/customer_accounts/di.js.map +2 -2
  22. package/dist/modules/customer_accounts/lib/customerAuth.js +18 -7
  23. package/dist/modules/customer_accounts/lib/customerAuth.js.map +2 -2
  24. package/dist/modules/customer_accounts/services/customerSessionService.js +23 -0
  25. package/dist/modules/customer_accounts/services/customerSessionService.js.map +2 -2
  26. package/dist/modules/customers/ai-tools/activities-tasks-pack.js +28 -24
  27. package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +2 -2
  28. package/dist/modules/customers/backend/customers/deals/map/page.js +10 -2
  29. package/dist/modules/customers/backend/customers/deals/map/page.js.map +2 -2
  30. package/dist/modules/customers/backend/customers/deals/page.js +10 -2
  31. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  32. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +41 -66
  33. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  34. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -2
  35. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  36. package/dist/modules/customers/commands/companies.js +5 -0
  37. package/dist/modules/customers/commands/companies.js.map +2 -2
  38. package/dist/modules/customers/components/formConfig.js +3 -3
  39. package/dist/modules/customers/components/formConfig.js.map +2 -2
  40. package/dist/modules/customers/data/validators.js +3 -1
  41. package/dist/modules/customers/data/validators.js.map +2 -2
  42. package/dist/modules/data_sync/lib/sync-engine.js +3 -7
  43. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  44. package/dist/modules/data_sync/lib/sync-schedule-service.js +52 -3
  45. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  46. package/dist/modules/progress/lib/progressServiceImpl.js +1 -1
  47. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  48. package/package.json +7 -7
  49. package/src/modules/attachments/lib/storage.ts +24 -1
  50. package/src/modules/auth/api/reset/confirm.ts +3 -2
  51. package/src/modules/auth/api/reset.ts +2 -1
  52. package/src/modules/auth/di.ts +6 -6
  53. package/src/modules/auth/lib/tenantHint.ts +49 -0
  54. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +19 -5
  55. package/src/modules/catalog/i18n/de.json +1 -0
  56. package/src/modules/catalog/i18n/en.json +1 -0
  57. package/src/modules/catalog/i18n/es.json +1 -0
  58. package/src/modules/catalog/i18n/ko.json +1 -0
  59. package/src/modules/catalog/i18n/pl.json +1 -0
  60. package/src/modules/customer_accounts/api/admin/users.ts +30 -14
  61. package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +11 -0
  62. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.tsx +11 -11
  63. package/src/modules/customer_accounts/di.ts +3 -3
  64. package/src/modules/customer_accounts/i18n/de.json +1 -0
  65. package/src/modules/customer_accounts/i18n/en.json +1 -0
  66. package/src/modules/customer_accounts/i18n/es.json +1 -0
  67. package/src/modules/customer_accounts/i18n/ko.json +1 -0
  68. package/src/modules/customer_accounts/i18n/pl.json +1 -0
  69. package/src/modules/customer_accounts/lib/customerAuth.ts +36 -7
  70. package/src/modules/customer_accounts/services/customerSessionService.ts +28 -0
  71. package/src/modules/customers/ai-tools/activities-tasks-pack.ts +49 -39
  72. package/src/modules/customers/backend/customers/deals/map/page.tsx +13 -2
  73. package/src/modules/customers/backend/customers/deals/page.tsx +13 -2
  74. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +69 -56
  75. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +8 -2
  76. package/src/modules/customers/commands/companies.ts +8 -0
  77. package/src/modules/customers/components/formConfig.tsx +3 -3
  78. package/src/modules/customers/data/validators.ts +2 -1
  79. package/src/modules/customers/i18n/de.json +1 -0
  80. package/src/modules/customers/i18n/en.json +1 -0
  81. package/src/modules/customers/i18n/es.json +1 -0
  82. package/src/modules/customers/i18n/ko.json +1 -0
  83. package/src/modules/customers/i18n/pl.json +1 -0
  84. package/src/modules/data_sync/AGENTS.md +1 -1
  85. package/src/modules/data_sync/lib/sync-engine.ts +23 -12
  86. package/src/modules/data_sync/lib/sync-schedule-service.ts +54 -3
  87. package/src/modules/progress/lib/progressServiceImpl.ts +7 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/customer_accounts/backend/customer_accounts/users/%5Bid%5D/PortalUserDetailPageClient.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { SwitchField } from '@open-mercato/ui/primitives/switch-field'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { buildPortalUrlPattern } from '../../../../lib/portalUrl'\n\ntype UserDetail = {\n id: string\n displayName: string\n email: string\n emailVerifiedAt: string | null\n isActive: boolean\n lastLoginAt: string | null\n personEntityId: string | null\n customerEntityId: string | null\n createdAt: string\n updatedAt: string | null\n roles: Array<{ id: string; name: string; slug: string }>\n sessions: Array<{\n id: string\n ipAddress: string | null\n userAgent: string | null\n lastUsedAt: string | null\n createdAt: string\n expiresAt: string\n }>\n}\n\nfunction formatDate(value: string | null | undefined, fallback: string): string {\n if (!value) return fallback\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return fallback\n return date.toLocaleString()\n}\n\nfunction ResetPasswordDialog({\n open,\n onOpenChange,\n userId,\n onRunMutation,\n}: {\n open: boolean\n onOpenChange: (next: boolean) => void\n userId: string\n onRunMutation: <T>(operation: () => Promise<T>) => Promise<T>\n}) {\n const t = useT()\n const [newPassword, setNewPassword] = React.useState('')\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const handleSubmit = React.useCallback(async (event: React.FormEvent) => {\n event.preventDefault()\n if (!newPassword.trim() || newPassword.length < 8) {\n flash(t('customer_accounts.admin.detail.resetPassword.error.minLength', 'Password must be at least 8 characters'), 'error')\n return\n }\n setIsSubmitting(true)\n try {\n await onRunMutation(async () => {\n const call = await apiCall<{ ok: boolean; error?: string }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(userId)}/reset-password`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ newPassword }),\n },\n )\n if (!call.ok) {\n flash(call.result?.error || t('customer_accounts.admin.detail.resetPassword.error.save', 'Failed to reset password'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.resetPassword.flash.success', 'Password reset successfully'), 'success')\n setNewPassword('')\n onOpenChange(false)\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.resetPassword.error.save', 'Failed to reset password')\n flash(message, 'error')\n } finally {\n setIsSubmitting(false)\n }\n }, [newPassword, onOpenChange, onRunMutation, t, userId])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n const form = (event.target as HTMLElement).closest('form')\n if (form) form.requestSubmit()\n }\n }, [])\n\n return (\n <Dialog open={open} onOpenChange={(next) => { if (!next) setNewPassword(''); onOpenChange(next) }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customer_accounts.admin.detail.resetPassword.title', 'Reset Password')}</DialogTitle>\n </DialogHeader>\n <form onSubmit={(event) => { void handleSubmit(event) }} onKeyDown={handleKeyDown} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"reset-password\">\n {t('customer_accounts.admin.detail.resetPassword.fields.newPassword', 'New Password')}\n </label>\n <PasswordInput\n id=\"reset-password\"\n required\n minLength={8}\n value={newPassword}\n onChange={(event) => setNewPassword(event.target.value)}\n placeholder={t('customer_accounts.admin.detail.resetPassword.fields.placeholder', 'Min. 8 characters')}\n autoComplete=\"new-password\"\n />\n </div>\n <div className=\"flex justify-end gap-2 pt-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => { setNewPassword(''); onOpenChange(false) }}>\n {t('customer_accounts.admin.detail.resetPassword.actions.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting\n ? t('customer_accounts.admin.detail.resetPassword.actions.resetting', 'Resetting...')\n : t('customer_accounts.admin.detail.resetPassword.actions.reset', 'Reset Password')}\n </Button>\n </div>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport type PortalUserDetailPageClientProps = {\n params?: { id?: string }\n portalOrigin: string\n}\n\nexport function PortalUserDetailPageClient({ params, portalOrigin }: PortalUserDetailPageClientProps) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [data, setData] = React.useState<UserDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [editActive, setEditActive] = React.useState<boolean | null>(null)\n const [editDisplayName, setEditDisplayName] = React.useState('')\n const [availableRoles, setAvailableRoles] = React.useState<Array<{ id: string; name: string }>>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [resetPasswordOpen, setResetPasswordOpen] = React.useState(false)\n const [isVerifying, setIsVerifying] = React.useState(false)\n const [editPersonEntityId, setEditPersonEntityId] = React.useState<string | null>(null)\n const [editCustomerEntityId, setEditCustomerEntityId] = React.useState<string | null>(null)\n const [personSearchQuery, setPersonSearchQuery] = React.useState('')\n const [companySearchQuery, setCompanySearchQuery] = React.useState('')\n const [personResults, setPersonResults] = React.useState<Array<{ id: string; label: string }>>([])\n const [companyResults, setCompanyResults] = React.useState<Array<{ id: string; label: string }>>([])\n const [personName, setPersonName] = React.useState<string | null>(null)\n const [companyName, setCompanyName] = React.useState<string | null>(null)\n const [isSendingResetLink, setIsSendingResetLink] = React.useState(false)\n const [resetLinkUrl, setResetLinkUrl] = React.useState<string | null>(null)\n\n const mutationContextId = `customer_accounts:user:${id ?? 'pending'}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n entityType: string\n entityId?: string\n }>({\n contextId: mutationContextId,\n })\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { entityType: 'customer_accounts:user', entityId: id },\n })\n },\n [id, runMutation],\n )\n\n React.useEffect(() => {\n if (!id) {\n setIsNotFound(true)\n setIsLoading(false)\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const payload = await readApiResultOrThrow<UserDetail>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id!)}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.detail.error.load', 'Failed to load user') },\n )\n if (cancelled) return\n setData(payload)\n setEditActive(payload.isActive)\n setEditDisplayName(payload.displayName)\n setSelectedRoleIds(payload.roles.map((role) => role.id))\n setEditPersonEntityId(payload.personEntityId)\n setEditCustomerEntityId(payload.customerEntityId)\n } catch (err) {\n if (cancelled) return\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.load', 'Failed to load user')\n setError(message)\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadRoles() {\n try {\n const call = await apiCall<{ items?: Array<{ id: string; name: string }> }>(\n '/api/customer_accounts/admin/roles?pageSize=100',\n )\n if (cancelled || !call.ok) return\n const items = Array.isArray(call.result?.items) ? call.result!.items : []\n setAvailableRoles(\n items.filter((item) => typeof item?.id === 'string' && typeof item?.name === 'string'),\n )\n } catch {\n // silently ignore role loading failures\n }\n }\n loadRoles()\n return () => { cancelled = true }\n }, [])\n\n React.useEffect(() => {\n if (!data) return\n let cancelled = false\n async function loadPersonName() {\n if (!data!.personEntityId) return\n try {\n const call = await apiCall<{ id?: string; firstName?: string; lastName?: string }>(`/api/customers/people/${encodeURIComponent(data!.personEntityId)}`)\n if (!cancelled && call.ok && call.result) {\n setPersonName([call.result.firstName, call.result.lastName].filter(Boolean).join(' ') || call.result.id || null)\n }\n } catch { /* ignore */ }\n }\n async function loadCompanyName() {\n if (!data!.customerEntityId) return\n try {\n const call = await apiCall<{ id?: string; name?: string }>(`/api/customers/${encodeURIComponent(data!.customerEntityId)}`)\n if (!cancelled && call.ok && call.result) {\n setCompanyName(call.result.name || call.result.id || null)\n }\n } catch { /* ignore */ }\n }\n void Promise.all([loadPersonName(), loadCompanyName()])\n return () => { cancelled = true }\n }, [data])\n\n const handleSearchPeople = React.useCallback(async (query: string) => {\n setPersonSearchQuery(query)\n if (query.trim().length < 2) { setPersonResults([]); return }\n try {\n const call = await apiCall<{ items?: Array<{ id: string; firstName?: string; lastName?: string; email?: string }> }>(\n `/api/customers/people?search=${encodeURIComponent(query.trim())}&pageSize=10`,\n )\n if (call.ok && Array.isArray(call.result?.items)) {\n setPersonResults(call.result!.items.map((person) => ({\n id: person.id,\n label: [person.firstName, person.lastName].filter(Boolean).join(' ') || person.email || person.id,\n })))\n }\n } catch { /* ignore */ }\n }, [])\n\n const handleSearchCompanies = React.useCallback(async (query: string) => {\n setCompanySearchQuery(query)\n if (query.trim().length < 2) { setCompanyResults([]); return }\n try {\n const call = await apiCall<{ items?: Array<{ id: string; name?: string }> }>(\n `/api/customers?search=${encodeURIComponent(query.trim())}&pageSize=10`,\n )\n if (call.ok && Array.isArray(call.result?.items)) {\n setCompanyResults(call.result!.items.map((company) => ({\n id: company.id,\n label: company.name || company.id,\n })))\n }\n } catch { /* ignore */ }\n }, [])\n\n const handleSendResetLink = React.useCallback(async () => {\n if (!id) return\n setIsSendingResetLink(true)\n try {\n await runMutationWithContext(async () => {\n const call = await apiCall<{ ok: boolean; resetLink?: string; error?: string }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/send-reset-link`,\n { method: 'POST' },\n )\n if (!call.ok || !call.result?.resetLink) {\n flash(call.result?.error || t('customer_accounts.admin.detail.sendResetLink.error', 'Failed to generate reset link'), 'error')\n return\n }\n setResetLinkUrl(call.result.resetLink)\n flash(t('customer_accounts.admin.detail.sendResetLink.flash.success', 'Reset link generated'), 'success')\n }, { id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.sendResetLink.error', 'Failed to generate reset link')\n flash(message, 'error')\n } finally {\n setIsSendingResetLink(false)\n }\n }, [id, runMutationWithContext, t])\n\n const handleSave = React.useCallback(async () => {\n if (!data || !id) return\n setIsSaving(true)\n try {\n await runMutationWithContext(async () => {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(data.updatedAt),\n () => apiCall<{ ok: boolean; error?: string; updatedAt?: string | null }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n displayName: editDisplayName.trim() || undefined,\n isActive: editActive,\n roleIds: selectedRoleIds,\n personEntityId: editPersonEntityId,\n customerEntityId: editCustomerEntityId,\n }),\n },\n ),\n )\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return\n flash(call.result?.error || t('customer_accounts.admin.detail.error.save', 'Failed to save user'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.flash.saved', 'User updated'), 'success')\n setData((prev) => prev ? {\n ...prev,\n isActive: editActive ?? prev.isActive,\n displayName: editDisplayName.trim() || prev.displayName,\n personEntityId: editPersonEntityId,\n customerEntityId: editCustomerEntityId,\n updatedAt: call.result?.updatedAt ?? prev.updatedAt,\n } : prev)\n }, { displayName: editDisplayName, isActive: editActive, roleIds: selectedRoleIds, personEntityId: editPersonEntityId, customerEntityId: editCustomerEntityId })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.save', 'Failed to save user')\n flash(message, 'error')\n } finally {\n setIsSaving(false)\n }\n }, [data, editActive, editCustomerEntityId, editDisplayName, editPersonEntityId, id, runMutationWithContext, selectedRoleIds, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!data || !id) return\n const confirmed = await confirm({\n title: t('customer_accounts.admin.confirm.delete', 'Delete user \"{{name}}\"?', {\n name: data.displayName || data.email,\n }),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutationWithContext(async () => {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(data.updatedAt),\n () => apiCall(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n ),\n )\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return\n flash(t('customer_accounts.admin.error.delete', 'Failed to delete user'), 'error')\n return\n }\n flash(t('customer_accounts.admin.flash.deleted', 'User deleted'), 'success')\n router.push('/backend/customer_accounts/users')\n }, { id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.error.delete', 'Failed to delete user')\n flash(message, 'error')\n }\n }, [confirm, data, id, router, runMutationWithContext, t])\n\n const handleVerifyEmail = React.useCallback(async () => {\n if (!data || !id) return\n const confirmed = await confirm({\n title: t('customer_accounts.admin.detail.verifyEmail.confirm', 'Mark email as verified for \"{{name}}\"?', {\n name: data.displayName || data.email,\n }),\n })\n if (!confirmed) return\n setIsVerifying(true)\n try {\n await runMutationWithContext(async () => {\n const call = await apiCall<{ ok: boolean; error?: string }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/verify-email`,\n { method: 'POST' },\n )\n if (!call.ok) {\n flash(call.result?.error || t('customer_accounts.admin.detail.verifyEmail.error', 'Failed to verify email'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.verifyEmail.flash.success', 'Email marked as verified'), 'success')\n setData((prev) => prev ? { ...prev, emailVerifiedAt: new Date().toISOString() } : prev)\n }, { id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.verifyEmail.error', 'Failed to verify email')\n flash(message, 'error')\n } finally {\n setIsVerifying(false)\n }\n }, [confirm, data, id, runMutationWithContext, t])\n\n const handleRevokeSession = React.useCallback(async (sessionId: string) => {\n if (!id) return\n try {\n await runMutationWithContext(async () => {\n const call = await apiCall(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/sessions/${encodeURIComponent(sessionId)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n flash(t('customer_accounts.admin.detail.error.revokeSession', 'Failed to revoke session'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.flash.sessionRevoked', 'Session revoked'), 'success')\n setData((prev) => {\n if (!prev) return prev\n return { ...prev, sessions: prev.sessions.filter((session) => session.id !== sessionId) }\n })\n }, { sessionId })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.revokeSession', 'Failed to revoke session')\n flash(message, 'error')\n }\n }, [id, runMutationWithContext, t])\n\n const handleRoleToggle = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) =>\n prev.includes(roleId)\n ? prev.filter((existingId) => existingId !== roleId)\n : [...prev, roleId],\n )\n }, [])\n\n if (isLoading) {\n return (\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('customer_accounts.admin.detail.loading', 'Loading user...')}</span>\n </div>\n )\n }\n\n if (isNotFound) {\n return (\n <RecordNotFoundState\n label={t('customer_accounts.admin.detail.error.notFound', 'User not found')}\n backHref=\"/backend/customer_accounts/users\"\n backLabel={t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n />\n )\n }\n\n if (error || !data) {\n return (\n <ErrorMessage\n label={error ?? t('customer_accounts.admin.detail.error.notFound', 'User not found')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer_accounts/users\">\n {t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n </Link>\n </Button>\n }\n />\n )\n }\n\n return (\n <>\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/customer_accounts/users\"\n backLabel={t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n title={data.displayName}\n subtitle={data.email}\n onDelete={() => { void handleDelete() }}\n deleteLabel={t('customer_accounts.admin.detail.actions.delete', 'Delete')}\n />\n\n <div className=\"rounded-lg border border-status-info-border bg-status-info-bg p-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1\">\n <h3 className=\"text-sm font-medium text-status-info-text\">\n {t('customer_accounts.admin.detail.portalAccess.title', 'Customer Portal Access')}\n </h3>\n <p className=\"mt-1 text-sm text-status-info-text\">\n {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.')}\n </p>\n <p className=\"mt-2 text-xs text-status-info-text\">\n {t('customer_accounts.admin.detail.portalAccess.url', 'Portal URL: {url}', {\n url: buildPortalUrlPattern(portalOrigin),\n })}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"grid gap-6 md:grid-cols-2\">\n <div className=\"rounded-lg border p-4 space-y-3\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.info', 'User Information')}</h2>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.email', 'Email')}</dt>\n <dd>{data.email}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.emailVerified', 'Email Verified')}</dt>\n <dd className=\"flex items-center gap-2\">\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n data.emailVerifiedAt\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-warning-bg text-status-warning-text'\n }`}>\n {data.emailVerifiedAt\n ? t('customer_accounts.admin.verified', 'Yes')\n : t('customer_accounts.admin.unverified', 'No')}\n </span>\n {!data.emailVerifiedAt && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void handleVerifyEmail() }}\n disabled={isVerifying}\n >\n {isVerifying\n ? t('customer_accounts.admin.detail.verifyEmail.actions.verifying', 'Verifying...')\n : t('customer_accounts.admin.detail.verifyEmail.actions.verify', 'Mark Verified')}\n </Button>\n )}\n </dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.lastLogin', 'Last Login')}</dt>\n <dd>{formatDate(data.lastLoginAt, '-')}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.createdAt', 'Created')}</dt>\n <dd>{formatDate(data.createdAt, '-')}</dd>\n </div>\n </dl>\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-3\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.crmLinks', 'CRM Links')}</h2>\n <div className=\"space-y-3 text-sm\">\n <div className=\"space-y-1.5\">\n <p className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.personEntity', 'Linked Person')}</p>\n {editPersonEntityId ? (\n <div className=\"flex items-center gap-2\">\n <Link href={`/backend/customers/people/${editPersonEntityId}`} className=\"text-primary hover:underline\">\n {personName || editPersonEntityId}\n </Link>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setEditPersonEntityId(null); setPersonName(null) }}>\n {t('customer_accounts.admin.detail.actions.unlink', 'Unlink')}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-1\">\n <div className=\"relative\">\n <Input\n type=\"text\"\n value={personSearchQuery}\n onChange={(event) => { void handleSearchPeople(event.target.value) }}\n placeholder={t('customer_accounts.admin.detail.fields.searchPerson', 'Search people by name...')}\n />\n {personResults.length > 0 && (\n <div className=\"absolute z-10 mt-1 w-full rounded-md border bg-background shadow-lg max-h-40 overflow-y-auto\">\n {personResults.map((person) => (\n <button\n key={person.id}\n type=\"button\"\n className=\"w-full px-3 py-2 text-left text-sm hover:bg-muted\"\n onClick={() => {\n setEditPersonEntityId(person.id)\n setPersonName(person.label)\n setPersonSearchQuery('')\n setPersonResults([])\n }}\n >\n {person.label}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n <div className=\"space-y-1.5\">\n <p className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.customerEntity', 'Linked Company')}</p>\n {editCustomerEntityId ? (\n <div className=\"flex items-center gap-2\">\n <Link href={`/backend/customers/companies/${editCustomerEntityId}`} className=\"text-primary hover:underline\">\n {companyName || editCustomerEntityId}\n </Link>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setEditCustomerEntityId(null); setCompanyName(null) }}>\n {t('customer_accounts.admin.detail.actions.unlink', 'Unlink')}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-1\">\n <div className=\"relative\">\n <Input\n type=\"text\"\n value={companySearchQuery}\n onChange={(event) => { void handleSearchCompanies(event.target.value) }}\n placeholder={t('customer_accounts.admin.detail.fields.searchCompany', 'Search companies by name...')}\n />\n {companyResults.length > 0 && (\n <div className=\"absolute z-10 mt-1 w-full rounded-md border bg-background shadow-lg max-h-40 overflow-y-auto\">\n {companyResults.map((company) => (\n <button\n key={company.id}\n type=\"button\"\n className=\"w-full px-3 py-2 text-left text-sm hover:bg-muted\"\n onClick={() => {\n setEditCustomerEntityId(company.id)\n setCompanyName(company.label)\n setCompanySearchQuery('')\n setCompanyResults([])\n }}\n >\n {company.label}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t('customer_accounts.admin.detail.crmLinks.hint', 'Changes to CRM links are saved when you click Save Changes below.')}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-4\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.settings', 'Account Settings')}</h2>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-3\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"user-display-name\">\n {t('customer_accounts.admin.detail.fields.displayName', 'Display Name')}\n </label>\n <Input\n id=\"user-display-name\"\n type=\"text\"\n value={editDisplayName}\n onChange={(event) => setEditDisplayName(event.target.value)}\n />\n </div>\n\n <SwitchField\n id=\"user-active-toggle\"\n label={t('customer_accounts.admin.detail.fields.isActive', 'Active')}\n checked={editActive ?? data.isActive}\n onCheckedChange={(next) => setEditActive(next)}\n />\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium\">{t('customer_accounts.admin.detail.fields.roles', 'Roles')}</p>\n <div className=\"flex flex-wrap gap-2\">\n {availableRoles.map((role) => {\n const isSelected = selectedRoleIds.includes(role.id)\n return (\n <Button\n key={role.id}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleRoleToggle(role.id)}\n className={`rounded-full ${\n isSelected\n ? 'border-primary bg-primary/10 text-primary'\n : ''\n }`}\n >\n {role.name}\n </Button>\n )\n })}\n {availableRoles.length === 0 && (\n <span className=\"text-sm text-muted-foreground\">\n {t('customer_accounts.admin.detail.noRolesAvailable', 'No roles available')}\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"pt-2\">\n <Button onClick={() => { void handleSave() }} disabled={isSaving}>\n {isSaving\n ? t('customer_accounts.admin.detail.actions.saving', 'Saving...')\n : t('customer_accounts.admin.detail.actions.save', 'Save Changes')}\n </Button>\n </div>\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.security', 'Security')}</h2>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" onClick={() => setResetPasswordOpen(true)}>\n {t('customer_accounts.admin.detail.resetPassword.actions.open', 'Reset Password')}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => { void handleSendResetLink() }}\n disabled={isSendingResetLink}\n >\n {isSendingResetLink\n ? t('customer_accounts.admin.detail.sendResetLink.actions.sending', 'Generating...')\n : t('customer_accounts.admin.detail.sendResetLink.actions.send', 'Send Reset Link')}\n </Button>\n </div>\n {resetLinkUrl && (\n <div className=\"rounded-md border border-status-info-border bg-status-info-bg p-3\">\n <p className=\"mb-1.5 text-sm font-medium text-status-info-text\">\n {t('customer_accounts.admin.detail.sendResetLink.linkLabel', 'Password reset link (valid for 60 minutes):')}\n </p>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 break-all rounded bg-status-info-bg px-2 py-1 text-xs text-status-info-text\">\n {resetLinkUrl}\n </code>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n void navigator.clipboard.writeText(resetLinkUrl)\n flash(t('customer_accounts.admin.detail.sendResetLink.flash.copied', 'Link copied to clipboard'), 'success')\n }}\n >\n {t('customer_accounts.admin.detail.sendResetLink.actions.copy', 'Copy')}\n </Button>\n </div>\n <p className=\"mt-1.5 text-xs text-status-info-text\">\n {t('customer_accounts.admin.detail.sendResetLink.hint', 'Share this link with the customer to let them set a new password.')}\n </p>\n </div>\n )}\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-3\">\n <h2 className=\"text-sm font-semibold\">\n {t('customer_accounts.admin.detail.sections.sessions', 'Active Sessions')}\n {data.sessions.length > 0 && (\n <span className=\"ml-2 text-xs font-normal text-muted-foreground\">({data.sessions.length})</span>\n )}\n </h2>\n {data.sessions.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customer_accounts.admin.detail.noSessions', 'No active sessions')}\n </p>\n ) : (\n <div className=\"divide-y\">\n {data.sessions.map((session) => (\n <div key={session.id} className=\"flex items-center justify-between py-2 text-sm\">\n <div className=\"space-y-0.5\">\n <p className=\"font-medium\">\n {session.ipAddress || t('customer_accounts.admin.detail.unknownIp', 'Unknown IP')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate max-w-xs\">\n {session.userAgent || t('customer_accounts.admin.detail.unknownDevice', 'Unknown device')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {t('customer_accounts.admin.detail.fields.lastUsed', 'Last used')}: {formatDate(session.lastUsedAt, '-')}\n </p>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void handleRevokeSession(session.id) }}\n >\n {t('customer_accounts.admin.detail.actions.revoke', 'Revoke')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n\n <ResetPasswordDialog\n open={resetPasswordOpen}\n onOpenChange={setResetPasswordOpen}\n userId={id!}\n onRunMutation={runMutationWithContext}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { SwitchField } from '@open-mercato/ui/primitives/switch-field'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { buildPortalUrlPattern } from '../../../../lib/portalUrl'\n\ntype UserDetail = {\n id: string\n displayName: string\n email: string\n emailVerifiedAt: string | null\n isActive: boolean\n lastLoginAt: string | null\n personEntityId: string | null\n customerEntityId: string | null\n createdAt: string\n updatedAt: string | null\n roles: Array<{ id: string; name: string; slug: string }>\n sessions: Array<{\n id: string\n ipAddress: string | null\n userAgent: string | null\n lastUsedAt: string | null\n createdAt: string\n expiresAt: string\n }>\n}\n\nfunction formatDate(value: string | null | undefined, fallback: string): string {\n if (!value) return fallback\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return fallback\n return date.toLocaleString()\n}\n\nfunction ResetPasswordDialog({\n open,\n onOpenChange,\n userId,\n onRunMutation,\n}: {\n open: boolean\n onOpenChange: (next: boolean) => void\n userId: string\n onRunMutation: <T>(operation: () => Promise<T>) => Promise<T>\n}) {\n const t = useT()\n const [newPassword, setNewPassword] = React.useState('')\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const handleSubmit = React.useCallback(async (event: React.FormEvent) => {\n event.preventDefault()\n if (!newPassword.trim() || newPassword.length < 8) {\n flash(t('customer_accounts.admin.detail.resetPassword.error.minLength', 'Password must be at least 8 characters'), 'error')\n return\n }\n setIsSubmitting(true)\n try {\n await onRunMutation(async () => {\n const call = await apiCall<{ ok: boolean; error?: string }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(userId)}/reset-password`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ newPassword }),\n },\n )\n if (!call.ok) {\n flash(call.result?.error || t('customer_accounts.admin.detail.resetPassword.error.save', 'Failed to reset password'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.resetPassword.flash.success', 'Password reset successfully'), 'success')\n setNewPassword('')\n onOpenChange(false)\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.resetPassword.error.save', 'Failed to reset password')\n flash(message, 'error')\n } finally {\n setIsSubmitting(false)\n }\n }, [newPassword, onOpenChange, onRunMutation, t, userId])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n const form = (event.target as HTMLElement).closest('form')\n if (form) form.requestSubmit()\n }\n }, [])\n\n return (\n <Dialog open={open} onOpenChange={(next) => { if (!next) setNewPassword(''); onOpenChange(next) }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customer_accounts.admin.detail.resetPassword.title', 'Reset Password')}</DialogTitle>\n </DialogHeader>\n <form onSubmit={(event) => { void handleSubmit(event) }} onKeyDown={handleKeyDown} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"reset-password\">\n {t('customer_accounts.admin.detail.resetPassword.fields.newPassword', 'New Password')}\n </label>\n <PasswordInput\n id=\"reset-password\"\n required\n minLength={8}\n value={newPassword}\n onChange={(event) => setNewPassword(event.target.value)}\n placeholder={t('customer_accounts.admin.detail.resetPassword.fields.placeholder', 'Min. 8 characters')}\n autoComplete=\"new-password\"\n />\n </div>\n <div className=\"flex justify-end gap-2 pt-2\">\n <Button type=\"button\" variant=\"outline\" onClick={() => { setNewPassword(''); onOpenChange(false) }}>\n {t('customer_accounts.admin.detail.resetPassword.actions.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting\n ? t('customer_accounts.admin.detail.resetPassword.actions.resetting', 'Resetting...')\n : t('customer_accounts.admin.detail.resetPassword.actions.reset', 'Reset Password')}\n </Button>\n </div>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport type PortalUserDetailPageClientProps = {\n params?: { id?: string }\n portalOrigin: string\n}\n\nexport function PortalUserDetailPageClient({ params, portalOrigin }: PortalUserDetailPageClientProps) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [data, setData] = React.useState<UserDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [editActive, setEditActive] = React.useState<boolean | null>(null)\n const [editDisplayName, setEditDisplayName] = React.useState('')\n const [availableRoles, setAvailableRoles] = React.useState<Array<{ id: string; name: string }>>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [resetPasswordOpen, setResetPasswordOpen] = React.useState(false)\n const [isVerifying, setIsVerifying] = React.useState(false)\n const [editPersonEntityId, setEditPersonEntityId] = React.useState<string | null>(null)\n const [editCustomerEntityId, setEditCustomerEntityId] = React.useState<string | null>(null)\n const [personSearchQuery, setPersonSearchQuery] = React.useState('')\n const [companySearchQuery, setCompanySearchQuery] = React.useState('')\n const [personResults, setPersonResults] = React.useState<Array<{ id: string; label: string }>>([])\n const [companyResults, setCompanyResults] = React.useState<Array<{ id: string; label: string }>>([])\n const [personName, setPersonName] = React.useState<string | null>(null)\n const [companyName, setCompanyName] = React.useState<string | null>(null)\n const [isSendingResetLink, setIsSendingResetLink] = React.useState(false)\n const [resetLinkUrl, setResetLinkUrl] = React.useState<string | null>(null)\n\n const mutationContextId = `customer_accounts:user:${id ?? 'pending'}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n entityType: string\n entityId?: string\n }>({\n contextId: mutationContextId,\n })\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { entityType: 'customer_accounts:user', entityId: id },\n })\n },\n [id, runMutation],\n )\n\n React.useEffect(() => {\n if (!id) {\n setIsNotFound(true)\n setIsLoading(false)\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const payload = await readApiResultOrThrow<UserDetail>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id!)}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.detail.error.load', 'Failed to load user') },\n )\n if (cancelled) return\n setData(payload)\n setEditActive(payload.isActive)\n setEditDisplayName(payload.displayName)\n setSelectedRoleIds(payload.roles.map((role) => role.id))\n setEditPersonEntityId(payload.personEntityId)\n setEditCustomerEntityId(payload.customerEntityId)\n } catch (err) {\n if (cancelled) return\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.load', 'Failed to load user')\n setError(message)\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadRoles() {\n try {\n const call = await apiCall<{ items?: Array<{ id: string; name: string }> }>(\n '/api/customer_accounts/admin/roles?pageSize=100',\n )\n if (cancelled || !call.ok) return\n const items = Array.isArray(call.result?.items) ? call.result!.items : []\n setAvailableRoles(\n items.filter((item) => typeof item?.id === 'string' && typeof item?.name === 'string'),\n )\n } catch {\n // silently ignore role loading failures\n }\n }\n loadRoles()\n return () => { cancelled = true }\n }, [])\n\n React.useEffect(() => {\n if (!data) return\n let cancelled = false\n async function loadPersonName() {\n if (!data!.personEntityId) return\n try {\n const call = await apiCall<{ person?: { id?: string; displayName?: string | null } }>(`/api/customers/people/${encodeURIComponent(data!.personEntityId)}`)\n if (!cancelled && call.ok && call.result?.person) {\n setPersonName(call.result.person.displayName || call.result.person.id || null)\n }\n } catch { /* ignore */ }\n }\n async function loadCompanyName() {\n if (!data!.customerEntityId) return\n try {\n const call = await apiCall<{ company?: { id?: string; displayName?: string | null } }>(`/api/customers/companies/${encodeURIComponent(data!.customerEntityId)}`)\n if (!cancelled && call.ok && call.result?.company) {\n setCompanyName(call.result.company.displayName || call.result.company.id || null)\n }\n } catch { /* ignore */ }\n }\n void Promise.all([loadPersonName(), loadCompanyName()])\n return () => { cancelled = true }\n }, [data])\n\n const handleSearchPeople = React.useCallback(async (query: string) => {\n setPersonSearchQuery(query)\n if (query.trim().length < 2) { setPersonResults([]); return }\n try {\n const call = await apiCall<{ items?: Array<{ id: string; first_name?: string; last_name?: string; primary_email?: string }> }>(\n `/api/customers/people?search=${encodeURIComponent(query.trim())}&pageSize=10`,\n )\n if (call.ok && Array.isArray(call.result?.items)) {\n setPersonResults(call.result!.items.map((person) => ({\n id: person.id,\n label: [person.first_name, person.last_name].filter(Boolean).join(' ') || person.primary_email || person.id,\n })))\n }\n } catch { /* ignore */ }\n }, [])\n\n const handleSearchCompanies = React.useCallback(async (query: string) => {\n setCompanySearchQuery(query)\n if (query.trim().length < 2) { setCompanyResults([]); return }\n try {\n const call = await apiCall<{ items?: Array<{ id: string; display_name?: string }> }>(\n `/api/customers/companies?search=${encodeURIComponent(query.trim())}&pageSize=10`,\n )\n if (call.ok && Array.isArray(call.result?.items)) {\n setCompanyResults(call.result!.items.map((company) => ({\n id: company.id,\n label: company.display_name || company.id,\n })))\n }\n } catch { /* ignore */ }\n }, [])\n\n const handleSendResetLink = React.useCallback(async () => {\n if (!id) return\n setIsSendingResetLink(true)\n try {\n await runMutationWithContext(async () => {\n const call = await apiCall<{ ok: boolean; resetLink?: string; error?: string }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/send-reset-link`,\n { method: 'POST' },\n )\n if (!call.ok || !call.result?.resetLink) {\n flash(call.result?.error || t('customer_accounts.admin.detail.sendResetLink.error', 'Failed to generate reset link'), 'error')\n return\n }\n setResetLinkUrl(call.result.resetLink)\n flash(t('customer_accounts.admin.detail.sendResetLink.flash.success', 'Reset link generated'), 'success')\n }, { id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.sendResetLink.error', 'Failed to generate reset link')\n flash(message, 'error')\n } finally {\n setIsSendingResetLink(false)\n }\n }, [id, runMutationWithContext, t])\n\n const handleSave = React.useCallback(async () => {\n if (!data || !id) return\n setIsSaving(true)\n try {\n await runMutationWithContext(async () => {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(data.updatedAt),\n () => apiCall<{ ok: boolean; error?: string; updatedAt?: string | null }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n displayName: editDisplayName.trim() || undefined,\n isActive: editActive,\n roleIds: selectedRoleIds,\n personEntityId: editPersonEntityId,\n customerEntityId: editCustomerEntityId,\n }),\n },\n ),\n )\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return\n flash(call.result?.error || t('customer_accounts.admin.detail.error.save', 'Failed to save user'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.flash.saved', 'User updated'), 'success')\n setData((prev) => prev ? {\n ...prev,\n isActive: editActive ?? prev.isActive,\n displayName: editDisplayName.trim() || prev.displayName,\n personEntityId: editPersonEntityId,\n customerEntityId: editCustomerEntityId,\n updatedAt: call.result?.updatedAt ?? prev.updatedAt,\n } : prev)\n }, { displayName: editDisplayName, isActive: editActive, roleIds: selectedRoleIds, personEntityId: editPersonEntityId, customerEntityId: editCustomerEntityId })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.save', 'Failed to save user')\n flash(message, 'error')\n } finally {\n setIsSaving(false)\n }\n }, [data, editActive, editCustomerEntityId, editDisplayName, editPersonEntityId, id, runMutationWithContext, selectedRoleIds, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!data || !id) return\n const confirmed = await confirm({\n title: t('customer_accounts.admin.confirm.delete', 'Delete user \"{{name}}\"?', {\n name: data.displayName || data.email,\n }),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutationWithContext(async () => {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(data.updatedAt),\n () => apiCall(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n ),\n )\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return\n flash(t('customer_accounts.admin.error.delete', 'Failed to delete user'), 'error')\n return\n }\n flash(t('customer_accounts.admin.flash.deleted', 'User deleted'), 'success')\n router.push('/backend/customer_accounts/users')\n }, { id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.error.delete', 'Failed to delete user')\n flash(message, 'error')\n }\n }, [confirm, data, id, router, runMutationWithContext, t])\n\n const handleVerifyEmail = React.useCallback(async () => {\n if (!data || !id) return\n const confirmed = await confirm({\n title: t('customer_accounts.admin.detail.verifyEmail.confirm', 'Mark email as verified for \"{{name}}\"?', {\n name: data.displayName || data.email,\n }),\n })\n if (!confirmed) return\n setIsVerifying(true)\n try {\n await runMutationWithContext(async () => {\n const call = await apiCall<{ ok: boolean; error?: string }>(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/verify-email`,\n { method: 'POST' },\n )\n if (!call.ok) {\n flash(call.result?.error || t('customer_accounts.admin.detail.verifyEmail.error', 'Failed to verify email'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.verifyEmail.flash.success', 'Email marked as verified'), 'success')\n setData((prev) => prev ? { ...prev, emailVerifiedAt: new Date().toISOString() } : prev)\n }, { id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.verifyEmail.error', 'Failed to verify email')\n flash(message, 'error')\n } finally {\n setIsVerifying(false)\n }\n }, [confirm, data, id, runMutationWithContext, t])\n\n const handleRevokeSession = React.useCallback(async (sessionId: string) => {\n if (!id) return\n try {\n await runMutationWithContext(async () => {\n const call = await apiCall(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}/sessions/${encodeURIComponent(sessionId)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n flash(t('customer_accounts.admin.detail.error.revokeSession', 'Failed to revoke session'), 'error')\n return\n }\n flash(t('customer_accounts.admin.detail.flash.sessionRevoked', 'Session revoked'), 'success')\n setData((prev) => {\n if (!prev) return prev\n return { ...prev, sessions: prev.sessions.filter((session) => session.id !== sessionId) }\n })\n }, { sessionId })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.revokeSession', 'Failed to revoke session')\n flash(message, 'error')\n }\n }, [id, runMutationWithContext, t])\n\n const handleRoleToggle = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) =>\n prev.includes(roleId)\n ? prev.filter((existingId) => existingId !== roleId)\n : [...prev, roleId],\n )\n }, [])\n\n if (isLoading) {\n return (\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('customer_accounts.admin.detail.loading', 'Loading user...')}</span>\n </div>\n )\n }\n\n if (isNotFound) {\n return (\n <RecordNotFoundState\n label={t('customer_accounts.admin.detail.error.notFound', 'User not found')}\n backHref=\"/backend/customer_accounts/users\"\n backLabel={t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n />\n )\n }\n\n if (error || !data) {\n return (\n <ErrorMessage\n label={error ?? t('customer_accounts.admin.detail.error.notFound', 'User not found')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer_accounts/users\">\n {t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n </Link>\n </Button>\n }\n />\n )\n }\n\n return (\n <>\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/customer_accounts/users\"\n backLabel={t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n title={data.displayName}\n subtitle={data.email}\n onDelete={() => { void handleDelete() }}\n deleteLabel={t('customer_accounts.admin.detail.actions.delete', 'Delete')}\n />\n\n <div className=\"rounded-lg border border-status-info-border bg-status-info-bg p-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1\">\n <h3 className=\"text-sm font-medium text-status-info-text\">\n {t('customer_accounts.admin.detail.portalAccess.title', 'Customer Portal Access')}\n </h3>\n <p className=\"mt-1 text-sm text-status-info-text\">\n {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.')}\n </p>\n <p className=\"mt-2 text-xs text-status-info-text\">\n {t('customer_accounts.admin.detail.portalAccess.url', 'Portal URL: {url}', {\n url: buildPortalUrlPattern(portalOrigin),\n })}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"grid gap-6 md:grid-cols-2\">\n <div className=\"rounded-lg border p-4 space-y-3\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.info', 'User Information')}</h2>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.email', 'Email')}</dt>\n <dd>{data.email}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.emailVerified', 'Email Verified')}</dt>\n <dd className=\"flex items-center gap-2\">\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n data.emailVerifiedAt\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-warning-bg text-status-warning-text'\n }`}>\n {data.emailVerifiedAt\n ? t('customer_accounts.admin.verified', 'Yes')\n : t('customer_accounts.admin.unverified', 'No')}\n </span>\n {!data.emailVerifiedAt && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void handleVerifyEmail() }}\n disabled={isVerifying}\n >\n {isVerifying\n ? t('customer_accounts.admin.detail.verifyEmail.actions.verifying', 'Verifying...')\n : t('customer_accounts.admin.detail.verifyEmail.actions.verify', 'Mark Verified')}\n </Button>\n )}\n </dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.lastLogin', 'Last Login')}</dt>\n <dd>{formatDate(data.lastLoginAt, '-')}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.createdAt', 'Created')}</dt>\n <dd>{formatDate(data.createdAt, '-')}</dd>\n </div>\n </dl>\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-3\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.crmLinks', 'CRM Links')}</h2>\n <div className=\"space-y-3 text-sm\">\n <div className=\"space-y-1.5\">\n <p className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.personEntity', 'Linked Person')}</p>\n {editPersonEntityId ? (\n <div className=\"flex items-center gap-2\">\n <Link href={`/backend/customers/people/${editPersonEntityId}`} className=\"text-primary hover:underline\">\n {personName || editPersonEntityId}\n </Link>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setEditPersonEntityId(null); setPersonName(null) }}>\n {t('customer_accounts.admin.detail.actions.unlink', 'Unlink')}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-1\">\n <div className=\"relative\">\n <Input\n type=\"text\"\n value={personSearchQuery}\n onChange={(event) => { void handleSearchPeople(event.target.value) }}\n placeholder={t('customer_accounts.admin.detail.fields.searchPerson', 'Search people by name...')}\n />\n {personResults.length > 0 && (\n <div className=\"absolute z-10 mt-1 w-full rounded-md border bg-background shadow-lg max-h-40 overflow-y-auto\">\n {personResults.map((person) => (\n <button\n key={person.id}\n type=\"button\"\n className=\"w-full px-3 py-2 text-left text-sm hover:bg-muted\"\n onClick={() => {\n setEditPersonEntityId(person.id)\n setPersonName(person.label)\n setPersonSearchQuery('')\n setPersonResults([])\n }}\n >\n {person.label}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n <div className=\"space-y-1.5\">\n <p className=\"text-muted-foreground\">{t('customer_accounts.admin.detail.fields.customerEntity', 'Linked Company')}</p>\n {editCustomerEntityId ? (\n <div className=\"flex items-center gap-2\">\n <Link href={`/backend/customers/companies/${editCustomerEntityId}`} className=\"text-primary hover:underline\">\n {companyName || editCustomerEntityId}\n </Link>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setEditCustomerEntityId(null); setCompanyName(null) }}>\n {t('customer_accounts.admin.detail.actions.unlink', 'Unlink')}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-1\">\n <div className=\"relative\">\n <Input\n type=\"text\"\n value={companySearchQuery}\n onChange={(event) => { void handleSearchCompanies(event.target.value) }}\n placeholder={t('customer_accounts.admin.detail.fields.searchCompany', 'Search companies by name...')}\n />\n {companyResults.length > 0 && (\n <div className=\"absolute z-10 mt-1 w-full rounded-md border bg-background shadow-lg max-h-40 overflow-y-auto\">\n {companyResults.map((company) => (\n <button\n key={company.id}\n type=\"button\"\n className=\"w-full px-3 py-2 text-left text-sm hover:bg-muted\"\n onClick={() => {\n setEditCustomerEntityId(company.id)\n setCompanyName(company.label)\n setCompanySearchQuery('')\n setCompanyResults([])\n }}\n >\n {company.label}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t('customer_accounts.admin.detail.crmLinks.hint', 'Changes to CRM links are saved when you click Save Changes below.')}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-4\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.settings', 'Account Settings')}</h2>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-3\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"user-display-name\">\n {t('customer_accounts.admin.detail.fields.displayName', 'Display Name')}\n </label>\n <Input\n id=\"user-display-name\"\n type=\"text\"\n value={editDisplayName}\n onChange={(event) => setEditDisplayName(event.target.value)}\n />\n </div>\n\n <SwitchField\n id=\"user-active-toggle\"\n label={t('customer_accounts.admin.detail.fields.isActive', 'Active')}\n checked={editActive ?? data.isActive}\n onCheckedChange={(next) => setEditActive(next)}\n />\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium\">{t('customer_accounts.admin.detail.fields.roles', 'Roles')}</p>\n <div className=\"flex flex-wrap gap-2\">\n {availableRoles.map((role) => {\n const isSelected = selectedRoleIds.includes(role.id)\n return (\n <Button\n key={role.id}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleRoleToggle(role.id)}\n className={`rounded-full ${\n isSelected\n ? 'border-primary bg-primary/10 text-primary'\n : ''\n }`}\n >\n {role.name}\n </Button>\n )\n })}\n {availableRoles.length === 0 && (\n <span className=\"text-sm text-muted-foreground\">\n {t('customer_accounts.admin.detail.noRolesAvailable', 'No roles available')}\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"pt-2\">\n <Button onClick={() => { void handleSave() }} disabled={isSaving}>\n {isSaving\n ? t('customer_accounts.admin.detail.actions.saving', 'Saving...')\n : t('customer_accounts.admin.detail.actions.save', 'Save Changes')}\n </Button>\n </div>\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold\">{t('customer_accounts.admin.detail.sections.security', 'Security')}</h2>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" onClick={() => setResetPasswordOpen(true)}>\n {t('customer_accounts.admin.detail.resetPassword.actions.open', 'Reset Password')}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => { void handleSendResetLink() }}\n disabled={isSendingResetLink}\n >\n {isSendingResetLink\n ? t('customer_accounts.admin.detail.sendResetLink.actions.sending', 'Generating...')\n : t('customer_accounts.admin.detail.sendResetLink.actions.send', 'Send Reset Link')}\n </Button>\n </div>\n {resetLinkUrl && (\n <div className=\"rounded-md border border-status-info-border bg-status-info-bg p-3\">\n <p className=\"mb-1.5 text-sm font-medium text-status-info-text\">\n {t('customer_accounts.admin.detail.sendResetLink.linkLabel', 'Password reset link (valid for 60 minutes):')}\n </p>\n <div className=\"flex items-center gap-2\">\n <code className=\"flex-1 break-all rounded bg-status-info-bg px-2 py-1 text-xs text-status-info-text\">\n {resetLinkUrl}\n </code>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n void navigator.clipboard.writeText(resetLinkUrl)\n flash(t('customer_accounts.admin.detail.sendResetLink.flash.copied', 'Link copied to clipboard'), 'success')\n }}\n >\n {t('customer_accounts.admin.detail.sendResetLink.actions.copy', 'Copy')}\n </Button>\n </div>\n <p className=\"mt-1.5 text-xs text-status-info-text\">\n {t('customer_accounts.admin.detail.sendResetLink.hint', 'Share this link with the customer to let them set a new password.')}\n </p>\n </div>\n )}\n </div>\n\n <div className=\"rounded-lg border p-4 space-y-3\">\n <h2 className=\"text-sm font-semibold\">\n {t('customer_accounts.admin.detail.sections.sessions', 'Active Sessions')}\n {data.sessions.length > 0 && (\n <span className=\"ml-2 text-xs font-normal text-muted-foreground\">({data.sessions.length})</span>\n )}\n </h2>\n {data.sessions.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customer_accounts.admin.detail.noSessions', 'No active sessions')}\n </p>\n ) : (\n <div className=\"divide-y\">\n {data.sessions.map((session) => (\n <div key={session.id} className=\"flex items-center justify-between py-2 text-sm\">\n <div className=\"space-y-0.5\">\n <p className=\"font-medium\">\n {session.ipAddress || t('customer_accounts.admin.detail.unknownIp', 'Unknown IP')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate max-w-xs\">\n {session.userAgent || t('customer_accounts.admin.detail.unknownDevice', 'Unknown device')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {t('customer_accounts.admin.detail.fields.lastUsed', 'Last used')}: {formatDate(session.lastUsedAt, '-')}\n </p>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void handleRevokeSession(session.id) }}\n >\n {t('customer_accounts.admin.detail.actions.revoke', 'Revoke')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n\n <ResetPasswordDialog\n open={resetPasswordOpen}\n onOpenChange={setResetPasswordOpen}\n userId={id!}\n onRunMutation={runMutationWithContext}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,7 @@ function register(container) {
18
18
  container.register({ customerInvitationService: asClass(CustomerInvitationService).scoped() });
19
19
  container.register({
20
20
  domainMappingService: asFunction(
21
- function domainMappingServiceFactory(em) {
21
+ function domainMappingServiceFactory(cradle) {
22
22
  let cacheService;
23
23
  try {
24
24
  if (container.hasRegistration("cache")) {
@@ -28,9 +28,9 @@ function register(container) {
28
28
  } catch {
29
29
  cacheService = void 0;
30
30
  }
31
- return new DomainMappingService(em, cacheService ? { cacheService } : void 0);
31
+ return new DomainMappingService(cradle.em, cacheService ? { cacheService } : void 0);
32
32
  }
33
- ).scoped()
33
+ ).scoped().proxy()
34
34
  });
35
35
  }
36
36
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/customer_accounts/di.ts"],
4
- "sourcesContent": ["import { asClass, asFunction } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { CustomerInvitationService } from '@open-mercato/core/modules/customer_accounts/services/customerInvitationService'\nimport { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\n\ntype DomainCacheService = {\n get: (key: string, options?: unknown) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number; tags?: string[] }) => Promise<void>\n deleteByTags: (tags: string[]) => Promise<number>\n}\n\nfunction isDomainCacheService(candidate: unknown): candidate is DomainCacheService {\n if (!candidate || typeof candidate !== 'object') return false\n const c = candidate as Record<string, unknown>\n return typeof c.get === 'function' && typeof c.set === 'function' && typeof c.deleteByTags === 'function'\n}\n\nexport function register(container: AppContainer) {\n container.register({ customerUserService: asClass(CustomerUserService).scoped() })\n container.register({ customerSessionService: asClass(CustomerSessionService).scoped() })\n container.register({ customerTokenService: asClass(CustomerTokenService).scoped() })\n container.register({ customerRbacService: asClass(CustomerRbacService).scoped() })\n container.register({ customerInvitationService: asClass(CustomerInvitationService).scoped() })\n container.register({\n domainMappingService: asFunction(\n function domainMappingServiceFactory(em: EntityManager) {\n // Resolve the cache lazily so registrations from `bootstrap(container)` (which runs after\n // module DI registrars) are visible. Awilix CLASSIC mode would fail to resolve a\n // destructured `cache` param if the key is not yet registered; using `hasRegistration`\n // keeps construction safe for CLI/test contexts where `cache` may never be wired.\n let cacheService: DomainCacheService | undefined\n try {\n if (container.hasRegistration('cache')) {\n const candidate = container.resolve('cache')\n if (isDomainCacheService(candidate)) cacheService = candidate\n }\n } catch {\n cacheService = undefined\n }\n return new DomainMappingService(em, cacheService ? { cacheService } : undefined)\n },\n ).scoped(),\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,SAAS,kBAAkB;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,iCAAiC;AAC1C,SAAS,4BAA4B;AAQrC,SAAS,qBAAqB,WAAqD;AACjF,MAAI,CAAC,aAAa,OAAO,cAAc,SAAU,QAAO;AACxD,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,QAAQ,cAAc,OAAO,EAAE,QAAQ,cAAc,OAAO,EAAE,iBAAiB;AACjG;AAEO,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS,EAAE,qBAAqB,QAAQ,mBAAmB,EAAE,OAAO,EAAE,CAAC;AACjF,YAAU,SAAS,EAAE,wBAAwB,QAAQ,sBAAsB,EAAE,OAAO,EAAE,CAAC;AACvF,YAAU,SAAS,EAAE,sBAAsB,QAAQ,oBAAoB,EAAE,OAAO,EAAE,CAAC;AACnF,YAAU,SAAS,EAAE,qBAAqB,QAAQ,mBAAmB,EAAE,OAAO,EAAE,CAAC;AACjF,YAAU,SAAS,EAAE,2BAA2B,QAAQ,yBAAyB,EAAE,OAAO,EAAE,CAAC;AAC7F,YAAU,SAAS;AAAA,IACjB,sBAAsB;AAAA,MACpB,SAAS,4BAA4B,IAAmB;AAKtD,YAAI;AACJ,YAAI;AACF,cAAI,UAAU,gBAAgB,OAAO,GAAG;AACtC,kBAAM,YAAY,UAAU,QAAQ,OAAO;AAC3C,gBAAI,qBAAqB,SAAS,EAAG,gBAAe;AAAA,UACtD;AAAA,QACF,QAAQ;AACN,yBAAe;AAAA,QACjB;AACA,eAAO,IAAI,qBAAqB,IAAI,eAAe,EAAE,aAAa,IAAI,MAAS;AAAA,MACjF;AAAA,IACF,EAAE,OAAO;AAAA,EACX,CAAC;AACH;",
4
+ "sourcesContent": ["import { asClass, asFunction } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUserService } from '@open-mercato/core/modules/customer_accounts/services/customerUserService'\nimport { CustomerSessionService } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\nimport { CustomerTokenService } from '@open-mercato/core/modules/customer_accounts/services/customerTokenService'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { CustomerInvitationService } from '@open-mercato/core/modules/customer_accounts/services/customerInvitationService'\nimport { DomainMappingService } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'\n\ntype DomainCacheService = {\n get: (key: string, options?: unknown) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number; tags?: string[] }) => Promise<void>\n deleteByTags: (tags: string[]) => Promise<number>\n}\n\nfunction isDomainCacheService(candidate: unknown): candidate is DomainCacheService {\n if (!candidate || typeof candidate !== 'object') return false\n const c = candidate as Record<string, unknown>\n return typeof c.get === 'function' && typeof c.set === 'function' && typeof c.deleteByTags === 'function'\n}\n\nexport function register(container: AppContainer) {\n container.register({ customerUserService: asClass(CustomerUserService).scoped() })\n container.register({ customerSessionService: asClass(CustomerSessionService).scoped() })\n container.register({ customerTokenService: asClass(CustomerTokenService).scoped() })\n container.register({ customerRbacService: asClass(CustomerRbacService).scoped() })\n container.register({ customerInvitationService: asClass(CustomerInvitationService).scoped() })\n container.register({\n domainMappingService: asFunction(\n function domainMappingServiceFactory(cradle: { em: EntityManager }) {\n // Resolve the cache lazily so registrations from `bootstrap(container)` (which runs after\n // module DI registrars) are visible. Awilix CLASSIC mode would fail to resolve a\n // destructured `cache` param if the key is not yet registered; using `hasRegistration`\n // keeps construction safe for CLI/test contexts where `cache` may never be wired.\n let cacheService: DomainCacheService | undefined\n try {\n if (container.hasRegistration('cache')) {\n const candidate = container.resolve('cache')\n if (isDomainCacheService(candidate)) cacheService = candidate\n }\n } catch {\n cacheService = undefined\n }\n return new DomainMappingService(cradle.em, cacheService ? { cacheService } : undefined)\n },\n ).scoped().proxy(),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS,kBAAkB;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,iCAAiC;AAC1C,SAAS,4BAA4B;AAQrC,SAAS,qBAAqB,WAAqD;AACjF,MAAI,CAAC,aAAa,OAAO,cAAc,SAAU,QAAO;AACxD,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,QAAQ,cAAc,OAAO,EAAE,QAAQ,cAAc,OAAO,EAAE,iBAAiB;AACjG;AAEO,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS,EAAE,qBAAqB,QAAQ,mBAAmB,EAAE,OAAO,EAAE,CAAC;AACjF,YAAU,SAAS,EAAE,wBAAwB,QAAQ,sBAAsB,EAAE,OAAO,EAAE,CAAC;AACvF,YAAU,SAAS,EAAE,sBAAsB,QAAQ,oBAAoB,EAAE,OAAO,EAAE,CAAC;AACnF,YAAU,SAAS,EAAE,qBAAqB,QAAQ,mBAAmB,EAAE,OAAO,EAAE,CAAC;AACjF,YAAU,SAAS,EAAE,2BAA2B,QAAQ,yBAAyB,EAAE,OAAO,EAAE,CAAC;AAC7F,YAAU,SAAS;AAAA,IACjB,sBAAsB;AAAA,MACpB,SAAS,4BAA4B,QAA+B;AAKlE,YAAI;AACJ,YAAI;AACF,cAAI,UAAU,gBAAgB,OAAO,GAAG;AACtC,kBAAM,YAAY,UAAU,QAAQ,OAAO;AAC3C,gBAAI,qBAAqB,SAAS,EAAG,gBAAe;AAAA,UACtD;AAAA,QACF,QAAQ;AACN,yBAAe;AAAA,QACjB;AACA,eAAO,IAAI,qBAAqB,OAAO,IAAI,eAAe,EAAE,aAAa,IAAI,MAAS;AAAA,MACxF;AAAA,IACF,EAAE,OAAO,EAAE,MAAM;AAAA,EACnB,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -2,20 +2,31 @@ import { NextResponse } from "next/server";
2
2
  import { verifyAudienceJwt, verifyJwt } from "@open-mercato/shared/lib/auth/jwt";
3
3
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
4
  import { CUSTOMER_JWT_AUDIENCE } from "@open-mercato/core/modules/customer_accounts/services/customerSessionService";
5
+ async function resolveSessionService() {
6
+ const [{ createRequestContainer }, { CustomerSessionService }] = await Promise.all([
7
+ import("@open-mercato/shared/lib/di/container"),
8
+ import("@open-mercato/core/modules/customer_accounts/services/customerSessionService")
9
+ ]);
10
+ const container = await createRequestContainer();
11
+ return container.resolve("customerSessionService");
12
+ }
5
13
  async function assertSessionStillActive(input) {
6
14
  try {
7
- const [{ createRequestContainer }, { CustomerSessionService }] = await Promise.all([
8
- import("@open-mercato/shared/lib/di/container"),
9
- import("@open-mercato/core/modules/customer_accounts/services/customerSessionService")
10
- ]);
11
- const container = await createRequestContainer();
12
- const service = container.resolve("customerSessionService");
15
+ const service = await resolveSessionService();
13
16
  const session = await service.findActiveSessionForClaims(input);
14
17
  return session !== null;
15
18
  } catch {
16
19
  return false;
17
20
  }
18
21
  }
22
+ async function assertUserStillHasActiveSession(input) {
23
+ try {
24
+ const service = await resolveSessionService();
25
+ return await service.hasActiveSessionForUser(input);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
19
30
  function readCookieFromHeader(header, name) {
20
31
  if (!header) return void 0;
21
32
  const parts = header.split(";");
@@ -88,7 +99,7 @@ async function getCustomerAuthFromRequest(req) {
88
99
  userId,
89
100
  tenantId,
90
101
  organizationId
91
- }) : true;
102
+ }) : await assertUserStillHasActiveSession({ userId, tenantId, organizationId });
92
103
  if (!stillActive) return null;
93
104
  const userState = await validateUserState(
94
105
  userId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_accounts/lib/customerAuth.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { verifyAudienceJwt, verifyJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport type { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CUSTOMER_JWT_AUDIENCE } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\n\nexport interface CustomerAuthContext {\n sub: string\n sid: string\n type: 'customer'\n tenantId: string\n orgId: string\n email: string\n displayName: string\n customerEntityId?: string | null\n personEntityId?: string | null\n resolvedFeatures: string[]\n isPortalAdmin?: boolean\n}\n\nasync function assertSessionStillActive(input: {\n sessionId: string\n userId: string\n tenantId: string\n organizationId: string\n}): Promise<boolean> {\n try {\n const [{ createRequestContainer }, { CustomerSessionService }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('@open-mercato/core/modules/customer_accounts/services/customerSessionService'),\n ])\n const container = await createRequestContainer()\n const service = container.resolve('customerSessionService') as InstanceType<typeof CustomerSessionService>\n const session = await service.findActiveSessionForClaims(input)\n return session !== null\n } catch {\n // Fail closed: if we cannot verify the session, treat the token as revoked to prevent\n // replay of leaked JWTs when the backend is partially degraded.\n return false\n }\n}\n\nexport function readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n const parts = header.split(';')\n for (const part of parts) {\n const trimmed = part.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return trimmed.slice(name.length + 1)\n }\n }\n return undefined\n}\n\nexport type UserValidationResult =\n | { valid: false }\n | { valid: true; resolvedFeatures: string[]; isPortalAdmin: boolean }\n\nexport async function validateUserState(\n sub: string,\n tenantId: string,\n orgId: string,\n iat: unknown,\n): Promise<UserValidationResult> {\n const [{ createRequestContainer }, { CustomerUser }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('@open-mercato/core/modules/customer_accounts/data/entities'),\n ])\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const user = await findOneWithDecryption(em, CustomerUser, { id: sub }, {\n fields: ['sessionsRevokedAt', 'deletedAt', 'isActive'],\n })\n if (!user) return { valid: false }\n if (user.deletedAt) return { valid: false }\n if (!user.isActive) return { valid: false }\n if (user.sessionsRevokedAt && typeof iat === 'number' && iat * 1000 < user.sessionsRevokedAt.getTime()) {\n return { valid: false }\n }\n\n const { CustomerRbacService } = await import(\n '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\n )\n const rbac = container.resolve('customerRbacService') as InstanceType<typeof CustomerRbacService>\n const acl = await rbac.loadAcl(sub, { tenantId, organizationId: orgId })\n const resolvedFeatures = await rbac.getEffectiveFeatures(sub, {\n tenantId,\n organizationId: orgId,\n })\n return { valid: true, resolvedFeatures, isPortalAdmin: acl.isPortalAdmin }\n}\n\nexport async function getCustomerAuthFromRequest(req: Request): Promise<CustomerAuthContext | null> {\n const cookieHeader = req.headers.get('cookie') || ''\n const authHeader = (req.headers.get('authorization') || '').trim()\n\n let token: string | undefined\n if (authHeader.toLowerCase().startsWith('bearer ')) {\n token = authHeader.slice(7).trim()\n }\n if (!token) {\n const cookieValue = readCookieFromHeader(cookieHeader, 'customer_auth_token')\n if (cookieValue) {\n try {\n token = decodeURIComponent(cookieValue)\n } catch {\n // Malformed percent-encoding; use raw value\n token = cookieValue\n }\n }\n }\n if (!token) return null\n\n try {\n let payload = verifyAudienceJwt(CUSTOMER_JWT_AUDIENCE, token) as Record<string, unknown> | null\n // Legacy fallback: accept a pre-migration customer token signed with the raw JWT_SECRET, but\n // only while `verifyJwt` itself still considers it legacy \u2014 it owns the grace window (token\n // `iat` vs JWT_LEGACY_GRACE_MINUTES / JWT_LEGACY_CUTOVER_AT) and marks the payload. Trusting\n // the bare return value would also let a staff-audience token through this branch, because\n // the default `verifyJwt` path verifies against the staff-derived key.\n if (!payload) {\n const legacyPayload = verifyJwt(token) as Record<string, unknown> | null\n if (legacyPayload && legacyPayload._legacyToken === true) payload = legacyPayload\n }\n if (!payload) return null\n if (payload.type !== 'customer') return null\n const sid = typeof payload.sid === 'string' ? payload.sid : ''\n if (!sid && payload._legacyToken !== true) return null\n const userId = String(payload.sub)\n const tenantId = String(payload.tenantId)\n const organizationId = String(payload.orgId)\n const stillActive = sid\n ? await assertSessionStillActive({\n sessionId: sid,\n userId,\n tenantId,\n organizationId,\n })\n : true\n if (!stillActive) return null\n\n const userState = await validateUserState(\n userId,\n tenantId,\n organizationId,\n payload.iat,\n )\n if (!userState.valid) return null\n\n return {\n sub: userId,\n sid,\n type: 'customer',\n tenantId,\n orgId: organizationId,\n email: String(payload.email || ''),\n displayName: String(payload.displayName || ''),\n customerEntityId: payload.customerEntityId ? String(payload.customerEntityId) : null,\n personEntityId: payload.personEntityId ? String(payload.personEntityId) : null,\n resolvedFeatures: userState.resolvedFeatures,\n isPortalAdmin: userState.isPortalAdmin,\n }\n } catch {\n // Invalid or expired JWT \u2014 treat as unauthenticated\n return null\n }\n}\n\nexport async function requireCustomerAuth(req: Request): Promise<CustomerAuthContext> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n throw NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n return auth\n}\n\nexport async function requireCustomerFeature(\n auth: CustomerAuthContext,\n features: string[],\n rbac: CustomerRbacService,\n): Promise<void> {\n if (!features.length) return\n const ok = await rbac.userHasAllFeatures(\n auth.sub,\n features,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!ok) {\n throw NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { verifyAudienceJwt, verifyJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport type { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CUSTOMER_JWT_AUDIENCE } from '@open-mercato/core/modules/customer_accounts/services/customerSessionService'\n\nexport interface CustomerAuthContext {\n sub: string\n sid: string\n type: 'customer'\n tenantId: string\n orgId: string\n email: string\n displayName: string\n customerEntityId?: string | null\n personEntityId?: string | null\n resolvedFeatures: string[]\n isPortalAdmin?: boolean\n}\n\nasync function resolveSessionService(): Promise<\n InstanceType<typeof import('@open-mercato/core/modules/customer_accounts/services/customerSessionService').CustomerSessionService>\n> {\n const [{ createRequestContainer }, { CustomerSessionService }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('@open-mercato/core/modules/customer_accounts/services/customerSessionService'),\n ])\n const container = await createRequestContainer()\n return container.resolve('customerSessionService') as InstanceType<typeof CustomerSessionService>\n}\n\nasync function assertSessionStillActive(input: {\n sessionId: string\n userId: string\n tenantId: string\n organizationId: string\n}): Promise<boolean> {\n try {\n const service = await resolveSessionService()\n const session = await service.findActiveSessionForClaims(input)\n return session !== null\n } catch {\n // Fail closed: if we cannot verify the session, treat the token as revoked to prevent\n // replay of leaked JWTs when the backend is partially degraded.\n return false\n }\n}\n\n/**\n * Liveness re-check for a legacy token that carries no `sid` claim. It cannot name the session it\n * was issued for, so the check falls back to the strongest available statement: the customer must\n * still be signed in somewhere. Once every session is revoked \u2014 logout, per-device revoke, admin\n * action \u2014 the token stops authenticating instead of surviving until its own expiry.\n */\nasync function assertUserStillHasActiveSession(input: {\n userId: string\n tenantId: string\n organizationId: string\n}): Promise<boolean> {\n try {\n const service = await resolveSessionService()\n return await service.hasActiveSessionForUser(input)\n } catch {\n // Same fail-closed contract as the sid-bound check above.\n return false\n }\n}\n\nexport function readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n const parts = header.split(';')\n for (const part of parts) {\n const trimmed = part.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return trimmed.slice(name.length + 1)\n }\n }\n return undefined\n}\n\nexport type UserValidationResult =\n | { valid: false }\n | { valid: true; resolvedFeatures: string[]; isPortalAdmin: boolean }\n\nexport async function validateUserState(\n sub: string,\n tenantId: string,\n orgId: string,\n iat: unknown,\n): Promise<UserValidationResult> {\n const [{ createRequestContainer }, { CustomerUser }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('@open-mercato/core/modules/customer_accounts/data/entities'),\n ])\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const user = await findOneWithDecryption(em, CustomerUser, { id: sub }, {\n fields: ['sessionsRevokedAt', 'deletedAt', 'isActive'],\n })\n if (!user) return { valid: false }\n if (user.deletedAt) return { valid: false }\n if (!user.isActive) return { valid: false }\n if (user.sessionsRevokedAt && typeof iat === 'number' && iat * 1000 < user.sessionsRevokedAt.getTime()) {\n return { valid: false }\n }\n\n const { CustomerRbacService } = await import(\n '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\n )\n const rbac = container.resolve('customerRbacService') as InstanceType<typeof CustomerRbacService>\n const acl = await rbac.loadAcl(sub, { tenantId, organizationId: orgId })\n const resolvedFeatures = await rbac.getEffectiveFeatures(sub, {\n tenantId,\n organizationId: orgId,\n })\n return { valid: true, resolvedFeatures, isPortalAdmin: acl.isPortalAdmin }\n}\n\nexport async function getCustomerAuthFromRequest(req: Request): Promise<CustomerAuthContext | null> {\n const cookieHeader = req.headers.get('cookie') || ''\n const authHeader = (req.headers.get('authorization') || '').trim()\n\n let token: string | undefined\n if (authHeader.toLowerCase().startsWith('bearer ')) {\n token = authHeader.slice(7).trim()\n }\n if (!token) {\n const cookieValue = readCookieFromHeader(cookieHeader, 'customer_auth_token')\n if (cookieValue) {\n try {\n token = decodeURIComponent(cookieValue)\n } catch {\n // Malformed percent-encoding; use raw value\n token = cookieValue\n }\n }\n }\n if (!token) return null\n\n try {\n let payload = verifyAudienceJwt(CUSTOMER_JWT_AUDIENCE, token) as Record<string, unknown> | null\n // Legacy fallback: accept a pre-migration customer token signed with the raw JWT_SECRET, but\n // only while `verifyJwt` itself still considers it legacy \u2014 it owns the grace window (token\n // `iat` vs JWT_LEGACY_GRACE_MINUTES / JWT_LEGACY_CUTOVER_AT) and marks the payload. Trusting\n // the bare return value would also let a staff-audience token through this branch, because\n // the default `verifyJwt` path verifies against the staff-derived key.\n if (!payload) {\n const legacyPayload = verifyJwt(token) as Record<string, unknown> | null\n if (legacyPayload && legacyPayload._legacyToken === true) payload = legacyPayload\n }\n if (!payload) return null\n if (payload.type !== 'customer') return null\n const sid = typeof payload.sid === 'string' ? payload.sid : ''\n if (!sid && payload._legacyToken !== true) return null\n const userId = String(payload.sub)\n const tenantId = String(payload.tenantId)\n const organizationId = String(payload.orgId)\n // Every accepted token is re-checked for liveness, exactly like the SSR path does \u2014 a token\n // without a `sid` is checked against the customer's remaining sessions rather than skipped,\n // so session revocation is never structurally unreachable on the API path.\n const stillActive = sid\n ? await assertSessionStillActive({\n sessionId: sid,\n userId,\n tenantId,\n organizationId,\n })\n : await assertUserStillHasActiveSession({ userId, tenantId, organizationId })\n if (!stillActive) return null\n\n const userState = await validateUserState(\n userId,\n tenantId,\n organizationId,\n payload.iat,\n )\n if (!userState.valid) return null\n\n return {\n sub: userId,\n sid,\n type: 'customer',\n tenantId,\n orgId: organizationId,\n email: String(payload.email || ''),\n displayName: String(payload.displayName || ''),\n customerEntityId: payload.customerEntityId ? String(payload.customerEntityId) : null,\n personEntityId: payload.personEntityId ? String(payload.personEntityId) : null,\n resolvedFeatures: userState.resolvedFeatures,\n isPortalAdmin: userState.isPortalAdmin,\n }\n } catch {\n // Invalid or expired JWT \u2014 treat as unauthenticated\n return null\n }\n}\n\nexport async function requireCustomerAuth(req: Request): Promise<CustomerAuthContext> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n throw NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n return auth\n}\n\nexport async function requireCustomerFeature(\n auth: CustomerAuthContext,\n features: string[],\n rbac: CustomerRbacService,\n): Promise<void> {\n if (!features.length) return\n const ok = await rbac.userHasAllFeatures(\n auth.sub,\n features,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n if (!ok) {\n throw NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -98,6 +98,29 @@ class CustomerSessionService {
98
98
  if (session.expiresAt.getTime() < Date.now()) return null;
99
99
  return session;
100
100
  }
101
+ /**
102
+ * Liveness probe for tokens that carry no `sid` claim — the pre-migration customer JWTs the
103
+ * legacy raw-secret fallback still admits during a key rotation. Such a token cannot name the
104
+ * session it was issued for, so the strongest statement we can make about it is whether the
105
+ * customer is still signed in anywhere. A customer whose sessions have all been revoked
106
+ * (logout, per-device revoke, admin action) has none, and the token must stop working.
107
+ *
108
+ * Existence is the whole answer, so this reads no encrypted column and needs no decryption
109
+ * round-trip on the authentication path — the same reason `findActiveSessionForClaims` next to
110
+ * it queries directly. Scope comes from the owning user, since sessions carry no scope columns.
111
+ */
112
+ async hasActiveSessionForUser(input) {
113
+ const session = await this.em.findOne(CustomerUserSession, {
114
+ user: {
115
+ id: input.userId,
116
+ tenantId: input.tenantId,
117
+ organizationId: input.organizationId
118
+ },
119
+ deletedAt: null,
120
+ expiresAt: { $gt: /* @__PURE__ */ new Date() }
121
+ });
122
+ return session !== null;
123
+ }
101
124
  async revokeSession(sessionId) {
102
125
  await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: /* @__PURE__ */ new Date() });
103
126
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_accounts/services/customerSessionService.ts"],
4
- "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { signAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const CUSTOMER_JWT_AUDIENCE = 'customer'\nconst CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8\n\nconst DEFAULT_SESSION_TTL_DAYS = 30\nconst DEFAULT_MAX_SESSIONS_PER_USER = 5\n\nfunction resolveMaxSessionsPerUser(): number {\n const raw = process.env.MAX_CUSTOMER_SESSIONS_PER_USER\n if (!raw) return DEFAULT_MAX_SESSIONS_PER_USER\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_SESSIONS_PER_USER\n return Math.floor(parsed)\n}\n\nexport class CustomerSessionService {\n constructor(private em: EntityManager) {}\n\n async createSession(\n user: CustomerUser,\n resolvedFeatures: string[],\n ip?: string | null,\n userAgent?: string | null,\n ): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {\n const rawToken = generateSecureToken()\n const tokenHash = hashToken(rawToken)\n const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)\n const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n\n await this.enforceSessionCap(user.id, user.tenantId, user.organizationId)\n\n const session = this.em.create(CustomerUserSession, {\n user,\n tokenHash,\n ipAddress: ip || null,\n userAgent: userAgent || null,\n expiresAt,\n lastUsedAt: new Date(),\n createdAt: new Date(),\n } as any) as CustomerUserSession\n await this.em.persist(session).flush()\n\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n\n return { rawToken, jwt, session }\n }\n\n signCustomerJwt(user: CustomerUser, resolvedFeatures: string[], sessionId: string): string {\n return signAudienceJwt(\n CUSTOMER_JWT_AUDIENCE,\n {\n sub: user.id,\n sid: sessionId,\n type: 'customer',\n tenantId: user.tenantId,\n orgId: user.organizationId,\n email: user.email,\n displayName: user.displayName || '',\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n resolvedFeatures,\n },\n CUSTOMER_JWT_TTL_SECONDS,\n )\n }\n\n async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {\n const tokenHash = hashToken(rawToken)\n const session = await this.em.findOne(CustomerUserSession, {\n tokenHash,\n deletedAt: null,\n }, { populate: ['user'] })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n const user = session.user as CustomerUser\n if (tenantId && user?.tenantId !== tenantId) return null\n return session\n }\n\n async refreshSession(\n rawToken: string,\n resolvedFeatures: string[],\n ): Promise<{ jwt: string; user: CustomerUser } | null> {\n const session = await this.findByToken(rawToken)\n if (!session) return null\n const user = session.user as CustomerUser\n if (!user || user.deletedAt || !user.isActive) return null\n\n await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n return { jwt, user }\n }\n\n async findActiveSessionById(sessionId: string): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: sessionId,\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async findActiveSessionForClaims(input: {\n sessionId: string\n userId: string\n tenantId: string\n organizationId: string\n }): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: input.sessionId,\n user: {\n id: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n },\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })\n }\n\n private async enforceSessionCap(\n userId: string,\n tenantId: string,\n organizationId: string,\n ): Promise<void> {\n const cap = resolveMaxSessionsPerUser()\n const existing = await findWithDecryption(\n this.em,\n CustomerUserSession,\n {\n user: userId as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n },\n { orderBy: { createdAt: 'asc' } },\n { tenantId, organizationId },\n )\n const toRevoke = existing.length - (cap - 1)\n if (toRevoke <= 0) return\n const oldestIds = existing.slice(0, toRevoke).map((s) => s.id)\n await this.em.nativeUpdate(\n CustomerUserSession,\n { id: { $in: oldestIds } },\n { deletedAt: new Date() },\n )\n }\n\n async revokeAllUserSessions(userId: string, em?: EntityManager): Promise<void> {\n const now = new Date()\n const target = em ?? this.em\n await target.nativeUpdate(\n CustomerUserSession,\n { user: userId as any, deletedAt: null },\n { deletedAt: now },\n )\n await target.nativeUpdate(\n CustomerUser,\n { id: userId },\n { sessionsRevokedAt: now },\n )\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { signAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const CUSTOMER_JWT_AUDIENCE = 'customer'\nconst CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8\n\nconst DEFAULT_SESSION_TTL_DAYS = 30\nconst DEFAULT_MAX_SESSIONS_PER_USER = 5\n\nfunction resolveMaxSessionsPerUser(): number {\n const raw = process.env.MAX_CUSTOMER_SESSIONS_PER_USER\n if (!raw) return DEFAULT_MAX_SESSIONS_PER_USER\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_SESSIONS_PER_USER\n return Math.floor(parsed)\n}\n\nexport class CustomerSessionService {\n constructor(private em: EntityManager) {}\n\n async createSession(\n user: CustomerUser,\n resolvedFeatures: string[],\n ip?: string | null,\n userAgent?: string | null,\n ): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {\n const rawToken = generateSecureToken()\n const tokenHash = hashToken(rawToken)\n const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)\n const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n\n await this.enforceSessionCap(user.id, user.tenantId, user.organizationId)\n\n const session = this.em.create(CustomerUserSession, {\n user,\n tokenHash,\n ipAddress: ip || null,\n userAgent: userAgent || null,\n expiresAt,\n lastUsedAt: new Date(),\n createdAt: new Date(),\n } as any) as CustomerUserSession\n await this.em.persist(session).flush()\n\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n\n return { rawToken, jwt, session }\n }\n\n signCustomerJwt(user: CustomerUser, resolvedFeatures: string[], sessionId: string): string {\n return signAudienceJwt(\n CUSTOMER_JWT_AUDIENCE,\n {\n sub: user.id,\n sid: sessionId,\n type: 'customer',\n tenantId: user.tenantId,\n orgId: user.organizationId,\n email: user.email,\n displayName: user.displayName || '',\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n resolvedFeatures,\n },\n CUSTOMER_JWT_TTL_SECONDS,\n )\n }\n\n async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {\n const tokenHash = hashToken(rawToken)\n const session = await this.em.findOne(CustomerUserSession, {\n tokenHash,\n deletedAt: null,\n }, { populate: ['user'] })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n const user = session.user as CustomerUser\n if (tenantId && user?.tenantId !== tenantId) return null\n return session\n }\n\n async refreshSession(\n rawToken: string,\n resolvedFeatures: string[],\n ): Promise<{ jwt: string; user: CustomerUser } | null> {\n const session = await this.findByToken(rawToken)\n if (!session) return null\n const user = session.user as CustomerUser\n if (!user || user.deletedAt || !user.isActive) return null\n\n await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n return { jwt, user }\n }\n\n async findActiveSessionById(sessionId: string): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: sessionId,\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async findActiveSessionForClaims(input: {\n sessionId: string\n userId: string\n tenantId: string\n organizationId: string\n }): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: input.sessionId,\n user: {\n id: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n },\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n /**\n * Liveness probe for tokens that carry no `sid` claim \u2014 the pre-migration customer JWTs the\n * legacy raw-secret fallback still admits during a key rotation. Such a token cannot name the\n * session it was issued for, so the strongest statement we can make about it is whether the\n * customer is still signed in anywhere. A customer whose sessions have all been revoked\n * (logout, per-device revoke, admin action) has none, and the token must stop working.\n *\n * Existence is the whole answer, so this reads no encrypted column and needs no decryption\n * round-trip on the authentication path \u2014 the same reason `findActiveSessionForClaims` next to\n * it queries directly. Scope comes from the owning user, since sessions carry no scope columns.\n */\n async hasActiveSessionForUser(input: {\n userId: string\n tenantId: string\n organizationId: string\n }): Promise<boolean> {\n const session = await this.em.findOne(CustomerUserSession, {\n user: {\n id: input.userId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n },\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n })\n return session !== null\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })\n }\n\n private async enforceSessionCap(\n userId: string,\n tenantId: string,\n organizationId: string,\n ): Promise<void> {\n const cap = resolveMaxSessionsPerUser()\n const existing = await findWithDecryption(\n this.em,\n CustomerUserSession,\n {\n user: userId as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n },\n { orderBy: { createdAt: 'asc' } },\n { tenantId, organizationId },\n )\n const toRevoke = existing.length - (cap - 1)\n if (toRevoke <= 0) return\n const oldestIds = existing.slice(0, toRevoke).map((s) => s.id)\n await this.em.nativeUpdate(\n CustomerUserSession,\n { id: { $in: oldestIds } },\n { deletedAt: new Date() },\n )\n }\n\n async revokeAllUserSessions(userId: string, em?: EntityManager): Promise<void> {\n const now = new Date()\n const target = em ?? this.em\n await target.nativeUpdate(\n CustomerUserSession,\n { user: userId as any, deletedAt: null },\n { deletedAt: now },\n )\n await target.nativeUpdate(\n CustomerUser,\n { id: userId },\n { sessionsRevokedAt: now },\n )\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }