@open-mercato/enterprise 0.7.1-develop.7152.1.a69e92f9c9 → 0.7.1-develop.7154.1.981330c924

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/modules/security/backend/security/enforcement/page.js +1 -0
  2. package/dist/modules/security/backend/security/enforcement/page.js.map +2 -2
  3. package/dist/modules/security/backend/security/sudo/page.js +1 -0
  4. package/dist/modules/security/backend/security/sudo/page.js.map +2 -2
  5. package/dist/modules/security/backend/security/users/page.js +1 -0
  6. package/dist/modules/security/backend/security/users/page.js.map +2 -2
  7. package/dist/modules/security/components/EnforcementPolicyForm.js +1 -0
  8. package/dist/modules/security/components/EnforcementPolicyForm.js.map +2 -2
  9. package/dist/modules/security/components/PasswordChangeForm.js +1 -0
  10. package/dist/modules/security/components/PasswordChangeForm.js.map +2 -2
  11. package/dist/modules/security/components/SecurityUserForm.js +1 -0
  12. package/dist/modules/security/components/SecurityUserForm.js.map +2 -2
  13. package/dist/modules/security/components/SudoConfigCrudPage.js +1 -0
  14. package/dist/modules/security/components/SudoConfigCrudPage.js.map +2 -2
  15. package/dist/modules/sso/backend/page.js +1 -0
  16. package/dist/modules/sso/backend/page.js.map +2 -2
  17. package/package.json +5 -5
  18. package/src/modules/security/backend/security/enforcement/page.tsx +1 -0
  19. package/src/modules/security/backend/security/sudo/page.tsx +1 -0
  20. package/src/modules/security/backend/security/users/page.tsx +1 -0
  21. package/src/modules/security/components/EnforcementPolicyForm.tsx +1 -0
  22. package/src/modules/security/components/PasswordChangeForm.tsx +1 -0
  23. package/src/modules/security/components/SecurityUserForm.tsx +1 -0
  24. package/src/modules/security/components/SudoConfigCrudPage.tsx +1 -0
  25. package/src/modules/sso/backend/page.tsx +2 -1
