@open-mercato/core 0.6.4-develop.4236.1.9fa6806b34 → 0.6.4-develop.4239.1.4a264a5828
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.
- package/dist/modules/business_rules/backend/logs/[id]/page.js +24 -5
- package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
- package/dist/modules/catalog/api/offers/route.js +15 -5
- package/dist/modules/catalog/api/offers/route.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +19 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +27 -7
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +27 -7
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/[id]/page.js +29 -8
- package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
- package/dist/modules/progress/acl.js +8 -4
- package/dist/modules/progress/acl.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +24 -6
- package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.js +27 -5
- package/dist/modules/workflows/backend/instances/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js +25 -6
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/business_rules/backend/logs/[id]/page.tsx +32 -7
- package/src/modules/catalog/api/offers/route.ts +20 -5
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +21 -2
- package/src/modules/currencies/i18n/de.json +1 -0
- package/src/modules/currencies/i18n/en.json +1 -0
- package/src/modules/currencies/i18n/es.json +1 -0
- package/src/modules/currencies/i18n/pl.json +1 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -11
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +34 -11
- package/src/modules/customers/backend/customers/people/[id]/page.tsx +35 -11
- package/src/modules/progress/acl.ts +4 -0
- package/src/modules/workflows/backend/events/[id]/page.tsx +32 -10
- package/src/modules/workflows/backend/instances/[id]/page.tsx +33 -9
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +33 -10
- package/src/modules/workflows/i18n/de.json +1 -0
- package/src/modules/workflows/i18n/en.json +1 -0
- package/src/modules/workflows/i18n/es.json +1 -0
- package/src/modules/workflows/i18n/pl.json +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/backend/customer_accounts/roles/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport type { CrudField, CrudFormGroup, CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype RoleDetail = {\n id: string\n name: string\n slug: string\n description: string | null\n isDefault: boolean\n isSystem: boolean\n customerAssignable: boolean\n features: string[]\n}\n\nconst PORTAL_FEATURES = [\n { id: 'portal.profile.view', labelKey: 'customer_accounts.admin.portalFeatures.profile.view', fallback: 'View profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.view.description', descriptionFallback: 'Allows viewing own profile information and account details' },\n { id: 'portal.profile.edit', labelKey: 'customer_accounts.admin.portalFeatures.profile.edit', fallback: 'Edit profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.edit.description', descriptionFallback: 'Allows editing display name and other profile settings' },\n { id: 'portal.orders.view', labelKey: 'customer_accounts.admin.portalFeatures.orders.view', fallback: 'View orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.view.description', descriptionFallback: 'Allows viewing order history and order details' },\n { id: 'portal.orders.create', labelKey: 'customer_accounts.admin.portalFeatures.orders.create', fallback: 'Create orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.create.description', descriptionFallback: 'Allows placing new orders through the portal' },\n { id: 'portal.invoices.view', labelKey: 'customer_accounts.admin.portalFeatures.invoices.view', fallback: 'View invoices', descriptionKey: 'customer_accounts.admin.portalFeatures.invoices.view.description', descriptionFallback: 'Allows viewing invoices and payment history' },\n { id: 'portal.quotes.view', labelKey: 'customer_accounts.admin.portalFeatures.quotes.view', fallback: 'View quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.view.description', descriptionFallback: 'Allows viewing received quotes and their details' },\n { id: 'portal.quotes.request', labelKey: 'customer_accounts.admin.portalFeatures.quotes.request', fallback: 'Request quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.request.description', descriptionFallback: 'Allows requesting new quotes from the company' },\n { id: 'portal.addresses.view', labelKey: 'customer_accounts.admin.portalFeatures.addresses.view', fallback: 'View addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.view.description', descriptionFallback: 'Allows viewing saved shipping and billing addresses' },\n { id: 'portal.addresses.manage', labelKey: 'customer_accounts.admin.portalFeatures.addresses.manage', fallback: 'Manage addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.manage.description', descriptionFallback: 'Allows adding, editing, and removing addresses' },\n { id: 'portal.users.view', labelKey: 'customer_accounts.admin.portalFeatures.users.view', fallback: 'View team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.view.description', descriptionFallback: 'Allows viewing other team members in the organization' },\n { id: 'portal.users.invite', labelKey: 'customer_accounts.admin.portalFeatures.users.invite', fallback: 'Invite team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.invite.description', descriptionFallback: 'Allows sending portal invitations to new team members' },\n { id: 'portal.users.manage', labelKey: 'customer_accounts.admin.portalFeatures.users.manage', fallback: 'Manage team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.manage.description', descriptionFallback: 'Allows editing roles and removing team members' },\n]\n\nconst FEATURE_GROUPS: Array<{ id: string; labelKey: string; fallback: string; features: string[] }> = (() => {\n const groups = new Map<string, string[]>()\n for (const feature of PORTAL_FEATURES) {\n const parts = feature.id.split('.')\n const groupKey = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : parts[0]\n const existing = groups.get(groupKey)\n if (existing) {\n existing.push(feature.id)\n } else {\n groups.set(groupKey, [feature.id])\n }\n }\n return Array.from(groups.entries()).map(([groupId, features]) => {\n const scope = groupId.split('.').slice(1).join('')\n const fallback = scope.replace(/^\\w/, (ch) => ch.toUpperCase())\n return {\n id: groupId,\n labelKey: `customer_accounts.admin.portalFeatures.groups.${scope}`,\n fallback,\n features,\n }\n })\n})()\n\nfunction PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentProps) {\n const t = useT()\n const features = React.useMemo(\n () => Array.isArray(values.features) ? values.features as string[] : [],\n [values.features],\n )\n\n const handleFeatureToggle = React.useCallback((featureId: string) => {\n const next = features.includes(featureId)\n ? features.filter((existingId) => existingId !== featureId)\n : [...features, featureId]\n setValue('features', next)\n }, [features, setValue])\n\n const handleGroupToggle = React.useCallback((featureIds: string[]) => {\n const allSelected = featureIds.every((featureId) => features.includes(featureId))\n let next: string[]\n if (allSelected) {\n next = features.filter((featureId) => !featureIds.includes(featureId))\n } else {\n next = [...features]\n for (const featureId of featureIds) {\n if (!next.includes(featureId)) next.push(featureId)\n }\n }\n setValue('features', next)\n }, [features, setValue])\n\n return (\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {FEATURE_GROUPS.map((group) => {\n const groupFeatures = group.features\n const allSelected = groupFeatures.every((featureId) => features.includes(featureId))\n const someSelected = groupFeatures.some((featureId) => features.includes(featureId))\n return (\n <div key={group.id} className=\"rounded-lg border\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <span className=\"text-sm font-semibold\">{t(group.labelKey, group.fallback)}</span>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n checked={allSelected}\n ref={(el) => { if (el) el.indeterminate = someSelected && !allSelected }}\n onChange={() => handleGroupToggle(groupFeatures)}\n className=\"rounded border-border\"\n />\n {t('customer_accounts.admin.roleDetail.selectAll', 'Select all')}\n </label>\n </div>\n <div className=\"divide-y\">\n {groupFeatures.map((featureId) => {\n const feature = PORTAL_FEATURES.find((portalFeature) => portalFeature.id === featureId)\n return (\n <label key={featureId} className=\"flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors\">\n <input\n type=\"checkbox\"\n checked={features.includes(featureId)}\n onChange={() => handleFeatureToggle(featureId)}\n className=\"mt-0.5 rounded border-border\"\n />\n <div className=\"space-y-0.5\">\n <div className=\"text-sm font-medium\">{feature ? t(feature.labelKey, feature.fallback) : featureId}</div>\n {feature && (\n <div className=\"text-xs text-muted-foreground\">{t(feature.descriptionKey, feature.descriptionFallback)}</div>\n )}\n </div>\n </label>\n )\n })}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default function CustomerRoleDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const [data, setData] = React.useState<RoleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!id) {\n setError(t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found'))\n setIsLoading(false)\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n try {\n const payload = await readApiResultOrThrow<RoleDetail>(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id!)}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role') },\n )\n if (cancelled) return\n setData(payload)\n } catch (err) {\n if (cancelled) return\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role')\n setError(message)\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n const fields = React.useMemo<CrudField[]>(() => {\n if (!data) return []\n return [\n {\n id: 'name',\n type: 'text' as const,\n label: t('customer_accounts.admin.roleDetail.fields.name', 'Name'),\n required: true,\n disabled: data.isSystem,\n },\n {\n id: 'description',\n type: 'textarea' as const,\n label: t('customer_accounts.admin.roleDetail.fields.description', 'Description'),\n },\n {\n id: 'isDefault',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.isDefault', 'Default role (auto-assigned to new users)'),\n },\n {\n id: 'customerAssignable',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.customerAssignable', 'Customers can self-assign'),\n },\n ]\n }, [data, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'details',\n title: t('customer_accounts.admin.roleDetail.sections.details', 'Role Details'),\n column: 1,\n fields: ['name', 'description'],\n },\n {\n id: 'options',\n title: t('customer_accounts.admin.roleDetail.sections.options', 'Options'),\n column: 1,\n fields: ['isDefault', 'customerAssignable'],\n },\n {\n id: 'permissions',\n title: t('customer_accounts.admin.roleDetail.sections.permissions', 'Portal Permissions'),\n column: 1,\n component: PortalPermissionsEditor,\n },\n ], [t])\n\n const initialValues = React.useMemo(() => {\n if (!data) return {}\n return {\n name: data.name,\n description: data.description || '',\n isDefault: data.isDefault,\n customerAssignable: data.customerAssignable,\n features: data.features,\n }\n }, [data])\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!id) return\n const roleCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: (values.name as string)?.trim(),\n description: (values.description as string)?.trim() || null,\n isDefault: values.isDefault,\n customerAssignable: values.customerAssignable,\n }),\n },\n )\n if (!roleCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.save', 'Failed to save role'), 'error')\n return\n }\n const features = Array.isArray(values.features) ? values.features : []\n const aclCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}/acl`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features }),\n },\n )\n if (!aclCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.saveAcl', 'Failed to save permissions'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roleDetail.flash.saved', 'Role updated'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!id) return\n const call = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n flash(t('customer_accounts.admin.roles.error.delete', 'Failed to delete role'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roles.flash.deleted', 'Role deleted'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\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.roleDetail.loading', 'Loading role...')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{error || t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/customer_accounts/roles\">\n {t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}\n </Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={data.name}\n backHref=\"/backend/customer_accounts/roles\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n entityId=\"customer_accounts:customer_role\"\n onSubmit={handleSubmit}\n onDelete={!data.isSystem ? handleDelete : undefined}\n cancelHref=\"/backend/customer_accounts/roles\"\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport type { CrudField, CrudFormGroup, CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype RoleDetail = {\n id: string\n name: string\n slug: string\n description: string | null\n isDefault: boolean\n isSystem: boolean\n customerAssignable: boolean\n features: string[]\n}\n\nconst PORTAL_FEATURES = [\n { id: 'portal.profile.view', labelKey: 'customer_accounts.admin.portalFeatures.profile.view', fallback: 'View profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.view.description', descriptionFallback: 'Allows viewing own profile information and account details' },\n { id: 'portal.profile.edit', labelKey: 'customer_accounts.admin.portalFeatures.profile.edit', fallback: 'Edit profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.edit.description', descriptionFallback: 'Allows editing display name and other profile settings' },\n { id: 'portal.orders.view', labelKey: 'customer_accounts.admin.portalFeatures.orders.view', fallback: 'View orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.view.description', descriptionFallback: 'Allows viewing order history and order details' },\n { id: 'portal.orders.create', labelKey: 'customer_accounts.admin.portalFeatures.orders.create', fallback: 'Create orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.create.description', descriptionFallback: 'Allows placing new orders through the portal' },\n { id: 'portal.invoices.view', labelKey: 'customer_accounts.admin.portalFeatures.invoices.view', fallback: 'View invoices', descriptionKey: 'customer_accounts.admin.portalFeatures.invoices.view.description', descriptionFallback: 'Allows viewing invoices and payment history' },\n { id: 'portal.quotes.view', labelKey: 'customer_accounts.admin.portalFeatures.quotes.view', fallback: 'View quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.view.description', descriptionFallback: 'Allows viewing received quotes and their details' },\n { id: 'portal.quotes.request', labelKey: 'customer_accounts.admin.portalFeatures.quotes.request', fallback: 'Request quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.request.description', descriptionFallback: 'Allows requesting new quotes from the company' },\n { id: 'portal.addresses.view', labelKey: 'customer_accounts.admin.portalFeatures.addresses.view', fallback: 'View addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.view.description', descriptionFallback: 'Allows viewing saved shipping and billing addresses' },\n { id: 'portal.addresses.manage', labelKey: 'customer_accounts.admin.portalFeatures.addresses.manage', fallback: 'Manage addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.manage.description', descriptionFallback: 'Allows adding, editing, and removing addresses' },\n { id: 'portal.users.view', labelKey: 'customer_accounts.admin.portalFeatures.users.view', fallback: 'View team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.view.description', descriptionFallback: 'Allows viewing other team members in the organization' },\n { id: 'portal.users.invite', labelKey: 'customer_accounts.admin.portalFeatures.users.invite', fallback: 'Invite team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.invite.description', descriptionFallback: 'Allows sending portal invitations to new team members' },\n { id: 'portal.users.manage', labelKey: 'customer_accounts.admin.portalFeatures.users.manage', fallback: 'Manage team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.manage.description', descriptionFallback: 'Allows editing roles and removing team members' },\n]\n\nconst FEATURE_GROUPS: Array<{ id: string; labelKey: string; fallback: string; features: string[] }> = (() => {\n const groups = new Map<string, string[]>()\n for (const feature of PORTAL_FEATURES) {\n const parts = feature.id.split('.')\n const groupKey = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : parts[0]\n const existing = groups.get(groupKey)\n if (existing) {\n existing.push(feature.id)\n } else {\n groups.set(groupKey, [feature.id])\n }\n }\n return Array.from(groups.entries()).map(([groupId, features]) => {\n const scope = groupId.split('.').slice(1).join('')\n const fallback = scope.replace(/^\\w/, (ch) => ch.toUpperCase())\n return {\n id: groupId,\n labelKey: `customer_accounts.admin.portalFeatures.groups.${scope}`,\n fallback,\n features,\n }\n })\n})()\n\nfunction PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentProps) {\n const t = useT()\n const features = React.useMemo(\n () => Array.isArray(values.features) ? values.features as string[] : [],\n [values.features],\n )\n\n const handleFeatureToggle = React.useCallback((featureId: string) => {\n const next = features.includes(featureId)\n ? features.filter((existingId) => existingId !== featureId)\n : [...features, featureId]\n setValue('features', next)\n }, [features, setValue])\n\n const handleGroupToggle = React.useCallback((featureIds: string[]) => {\n const allSelected = featureIds.every((featureId) => features.includes(featureId))\n let next: string[]\n if (allSelected) {\n next = features.filter((featureId) => !featureIds.includes(featureId))\n } else {\n next = [...features]\n for (const featureId of featureIds) {\n if (!next.includes(featureId)) next.push(featureId)\n }\n }\n setValue('features', next)\n }, [features, setValue])\n\n return (\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {FEATURE_GROUPS.map((group) => {\n const groupFeatures = group.features\n const allSelected = groupFeatures.every((featureId) => features.includes(featureId))\n const someSelected = groupFeatures.some((featureId) => features.includes(featureId))\n return (\n <div key={group.id} className=\"rounded-lg border\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <span className=\"text-sm font-semibold\">{t(group.labelKey, group.fallback)}</span>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n checked={allSelected}\n ref={(el) => { if (el) el.indeterminate = someSelected && !allSelected }}\n onChange={() => handleGroupToggle(groupFeatures)}\n className=\"rounded border-border\"\n />\n {t('customer_accounts.admin.roleDetail.selectAll', 'Select all')}\n </label>\n </div>\n <div className=\"divide-y\">\n {groupFeatures.map((featureId) => {\n const feature = PORTAL_FEATURES.find((portalFeature) => portalFeature.id === featureId)\n return (\n <label key={featureId} className=\"flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors\">\n <input\n type=\"checkbox\"\n checked={features.includes(featureId)}\n onChange={() => handleFeatureToggle(featureId)}\n className=\"mt-0.5 rounded border-border\"\n />\n <div className=\"space-y-0.5\">\n <div className=\"text-sm font-medium\">{feature ? t(feature.labelKey, feature.fallback) : featureId}</div>\n {feature && (\n <div className=\"text-xs text-muted-foreground\">{t(feature.descriptionKey, feature.descriptionFallback)}</div>\n )}\n </div>\n </label>\n )\n })}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default function CustomerRoleDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const [data, setData] = React.useState<RoleDetail | 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\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<RoleDetail>(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id!)}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role') },\n )\n if (cancelled) return\n setData(payload)\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.roleDetail.error.load', 'Failed to load role')\n setError(message)\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n const fields = React.useMemo<CrudField[]>(() => {\n if (!data) return []\n return [\n {\n id: 'name',\n type: 'text' as const,\n label: t('customer_accounts.admin.roleDetail.fields.name', 'Name'),\n required: true,\n disabled: data.isSystem,\n },\n {\n id: 'description',\n type: 'textarea' as const,\n label: t('customer_accounts.admin.roleDetail.fields.description', 'Description'),\n },\n {\n id: 'isDefault',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.isDefault', 'Default role (auto-assigned to new users)'),\n },\n {\n id: 'customerAssignable',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.customerAssignable', 'Customers can self-assign'),\n },\n ]\n }, [data, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'details',\n title: t('customer_accounts.admin.roleDetail.sections.details', 'Role Details'),\n column: 1,\n fields: ['name', 'description'],\n },\n {\n id: 'options',\n title: t('customer_accounts.admin.roleDetail.sections.options', 'Options'),\n column: 1,\n fields: ['isDefault', 'customerAssignable'],\n },\n {\n id: 'permissions',\n title: t('customer_accounts.admin.roleDetail.sections.permissions', 'Portal Permissions'),\n column: 1,\n component: PortalPermissionsEditor,\n },\n ], [t])\n\n const initialValues = React.useMemo(() => {\n if (!data) return {}\n return {\n name: data.name,\n description: data.description || '',\n isDefault: data.isDefault,\n customerAssignable: data.customerAssignable,\n features: data.features,\n }\n }, [data])\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!id) return\n const roleCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: (values.name as string)?.trim(),\n description: (values.description as string)?.trim() || null,\n isDefault: values.isDefault,\n customerAssignable: values.customerAssignable,\n }),\n },\n )\n if (!roleCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.save', 'Failed to save role'), 'error')\n return\n }\n const features = Array.isArray(values.features) ? values.features : []\n const aclCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}/acl`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features }),\n },\n )\n if (!aclCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.saveAcl', 'Failed to save permissions'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roleDetail.flash.saved', 'Role updated'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!id) return\n const call = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n flash(t('customer_accounts.admin.roles.error.delete', 'Failed to delete role'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roles.flash.deleted', 'Role deleted'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\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.roleDetail.loading', 'Loading role...')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}\n backHref=\"/backend/customer_accounts/roles\"\n backLabel={t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={error ?? t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer_accounts/roles\">\n {t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}\n </Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={data.name}\n backHref=\"/backend/customer_accounts/roles\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n entityId=\"customer_accounts:customer_role\"\n onSubmit={handleSubmit}\n onDelete={!data.isSystem ? handleDelete : undefined}\n cancelHref=\"/backend/customer_accounts/roles\"\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
|
16
16
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
17
17
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
18
18
|
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
19
|
+
import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
19
20
|
function formatDate(value, fallback) {
|
|
20
21
|
if (!value) return fallback;
|
|
21
22
|
const date = new Date(value);
|
|
@@ -111,6 +112,7 @@ function CustomerUserDetailPage({ params }) {
|
|
|
111
112
|
const [data, setData] = React.useState(null);
|
|
112
113
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
113
114
|
const [error, setError] = React.useState(null);
|
|
115
|
+
const [isNotFound, setIsNotFound] = React.useState(false);
|
|
114
116
|
const [isSaving, setIsSaving] = React.useState(false);
|
|
115
117
|
const [editActive, setEditActive] = React.useState(null);
|
|
116
118
|
const [editDisplayName, setEditDisplayName] = React.useState("");
|
|
@@ -144,7 +146,7 @@ function CustomerUserDetailPage({ params }) {
|
|
|
144
146
|
);
|
|
145
147
|
React.useEffect(() => {
|
|
146
148
|
if (!id) {
|
|
147
|
-
|
|
149
|
+
setIsNotFound(true);
|
|
148
150
|
setIsLoading(false);
|
|
149
151
|
return;
|
|
150
152
|
}
|
|
@@ -152,6 +154,7 @@ function CustomerUserDetailPage({ params }) {
|
|
|
152
154
|
async function load() {
|
|
153
155
|
setIsLoading(true);
|
|
154
156
|
setError(null);
|
|
157
|
+
setIsNotFound(false);
|
|
155
158
|
try {
|
|
156
159
|
const payload = await readApiResultOrThrow(
|
|
157
160
|
`/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,
|
|
@@ -167,8 +170,12 @@ function CustomerUserDetailPage({ params }) {
|
|
|
167
170
|
setEditCustomerEntityId(payload.customerEntityId);
|
|
168
171
|
} catch (err) {
|
|
169
172
|
if (cancelled) return;
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
if (err.status === 404) {
|
|
174
|
+
setIsNotFound(true);
|
|
175
|
+
} else {
|
|
176
|
+
const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.error.load", "Failed to load user");
|
|
177
|
+
setError(message);
|
|
178
|
+
}
|
|
172
179
|
} finally {
|
|
173
180
|
if (!cancelled) setIsLoading(false);
|
|
174
181
|
}
|
|
@@ -416,11 +423,24 @@ function CustomerUserDetailPage({ params }) {
|
|
|
416
423
|
/* @__PURE__ */ jsx("span", { children: t("customer_accounts.admin.detail.loading", "Loading user...") })
|
|
417
424
|
] }) }) });
|
|
418
425
|
}
|
|
426
|
+
if (isNotFound) {
|
|
427
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
428
|
+
RecordNotFoundState,
|
|
429
|
+
{
|
|
430
|
+
label: t("customer_accounts.admin.detail.error.notFound", "User not found"),
|
|
431
|
+
backHref: "/backend/customer_accounts/users",
|
|
432
|
+
backLabel: t("customer_accounts.admin.detail.actions.backToList", "Back to list")
|
|
433
|
+
}
|
|
434
|
+
) }) });
|
|
435
|
+
}
|
|
419
436
|
if (error || !data) {
|
|
420
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
438
|
+
ErrorMessage,
|
|
439
|
+
{
|
|
440
|
+
label: error ?? t("customer_accounts.admin.detail.error.notFound", "User not found"),
|
|
441
|
+
action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer_accounts/users", children: t("customer_accounts.admin.detail.actions.backToList", "Back to list") }) })
|
|
442
|
+
}
|
|
443
|
+
) }) });
|
|
424
444
|
}
|
|
425
445
|
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
426
446
|
/* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/backend/customer_accounts/users/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\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 } from '@open-mercato/ui/backend/utils/apiCall'\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'\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 default function CustomerUserDetailPage({ params }: { params?: { id?: string } }) {\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 [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 setError(t('customer_accounts.admin.detail.error.notFound', 'User not found'))\n setIsLoading(false)\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\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 const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.load', 'Failed to load user')\n setError(message)\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 loadCrmNames() {\n if (data!.personEntityId) {\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 if (data!.customerEntityId) {\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 }\n loadCrmNames()\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 apiCall<{ ok: boolean; error?: string }>(\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 if (!call.ok) {\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 } : 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 apiCall(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\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 <Page>\n <PageBody>\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 </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{error || t('customer_accounts.admin.detail.error.notFound', 'User not found')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/customer_accounts/users\">\n {t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}\n </Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\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: `${typeof window !== 'undefined' ? window.location.origin : ''}/[org-slug]/portal`,\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 </PageBody>\n {ConfirmDialogElement}\n </Page>\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 { Page, PageBody } from '@open-mercato/ui/backend/Page'\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 } from '@open-mercato/ui/backend/utils/apiCall'\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'\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 default function CustomerUserDetailPage({ params }: { params?: { id?: string } }) {\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 loadCrmNames() {\n if (data!.personEntityId) {\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 if (data!.customerEntityId) {\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 }\n loadCrmNames()\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 apiCall<{ ok: boolean; error?: string }>(\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 if (!call.ok) {\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 } : 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 apiCall(\n `/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\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 <Page>\n <PageBody>\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 </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\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 </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\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 </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\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: `${typeof window !== 'undefined' ? window.location.origin : ''}/[org-slug]/portal`,\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 </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,7 +17,9 @@ import {
|
|
|
17
17
|
ActivitiesSection
|
|
18
18
|
} from "../../../../components/detail/ActivitiesSection.js";
|
|
19
19
|
import {
|
|
20
|
-
NotesSection
|
|
20
|
+
NotesSection,
|
|
21
|
+
RecordNotFoundState,
|
|
22
|
+
ErrorMessage
|
|
21
23
|
} from "@open-mercato/ui/backend/detail";
|
|
22
24
|
import {
|
|
23
25
|
TagsSection
|
|
@@ -63,6 +65,7 @@ function CustomerPersonDetailPage({ params }) {
|
|
|
63
65
|
const [data, setData] = React.useState(null);
|
|
64
66
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
65
67
|
const [error, setError] = React.useState(null);
|
|
68
|
+
const [isNotFound, setIsNotFound] = React.useState(false);
|
|
66
69
|
const [activeTab, setActiveTab] = React.useState(initialTab);
|
|
67
70
|
const [sectionAction, setSectionAction] = React.useState(null);
|
|
68
71
|
const [isDeleting, setIsDeleting] = React.useState(false);
|
|
@@ -197,7 +200,7 @@ function CustomerPersonDetailPage({ params }) {
|
|
|
197
200
|
const initialLoadDoneRef = React.useRef(false);
|
|
198
201
|
const loadData = React.useCallback(async () => {
|
|
199
202
|
if (!id) {
|
|
200
|
-
|
|
203
|
+
setIsNotFound(true);
|
|
201
204
|
setIsLoading(false);
|
|
202
205
|
return;
|
|
203
206
|
}
|
|
@@ -205,6 +208,7 @@ function CustomerPersonDetailPage({ params }) {
|
|
|
205
208
|
setIsLoading(true);
|
|
206
209
|
}
|
|
207
210
|
setError(null);
|
|
211
|
+
setIsNotFound(false);
|
|
208
212
|
try {
|
|
209
213
|
const payload = await readApiResultOrThrow(
|
|
210
214
|
`/api/customers/people/${encodeURIComponent(id)}?include=todos`,
|
|
@@ -213,8 +217,12 @@ function CustomerPersonDetailPage({ params }) {
|
|
|
213
217
|
);
|
|
214
218
|
setData(payload);
|
|
215
219
|
} catch (err) {
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
if (err.status === 404) {
|
|
221
|
+
setIsNotFound(true);
|
|
222
|
+
} else {
|
|
223
|
+
const message = err instanceof Error ? err.message : t("customers.people.detail.error.load");
|
|
224
|
+
setError(message);
|
|
225
|
+
}
|
|
218
226
|
if (!initialLoadDoneRef.current) setData(null);
|
|
219
227
|
} finally {
|
|
220
228
|
setIsLoading(false);
|
|
@@ -372,11 +380,24 @@ function CustomerPersonDetailPage({ params }) {
|
|
|
372
380
|
/* @__PURE__ */ jsx("span", { children: t("customers.people.detail.loading") })
|
|
373
381
|
] }) }) });
|
|
374
382
|
}
|
|
383
|
+
if (isNotFound) {
|
|
384
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
385
|
+
RecordNotFoundState,
|
|
386
|
+
{
|
|
387
|
+
label: t("customers.people.detail.error.notFound", "Person not found."),
|
|
388
|
+
backHref: "/backend/customers/people",
|
|
389
|
+
backLabel: t("customers.people.detail.actions.backToList", "Back to people")
|
|
390
|
+
}
|
|
391
|
+
) }) });
|
|
392
|
+
}
|
|
375
393
|
if (error || !data || !personId) {
|
|
376
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
394
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
395
|
+
ErrorMessage,
|
|
396
|
+
{
|
|
397
|
+
label: error ?? t("customers.people.detail.error.notFound", "Person not found."),
|
|
398
|
+
action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customers/people", children: t("customers.people.detail.actions.backToList", "Back to people") }) })
|
|
399
|
+
}
|
|
400
|
+
) }) });
|
|
380
401
|
}
|
|
381
402
|
const { person, profile } = data;
|
|
382
403
|
const detailFields = [
|