@@ -198,6 +198,7 @@ function SecurityEnforcementPage() {
198
198
  DataTable,
199
199
  {
200
200
  title: t("security.admin.enforcement.list.title", "Existing policies"),
201
+ titleHeadingLevel: 1,
201
202
  actions: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/security/enforcement/create", children: t("security.admin.enforcement.list.new", "Create new policy") }) }),
202
203
  columns,
203
204
  data: pagedPolicies,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/security/backend/security/enforcement/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Pencil, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport type { EnforcementPoliciesResponse, EnforcementPolicyDto } from './_shared'\n\nconst PAGE_SIZE = 20\n\nfunction buildTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string): string {\n if (policy.scope === 'platform') return allTenantsLabel\n if (policy.scope === 'tenant') return policy.tenantName ?? policy.tenantId ?? '-'\n return `${policy.tenantName ?? policy.tenantId ?? '-'} / ${policy.organizationName ?? policy.organizationId ?? '-'}`\n}\n\nfunction renderTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string) {\n if (policy.scope === 'platform') {\n return <span>{allTenantsLabel}</span>\n }\n\n const tenantLabel = policy.tenantName ?? policy.tenantId ?? '-'\n if (policy.scope === 'tenant') {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n\n const organizationLabel = policy.organizationName ?? policy.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{organizationLabel}</div>\n </div>\n )\n}\n\nexport default function SecurityEnforcementPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'deadline', desc: true }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [policies, setPolicies] = React.useState<EnforcementPolicyDto[]>([])\n const [saving, setSaving] = React.useState(false)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-enforcement-management',\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: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadPolicies = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<EnforcementPoliciesResponse>('/api/security/enforcement')\n if (!response.ok || !response.result) {\n setPolicies([])\n setError(\n t(\n 'security.admin.enforcement.errors.load',\n 'Failed to load enforcement policies.',\n ),\n )\n setLoading(false)\n return\n }\n\n setPolicies(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadPolicies()\n }, [loadPolicies])\n\n const filteredPolicies = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const selectedScope = typeof filterValues.scope === 'string' ? filterValues.scope : ''\n const selectedStatus = typeof filterValues.status === 'string' ? filterValues.status : ''\n\n return policies.filter((policy) => {\n if (selectedScope && policy.scope !== selectedScope) return false\n if (selectedStatus === 'enforced' && !policy.isEnforced) return false\n if (selectedStatus === 'disabled' && policy.isEnforced) return false\n\n if (!normalizedSearch) return true\n const searchableChunks = [\n policy.scope,\n policy.tenantId ?? '',\n policy.tenantName ?? '',\n policy.organizationId ?? '',\n policy.organizationName ?? '',\n policy.allowedMethods?.join(',') ?? '',\n buildTargetLabel(policy, t('security.admin.enforcement.target.platform', 'All tenants')),\n ]\n return searchableChunks.some((chunk) => chunk.toLowerCase().includes(normalizedSearch))\n })\n }, [filterValues.scope, filterValues.status, policies, search, t])\n\n const total = filteredPolicies.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedPolicies = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredPolicies.slice(start, start + PAGE_SIZE)\n }, [filteredPolicies, page])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'scope',\n label: t('security.admin.enforcement.list.scope', 'Scope'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'platform', label: t('security.admin.enforcement.scope.platform', 'Platform') },\n { value: 'tenant', label: t('security.admin.enforcement.scope.tenant', 'Tenant') },\n { value: 'organisation', label: t('security.admin.enforcement.scope.organisation', 'Organisation') },\n ],\n },\n {\n id: 'status',\n label: t('security.admin.enforcement.list.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'enforced', label: t('security.admin.enforcement.status.enforced', 'Enforced') },\n { value: 'disabled', label: t('security.admin.enforcement.status.disabled', 'Disabled') },\n ],\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<EnforcementPolicyDto>[]>(() => [\n {\n accessorKey: 'scope',\n header: t('security.admin.enforcement.list.scope', 'Scope'),\n cell: ({ row }) => t(`security.admin.enforcement.scope.${row.original.scope}`, row.original.scope),\n },\n {\n id: 'target',\n header: t('security.admin.enforcement.list.target', 'Target'),\n cell: ({ row }) => renderTargetLabel(\n row.original,\n t('security.admin.enforcement.target.platform', 'All tenants'),\n ),\n },\n {\n id: 'deadline',\n header: t('security.admin.enforcement.list.deadline', 'Deadline'),\n cell: ({ row }) => row.original.enforcementDeadline\n ? new Date(row.original.enforcementDeadline).toLocaleString()\n : t('security.admin.enforcement.deadline.none', 'No deadline'),\n },\n {\n id: 'methods',\n header: t('security.admin.enforcement.list.methods', 'Allowed methods'),\n cell: ({ row }) => row.original.allowedMethods?.length\n ? row.original.allowedMethods.join(', ')\n : t('security.admin.enforcement.methods.all', 'All'),\n },\n {\n id: 'status',\n header: t('security.admin.enforcement.list.status', 'Status'),\n cell: ({ row }) => row.original.isEnforced\n ? t('security.admin.enforcement.status.enforced', 'Enforced')\n : t('security.admin.enforcement.status.disabled', 'Disabled'),\n },\n ], [t])\n\n const handleDelete = React.useCallback(async (policy: EnforcementPolicyDto) => {\n const accepted = await confirm({\n title: t('security.admin.enforcement.delete.title', 'Delete enforcement policy?'),\n text: t(\n 'security.admin.enforcement.delete.text',\n 'This removes the selected policy. Continue?',\n ),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n\n if (!accepted) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/enforcement/${encodeURIComponent(policy.id)}`, {\n method: 'DELETE',\n }),\n { id: policy.id },\n )\n flash(t('security.admin.enforcement.flash.deleted', 'Enforcement policy deleted.'), 'success')\n await loadPolicies()\n } catch {\n flash(t('security.admin.enforcement.flash.deleteError', 'Failed to delete enforcement policy.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadPolicies, runMutationWithContext, t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<EnforcementPolicyDto>\n title={t('security.admin.enforcement.list.title', 'Existing policies')}\n actions={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" asChild>\n <Link href=\"/backend/security/enforcement/create\">\n {t('security.admin.enforcement.list.new', 'Create new policy')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={pagedPolicies}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.enforcement.search', 'Search policies...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(policy) => {\n router.push(`/backend/security/enforcement/${encodeURIComponent(policy.id)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadPolicies(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.enforcementTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadPolicies()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.enforcement.empty', 'No enforcement policies found.')}\n rowActions={(policy) => (\n <div className=\"flex items-center gap-1\">\n <IconButton\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n title={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/enforcement/${encodeURIComponent(policy.id)}`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n title={t('ui.actions.delete', 'Delete')}\n onClick={() => void handleDelete(policy)}\n disabled={saving}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )}\n />\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Pencil, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport type { EnforcementPoliciesResponse, EnforcementPolicyDto } from './_shared'\n\nconst PAGE_SIZE = 20\n\nfunction buildTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string): string {\n if (policy.scope === 'platform') return allTenantsLabel\n if (policy.scope === 'tenant') return policy.tenantName ?? policy.tenantId ?? '-'\n return `${policy.tenantName ?? policy.tenantId ?? '-'} / ${policy.organizationName ?? policy.organizationId ?? '-'}`\n}\n\nfunction renderTargetLabel(policy: EnforcementPolicyDto, allTenantsLabel: string) {\n if (policy.scope === 'platform') {\n return <span>{allTenantsLabel}</span>\n }\n\n const tenantLabel = policy.tenantName ?? policy.tenantId ?? '-'\n if (policy.scope === 'tenant') {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n\n const organizationLabel = policy.organizationName ?? policy.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{organizationLabel}</div>\n </div>\n )\n}\n\nexport default function SecurityEnforcementPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'deadline', desc: true }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [policies, setPolicies] = React.useState<EnforcementPolicyDto[]>([])\n const [saving, setSaving] = React.useState(false)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-enforcement-management',\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: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadPolicies = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<EnforcementPoliciesResponse>('/api/security/enforcement')\n if (!response.ok || !response.result) {\n setPolicies([])\n setError(\n t(\n 'security.admin.enforcement.errors.load',\n 'Failed to load enforcement policies.',\n ),\n )\n setLoading(false)\n return\n }\n\n setPolicies(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadPolicies()\n }, [loadPolicies])\n\n const filteredPolicies = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const selectedScope = typeof filterValues.scope === 'string' ? filterValues.scope : ''\n const selectedStatus = typeof filterValues.status === 'string' ? filterValues.status : ''\n\n return policies.filter((policy) => {\n if (selectedScope && policy.scope !== selectedScope) return false\n if (selectedStatus === 'enforced' && !policy.isEnforced) return false\n if (selectedStatus === 'disabled' && policy.isEnforced) return false\n\n if (!normalizedSearch) return true\n const searchableChunks = [\n policy.scope,\n policy.tenantId ?? '',\n policy.tenantName ?? '',\n policy.organizationId ?? '',\n policy.organizationName ?? '',\n policy.allowedMethods?.join(',') ?? '',\n buildTargetLabel(policy, t('security.admin.enforcement.target.platform', 'All tenants')),\n ]\n return searchableChunks.some((chunk) => chunk.toLowerCase().includes(normalizedSearch))\n })\n }, [filterValues.scope, filterValues.status, policies, search, t])\n\n const total = filteredPolicies.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedPolicies = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredPolicies.slice(start, start + PAGE_SIZE)\n }, [filteredPolicies, page])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'scope',\n label: t('security.admin.enforcement.list.scope', 'Scope'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'platform', label: t('security.admin.enforcement.scope.platform', 'Platform') },\n { value: 'tenant', label: t('security.admin.enforcement.scope.tenant', 'Tenant') },\n { value: 'organisation', label: t('security.admin.enforcement.scope.organisation', 'Organisation') },\n ],\n },\n {\n id: 'status',\n label: t('security.admin.enforcement.list.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'enforced', label: t('security.admin.enforcement.status.enforced', 'Enforced') },\n { value: 'disabled', label: t('security.admin.enforcement.status.disabled', 'Disabled') },\n ],\n },\n ], [t])\n\n const columns = React.useMemo<ColumnDef<EnforcementPolicyDto>[]>(() => [\n {\n accessorKey: 'scope',\n header: t('security.admin.enforcement.list.scope', 'Scope'),\n cell: ({ row }) => t(`security.admin.enforcement.scope.${row.original.scope}`, row.original.scope),\n },\n {\n id: 'target',\n header: t('security.admin.enforcement.list.target', 'Target'),\n cell: ({ row }) => renderTargetLabel(\n row.original,\n t('security.admin.enforcement.target.platform', 'All tenants'),\n ),\n },\n {\n id: 'deadline',\n header: t('security.admin.enforcement.list.deadline', 'Deadline'),\n cell: ({ row }) => row.original.enforcementDeadline\n ? new Date(row.original.enforcementDeadline).toLocaleString()\n : t('security.admin.enforcement.deadline.none', 'No deadline'),\n },\n {\n id: 'methods',\n header: t('security.admin.enforcement.list.methods', 'Allowed methods'),\n cell: ({ row }) => row.original.allowedMethods?.length\n ? row.original.allowedMethods.join(', ')\n : t('security.admin.enforcement.methods.all', 'All'),\n },\n {\n id: 'status',\n header: t('security.admin.enforcement.list.status', 'Status'),\n cell: ({ row }) => row.original.isEnforced\n ? t('security.admin.enforcement.status.enforced', 'Enforced')\n : t('security.admin.enforcement.status.disabled', 'Disabled'),\n },\n ], [t])\n\n const handleDelete = React.useCallback(async (policy: EnforcementPolicyDto) => {\n const accepted = await confirm({\n title: t('security.admin.enforcement.delete.title', 'Delete enforcement policy?'),\n text: t(\n 'security.admin.enforcement.delete.text',\n 'This removes the selected policy. Continue?',\n ),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n\n if (!accepted) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/enforcement/${encodeURIComponent(policy.id)}`, {\n method: 'DELETE',\n }),\n { id: policy.id },\n )\n flash(t('security.admin.enforcement.flash.deleted', 'Enforcement policy deleted.'), 'success')\n await loadPolicies()\n } catch {\n flash(t('security.admin.enforcement.flash.deleteError', 'Failed to delete enforcement policy.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadPolicies, runMutationWithContext, t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<EnforcementPolicyDto>\n title={t('security.admin.enforcement.list.title', 'Existing policies')}\n titleHeadingLevel={1}\n actions={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" asChild>\n <Link href=\"/backend/security/enforcement/create\">\n {t('security.admin.enforcement.list.new', 'Create new policy')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={pagedPolicies}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.enforcement.search', 'Search policies...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(policy) => {\n router.push(`/backend/security/enforcement/${encodeURIComponent(policy.id)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadPolicies(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.enforcementTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadPolicies()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.enforcement.empty', 'No enforcement policies found.')}\n rowActions={(policy) => (\n <div className=\"flex items-center gap-1\">\n <IconButton\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n title={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/enforcement/${encodeURIComponent(policy.id)}`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n title={t('ui.actions.delete', 'Delete')}\n onClick={() => void handleDelete(policy)}\n disabled={saving}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )}\n />\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -193,6 +193,7 @@ function SecuritySudoPageInner() {
193
193
  DataTable,
194
194
  {
195
195
  title: t("security.admin.sudo.title", "Sudo protection"),
196
+ titleHeadingLevel: 1,
196
197
  columns,
197
198
  data: rows,
198
199
  actions: /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/security/backend/security/sudo/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Pencil, Plus, ShieldAlert, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ChallengeMethod } from '../../../data/constants'\nimport { SudoProvider } from '../../../components/SudoProvider'\nimport { useSudoChallenge } from '../../../components/hooks/useSudoChallenge'\n\ntype SudoConfigRow = {\n id: string\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n isDeveloperDefault: boolean\n ttlSeconds: number\n challengeMethod: ChallengeMethod\n configuredBy: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype SudoConfigListResponse = {\n items: SudoConfigRow[]\n}\n\nfunction renderScopeLabel(row: SudoConfigRow, platformLabel: string) {\n if (!row.tenantId && !row.organizationId) {\n return <span>{platformLabel}</span>\n }\n const tenantLabel = row.tenantName ?? row.tenantId ?? '-'\n if (!row.organizationId) {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n const orgLabel = row.organizationName ?? row.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{orgLabel}</div>\n </div>\n )\n}\n\nfunction SecuritySudoPageInner() {\n const t = useT()\n const { requireSudo } = useSudoChallenge()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-sudo-management',\n })\n\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [rows, setRows] = React.useState<SudoConfigRow[]>([])\n const [saving, setSaving] = React.useState(false)\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: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadRows = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<SudoConfigListResponse>('/api/security/sudo/configs')\n if (!response.ok || !response.result) {\n setRows([])\n setError(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'))\n setLoading(false)\n return\n }\n setRows(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleDelete = React.useCallback(async (row: SudoConfigRow) => {\n const accepted = await confirm({\n title: t('security.admin.sudo.delete.title', 'Delete sudo rule?'),\n text: t('security.admin.sudo.delete.text', 'This removes the selected sudo protection rule.'),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n if (!accepted) return\n\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/sudo/configs/${encodeURIComponent(row.id)}`, {\n method: 'DELETE',\n headers: { 'x-sudo-token': sudoToken },\n }),\n { id: row.id },\n )\n flash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'), 'success')\n await loadRows()\n } catch {\n flash(t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadRows, requestToken, runMutationWithContext, t])\n\n const columns = React.useMemo<ColumnDef<SudoConfigRow>[]>(() => [\n {\n accessorKey: 'label',\n header: t('security.admin.sudo.table.label', 'Label'),\n cell: ({ row }) => row.original.label ?? '\u2014',\n },\n {\n accessorKey: 'targetIdentifier',\n header: t('security.admin.sudo.table.targetIdentifier', 'Target'),\n },\n {\n id: 'scope',\n header: t('security.admin.sudo.table.scope', 'Scope'),\n cell: ({ row }) => renderScopeLabel(row.original, t('security.admin.sudo.scope.platform', 'Platform')),\n },\n {\n accessorKey: 'challengeMethod',\n header: t('security.admin.sudo.table.challengeMethod', 'Method'),\n cell: ({ row }) => t(`security.admin.sudo.challengeMethod.${row.original.challengeMethod}`, row.original.challengeMethod),\n },\n {\n accessorKey: 'ttlSeconds',\n header: t('security.admin.sudo.table.ttl', 'TTL'),\n cell: ({ row }) => t('security.admin.sudo.table.ttlValue', '{value}s', { value: String(row.original.ttlSeconds) }),\n },\n {\n id: 'source',\n header: t('security.admin.sudo.table.source', 'Source'),\n cell: ({ row }) => row.original.isDeveloperDefault\n ? t('security.admin.sudo.source.developer', 'Developer default')\n : t('security.admin.sudo.source.admin', 'Admin override'),\n },\n {\n accessorKey: 'isEnabled',\n header: t('security.admin.sudo.table.status', 'Status'),\n cell: ({ row }) => row.original.isEnabled\n ? t('security.admin.sudo.status.enabled', 'Enabled')\n : t('security.admin.sudo.status.disabled', 'Disabled'),\n },\n {\n id: 'actions',\n header: t('security.admin.sudo.table.actions', 'Actions'),\n cell: ({ row }) => {\n const isDeveloperDefault = row.original.isDeveloperDefault\n return (\n <div className=\"flex items-center gap-2\">\n {isDeveloperDefault ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n disabled\n >\n <Pencil className=\"size-4\" />\n </IconButton>\n ) : (\n <IconButton\n asChild\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/sudo/${row.original.id}/edit`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n )}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n disabled={isDeveloperDefault}\n onClick={() => void handleDelete(row.original)}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )\n },\n },\n ], [handleDelete, t])\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div className=\"rounded-xl border bg-muted/30 p-4\">\n <div className=\"flex items-start gap-3\">\n <ShieldAlert className=\"mt-0.5 size-5 text-amber-600\" />\n <div className=\"space-y-1\">\n <h2 className=\"text-sm font-semibold\">\n {t('security.admin.sudo.notice.title', 'Sensitive operations require re-authentication')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'security.admin.sudo.notice.description',\n 'Use sudo rules to require a fresh password or MFA challenge for selected features, routes, modules, or packages.',\n )}\n </p>\n </div>\n </div>\n </div>\n\n <DataTable<SudoConfigRow>\n title={t('security.admin.sudo.title', 'Sudo protection')}\n columns={columns}\n data={rows}\n actions={(\n <Button\n asChild\n variant=\"outline\"\n size=\"sm\"\n >\n <Link href=\"/backend/security/sudo/create\">\n <Plus className=\"mr-2 size-4\" />\n {t('security.admin.sudo.actions.add', 'Add rule')}\n </Link>\n </Button>\n )}\n perspective={{ tableId: extensionPoints.hosts.sudoTable.tableId }}\n isLoading={loading}\n error={error ? <span>{error}</span> : null}\n />\n\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n\nexport default function SecuritySudoPage() {\n return (\n <SudoProvider>\n <SecuritySudoPageInner />\n </SudoProvider>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport Link from 'next/link'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Pencil, Plus, ShieldAlert, Trash2 } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ChallengeMethod } from '../../../data/constants'\nimport { SudoProvider } from '../../../components/SudoProvider'\nimport { useSudoChallenge } from '../../../components/hooks/useSudoChallenge'\n\ntype SudoConfigRow = {\n id: string\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n isDeveloperDefault: boolean\n ttlSeconds: number\n challengeMethod: ChallengeMethod\n configuredBy: string | null\n createdAt: string\n updatedAt: string\n}\n\ntype SudoConfigListResponse = {\n items: SudoConfigRow[]\n}\n\nfunction renderScopeLabel(row: SudoConfigRow, platformLabel: string) {\n if (!row.tenantId && !row.organizationId) {\n return <span>{platformLabel}</span>\n }\n const tenantLabel = row.tenantName ?? row.tenantId ?? '-'\n if (!row.organizationId) {\n return <span className=\"whitespace-normal break-words\">{tenantLabel}</span>\n }\n const orgLabel = row.organizationName ?? row.organizationId ?? '-'\n return (\n <div className=\"space-y-1 whitespace-normal break-words\">\n <div>{tenantLabel}</div>\n <div className=\"text-muted-foreground\">{orgLabel}</div>\n </div>\n )\n}\n\nfunction SecuritySudoPageInner() {\n const t = useT()\n const { requireSudo } = useSudoChallenge()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'security-sudo-management',\n })\n\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [rows, setRows] = React.useState<SudoConfigRow[]>([])\n const [saving, setSaving] = React.useState(false)\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: { retryLastMutation },\n })\n },\n [retryLastMutation, runMutation],\n )\n\n const loadRows = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<SudoConfigListResponse>('/api/security/sudo/configs')\n if (!response.ok || !response.result) {\n setRows([])\n setError(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'))\n setLoading(false)\n return\n }\n setRows(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadRows()\n }, [loadRows])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleDelete = React.useCallback(async (row: SudoConfigRow) => {\n const accepted = await confirm({\n title: t('security.admin.sudo.delete.title', 'Delete sudo rule?'),\n text: t('security.admin.sudo.delete.text', 'This removes the selected sudo protection rule.'),\n variant: 'destructive',\n confirmText: t('ui.actions.delete', 'Delete'),\n cancelText: t('ui.actions.cancel', 'Cancel'),\n })\n if (!accepted) return\n\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSaving(true)\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/security/sudo/configs/${encodeURIComponent(row.id)}`, {\n method: 'DELETE',\n headers: { 'x-sudo-token': sudoToken },\n }),\n { id: row.id },\n )\n flash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'), 'success')\n await loadRows()\n } catch {\n flash(t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [confirm, loadRows, requestToken, runMutationWithContext, t])\n\n const columns = React.useMemo<ColumnDef<SudoConfigRow>[]>(() => [\n {\n accessorKey: 'label',\n header: t('security.admin.sudo.table.label', 'Label'),\n cell: ({ row }) => row.original.label ?? '\u2014',\n },\n {\n accessorKey: 'targetIdentifier',\n header: t('security.admin.sudo.table.targetIdentifier', 'Target'),\n },\n {\n id: 'scope',\n header: t('security.admin.sudo.table.scope', 'Scope'),\n cell: ({ row }) => renderScopeLabel(row.original, t('security.admin.sudo.scope.platform', 'Platform')),\n },\n {\n accessorKey: 'challengeMethod',\n header: t('security.admin.sudo.table.challengeMethod', 'Method'),\n cell: ({ row }) => t(`security.admin.sudo.challengeMethod.${row.original.challengeMethod}`, row.original.challengeMethod),\n },\n {\n accessorKey: 'ttlSeconds',\n header: t('security.admin.sudo.table.ttl', 'TTL'),\n cell: ({ row }) => t('security.admin.sudo.table.ttlValue', '{value}s', { value: String(row.original.ttlSeconds) }),\n },\n {\n id: 'source',\n header: t('security.admin.sudo.table.source', 'Source'),\n cell: ({ row }) => row.original.isDeveloperDefault\n ? t('security.admin.sudo.source.developer', 'Developer default')\n : t('security.admin.sudo.source.admin', 'Admin override'),\n },\n {\n accessorKey: 'isEnabled',\n header: t('security.admin.sudo.table.status', 'Status'),\n cell: ({ row }) => row.original.isEnabled\n ? t('security.admin.sudo.status.enabled', 'Enabled')\n : t('security.admin.sudo.status.disabled', 'Disabled'),\n },\n {\n id: 'actions',\n header: t('security.admin.sudo.table.actions', 'Actions'),\n cell: ({ row }) => {\n const isDeveloperDefault = row.original.isDeveloperDefault\n return (\n <div className=\"flex items-center gap-2\">\n {isDeveloperDefault ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n disabled\n >\n <Pencil className=\"size-4\" />\n </IconButton>\n ) : (\n <IconButton\n asChild\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n <Link href={`/backend/security/sudo/${row.original.id}/edit`}>\n <Pencil className=\"size-4\" />\n </Link>\n </IconButton>\n )}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('ui.actions.delete', 'Delete')}\n disabled={isDeveloperDefault}\n onClick={() => void handleDelete(row.original)}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n )\n },\n },\n ], [handleDelete, t])\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div className=\"rounded-xl border bg-muted/30 p-4\">\n <div className=\"flex items-start gap-3\">\n <ShieldAlert className=\"mt-0.5 size-5 text-amber-600\" />\n <div className=\"space-y-1\">\n <h2 className=\"text-sm font-semibold\">\n {t('security.admin.sudo.notice.title', 'Sensitive operations require re-authentication')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'security.admin.sudo.notice.description',\n 'Use sudo rules to require a fresh password or MFA challenge for selected features, routes, modules, or packages.',\n )}\n </p>\n </div>\n </div>\n </div>\n\n <DataTable<SudoConfigRow>\n title={t('security.admin.sudo.title', 'Sudo protection')}\n titleHeadingLevel={1}\n columns={columns}\n data={rows}\n actions={(\n <Button\n asChild\n variant=\"outline\"\n size=\"sm\"\n >\n <Link href=\"/backend/security/sudo/create\">\n <Plus className=\"mr-2 size-4\" />\n {t('security.admin.sudo.actions.add', 'Add rule')}\n </Link>\n </Button>\n )}\n perspective={{ tableId: extensionPoints.hosts.sudoTable.tableId }}\n isLoading={loading}\n error={error ? <span>{error}</span> : null}\n />\n\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n\nexport default function SecuritySudoPage() {\n return (\n <SudoProvider>\n <SecuritySudoPageInner />\n </SudoProvider>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -116,6 +116,7 @@ function SecurityUsersPage() {
116
116
  DataTable,
117
117
  {
118
118
  title: t("security.admin.users.table.title", "Users"),
119
+ titleHeadingLevel: 1,
119
120
  columns,
120
121
  data: pagedItems,
121
122
  searchValue: search,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/security/backend/security/users/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport MfaComplianceBadge from '../../../components/MfaComplianceBadge'\nimport type { ComplianceItem, ComplianceResponse } from './_shared'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nconst PAGE_SIZE = 20\n\nfunction formatLastLogin(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nexport default function SecurityUsersPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'email', desc: false }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [items, setItems] = React.useState<ComplianceItem[]>([])\n\n const loadUsers = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<ComplianceResponse>('/api/security/users/mfa/compliance')\n if (!response.ok || !response.result) {\n setItems([])\n setError(t('security.admin.users.errors.load', 'Failed to load user compliance data.'))\n setLoading(false)\n return\n }\n\n setItems(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadUsers()\n }, [loadUsers])\n\n const filteredItems = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const enrolledFilter = typeof filterValues.enrolled === 'string' ? filterValues.enrolled : ''\n const compliantFilter = typeof filterValues.compliant === 'string' ? filterValues.compliant : ''\n\n return items.filter((item) => {\n if (enrolledFilter === 'true' && !item.enrolled) return false\n if (enrolledFilter === 'false' && item.enrolled) return false\n if (compliantFilter === 'true' && !item.compliant) return false\n if (compliantFilter === 'false' && item.compliant) return false\n\n if (!normalizedSearch) return true\n return (\n item.userId.toLowerCase().includes(normalizedSearch)\n || item.email.toLowerCase().includes(normalizedSearch)\n )\n })\n }, [filterValues.compliant, filterValues.enrolled, items, search])\n\n const total = filteredItems.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedItems = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredItems.slice(start, start + PAGE_SIZE)\n }, [filteredItems, page])\n\n const columns = React.useMemo<ColumnDef<ComplianceItem>[]>(() => [\n {\n accessorKey: 'email',\n header: t('security.admin.users.table.email', 'Email'),\n },\n {\n id: 'mfaStatus',\n header: t('security.admin.users.table.mfaStatus', 'MFA status'),\n cell: ({ row }) => row.original.enrolled\n ? t('security.admin.users.mfa.enabled', 'Enabled')\n : t('security.admin.users.mfa.disabled', 'Not enrolled'),\n },\n {\n id: 'compliance',\n header: t('security.admin.users.table.compliance', 'Compliance'),\n cell: ({ row }) => (\n <MfaComplianceBadge enrolled={row.original.enrolled} compliant={row.original.compliant} />\n ),\n },\n {\n id: 'lastLogin',\n header: t('security.admin.users.table.lastLogin', 'Last login'),\n cell: ({ row }) => formatLastLogin(\n row.original.lastLoginAt,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n ),\n },\n ], [t])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'enrolled',\n label: t('security.admin.users.table.mfaStatus', 'MFA status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('security.admin.users.mfa.enabled', 'Enabled') },\n { value: 'false', label: t('security.admin.users.mfa.disabled', 'Not enrolled') },\n ],\n },\n {\n id: 'compliant',\n label: t('security.admin.users.table.compliance', 'Compliance'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('ui.common.yes', 'Yes') },\n { value: 'false', label: t('ui.common.no', 'No') },\n ],\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<ComplianceItem>\n title={t('security.admin.users.table.title', 'Users')}\n columns={columns}\n data={pagedItems}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.users.search', 'Search users...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(item) => {\n router.push(`/backend/security/users/${encodeURIComponent(item.userId)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadUsers(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.usersTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadUsers()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.users.empty', 'No users found for this tenant.')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'\nimport { useRouter } from 'next/navigation.js'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport MfaComplianceBadge from '../../../components/MfaComplianceBadge'\nimport type { ComplianceItem, ComplianceResponse } from './_shared'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nconst PAGE_SIZE = 20\n\nfunction formatLastLogin(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nexport default function SecurityUsersPage() {\n const router = useRouter()\n const t = useT()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'email', desc: false }])\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [items, setItems] = React.useState<ComplianceItem[]>([])\n\n const loadUsers = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n const response = await apiCall<ComplianceResponse>('/api/security/users/mfa/compliance')\n if (!response.ok || !response.result) {\n setItems([])\n setError(t('security.admin.users.errors.load', 'Failed to load user compliance data.'))\n setLoading(false)\n return\n }\n\n setItems(Array.isArray(response.result.items) ? response.result.items : [])\n setLoading(false)\n }, [t])\n\n React.useEffect(() => {\n void loadUsers()\n }, [loadUsers])\n\n const filteredItems = React.useMemo(() => {\n const normalizedSearch = search.trim().toLowerCase()\n const enrolledFilter = typeof filterValues.enrolled === 'string' ? filterValues.enrolled : ''\n const compliantFilter = typeof filterValues.compliant === 'string' ? filterValues.compliant : ''\n\n return items.filter((item) => {\n if (enrolledFilter === 'true' && !item.enrolled) return false\n if (enrolledFilter === 'false' && item.enrolled) return false\n if (compliantFilter === 'true' && !item.compliant) return false\n if (compliantFilter === 'false' && item.compliant) return false\n\n if (!normalizedSearch) return true\n return (\n item.userId.toLowerCase().includes(normalizedSearch)\n || item.email.toLowerCase().includes(normalizedSearch)\n )\n })\n }, [filterValues.compliant, filterValues.enrolled, items, search])\n\n const total = filteredItems.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n\n React.useEffect(() => {\n if (page > totalPages) {\n setPage(totalPages)\n }\n }, [page, totalPages])\n\n const pagedItems = React.useMemo(() => {\n const start = (page - 1) * PAGE_SIZE\n return filteredItems.slice(start, start + PAGE_SIZE)\n }, [filteredItems, page])\n\n const columns = React.useMemo<ColumnDef<ComplianceItem>[]>(() => [\n {\n accessorKey: 'email',\n header: t('security.admin.users.table.email', 'Email'),\n },\n {\n id: 'mfaStatus',\n header: t('security.admin.users.table.mfaStatus', 'MFA status'),\n cell: ({ row }) => row.original.enrolled\n ? t('security.admin.users.mfa.enabled', 'Enabled')\n : t('security.admin.users.mfa.disabled', 'Not enrolled'),\n },\n {\n id: 'compliance',\n header: t('security.admin.users.table.compliance', 'Compliance'),\n cell: ({ row }) => (\n <MfaComplianceBadge enrolled={row.original.enrolled} compliant={row.original.compliant} />\n ),\n },\n {\n id: 'lastLogin',\n header: t('security.admin.users.table.lastLogin', 'Last login'),\n cell: ({ row }) => formatLastLogin(\n row.original.lastLoginAt,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n ),\n },\n ], [t])\n\n const filterDefs = React.useMemo<FilterDef[]>(() => [\n {\n id: 'enrolled',\n label: t('security.admin.users.table.mfaStatus', 'MFA status'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('security.admin.users.mfa.enabled', 'Enabled') },\n { value: 'false', label: t('security.admin.users.mfa.disabled', 'Not enrolled') },\n ],\n },\n {\n id: 'compliant',\n label: t('security.admin.users.table.compliance', 'Compliance'),\n type: 'select',\n options: [\n { value: '', label: t('ui.filters.all', 'All') },\n { value: 'true', label: t('ui.common.yes', 'Yes') },\n { value: 'false', label: t('ui.common.no', 'No') },\n ],\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<ComplianceItem>\n title={t('security.admin.users.table.title', 'Users')}\n titleHeadingLevel={1}\n columns={columns}\n data={pagedItems}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('security.admin.users.search', 'Search users...')}\n filters={filterDefs}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n onRowClick={(item) => {\n router.push(`/backend/security/users/${encodeURIComponent(item.userId)}`)\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={{\n label: t('ui.actions.refresh', 'Refresh'),\n onRefresh: () => void loadUsers(),\n isRefreshing: loading,\n }}\n perspective={{ tableId: extensionPoints.hosts.usersTable.tableId }}\n isLoading={loading}\n error={error ? (\n <div className=\"flex items-center justify-center gap-3\">\n <span>{error}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadUsers()}>\n {t('ui.actions.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n emptyState={t('security.admin.users.empty', 'No users found for this tenant.')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -331,6 +331,7 @@ function EnforcementPolicyForm({
331
331
  CrudForm,
332
332
  {
333
333
  title: value?.id ? t("security.admin.enforcement.form.titleEdit", "Edit enforcement policy") : t("security.admin.enforcement.form.titleCreate", "Create enforcement policy"),
334
+ titleHeadingLevel: 1,
334
335
  backHref,
335
336
  schema,
336
337
  formId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/security/components/EnforcementPolicyForm.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { CrudForm, type CrudCustomFieldRenderProps, type CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\n\nexport type EnforcementScope = 'platform' | 'tenant' | 'organisation'\n\nexport type EnforcementPolicyFormValue = {\n id?: string\n scope: EnforcementScope\n tenantId: string\n organizationId: string\n isEnforced: boolean\n allowedMethods: string[]\n enforcementDeadline: string\n}\ntype EnforcementPolicyFormProps = {\n value?: Partial<EnforcementPolicyFormValue> | null\n submitting: boolean\n onSubmit: (value: EnforcementPolicyFormValue) => Promise<void> | void\n onCancel: () => void\n backHref: string\n}\n\nconst METHOD_OPTIONS = ['totp', 'passkey', 'otp_email'] as const\nconst METHOD_OPTION_VALUES = [...METHOD_OPTIONS] as [typeof METHOD_OPTIONS[number], ...typeof METHOD_OPTIONS[number][]]\nconst METHOD_DESCRIPTION_KEYS: Record<typeof METHOD_OPTIONS[number], { key: string; fallback: string }> = {\n totp: {\n key: 'security.admin.enforcement.methods.totpDescription',\n fallback: 'Authenticator app code (TOTP).',\n },\n passkey: {\n key: 'security.admin.enforcement.methods.passkeyDescription',\n fallback: 'Biometric or security key passkey.',\n },\n otp_email: {\n key: 'security.admin.enforcement.methods.otpEmailDescription',\n fallback: 'One-time verification code sent by email.',\n },\n}\n\nfunction toDatetimeLocal(value: string | null | undefined): string {\n if (!value) return ''\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return ''\n\n const year = parsed.getFullYear()\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\n const day = String(parsed.getDate()).padStart(2, '0')\n const hours = String(parsed.getHours()).padStart(2, '0')\n const minutes = String(parsed.getMinutes()).padStart(2, '0')\n\n return `${year}-${month}-${day}T${hours}:${minutes}`\n}\n\nfunction ScopeTenantField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n if (scope !== 'tenant' && scope !== 'organisation') return null\n\n const normalizedValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n\n return (\n <TenantSelect\n id=\"enforcement-tenant-id\"\n value={normalizedValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.tenantPlaceholder', 'Select tenant')}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction ScopeOrganizationField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n const organizationValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n const tenantId = typeof values?.tenantId === 'string' && values?.tenantId?.trim().length > 0 ? values?.tenantId : null\n const previousTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n previousTenantRef.current = tenantId\n return\n }\n if (previousTenantRef.current !== tenantId) {\n previousTenantRef.current = tenantId\n setValue('')\n }\n }, [setValue, tenantId])\n\n if (scope !== 'organisation') return null\n\n return (\n <OrganizationSelect\n id=\"enforcement-organization-id\"\n value={organizationValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled || !tenantId}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.organizationPlaceholder', 'Select organization')}\n tenantId={tenantId}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction AllowedMethodsField({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const selectedMethods = React.useMemo(() => {\n if (!Array.isArray(value)) return []\n return value\n .filter((item): item is typeof METHOD_OPTIONS[number] => typeof item === 'string' && METHOD_OPTIONS.includes(item as typeof METHOD_OPTIONS[number]))\n }, [value])\n const [mode, setMode] = React.useState<'all' | 'restricted'>(() => (selectedMethods.length > 0 ? 'restricted' : 'all'))\n\n React.useEffect(() => {\n setMode(selectedMethods.length > 0 ? 'restricted' : 'all')\n }, [selectedMethods.length])\n\n const setAllMethods = React.useCallback(() => {\n if (disabled) return\n setMode('all')\n setValue([])\n }, [disabled, setValue])\n\n const setRestrictedMethods = React.useCallback(() => {\n if (disabled) return\n setMode('restricted')\n if (selectedMethods.length === 0) {\n setValue([METHOD_OPTIONS[0]])\n }\n }, [disabled, selectedMethods.length, setValue])\n\n const toggleMethod = React.useCallback((method: typeof METHOD_OPTIONS[number], checked: boolean) => {\n if (disabled) return\n if (!checked) {\n if (selectedMethods.length <= 1) return\n setValue(selectedMethods.filter((item) => item !== method))\n return\n }\n if (selectedMethods.includes(method)) return\n setValue([...selectedMethods, method])\n }, [disabled, selectedMethods, setValue])\n\n return (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'all' ? 'default' : 'outline'}\n onClick={setAllMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsAll', 'Allow all methods')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'restricted' ? 'default' : 'outline'}\n onClick={setRestrictedMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsRestricted', 'Restrict to selected methods')}\n </Button>\n </div>\n\n {mode === 'restricted' ? (\n <div className=\"space-y-2\">\n {METHOD_OPTIONS.map((method) => {\n const isChecked = selectedMethods.includes(method)\n const isLastSelected = isChecked && selectedMethods.length === 1\n return (\n <label\n key={method}\n className=\"flex items-start gap-3 rounded-md border bg-background px-3 py-2\"\n >\n <Checkbox\n checked={isChecked}\n onCheckedChange={(checked) => toggleMethod(method, checked === true)}\n disabled={disabled || isLastSelected}\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {t(`security.admin.enforcement.methods.${method}`, method)}\n </span>\n <span className=\"block text-xs text-muted-foreground\">\n {t(METHOD_DESCRIPTION_KEYS[method].key, METHOD_DESCRIPTION_KEYS[method].fallback)}\n </span>\n </span>\n </label>\n )\n })}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'security.admin.enforcement.form.allowedMethodsHint',\n 'If no methods are selected, all available methods are allowed.',\n )}\n </p>\n )}\n </div>\n )\n}\n\nexport default function EnforcementPolicyForm({\n value,\n backHref,\n submitting = false,\n onSubmit,\n onCancel,\n}: EnforcementPolicyFormProps) {\n const t = useT()\n const formId = 'security-enforcement-policy-form'\n\n const initialValues = React.useMemo<EnforcementPolicyFormValue>(\n () => ({\n id: value?.id,\n scope: value?.scope === 'tenant' || value?.scope === 'organisation' ? value.scope : 'platform',\n tenantId: value?.tenantId ?? '',\n organizationId: value?.organizationId ?? '',\n isEnforced: value?.isEnforced ?? true,\n enforcementDeadline: toDatetimeLocal(value?.enforcementDeadline),\n allowedMethods: Array.isArray(value?.allowedMethods) ? value.allowedMethods : [],\n }),\n [value],\n )\n const [selectedScope, setSelectedScope] = React.useState<EnforcementScope>(initialValues.scope)\n\n React.useEffect(() => {\n setSelectedScope(initialValues.scope)\n }, [initialValues.scope])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n scope: z.enum(['platform', 'tenant', 'organisation']),\n tenantId: z.string().optional().default(''),\n organizationId: z.string().optional().default(''),\n isEnforced: z.boolean().default(true),\n allowedMethods: z.array(z.enum(METHOD_OPTION_VALUES)).default([]),\n enforcementDeadline: z.string().optional().default(''),\n })\n .superRefine((values, context) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n if (values.scope !== 'platform' && !tenantId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.tenantRequired',\n 'Tenant ID is required for tenant and organisation scopes.',\n ),\n path: ['tenantId'],\n })\n }\n\n if (values.scope === 'organisation' && !organizationId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.organizationRequired',\n 'Organization ID is required for organisation scope.',\n ),\n path: ['organizationId'],\n })\n }\n }),\n [t],\n )\n\n type EnforcementPolicyCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(\n () => {\n const baseFields: CrudField[] = [\n {\n id: 'scope',\n label: t('security.admin.enforcement.form.scope', 'Scope'),\n type: 'custom',\n required: true,\n disabled: submitting,\n layout: 'half',\n component: ({ value, setValue, disabled, autoFocus }) => {\n const resolvedScope = value === 'tenant' || value === 'organisation' ? value : 'platform'\n return (\n <select\n className=\"w-full h-9 rounded border px-2 text-sm\"\n value={resolvedScope}\n onChange={(event) => {\n const next = event.target.value === 'tenant' || event.target.value === 'organisation'\n ? event.target.value\n : 'platform'\n setValue(next)\n setSelectedScope(next)\n }}\n autoFocus={autoFocus}\n disabled={disabled}\n data-crud-focus-target=\"\"\n >\n <option value=\"platform\">{t('security.admin.enforcement.scope.platform', 'Platform')}</option>\n <option value=\"tenant\">{t('security.admin.enforcement.scope.tenant', 'Tenant')}</option>\n <option value=\"organisation\">{t('security.admin.enforcement.scope.organisation', 'Organisation')}</option>\n </select>\n )\n },\n },\n {\n id: 'enforcementDeadline',\n label: t('security.admin.enforcement.form.deadline', 'Enforcement deadline'),\n type: 'datetime-local',\n disabled: submitting,\n layout: 'half',\n },\n ]\n\n if (selectedScope !== 'platform') {\n baseFields.push({\n id: 'tenantId',\n label: t('security.admin.enforcement.form.tenant', 'Tenant'),\n type: 'custom',\n component: ScopeTenantField,\n disabled: submitting,\n })\n }\n\n if (selectedScope === 'organisation') {\n baseFields.push({\n id: 'organizationId',\n label: t('security.admin.enforcement.form.organization', 'Organization'),\n type: 'custom',\n component: ScopeOrganizationField,\n disabled: submitting,\n })\n }\n\n baseFields.push(\n {\n id: 'isEnforced',\n label: t('security.admin.enforcement.form.enabled', 'Policy enforced'),\n type: 'checkbox',\n disabled: submitting,\n },\n {\n id: 'allowedMethods',\n label: t('security.admin.enforcement.form.allowedMethods', 'Allowed methods'),\n type: 'custom',\n component: AllowedMethodsField,\n disabled: submitting,\n },\n )\n\n return baseFields\n },\n [selectedScope, submitting, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: EnforcementPolicyCrudValues) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n await onSubmit({\n id: value?.id,\n scope: values.scope as EnforcementScope,\n tenantId,\n organizationId,\n isEnforced: values.isEnforced,\n enforcementDeadline: values.enforcementDeadline,\n allowedMethods: values.allowedMethods,\n })\n },\n [onSubmit, value?.id],\n )\n\n return (\n <CrudForm<EnforcementPolicyCrudValues>\n title={\n value?.id\n ? t('security.admin.enforcement.form.titleEdit', 'Edit enforcement policy')\n : t('security.admin.enforcement.form.titleCreate', 'Create enforcement policy')\n }\n backHref={backHref}\n schema={schema}\n formId={formId}\n fields={fields}\n initialValues={initialValues as Partial<EnforcementPolicyCrudValues>}\n hideFooterActions\n embedded={false}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { CrudForm, type CrudCustomFieldRenderProps, type CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\n\nexport type EnforcementScope = 'platform' | 'tenant' | 'organisation'\n\nexport type EnforcementPolicyFormValue = {\n id?: string\n scope: EnforcementScope\n tenantId: string\n organizationId: string\n isEnforced: boolean\n allowedMethods: string[]\n enforcementDeadline: string\n}\ntype EnforcementPolicyFormProps = {\n value?: Partial<EnforcementPolicyFormValue> | null\n submitting: boolean\n onSubmit: (value: EnforcementPolicyFormValue) => Promise<void> | void\n onCancel: () => void\n backHref: string\n}\n\nconst METHOD_OPTIONS = ['totp', 'passkey', 'otp_email'] as const\nconst METHOD_OPTION_VALUES = [...METHOD_OPTIONS] as [typeof METHOD_OPTIONS[number], ...typeof METHOD_OPTIONS[number][]]\nconst METHOD_DESCRIPTION_KEYS: Record<typeof METHOD_OPTIONS[number], { key: string; fallback: string }> = {\n totp: {\n key: 'security.admin.enforcement.methods.totpDescription',\n fallback: 'Authenticator app code (TOTP).',\n },\n passkey: {\n key: 'security.admin.enforcement.methods.passkeyDescription',\n fallback: 'Biometric or security key passkey.',\n },\n otp_email: {\n key: 'security.admin.enforcement.methods.otpEmailDescription',\n fallback: 'One-time verification code sent by email.',\n },\n}\n\nfunction toDatetimeLocal(value: string | null | undefined): string {\n if (!value) return ''\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return ''\n\n const year = parsed.getFullYear()\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\n const day = String(parsed.getDate()).padStart(2, '0')\n const hours = String(parsed.getHours()).padStart(2, '0')\n const minutes = String(parsed.getMinutes()).padStart(2, '0')\n\n return `${year}-${month}-${day}T${hours}:${minutes}`\n}\n\nfunction ScopeTenantField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n if (scope !== 'tenant' && scope !== 'organisation') return null\n\n const normalizedValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n\n return (\n <TenantSelect\n id=\"enforcement-tenant-id\"\n value={normalizedValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.tenantPlaceholder', 'Select tenant')}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction ScopeOrganizationField({ value, setValue, values, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const scope = values?.scope\n const organizationValue = typeof value === 'string' && value.trim().length > 0 ? value : null\n const tenantId = typeof values?.tenantId === 'string' && values?.tenantId?.trim().length > 0 ? values?.tenantId : null\n const previousTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n previousTenantRef.current = tenantId\n return\n }\n if (previousTenantRef.current !== tenantId) {\n previousTenantRef.current = tenantId\n setValue('')\n }\n }, [setValue, tenantId])\n\n if (scope !== 'organisation') return null\n\n return (\n <OrganizationSelect\n id=\"enforcement-organization-id\"\n value={organizationValue}\n onChange={(next) => setValue(next ?? '')}\n disabled={disabled || !tenantId}\n includeEmptyOption\n emptyOptionLabel={t('security.admin.enforcement.form.organizationPlaceholder', 'Select organization')}\n tenantId={tenantId}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n />\n )\n}\n\nfunction AllowedMethodsField({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n const t = useT()\n const selectedMethods = React.useMemo(() => {\n if (!Array.isArray(value)) return []\n return value\n .filter((item): item is typeof METHOD_OPTIONS[number] => typeof item === 'string' && METHOD_OPTIONS.includes(item as typeof METHOD_OPTIONS[number]))\n }, [value])\n const [mode, setMode] = React.useState<'all' | 'restricted'>(() => (selectedMethods.length > 0 ? 'restricted' : 'all'))\n\n React.useEffect(() => {\n setMode(selectedMethods.length > 0 ? 'restricted' : 'all')\n }, [selectedMethods.length])\n\n const setAllMethods = React.useCallback(() => {\n if (disabled) return\n setMode('all')\n setValue([])\n }, [disabled, setValue])\n\n const setRestrictedMethods = React.useCallback(() => {\n if (disabled) return\n setMode('restricted')\n if (selectedMethods.length === 0) {\n setValue([METHOD_OPTIONS[0]])\n }\n }, [disabled, selectedMethods.length, setValue])\n\n const toggleMethod = React.useCallback((method: typeof METHOD_OPTIONS[number], checked: boolean) => {\n if (disabled) return\n if (!checked) {\n if (selectedMethods.length <= 1) return\n setValue(selectedMethods.filter((item) => item !== method))\n return\n }\n if (selectedMethods.includes(method)) return\n setValue([...selectedMethods, method])\n }, [disabled, selectedMethods, setValue])\n\n return (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'all' ? 'default' : 'outline'}\n onClick={setAllMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsAll', 'Allow all methods')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant={mode === 'restricted' ? 'default' : 'outline'}\n onClick={setRestrictedMethods}\n disabled={disabled}\n >\n {t('security.admin.enforcement.form.allowedMethodsRestricted', 'Restrict to selected methods')}\n </Button>\n </div>\n\n {mode === 'restricted' ? (\n <div className=\"space-y-2\">\n {METHOD_OPTIONS.map((method) => {\n const isChecked = selectedMethods.includes(method)\n const isLastSelected = isChecked && selectedMethods.length === 1\n return (\n <label\n key={method}\n className=\"flex items-start gap-3 rounded-md border bg-background px-3 py-2\"\n >\n <Checkbox\n checked={isChecked}\n onCheckedChange={(checked) => toggleMethod(method, checked === true)}\n disabled={disabled || isLastSelected}\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">\n {t(`security.admin.enforcement.methods.${method}`, method)}\n </span>\n <span className=\"block text-xs text-muted-foreground\">\n {t(METHOD_DESCRIPTION_KEYS[method].key, METHOD_DESCRIPTION_KEYS[method].fallback)}\n </span>\n </span>\n </label>\n )\n })}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'security.admin.enforcement.form.allowedMethodsHint',\n 'If no methods are selected, all available methods are allowed.',\n )}\n </p>\n )}\n </div>\n )\n}\n\nexport default function EnforcementPolicyForm({\n value,\n backHref,\n submitting = false,\n onSubmit,\n onCancel,\n}: EnforcementPolicyFormProps) {\n const t = useT()\n const formId = 'security-enforcement-policy-form'\n\n const initialValues = React.useMemo<EnforcementPolicyFormValue>(\n () => ({\n id: value?.id,\n scope: value?.scope === 'tenant' || value?.scope === 'organisation' ? value.scope : 'platform',\n tenantId: value?.tenantId ?? '',\n organizationId: value?.organizationId ?? '',\n isEnforced: value?.isEnforced ?? true,\n enforcementDeadline: toDatetimeLocal(value?.enforcementDeadline),\n allowedMethods: Array.isArray(value?.allowedMethods) ? value.allowedMethods : [],\n }),\n [value],\n )\n const [selectedScope, setSelectedScope] = React.useState<EnforcementScope>(initialValues.scope)\n\n React.useEffect(() => {\n setSelectedScope(initialValues.scope)\n }, [initialValues.scope])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n scope: z.enum(['platform', 'tenant', 'organisation']),\n tenantId: z.string().optional().default(''),\n organizationId: z.string().optional().default(''),\n isEnforced: z.boolean().default(true),\n allowedMethods: z.array(z.enum(METHOD_OPTION_VALUES)).default([]),\n enforcementDeadline: z.string().optional().default(''),\n })\n .superRefine((values, context) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n if (values.scope !== 'platform' && !tenantId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.tenantRequired',\n 'Tenant ID is required for tenant and organisation scopes.',\n ),\n path: ['tenantId'],\n })\n }\n\n if (values.scope === 'organisation' && !organizationId) {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: t(\n 'security.admin.enforcement.form.errors.organizationRequired',\n 'Organization ID is required for organisation scope.',\n ),\n path: ['organizationId'],\n })\n }\n }),\n [t],\n )\n\n type EnforcementPolicyCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(\n () => {\n const baseFields: CrudField[] = [\n {\n id: 'scope',\n label: t('security.admin.enforcement.form.scope', 'Scope'),\n type: 'custom',\n required: true,\n disabled: submitting,\n layout: 'half',\n component: ({ value, setValue, disabled, autoFocus }) => {\n const resolvedScope = value === 'tenant' || value === 'organisation' ? value : 'platform'\n return (\n <select\n className=\"w-full h-9 rounded border px-2 text-sm\"\n value={resolvedScope}\n onChange={(event) => {\n const next = event.target.value === 'tenant' || event.target.value === 'organisation'\n ? event.target.value\n : 'platform'\n setValue(next)\n setSelectedScope(next)\n }}\n autoFocus={autoFocus}\n disabled={disabled}\n data-crud-focus-target=\"\"\n >\n <option value=\"platform\">{t('security.admin.enforcement.scope.platform', 'Platform')}</option>\n <option value=\"tenant\">{t('security.admin.enforcement.scope.tenant', 'Tenant')}</option>\n <option value=\"organisation\">{t('security.admin.enforcement.scope.organisation', 'Organisation')}</option>\n </select>\n )\n },\n },\n {\n id: 'enforcementDeadline',\n label: t('security.admin.enforcement.form.deadline', 'Enforcement deadline'),\n type: 'datetime-local',\n disabled: submitting,\n layout: 'half',\n },\n ]\n\n if (selectedScope !== 'platform') {\n baseFields.push({\n id: 'tenantId',\n label: t('security.admin.enforcement.form.tenant', 'Tenant'),\n type: 'custom',\n component: ScopeTenantField,\n disabled: submitting,\n })\n }\n\n if (selectedScope === 'organisation') {\n baseFields.push({\n id: 'organizationId',\n label: t('security.admin.enforcement.form.organization', 'Organization'),\n type: 'custom',\n component: ScopeOrganizationField,\n disabled: submitting,\n })\n }\n\n baseFields.push(\n {\n id: 'isEnforced',\n label: t('security.admin.enforcement.form.enabled', 'Policy enforced'),\n type: 'checkbox',\n disabled: submitting,\n },\n {\n id: 'allowedMethods',\n label: t('security.admin.enforcement.form.allowedMethods', 'Allowed methods'),\n type: 'custom',\n component: AllowedMethodsField,\n disabled: submitting,\n },\n )\n\n return baseFields\n },\n [selectedScope, submitting, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: EnforcementPolicyCrudValues) => {\n const tenantId = values.tenantId.trim()\n const organizationId = values.organizationId.trim()\n\n await onSubmit({\n id: value?.id,\n scope: values.scope as EnforcementScope,\n tenantId,\n organizationId,\n isEnforced: values.isEnforced,\n enforcementDeadline: values.enforcementDeadline,\n allowedMethods: values.allowedMethods,\n })\n },\n [onSubmit, value?.id],\n )\n\n return (\n <CrudForm<EnforcementPolicyCrudValues>\n title={\n value?.id\n ? t('security.admin.enforcement.form.titleEdit', 'Edit enforcement policy')\n : t('security.admin.enforcement.form.titleCreate', 'Create enforcement policy')\n }\n titleHeadingLevel={1}\n backHref={backHref}\n schema={schema}\n formId={formId}\n fields={fields}\n initialValues={initialValues as Partial<EnforcementPolicyCrudValues>}\n hideFooterActions\n embedded={false}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["value"]
7
7
  }
@@ -150,6 +150,7 @@ function PasswordChangeForm() {
150
150
  CrudForm,
151
151
  {
152
152
  title: t("security.profile.password.form.title", "Change password"),
153
+ titleHeadingLevel: 1,
153
154
  formId,
154
155
  schema,
155
156
  fields,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/security/components/PasswordChangeForm.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { CrudForm, type CrudField, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getPasswordPolicy, getPasswordRequirements, buildPasswordSchema, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { PasswordStrengthStatus } from './PasswordStrengthStatus'\n\ntype PasswordChangeValues = {\n currentPassword: string\n newPassword: string\n confirmPassword: string\n}\n\ntype ChangePasswordResponse = {\n ok: boolean\n}\n\nfunction PasswordInputField({\n value,\n setValue,\n disabled,\n autoFocus,\n}: CrudCustomFieldRenderProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => setValue(event.target.value)}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\ntype TrackedPasswordInputProps = CrudCustomFieldRenderProps & {\n onValueChange: (value: string) => void\n}\n\nfunction TrackedPasswordInput({\n value,\n setValue,\n disabled,\n autoFocus,\n onValueChange,\n}: TrackedPasswordInputProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => {\n const nextValue = event.target.value\n onValueChange(nextValue)\n setValue(nextValue)\n }}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\nexport default function PasswordChangeForm() {\n const t = useT()\n const [formKey, setFormKey] = React.useState(0)\n const [newPasswordValue, setNewPasswordValue] = React.useState('')\n const formId = 'security-password-change-form'\n\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(() => getPasswordRequirements(passwordPolicy), [passwordPolicy])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n currentPassword: z.string().min(1, t('security.profile.password.form.errors.currentRequired', 'Current password is required.')),\n newPassword: buildPasswordSchema({\n policy: passwordPolicy,\n message: t('security.profile.password.form.errors.passwordPolicy', 'Password does not meet the requirements.'),\n }),\n confirmPassword: z.string().min(1, t('security.profile.password.form.errors.confirmRequired', 'Please confirm the new password.')),\n })\n .superRefine((values, ctx) => {\n if (values.newPassword !== values.confirmPassword) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: t('security.profile.password.form.errors.passwordMismatch', 'Passwords do not match.'),\n path: ['confirmPassword'],\n })\n }\n }),\n [passwordPolicy, t],\n )\n\n const passwordValidation = React.useMemo(\n () => validatePassword(newPasswordValue, passwordPolicy),\n [newPasswordValue, passwordPolicy],\n )\n\n const hasNewPasswordInput = newPasswordValue.trim().length > 0\n const fields = React.useMemo<CrudField[]>(\n () => [\n {\n id: 'currentPassword',\n label: t('security.profile.password.form.currentPassword', 'Current password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'newPassword',\n label: t('security.profile.password.form.newPassword', 'New password'),\n type: 'custom',\n required: true,\n component: (props) => <TrackedPasswordInput {...props} onValueChange={setNewPasswordValue} />,\n },\n {\n id: 'confirmPassword',\n label: t('security.profile.password.form.confirmPassword', 'Confirm new password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'passwordRequirements',\n label: '',\n type: 'custom',\n component: () => (\n <PasswordStrengthStatus\n hasInput={hasNewPasswordInput}\n passwordPolicy={passwordPolicy}\n passwordRequirements={passwordRequirements}\n passwordValidation={passwordValidation}\n framed={false}\n />\n ),\n },\n ],\n [hasNewPasswordInput, passwordPolicy, passwordRequirements, passwordValidation, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: PasswordChangeValues) => {\n if (!values.currentPassword.trim() || !values.newPassword.trim()) {\n throw createCrudFormError(\n t('security.profile.password.form.errors.required', 'Both current and new password are required.'),\n )\n }\n\n await readApiResultOrThrow<ChangePasswordResponse>(\n '/api/security/profile/password',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n currentPassword: values.currentPassword,\n newPassword: values.newPassword,\n }),\n },\n { errorMessage: t('security.profile.password.form.errors.save', 'Failed to update password.') },\n )\n\n setFormKey((previous) => previous + 1)\n setNewPasswordValue('')\n flash(t('security.profile.password.form.success', 'Password updated.'), 'success')\n },\n [t],\n )\n\n return (\n <CrudForm<PasswordChangeValues>\n title={t('security.profile.password.form.title', 'Change password')}\n key={formKey}\n formId={formId}\n schema={schema}\n fields={fields}\n initialValues={{\n currentPassword: '',\n newPassword: '',\n confirmPassword: '',\n }}\n hideFooterActions\n onSubmit={handleSubmit}\n embedded={false}\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { CrudForm, type CrudField, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getPasswordPolicy, getPasswordRequirements, buildPasswordSchema, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { PasswordStrengthStatus } from './PasswordStrengthStatus'\n\ntype PasswordChangeValues = {\n currentPassword: string\n newPassword: string\n confirmPassword: string\n}\n\ntype ChangePasswordResponse = {\n ok: boolean\n}\n\nfunction PasswordInputField({\n value,\n setValue,\n disabled,\n autoFocus,\n}: CrudCustomFieldRenderProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => setValue(event.target.value)}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\ntype TrackedPasswordInputProps = CrudCustomFieldRenderProps & {\n onValueChange: (value: string) => void\n}\n\nfunction TrackedPasswordInput({\n value,\n setValue,\n disabled,\n autoFocus,\n onValueChange,\n}: TrackedPasswordInputProps) {\n return (\n <PasswordInput\n className=\"max-w-xl\"\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => {\n const nextValue = event.target.value\n onValueChange(nextValue)\n setValue(nextValue)\n }}\n disabled={disabled}\n autoFocus={autoFocus}\n />\n )\n}\n\nexport default function PasswordChangeForm() {\n const t = useT()\n const [formKey, setFormKey] = React.useState(0)\n const [newPasswordValue, setNewPasswordValue] = React.useState('')\n const formId = 'security-password-change-form'\n\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(() => getPasswordRequirements(passwordPolicy), [passwordPolicy])\n\n const schema = React.useMemo(\n () =>\n z\n .object({\n currentPassword: z.string().min(1, t('security.profile.password.form.errors.currentRequired', 'Current password is required.')),\n newPassword: buildPasswordSchema({\n policy: passwordPolicy,\n message: t('security.profile.password.form.errors.passwordPolicy', 'Password does not meet the requirements.'),\n }),\n confirmPassword: z.string().min(1, t('security.profile.password.form.errors.confirmRequired', 'Please confirm the new password.')),\n })\n .superRefine((values, ctx) => {\n if (values.newPassword !== values.confirmPassword) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: t('security.profile.password.form.errors.passwordMismatch', 'Passwords do not match.'),\n path: ['confirmPassword'],\n })\n }\n }),\n [passwordPolicy, t],\n )\n\n const passwordValidation = React.useMemo(\n () => validatePassword(newPasswordValue, passwordPolicy),\n [newPasswordValue, passwordPolicy],\n )\n\n const hasNewPasswordInput = newPasswordValue.trim().length > 0\n const fields = React.useMemo<CrudField[]>(\n () => [\n {\n id: 'currentPassword',\n label: t('security.profile.password.form.currentPassword', 'Current password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'newPassword',\n label: t('security.profile.password.form.newPassword', 'New password'),\n type: 'custom',\n required: true,\n component: (props) => <TrackedPasswordInput {...props} onValueChange={setNewPasswordValue} />,\n },\n {\n id: 'confirmPassword',\n label: t('security.profile.password.form.confirmPassword', 'Confirm new password'),\n type: 'custom',\n required: true,\n component: PasswordInputField,\n },\n {\n id: 'passwordRequirements',\n label: '',\n type: 'custom',\n component: () => (\n <PasswordStrengthStatus\n hasInput={hasNewPasswordInput}\n passwordPolicy={passwordPolicy}\n passwordRequirements={passwordRequirements}\n passwordValidation={passwordValidation}\n framed={false}\n />\n ),\n },\n ],\n [hasNewPasswordInput, passwordPolicy, passwordRequirements, passwordValidation, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: PasswordChangeValues) => {\n if (!values.currentPassword.trim() || !values.newPassword.trim()) {\n throw createCrudFormError(\n t('security.profile.password.form.errors.required', 'Both current and new password are required.'),\n )\n }\n\n await readApiResultOrThrow<ChangePasswordResponse>(\n '/api/security/profile/password',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n currentPassword: values.currentPassword,\n newPassword: values.newPassword,\n }),\n },\n { errorMessage: t('security.profile.password.form.errors.save', 'Failed to update password.') },\n )\n\n setFormKey((previous) => previous + 1)\n setNewPasswordValue('')\n flash(t('security.profile.password.form.success', 'Password updated.'), 'success')\n },\n [t],\n )\n\n return (\n <CrudForm<PasswordChangeValues>\n title={t('security.profile.password.form.title', 'Change password')}\n titleHeadingLevel={1}\n key={formKey}\n formId={formId}\n schema={schema}\n fields={fields}\n initialValues={{\n currentPassword: '',\n newPassword: '',\n confirmPassword: '',\n }}\n hideFooterActions\n onSubmit={handleSubmit}\n embedded={false}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -103,6 +103,7 @@ function SecurityUserForm({
103
103
  CrudForm,
104
104
  {
105
105
  title: t("security.admin.users.detail.title", "User MFA detail"),
106
+ titleHeadingLevel: 1,
106
107
  backHref: "/backend/security/users",
107
108
  schema,
108
109
  formId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/security/components/SecurityUserForm.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport MfaComplianceBadge from './MfaComplianceBadge'\n\nexport type SecurityUserMethod = {\n type: string\n label?: string\n lastUsed?: string\n}\n\nexport type SecurityUserFormValue = {\n userId: string\n email: string\n enrolled: boolean\n recoveryCodesRemaining: number\n compliant: boolean\n methods: SecurityUserMethod[]\n resetReason: string\n}\n\ntype SecurityUserFormProps = {\n value: SecurityUserFormValue\n submitting: boolean\n onSubmit: (value: SecurityUserFormValue) => Promise<void> | void\n onCancel: () => void\n}\n\nfunction formatMethodLastUsed(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nfunction UserSummarySection({ value }: { value: SecurityUserFormValue }) {\n const t = useT()\n return (\n <div className=\"grid gap-2 md:grid-cols-4\">\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.email', 'Email')}</p>\n <p className=\"text-sm font-medium break-all\">{value.email}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.detail.recoveryCodes', 'Recovery codes remaining')}</p>\n <p className=\"text-sm font-medium\">{String(value.recoveryCodesRemaining)}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.compliance', 'Compliance')}</p>\n <div className=\"pt-1\">\n <MfaComplianceBadge enrolled={value.enrolled} compliant={value.compliant} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction MethodsSection({ methods }: { methods: SecurityUserMethod[] }) {\n const t = useT()\n if (!methods.length) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n {t('security.admin.users.detail.noMethods', 'No active methods.')}\n </p>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n {methods.map((method, index) => (\n <div key={`${method.type}-${index}`} className=\"rounded border bg-background px-3 py-2\">\n <div className=\"grid gap-2 md:grid-cols-3\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodType', 'Type')}\n </p>\n <p className=\"text-sm font-medium\">{method.type}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLabel', 'Label')}\n </p>\n <p className=\"text-sm font-medium\">{method.label ?? '-'}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLastUsed', 'Last used')}\n </p>\n <p className=\"text-sm font-medium\">\n {formatMethodLastUsed(\n method.lastUsed,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n )}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n}\n\nexport default function SecurityUserForm({\n value,\n submitting,\n onSubmit,\n onCancel,\n}: SecurityUserFormProps) {\n const t = useT()\n const formId = 'security-user-form'\n\n const schema = React.useMemo(\n () => z.object({\n resetReason: z\n .string()\n .trim()\n .min(1, t('security.admin.users.errors.reasonRequired', 'Reset reason is required.')),\n }),\n [t],\n )\n\n type SecurityUserCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'resetReason',\n label: t('security.admin.users.reset.reason', 'Reset reason'),\n type: 'textarea',\n required: true,\n disabled: submitting,\n description: t('security.admin.users.reset.reasonPlaceholder', 'Enter reason for audit trail'),\n },\n ], [submitting, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'summary',\n column: 1,\n component: () => <UserSummarySection value={value} />,\n },\n {\n id: 'methods',\n column: 1,\n component: () => <MethodsSection methods={value.methods} />,\n },\n {\n id: 'reset',\n column: 1,\n fields: ['resetReason'],\n },\n ], [value])\n\n const handleSubmit = React.useCallback(async (values: SecurityUserCrudValues) => {\n await onSubmit({\n ...value,\n resetReason: values.resetReason.trim(),\n })\n }, [onSubmit, value])\n\n return (\n <CrudForm<SecurityUserCrudValues>\n title={t('security.admin.users.detail.title', 'User MFA detail')}\n backHref=\"/backend/security/users\"\n schema={schema}\n formId={formId}\n fields={fields}\n groups={groups}\n initialValues={{ resetReason: '' }}\n embedded={false}\n submitLabel={t('security.admin.users.actions.resetMfa', 'Reset MFA')}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport MfaComplianceBadge from './MfaComplianceBadge'\n\nexport type SecurityUserMethod = {\n type: string\n label?: string\n lastUsed?: string\n}\n\nexport type SecurityUserFormValue = {\n userId: string\n email: string\n enrolled: boolean\n recoveryCodesRemaining: number\n compliant: boolean\n methods: SecurityUserMethod[]\n resetReason: string\n}\n\ntype SecurityUserFormProps = {\n value: SecurityUserFormValue\n submitting: boolean\n onSubmit: (value: SecurityUserFormValue) => Promise<void> | void\n onCancel: () => void\n}\n\nfunction formatMethodLastUsed(value: string | undefined, fallback: string): string {\n if (!value) return fallback\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return fallback\n return parsed.toLocaleString()\n}\n\nfunction UserSummarySection({ value }: { value: SecurityUserFormValue }) {\n const t = useT()\n return (\n <div className=\"grid gap-2 md:grid-cols-4\">\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.email', 'Email')}</p>\n <p className=\"text-sm font-medium break-all\">{value.email}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.detail.recoveryCodes', 'Recovery codes remaining')}</p>\n <p className=\"text-sm font-medium\">{String(value.recoveryCodesRemaining)}</p>\n </div>\n <div className=\"rounded border bg-muted/30 px-3 py-2\">\n <p className=\"text-xs text-muted-foreground\">{t('security.admin.users.table.compliance', 'Compliance')}</p>\n <div className=\"pt-1\">\n <MfaComplianceBadge enrolled={value.enrolled} compliant={value.compliant} />\n </div>\n </div>\n </div>\n )\n}\n\nfunction MethodsSection({ methods }: { methods: SecurityUserMethod[] }) {\n const t = useT()\n if (!methods.length) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n {t('security.admin.users.detail.noMethods', 'No active methods.')}\n </p>\n )\n }\n\n return (\n <div className=\"space-y-2\">\n {methods.map((method, index) => (\n <div key={`${method.type}-${index}`} className=\"rounded border bg-background px-3 py-2\">\n <div className=\"grid gap-2 md:grid-cols-3\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodType', 'Type')}\n </p>\n <p className=\"text-sm font-medium\">{method.type}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLabel', 'Label')}\n </p>\n <p className=\"text-sm font-medium\">{method.label ?? '-'}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {t('security.admin.users.detail.methodLastUsed', 'Last used')}\n </p>\n <p className=\"text-sm font-medium\">\n {formatMethodLastUsed(\n method.lastUsed,\n t('security.admin.users.lastLogin.unknown', 'Unknown'),\n )}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n}\n\nexport default function SecurityUserForm({\n value,\n submitting,\n onSubmit,\n onCancel,\n}: SecurityUserFormProps) {\n const t = useT()\n const formId = 'security-user-form'\n\n const schema = React.useMemo(\n () => z.object({\n resetReason: z\n .string()\n .trim()\n .min(1, t('security.admin.users.errors.reasonRequired', 'Reset reason is required.')),\n }),\n [t],\n )\n\n type SecurityUserCrudValues = z.infer<typeof schema>\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'resetReason',\n label: t('security.admin.users.reset.reason', 'Reset reason'),\n type: 'textarea',\n required: true,\n disabled: submitting,\n description: t('security.admin.users.reset.reasonPlaceholder', 'Enter reason for audit trail'),\n },\n ], [submitting, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'summary',\n column: 1,\n component: () => <UserSummarySection value={value} />,\n },\n {\n id: 'methods',\n column: 1,\n component: () => <MethodsSection methods={value.methods} />,\n },\n {\n id: 'reset',\n column: 1,\n fields: ['resetReason'],\n },\n ], [value])\n\n const handleSubmit = React.useCallback(async (values: SecurityUserCrudValues) => {\n await onSubmit({\n ...value,\n resetReason: values.resetReason.trim(),\n })\n }, [onSubmit, value])\n\n return (\n <CrudForm<SecurityUserCrudValues>\n title={t('security.admin.users.detail.title', 'User MFA detail')}\n titleHeadingLevel={1}\n backHref=\"/backend/security/users\"\n schema={schema}\n formId={formId}\n fields={fields}\n groups={groups}\n initialValues={{ resetReason: '' }}\n embedded={false}\n submitLabel={t('security.admin.users.actions.resetMfa', 'Reset MFA')}\n onSubmit={handleSubmit}\n extraActions={(\n <Button type=\"button\" variant=\"outline\" onClick={onCancel} disabled={submitting}>\n {t('ui.actions.cancel', 'Cancel')}\n </Button>\n )}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -135,6 +135,7 @@ function SudoConfigCrudPageInner({
135
135
  CrudForm,
136
136
  {
137
137
  title: mode === "edit" ? t("security.admin.sudo.form.title.edit", "Edit sudo rule") : t("security.admin.sudo.form.title.create", "Create sudo rule"),
138
+ titleHeadingLevel: 1,
138
139
  backHref: "/backend/security/sudo",
139
140
  fields,
140
141
  groups,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/security/components/SudoConfigCrudPage.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SudoProvider } from './SudoProvider'\nimport { useSudoChallenge } from './hooks/useSudoChallenge'\nimport {\n buildSudoConfigInitialValues,\n buildSudoConfigPayload,\n createSudoConfigFormFields,\n createSudoConfigFormSchema,\n type SudoConfigFormValues,\n} from './SudoConfigForm'\n\ntype SudoConfigRecord = {\n id: string\n tenantId: string | null\n organizationId: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n ttlSeconds: number\n challengeMethod: SudoConfigFormValues['challengeMethod']\n}\n\ntype SudoConfigCrudPageProps = {\n mode: 'create' | 'edit'\n id?: string\n defaultTtlSeconds?: number\n maxTtlSeconds?: number\n}\n\nfunction SudoConfigCrudPageInner({\n mode,\n id,\n defaultTtlSeconds,\n maxTtlSeconds,\n}: SudoConfigCrudPageProps) {\n const t = useT()\n const router = useRouter()\n const { requireSudo } = useSudoChallenge()\n const [initialValues, setInitialValues] = React.useState<SudoConfigFormValues>(() => buildSudoConfigInitialValues(\n undefined,\n { defaultTtlSeconds },\n ))\n const [loading, setLoading] = React.useState(mode === 'edit')\n const [submitting, setSubmitting] = React.useState(false)\n const schema = React.useMemo(\n () => createSudoConfigFormSchema(t, { maxTtlSeconds }),\n [maxTtlSeconds, t],\n )\n const fields = React.useMemo(() => createSudoConfigFormFields(t, { disabled: submitting }), [submitting, t])\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'target',\n title: t('security.admin.sudo.form.group.target', 'Sudo target'),\n column: 1,\n fields: ['targetIdentifier', 'label'],\n },\n {\n id: 'details',\n title: t('security.admin.sudo.form.group.details', 'Configuration'),\n column: 1,\n fields: ['tenantId', 'organizationId', 'ttlSeconds', 'challengeMethod', 'isEnabled'],\n },\n ], [t])\n const redirectWithFlash = React.useCallback((message: string, type: 'success' | 'error' = 'success') => {\n router.push(`/backend/security/sudo?flash=${encodeURIComponent(message)}&type=${type}`)\n }, [router])\n\n React.useEffect(() => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n let cancelled = false\n\n async function loadConfig() {\n setLoading(true)\n const response = await apiCall<SudoConfigRecord>(`/api/security/sudo/configs/${encodeURIComponent(configId)}`)\n if (!cancelled) {\n if (response.ok && response.result) {\n setInitialValues(buildSudoConfigInitialValues({\n id: response.result.id,\n tenantId: response.result.tenantId ?? '',\n organizationId: response.result.organizationId ?? '',\n label: response.result.label ?? '',\n targetIdentifier: response.result.targetIdentifier,\n isEnabled: response.result.isEnabled,\n ttlSeconds: response.result.ttlSeconds,\n challengeMethod: response.result.challengeMethod,\n }, { defaultTtlSeconds }))\n } else {\n redirectWithFlash(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'), 'error')\n }\n setLoading(false)\n }\n }\n\n void loadConfig()\n\n return () => {\n cancelled = true\n }\n }, [defaultTtlSeconds, id, mode, redirectWithFlash, t])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleSubmit = React.useCallback(async (values: SudoConfigFormValues) => {\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n const payload = buildSudoConfigPayload(values)\n if (mode === 'edit' && id) {\n const configId = id\n await updateCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.updated', 'Sudo configuration updated.'))\n } else {\n await createCrud('security/sudo/configs', payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.created', 'Sudo configuration created.'))\n }\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n const handleDelete = React.useCallback(async () => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n await deleteCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'))\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n return (\n <Page>\n <PageBody>\n <CrudForm<SudoConfigFormValues>\n title={mode === 'edit'\n ? t('security.admin.sudo.form.title.edit', 'Edit sudo rule')\n : t('security.admin.sudo.form.title.create', 'Create sudo rule')}\n backHref=\"/backend/security/sudo\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('security.admin.sudo.loading', 'Loading sudo configuration...')}\n cancelHref=\"/backend/security/sudo\"\n submitLabel={mode === 'edit' ? t('ui.actions.save', 'Save') : t('ui.actions.create', 'Create')}\n schema={schema}\n onSubmit={handleSubmit}\n onDelete={mode === 'edit' ? handleDelete : undefined}\n />\n </PageBody>\n </Page>\n )\n}\n\nexport default function SudoConfigCrudPage(props: SudoConfigCrudPageProps) {\n return (\n <SudoProvider>\n <SudoConfigCrudPageInner {...props} />\n </SudoProvider>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SudoProvider } from './SudoProvider'\nimport { useSudoChallenge } from './hooks/useSudoChallenge'\nimport {\n buildSudoConfigInitialValues,\n buildSudoConfigPayload,\n createSudoConfigFormFields,\n createSudoConfigFormSchema,\n type SudoConfigFormValues,\n} from './SudoConfigForm'\n\ntype SudoConfigRecord = {\n id: string\n tenantId: string | null\n organizationId: string | null\n label: string | null\n targetIdentifier: string\n isEnabled: boolean\n ttlSeconds: number\n challengeMethod: SudoConfigFormValues['challengeMethod']\n}\n\ntype SudoConfigCrudPageProps = {\n mode: 'create' | 'edit'\n id?: string\n defaultTtlSeconds?: number\n maxTtlSeconds?: number\n}\n\nfunction SudoConfigCrudPageInner({\n mode,\n id,\n defaultTtlSeconds,\n maxTtlSeconds,\n}: SudoConfigCrudPageProps) {\n const t = useT()\n const router = useRouter()\n const { requireSudo } = useSudoChallenge()\n const [initialValues, setInitialValues] = React.useState<SudoConfigFormValues>(() => buildSudoConfigInitialValues(\n undefined,\n { defaultTtlSeconds },\n ))\n const [loading, setLoading] = React.useState(mode === 'edit')\n const [submitting, setSubmitting] = React.useState(false)\n const schema = React.useMemo(\n () => createSudoConfigFormSchema(t, { maxTtlSeconds }),\n [maxTtlSeconds, t],\n )\n const fields = React.useMemo(() => createSudoConfigFormFields(t, { disabled: submitting }), [submitting, t])\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'target',\n title: t('security.admin.sudo.form.group.target', 'Sudo target'),\n column: 1,\n fields: ['targetIdentifier', 'label'],\n },\n {\n id: 'details',\n title: t('security.admin.sudo.form.group.details', 'Configuration'),\n column: 1,\n fields: ['tenantId', 'organizationId', 'ttlSeconds', 'challengeMethod', 'isEnabled'],\n },\n ], [t])\n const redirectWithFlash = React.useCallback((message: string, type: 'success' | 'error' = 'success') => {\n router.push(`/backend/security/sudo?flash=${encodeURIComponent(message)}&type=${type}`)\n }, [router])\n\n React.useEffect(() => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n let cancelled = false\n\n async function loadConfig() {\n setLoading(true)\n const response = await apiCall<SudoConfigRecord>(`/api/security/sudo/configs/${encodeURIComponent(configId)}`)\n if (!cancelled) {\n if (response.ok && response.result) {\n setInitialValues(buildSudoConfigInitialValues({\n id: response.result.id,\n tenantId: response.result.tenantId ?? '',\n organizationId: response.result.organizationId ?? '',\n label: response.result.label ?? '',\n targetIdentifier: response.result.targetIdentifier,\n isEnabled: response.result.isEnabled,\n ttlSeconds: response.result.ttlSeconds,\n challengeMethod: response.result.challengeMethod,\n }, { defaultTtlSeconds }))\n } else {\n redirectWithFlash(t('security.admin.sudo.errors.load', 'Failed to load sudo configuration.'), 'error')\n }\n setLoading(false)\n }\n }\n\n void loadConfig()\n\n return () => {\n cancelled = true\n }\n }, [defaultTtlSeconds, id, mode, redirectWithFlash, t])\n\n const requestToken = React.useCallback(async () => {\n const sudoToken = await requireSudo('security.sudo.manage')\n if (!sudoToken) {\n flash(t('security.admin.sudo.flash.cancelled', 'Sudo challenge cancelled.'), 'error')\n return null\n }\n return sudoToken\n }, [requireSudo, t])\n\n const handleSubmit = React.useCallback(async (values: SudoConfigFormValues) => {\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n const payload = buildSudoConfigPayload(values)\n if (mode === 'edit' && id) {\n const configId = id\n await updateCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.updated', 'Sudo configuration updated.'))\n } else {\n await createCrud('security/sudo/configs', payload, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.saveError', 'Failed to save sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.created', 'Sudo configuration created.'))\n }\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n const handleDelete = React.useCallback(async () => {\n if (mode !== 'edit' || !id) return\n\n const configId = id\n const sudoToken = await requestToken()\n if (!sudoToken) return\n\n setSubmitting(true)\n try {\n await deleteCrud(`security/sudo/configs/${encodeURIComponent(configId)}`, {\n headers: { 'x-sudo-token': sudoToken },\n errorMessage: t('security.admin.sudo.flash.deleteError', 'Failed to delete sudo configuration.'),\n })\n redirectWithFlash(t('security.admin.sudo.flash.deleted', 'Sudo configuration deleted.'))\n } finally {\n setSubmitting(false)\n }\n }, [id, mode, redirectWithFlash, requestToken, t])\n\n return (\n <Page>\n <PageBody>\n <CrudForm<SudoConfigFormValues>\n title={mode === 'edit'\n ? t('security.admin.sudo.form.title.edit', 'Edit sudo rule')\n : t('security.admin.sudo.form.title.create', 'Create sudo rule')}\n titleHeadingLevel={1}\n backHref=\"/backend/security/sudo\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('security.admin.sudo.loading', 'Loading sudo configuration...')}\n cancelHref=\"/backend/security/sudo\"\n submitLabel={mode === 'edit' ? t('ui.actions.save', 'Save') : t('ui.actions.create', 'Create')}\n schema={schema}\n onSubmit={handleSubmit}\n onDelete={mode === 'edit' ? handleDelete : undefined}\n />\n </PageBody>\n </Page>\n )\n}\n\nexport default function SudoConfigCrudPage(props: SudoConfigCrudPageProps) {\n return (\n <SudoProvider>\n <SudoConfigCrudPageInner {...props} />\n </SudoProvider>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -129,6 +129,7 @@ function SsoConfigListPage() {
129
129
  DataTable,
130
130
  {
131
131
  title: t("sso.admin.title", "Single Sign-On"),
132
+ titleHeadingLevel: 1,
132
133
  actions: canCreateNew ? /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/sso/config/new", children: t("sso.admin.new", "New SSO Config") }) }) : void 0,
133
134
  columns,
134
135
  data: data.items,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sso/backend/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport Link from 'next/link'\n\ninterface SsoConfigRow {\n id: string\n name: string | null\n protocol: string\n issuer: string | null\n allowedDomains: string[]\n isActive: boolean\n hasClientSecret: boolean\n organizationId: string\n tenantId: string | null\n createdAt: string\n}\n\ninterface ListResponse {\n items: SsoConfigRow[]\n total: number\n totalPages: number\n totalIsCapped?: boolean\n isSuperAdmin?: boolean\n}\n\nconst fallback: ListResponse = { items: [], total: 0, totalPages: 1 }\n\nexport default function SsoConfigListPage() {\n const router = useRouter()\n const t = useT()\n\n const [data, setData] = React.useState<ListResponse>(fallback)\n const [isLoading, setIsLoading] = React.useState(true)\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const isSuperAdmin = !!data.isSuperAdmin\n\n const fetchData = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '50')\n if (search) params.set('search', search)\n\n const call = await apiCall<ListResponse>(`/api/sso/config?${params}`, undefined, { fallback })\n if (call.ok && call.result) {\n setData(call.result)\n }\n setIsLoading(false)\n }, [page, search])\n\n React.useEffect(() => { fetchData() }, [fetchData])\n\n const handleDelete = async (row: SsoConfigRow) => {\n if (row.isActive) {\n flash(t('sso.admin.error.deleteActive', 'Cannot delete an active SSO configuration \u2014 deactivate it first'), 'error')\n return\n }\n\n const confirmed = await confirm({\n title: t('sso.admin.delete.title', 'Delete SSO Configuration'),\n text: t('sso.admin.delete.confirm', 'Are you sure? This will remove the SSO configuration. Users with linked SSO identities will need to use password login.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n\n if (!confirmed) return\n\n await apiCallOrThrow(`/api/sso/config/${row.id}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.error.deleteFailed', 'Failed to delete SSO configuration'),\n })\n flash(t('sso.admin.delete.success', 'SSO configuration deleted'), 'success')\n fetchData()\n }\n\n const handleToggleActivation = async (row: SsoConfigRow) => {\n try {\n await apiCallOrThrow(\n `/api/sso/config/${row.id}/activate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active: !row.isActive }),\n },\n { errorMessage: t('sso.admin.error.activationFailed', 'Failed to update activation status') },\n )\n flash(\n row.isActive\n ? t('sso.admin.deactivated', 'SSO configuration deactivated')\n : t('sso.admin.activated', 'SSO configuration activated'),\n 'success',\n )\n fetchData()\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const handleTestConnection = async (row: SsoConfigRow) => {\n try {\n const call = await apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/sso/config/${row.id}/test`,\n { method: 'POST' },\n { errorMessage: t('sso.admin.error.testFailed', 'Connection test failed') },\n )\n if (call.result?.ok) {\n flash(t('sso.admin.test.success', 'Discovery successful \u2014 issuer is reachable'), 'success')\n } else {\n flash(call.result?.error || t('sso.admin.test.failed', 'Discovery failed'), 'error')\n }\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const columns = React.useMemo<ColumnDef<SsoConfigRow>[]>(() => {\n const cols: ColumnDef<SsoConfigRow>[] = [\n {\n accessorKey: 'name',\n header: t('sso.admin.column.name', 'Name'),\n cell: ({ row }) => row.original.name || row.original.issuer || '\u2014',\n },\n {\n accessorKey: 'protocol',\n header: t('sso.admin.column.protocol', 'Protocol'),\n cell: ({ row }) => row.original.protocol.toUpperCase(),\n },\n {\n accessorKey: 'allowedDomains',\n header: t('sso.admin.column.domains', 'Domains'),\n cell: ({ row }) => row.original.allowedDomains.join(', ') || '\u2014',\n },\n {\n accessorKey: 'isActive',\n header: t('sso.admin.column.status', 'Status'),\n cell: ({ row }) => (\n <span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${row.original.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'}`}>\n {row.original.isActive\n ? t('sso.admin.status.active', 'Active')\n : t('sso.admin.status.inactive', 'Inactive')}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('sso.admin.column.created', 'Created'),\n cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),\n },\n ]\n\n return cols\n }, [t, isSuperAdmin])\n\n const hasConfigs = data.items.length > 0 || search\n const canCreateNew = isSuperAdmin || data.items.length === 0\n\n return (\n <Page>\n <PageBody>\n {!hasConfigs && !isLoading ? (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <h3 className=\"text-lg font-semibold mb-2\">\n {t('sso.admin.empty.title', 'No SSO configured')}\n </h3>\n <p className=\"text-muted-foreground mb-4 max-w-md\">\n {t('sso.admin.empty.description', 'Configure Single Sign-On to let your users authenticate with your identity provider.')}\n </p>\n <Button asChild>\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.empty.cta', 'Configure SSO')}\n </Link>\n </Button>\n </div>\n ) : (\n <DataTable<SsoConfigRow>\n title={t('sso.admin.title', 'Single Sign-On')}\n actions={canCreateNew ? (\n <Button asChild size=\"sm\">\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.new', 'New SSO Config')}\n </Link>\n </Button>\n ) : undefined}\n columns={columns}\n data={data.items}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('sso.admin.search', 'Search by name or issuer...')}\n onRowClick={(row) => router.push(`/backend/sso/config/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => router.push(`/backend/sso/config/${row.id}`) },\n { id: 'test', label: t('sso.admin.action.test', 'Verify Discovery'), onSelect: () => handleTestConnection(row) },\n {\n id: 'toggle',\n label: row.isActive\n ? t('sso.admin.action.deactivate', 'Deactivate')\n : t('sso.admin.action.activate', 'Activate'),\n onSelect: () => handleToggleActivation(row),\n },\n { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => handleDelete(row) },\n ]}\n />\n )}\n pagination={{\n page,\n pageSize: 50,\n total: data.total,\n totalPages: data.totalPages,\n totalIsCapped: data?.totalIsCapped === true,\n onPageChange: setPage,\n }}\n isLoading={isLoading}\n />\n )}\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": ";AAqJU,cAwBA,YAxBA;AAnJV,OAAO,WAAW;AAClB,SAAS,iBAAiB;AAE1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,SAAS,sBAAsB;AACxC,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,YAAY;AACrB,OAAO,UAAU;AAuBjB,MAAM,WAAyB,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE;AAErD,SAAR,oBAAqC;AAC1C,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AAEf,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAuB,QAAQ;AAC7D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,eAAe,CAAC,CAAC,KAAK;AAE5B,QAAM,YAAY,MAAM,YAAY,YAAY;AAC9C,iBAAa,IAAI;AACjB,UAAM,SAAS,IAAI,gBAAgB;AACnC,WAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,WAAO,IAAI,YAAY,IAAI;AAC3B,QAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AAEvC,UAAM,OAAO,MAAM,QAAsB,mBAAmB,MAAM,IAAI,QAAW,EAAE,SAAS,CAAC;AAC7F,QAAI,KAAK,MAAM,KAAK,QAAQ;AAC1B,cAAQ,KAAK,MAAM;AAAA,IACrB;AACA,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,MAAM,MAAM,CAAC;AAEjB,QAAM,UAAU,MAAM;AAAE,cAAU;AAAA,EAAE,GAAG,CAAC,SAAS,CAAC;AAElD,QAAM,eAAe,OAAO,QAAsB;AAChD,QAAI,IAAI,UAAU;AAChB,YAAM,EAAE,gCAAgC,sEAAiE,GAAG,OAAO;AACnH;AAAA,IACF;AAEA,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,0BAA0B,0BAA0B;AAAA,MAC7D,MAAM,EAAE,4BAA4B,yHAAyH;AAAA,MAC7J,aAAa,EAAE,iBAAiB,QAAQ;AAAA,MACxC,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,UAAW;AAEhB,UAAM,eAAe,mBAAmB,IAAI,EAAE,IAAI,EAAE,QAAQ,SAAS,GAAG;AAAA,MACtE,cAAc,EAAE,gCAAgC,oCAAoC;AAAA,IACtF,CAAC;AACD,UAAM,EAAE,4BAA4B,2BAA2B,GAAG,SAAS;AAC3E,cAAU;AAAA,EACZ;AAEA,QAAM,yBAAyB,OAAO,QAAsB;AAC1D,QAAI;AACF,YAAM;AAAA,QACJ,mBAAmB,IAAI,EAAE;AAAA,QACzB;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC,IAAI,SAAS,CAAC;AAAA,QAChD;AAAA,QACA,EAAE,cAAc,EAAE,oCAAoC,oCAAoC,EAAE;AAAA,MAC9F;AACA;AAAA,QACE,IAAI,WACA,EAAE,yBAAyB,+BAA+B,IAC1D,EAAE,uBAAuB,6BAA6B;AAAA,QAC1D;AAAA,MACF;AACA,gBAAU;AAAA,IACZ,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,uBAAuB,OAAO,QAAsB;AACxD,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,mBAAmB,IAAI,EAAE;AAAA,QACzB,EAAE,QAAQ,OAAO;AAAA,QACjB,EAAE,cAAc,EAAE,8BAA8B,wBAAwB,EAAE;AAAA,MAC5E;AACA,UAAI,KAAK,QAAQ,IAAI;AACnB,cAAM,EAAE,0BAA0B,iDAA4C,GAAG,SAAS;AAAA,MAC5F,OAAO;AACL,cAAM,KAAK,QAAQ,SAAS,EAAE,yBAAyB,kBAAkB,GAAG,OAAO;AAAA,MACrF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,QAAmC,MAAM;AAC7D,UAAM,OAAkC;AAAA,MACtC;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,yBAAyB,MAAM;AAAA,QACzC,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,QAAQ,IAAI,SAAS,UAAU;AAAA,MACjE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,6BAA6B,UAAU;AAAA,QACjD,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,SAAS,YAAY;AAAA,MACvD;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,4BAA4B,SAAS;AAAA,QAC/C,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,eAAe,KAAK,IAAI,KAAK;AAAA,MAC/D;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,2BAA2B,QAAQ;AAAA,QAC7C,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,UAAK,WAAW,uEAAuE,IAAI,SAAS,WAAW,+BAA+B,2BAA2B,IACvK,cAAI,SAAS,WACV,EAAE,2BAA2B,QAAQ,IACrC,EAAE,6BAA6B,UAAU,GAC/C;AAAA,MAEJ;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,4BAA4B,SAAS;AAAA,QAC/C,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,KAAK,IAAI,SAAS,SAAS,EAAE,mBAAmB;AAAA,MACzE;AAAA,IACF;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,GAAG,YAAY,CAAC;AAEpB,QAAM,aAAa,KAAK,MAAM,SAAS,KAAK;AAC5C,QAAM,eAAe,gBAAgB,KAAK,MAAM,WAAW;AAE3D,SACE,oBAAC,QACC,+BAAC,YACE;AAAA,KAAC,cAAc,CAAC,YACf,qBAAC,SAAI,WAAU,+DACb;AAAA,0BAAC,QAAG,WAAU,8BACX,YAAE,yBAAyB,mBAAmB,GACjD;AAAA,MACA,oBAAC,OAAE,WAAU,uCACV,YAAE,+BAA+B,sFAAsF,GAC1H;AAAA,MACA,oBAAC,UAAO,SAAO,MACb,8BAAC,QAAK,MAAK,2BACR,YAAE,uBAAuB,eAAe,GAC3C,GACF;AAAA,OACF,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,mBAAmB,gBAAgB;AAAA,QAC5C,SAAS,eACP,oBAAC,UAAO,SAAO,MAAC,MAAK,MACnB,8BAAC,QAAK,MAAK,2BACR,YAAE,iBAAiB,gBAAgB,GACtC,GACF,IACE;AAAA,QACJ;AAAA,QACA,MAAM,KAAK;AAAA,QACX,aAAa;AAAA,QACb,gBAAgB,CAAC,UAAU;AAAE,oBAAU,KAAK;AAAG,kBAAQ,CAAC;AAAA,QAAE;AAAA,QAC1D,mBAAmB,EAAE,oBAAoB,6BAA6B;AAAA,QACtE,YAAY,CAAC,QAAQ,OAAO,KAAK,uBAAuB,IAAI,EAAE,EAAE;AAAA,QAChE,YAAY,CAAC,QACX;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,EAAE,IAAI,QAAQ,OAAO,EAAE,eAAe,MAAM,GAAG,UAAU,MAAM,OAAO,KAAK,uBAAuB,IAAI,EAAE,EAAE,EAAE;AAAA,cAC5G,EAAE,IAAI,QAAQ,OAAO,EAAE,yBAAyB,kBAAkB,GAAG,UAAU,MAAM,qBAAqB,GAAG,EAAE;AAAA,cAC/G;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,IAAI,WACP,EAAE,+BAA+B,YAAY,IAC7C,EAAE,6BAA6B,UAAU;AAAA,gBAC7C,UAAU,MAAM,uBAAuB,GAAG;AAAA,cAC5C;AAAA,cACA,EAAE,IAAI,UAAU,OAAO,EAAE,iBAAiB,QAAQ,GAAG,aAAa,MAAM,UAAU,MAAM,aAAa,GAAG,EAAE;AAAA,YAC5G;AAAA;AAAA,QACF;AAAA,QAEF,YAAY;AAAA,UACV;AAAA,UACA,UAAU;AAAA,UACV,OAAO,KAAK;AAAA,UACZ,YAAY,KAAK;AAAA,UACjB,eAAe,MAAM,kBAAkB;AAAA,UACvC,cAAc;AAAA,QAChB;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAED;AAAA,KACH,GACF;AAEJ;",
4
+ "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport Link from 'next/link'\n\ninterface SsoConfigRow {\n id: string\n name: string | null\n protocol: string\n issuer: string | null\n allowedDomains: string[]\n isActive: boolean\n hasClientSecret: boolean\n organizationId: string\n tenantId: string | null\n createdAt: string\n}\n\ninterface ListResponse {\n items: SsoConfigRow[]\n total: number\n totalPages: number\n totalIsCapped?: boolean\n isSuperAdmin?: boolean\n}\n\nconst fallback: ListResponse = { items: [], total: 0, totalPages: 1 }\n\nexport default function SsoConfigListPage() {\n const router = useRouter()\n const t = useT()\n\n const [data, setData] = React.useState<ListResponse>(fallback)\n const [isLoading, setIsLoading] = React.useState(true)\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const isSuperAdmin = !!data.isSuperAdmin\n\n const fetchData = React.useCallback(async () => {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '50')\n if (search) params.set('search', search)\n\n const call = await apiCall<ListResponse>(`/api/sso/config?${params}`, undefined, { fallback })\n if (call.ok && call.result) {\n setData(call.result)\n }\n setIsLoading(false)\n }, [page, search])\n\n React.useEffect(() => { fetchData() }, [fetchData])\n\n const handleDelete = async (row: SsoConfigRow) => {\n if (row.isActive) {\n flash(t('sso.admin.error.deleteActive', 'Cannot delete an active SSO configuration \u2014 deactivate it first'), 'error')\n return\n }\n\n const confirmed = await confirm({\n title: t('sso.admin.delete.title', 'Delete SSO Configuration'),\n text: t('sso.admin.delete.confirm', 'Are you sure? This will remove the SSO configuration. Users with linked SSO identities will need to use password login.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n\n if (!confirmed) return\n\n await apiCallOrThrow(`/api/sso/config/${row.id}`, { method: 'DELETE' }, {\n errorMessage: t('sso.admin.error.deleteFailed', 'Failed to delete SSO configuration'),\n })\n flash(t('sso.admin.delete.success', 'SSO configuration deleted'), 'success')\n fetchData()\n }\n\n const handleToggleActivation = async (row: SsoConfigRow) => {\n try {\n await apiCallOrThrow(\n `/api/sso/config/${row.id}/activate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ active: !row.isActive }),\n },\n { errorMessage: t('sso.admin.error.activationFailed', 'Failed to update activation status') },\n )\n flash(\n row.isActive\n ? t('sso.admin.deactivated', 'SSO configuration deactivated')\n : t('sso.admin.activated', 'SSO configuration activated'),\n 'success',\n )\n fetchData()\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const handleTestConnection = async (row: SsoConfigRow) => {\n try {\n const call = await apiCallOrThrow<{ ok: boolean; error?: string }>(\n `/api/sso/config/${row.id}/test`,\n { method: 'POST' },\n { errorMessage: t('sso.admin.error.testFailed', 'Connection test failed') },\n )\n if (call.result?.ok) {\n flash(t('sso.admin.test.success', 'Discovery successful \u2014 issuer is reachable'), 'success')\n } else {\n flash(call.result?.error || t('sso.admin.test.failed', 'Discovery failed'), 'error')\n }\n } catch {\n // apiCallOrThrow already flashes the error\n }\n }\n\n const columns = React.useMemo<ColumnDef<SsoConfigRow>[]>(() => {\n const cols: ColumnDef<SsoConfigRow>[] = [\n {\n accessorKey: 'name',\n header: t('sso.admin.column.name', 'Name'),\n cell: ({ row }) => row.original.name || row.original.issuer || '\u2014',\n },\n {\n accessorKey: 'protocol',\n header: t('sso.admin.column.protocol', 'Protocol'),\n cell: ({ row }) => row.original.protocol.toUpperCase(),\n },\n {\n accessorKey: 'allowedDomains',\n header: t('sso.admin.column.domains', 'Domains'),\n cell: ({ row }) => row.original.allowedDomains.join(', ') || '\u2014',\n },\n {\n accessorKey: 'isActive',\n header: t('sso.admin.column.status', 'Status'),\n cell: ({ row }) => (\n <span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${row.original.isActive ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'}`}>\n {row.original.isActive\n ? t('sso.admin.status.active', 'Active')\n : t('sso.admin.status.inactive', 'Inactive')}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('sso.admin.column.created', 'Created'),\n cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),\n },\n ]\n\n return cols\n }, [t, isSuperAdmin])\n\n const hasConfigs = data.items.length > 0 || search\n const canCreateNew = isSuperAdmin || data.items.length === 0\n\n return (\n <Page>\n <PageBody>\n {!hasConfigs && !isLoading ? (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <h3 className=\"text-lg font-semibold mb-2\">\n {t('sso.admin.empty.title', 'No SSO configured')}\n </h3>\n <p className=\"text-muted-foreground mb-4 max-w-md\">\n {t('sso.admin.empty.description', 'Configure Single Sign-On to let your users authenticate with your identity provider.')}\n </p>\n <Button asChild>\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.empty.cta', 'Configure SSO')}\n </Link>\n </Button>\n </div>\n ) : (\n <DataTable<SsoConfigRow>\n title={t('sso.admin.title', 'Single Sign-On')}\n titleHeadingLevel={1}\n actions={canCreateNew ? (\n <Button asChild size=\"sm\">\n <Link href=\"/backend/sso/config/new\">\n {t('sso.admin.new', 'New SSO Config')}\n </Link>\n </Button>\n ) : undefined}\n columns={columns}\n data={data.items}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('sso.admin.search', 'Search by name or issuer...')}\n onRowClick={(row) => router.push(`/backend/sso/config/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => router.push(`/backend/sso/config/${row.id}`) },\n { id: 'test', label: t('sso.admin.action.test', 'Verify Discovery'), onSelect: () => handleTestConnection(row) },\n {\n id: 'toggle',\n label: row.isActive\n ? t('sso.admin.action.deactivate', 'Deactivate')\n : t('sso.admin.action.activate', 'Activate'),\n onSelect: () => handleToggleActivation(row),\n },\n { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => handleDelete(row) },\n ]}\n />\n )}\n pagination={{\n page,\n pageSize: 50,\n total: data.total,\n totalPages: data.totalPages,\n totalIsCapped: data?.totalIsCapped === true,\n onPageChange: setPage,\n }}\n isLoading={isLoading}\n />\n )}\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.7.1-develop.7152.1.a69e92f9c9",
3
+ "version": "0.7.1-develop.7154.1.981330c924",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -65,8 +65,8 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@open-mercato/core": "0.7.1-develop.7152.1.a69e92f9c9",
69
- "@open-mercato/ui": "0.7.1-develop.7152.1.a69e92f9c9",
68
+ "@open-mercato/core": "0.7.1-develop.7154.1.981330c924",
69
+ "@open-mercato/ui": "0.7.1-develop.7154.1.981330c924",
70
70
  "@simplewebauthn/browser": "^13.3.0",
71
71
  "@simplewebauthn/server": "^13.3.2",
72
72
  "@simplewebauthn/types": "^12.0.0",
@@ -76,12 +76,12 @@
76
76
  "qrcode": "^1.5.4"
77
77
  },
78
78
  "peerDependencies": {
79
- "@open-mercato/shared": "0.7.1-develop.7152.1.a69e92f9c9",
79
+ "@open-mercato/shared": "0.7.1-develop.7154.1.981330c924",
80
80
  "react": "^19.0.0",
81
81
  "react-dom": "^19.0.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@open-mercato/shared": "0.7.1-develop.7152.1.a69e92f9c9",
84
+ "@open-mercato/shared": "0.7.1-develop.7154.1.981330c924",
85
85
  "@types/jest": "^30.0.0",
86
86
  "@types/react": "^19.2.17",
87
87
  "@types/react-dom": "^19.2.3",
@@ -233,6 +233,7 @@ export default function SecurityEnforcementPage() {
233
233
  <PageBody>
234
234
  <DataTable<EnforcementPolicyDto>
235
235
  title={t('security.admin.enforcement.list.title', 'Existing policies')}
236
+ titleHeadingLevel={1}
236
237
  actions={(
237
238
  <Button type="button" variant="outline" size="sm" asChild>
238
239
  <Link href="/backend/security/enforcement/create">
@@ -244,6 +244,7 @@ function SecuritySudoPageInner() {
244
244
 
245
245
  <DataTable<SudoConfigRow>
246
246
  title={t('security.admin.sudo.title', 'Sudo protection')}
247
+ titleHeadingLevel={1}
247
248
  columns={columns}
248
249
  data={rows}
249
250
  actions={(
@@ -143,6 +143,7 @@ export default function SecurityUsersPage() {
143
143
  <PageBody>
144
144
  <DataTable<ComplianceItem>
145
145
  title={t('security.admin.users.table.title', 'Users')}
146
+ titleHeadingLevel={1}
146
147
  columns={columns}
147
148
  data={pagedItems}
148
149
  searchValue={search}
@@ -394,6 +394,7 @@ export default function EnforcementPolicyForm({
394
394
  ? t('security.admin.enforcement.form.titleEdit', 'Edit enforcement policy')
395
395
  : t('security.admin.enforcement.form.titleCreate', 'Create enforcement policy')
396
396
  }
397
+ titleHeadingLevel={1}
397
398
  backHref={backHref}
398
399
  schema={schema}
399
400
  formId={formId}
@@ -174,6 +174,7 @@ export default function PasswordChangeForm() {
174
174
  return (
175
175
  <CrudForm<PasswordChangeValues>
176
176
  title={t('security.profile.password.form.title', 'Change password')}
177
+ titleHeadingLevel={1}
177
178
  key={formKey}
178
179
  formId={formId}
179
180
  schema={schema}
@@ -164,6 +164,7 @@ export default function SecurityUserForm({
164
164
  return (
165
165
  <CrudForm<SecurityUserCrudValues>
166
166
  title={t('security.admin.users.detail.title', 'User MFA detail')}
167
+ titleHeadingLevel={1}
167
168
  backHref="/backend/security/users"
168
169
  schema={schema}
169
170
  formId={formId}
@@ -170,6 +170,7 @@ function SudoConfigCrudPageInner({
170
170
  title={mode === 'edit'
171
171
  ? t('security.admin.sudo.form.title.edit', 'Edit sudo rule')
172
172
  : t('security.admin.sudo.form.title.create', 'Create sudo rule')}
173
+ titleHeadingLevel={1}
173
174
  backHref="/backend/security/sudo"
174
175
  fields={fields}
175
176
  groups={groups}
@@ -186,7 +186,8 @@ export default function SsoConfigListPage() {
186
186
  </div>
187
187
  ) : (
188
188
  <DataTable<SsoConfigRow>
189
- title={t('sso.admin.title', 'Single Sign-On')}
189
+ title={t('sso.admin.title', 'Single Sign-On')}
190
+ titleHeadingLevel={1}
190
191
  actions={canCreateNew ? (
191
192
  <Button asChild size="sm">
192
193
  <Link href="/backend/sso/config/new">