@open-mercato/enterprise 0.6.8-develop.6911.1.f853dd04a9 → 0.6.8-develop.6913.1.721e521f55

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/security/backend/profile/security/mfa/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation.js'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\n\nimport MfaEnrollmentNotice from '../../../../components/MfaEnrollmentNotice'\nimport { useMfaStatus } from '../../../../components/hooks/useMfaStatus'\nimport { FormHeader } from '@open-mercato/ui/backend/forms/FormHeader'\nimport {\n removeMfaEnrollmentNoticeQueryFromHref,\n resolveMfaEnrollmentNotice,\n} from '../../../../lib/mfa-enrollment-notice'\nimport type { MfaProvider } from '../../../../types'\nimport { useProviderListComponent } from '../../../../components/mfa-ui-registry'\nimport RecoveryCodesListItem from '../../../../components/mfa-provider-list-items/RecoveryCodesListItem'\n\ntype MfaProviderRow = {\n kind: 'provider' | 'recovery_codes'\n provider?: MfaProvider\n configuredCount?: number\n}\n\ntype ProviderListCellProps = {\n row: MfaProviderRow\n onOpenProvider: (providerType: string) => void\n}\n\ntype ProviderRowItemProps = {\n provider: MfaProvider\n configuredCount: number\n onOpenProvider: (providerType: string) => void\n}\n\n// Always receives a provider so useProviderListComponent runs unconditionally,\n// keeping the hook count stable. The recovery_codes / missing-provider branches\n// live in the parent (ProviderListCell), which now calls no hooks at all.\nexport function ProviderRowItem({ provider, configuredCount, onOpenProvider }: ProviderRowItemProps) {\n const ListComponent = useProviderListComponent(provider)\n\n return (\n <ListComponent\n provider={provider}\n configuredCount={configuredCount}\n onClick={() => onOpenProvider(provider.type)}\n />\n )\n}\n\nexport function ProviderListCell({ row, onOpenProvider }: ProviderListCellProps) {\n if (row.kind === 'recovery_codes') {\n return <RecoveryCodesListItem onClick={() => onOpenProvider('recovery_codes')} />\n }\n\n const provider = row.provider\n if (!provider) return null\n\n return (\n <ProviderRowItem\n provider={provider}\n configuredCount={row.configuredCount ?? 0}\n onOpenProvider={onOpenProvider}\n />\n )\n}\n\nexport default function SecurityMfaPage() {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const { loading, methods, providers } = useMfaStatus()\n const [showEnrollmentNotice, setShowEnrollmentNotice] = React.useState(false)\n const [showOverdueEnrollmentNotice, setShowOverdueEnrollmentNotice] = React.useState(false)\n\n React.useEffect(() => {\n const noticeState = resolveMfaEnrollmentNotice(searchParams)\n if (!noticeState.visible) return\n\n setShowEnrollmentNotice(true)\n setShowOverdueEnrollmentNotice(noticeState.overdue)\n\n if (typeof window === 'undefined') return\n const nextUrl = removeMfaEnrollmentNoticeQueryFromHref(window.location.href)\n if (!nextUrl) return\n window.history.replaceState(window.history.state, '', nextUrl)\n }, [searchParams])\n\n const providerRows = React.useMemo<MfaProviderRow[]>(() => {\n const configuredCountByType = new Map<string, number>()\n for (const method of methods) {\n const currentCount = configuredCountByType.get(method.type) ?? 0\n configuredCountByType.set(method.type, currentCount + 1)\n }\n\n const providerRows: MfaProviderRow[] = providers.map((provider) => ({\n kind: 'provider' as const,\n provider,\n configuredCount: configuredCountByType.get(provider.type) ?? 0,\n }))\n providerRows.push({ kind: 'recovery_codes' })\n return providerRows\n }, [methods, providers])\n\n const handleOpenProvider = React.useCallback((providerType: string) => {\n if (providerType === 'recovery_codes') {\n router.push('/backend/profile/security/mfa/recovery-codes')\n return\n }\n router.push(`/backend/profile/security/mfa/${encodeURIComponent(providerType)}`)\n }, [router])\n\n const columns = React.useMemo<ColumnDef<MfaProviderRow>[]>(() => [\n {\n accessorKey: 'provider.type',\n header: t('security.profile.mfa.providers.tableColumn', 'Methods'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => (\n <ProviderListCell row={row.original} onOpenProvider={handleOpenProvider} />\n ),\n },\n ], [handleOpenProvider, t])\n\n if (loading) {\n return <LoadingMessage label={t('security.profile.mfa.loading', 'Loading MFA settings...')} />\n }\n\n return (\n <Page>\n <MfaEnrollmentNotice\n visible={showEnrollmentNotice}\n overdue={showOverdueEnrollmentNotice}\n onDismiss={() => setShowEnrollmentNotice(false)}\n />\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/profile/security\"\n backLabel={t('security.profile.backToList', 'Back to security settings')}\n title={t('security.profile.mfa.title', 'Multi-factor authentication')}\n subtitle={t('security.profile.mfa.description', 'Manage your MFA methods and recovery codes.')}\n />\n <PageBody>\n <DataTable\n columns={columns}\n data={providerRows}\n emptyState={(\n <EmptyState\n title={t('security.profile.mfa.providers.empty', 'No MFA providers are currently available.')}\n />\n )}\n embedded\n />\n </PageBody>\n </Page>\n )\n}\n"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation.js'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\n\nimport MfaEnrollmentNotice from '../../../../components/MfaEnrollmentNotice'\nimport { useMfaStatus } from '../../../../components/hooks/useMfaStatus'\nimport { FormHeader } from '@open-mercato/ui/backend/forms/FormHeader'\nimport {\n removeMfaEnrollmentNoticeQueryFromHref,\n resolveMfaEnrollmentNotice,\n} from '../../../../lib/mfa-enrollment-notice'\nimport type { MfaProvider } from '../../../../types'\nimport { useProviderListComponent } from '../../../../components/mfa-ui-registry'\nimport RecoveryCodesListItem from '../../../../components/mfa-provider-list-items/RecoveryCodesListItem'\n\ntype MfaProviderRow = {\n kind: 'provider' | 'recovery_codes'\n provider?: MfaProvider\n configuredCount?: number\n}\n\ntype ProviderListCellProps = {\n row: MfaProviderRow\n onOpenProvider: (providerType: string) => void\n}\n\ntype ProviderRowItemProps = {\n provider: MfaProvider\n configuredCount: number\n onOpenProvider: (providerType: string) => void\n}\n\n// Always receives a provider so useProviderListComponent runs unconditionally,\n// keeping the hook count stable. The recovery_codes / missing-provider branches\n// live in the parent (ProviderListCell), which now calls no hooks at all.\nexport function ProviderRowItem({ provider, configuredCount, onOpenProvider }: ProviderRowItemProps) {\n const ListComponent = useProviderListComponent(provider)\n\n return (\n <ListComponent\n provider={provider}\n configuredCount={configuredCount}\n onClick={() => onOpenProvider(provider.type)}\n />\n )\n}\n\nexport function ProviderListCell({ row, onOpenProvider }: ProviderListCellProps) {\n if (row.kind === 'recovery_codes') {\n return <RecoveryCodesListItem onClick={() => onOpenProvider('recovery_codes')} />\n }\n\n const provider = row.provider\n if (!provider) return null\n\n return (\n <ProviderRowItem\n provider={provider}\n configuredCount={row.configuredCount ?? 0}\n onOpenProvider={onOpenProvider}\n />\n )\n}\n\nexport default function SecurityMfaPage() {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const { loading, methods, providers } = useMfaStatus()\n const [showEnrollmentNotice, setShowEnrollmentNotice] = React.useState(false)\n const [showOverdueEnrollmentNotice, setShowOverdueEnrollmentNotice] = React.useState(false)\n\n React.useEffect(() => {\n const noticeState = resolveMfaEnrollmentNotice(searchParams)\n if (!noticeState.visible) return\n\n setShowEnrollmentNotice(true)\n setShowOverdueEnrollmentNotice(noticeState.overdue)\n\n if (typeof window === 'undefined') return\n const nextUrl = removeMfaEnrollmentNoticeQueryFromHref(window.location.href)\n if (!nextUrl) return\n window.history.replaceState(window.history.state, '', nextUrl)\n }, [searchParams])\n\n const providerRows = React.useMemo<MfaProviderRow[]>(() => {\n const configuredCountByType = new Map<string, number>()\n for (const method of methods) {\n const currentCount = configuredCountByType.get(method.type) ?? 0\n configuredCountByType.set(method.type, currentCount + 1)\n }\n\n const providerRows: MfaProviderRow[] = providers.map((provider) => ({\n kind: 'provider' as const,\n provider,\n configuredCount: configuredCountByType.get(provider.type) ?? 0,\n }))\n providerRows.push({ kind: 'recovery_codes' })\n return providerRows\n }, [methods, providers])\n\n const handleOpenProvider = React.useCallback((providerType: string) => {\n if (providerType === 'recovery_codes') {\n router.push('/backend/profile/security/mfa/recovery-codes')\n return\n }\n router.push(`/backend/profile/security/mfa/${encodeURIComponent(providerType)}`)\n }, [router])\n\n const columns = React.useMemo<ColumnDef<MfaProviderRow>[]>(() => [\n {\n accessorKey: 'provider.type',\n header: t('security.profile.mfa.providers.tableColumn', 'Methods'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => (\n <ProviderListCell row={row.original} onOpenProvider={handleOpenProvider} />\n ),\n },\n ], [handleOpenProvider, t])\n\n if (loading) {\n return <LoadingMessage label={t('security.profile.mfa.loading', 'Loading MFA settings...')} />\n }\n\n return (\n <Page>\n <MfaEnrollmentNotice\n visible={showEnrollmentNotice}\n overdue={showOverdueEnrollmentNotice}\n onDismiss={() => setShowEnrollmentNotice(false)}\n />\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/profile/security\"\n backLabel={t('security.profile.backToList', 'Back to security settings')}\n title={t('security.profile.mfa.title', 'Multi-factor authentication')}\n subtitle={t('security.profile.mfa.description', 'Manage your MFA methods and recovery codes.')}\n />\n <PageBody>\n <DataTable\n columns={columns}\n data={providerRows}\n emptyState={(\n <EmptyState\n title={t('security.profile.mfa.providers.empty', 'No MFA providers are currently available.')}\n />\n )}\n embedded\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["providerRows"]
7
7
  }
@@ -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 { ColumnDef, 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 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
  }
@@ -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 { ColumnDef } from '@tanstack/react-table'\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"],
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
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -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 { ColumnDef, 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 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
  }
@@ -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 { ColumnDef } from '@tanstack/react-table'\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 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 onPageChange: setPage,\n }}\n isLoading={isLoading}\n />\n )}\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
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 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 onPageChange: setPage,\n }}\n isLoading={isLoading}\n />\n )}\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/jest.config.cjs CHANGED
@@ -27,7 +27,7 @@ module.exports = {
27
27
  ],
28
28
  },
29
29
  transformIgnorePatterns: [
30
- 'node_modules/(?!(@mikro-orm|kysely|ai|@ai-sdk|ai-sdk-ollama|@workflow|@standard-schema)/)',
30
+ 'node_modules/(?!(@mikro-orm|kysely|ai|@ai-sdk|ai-sdk-ollama|@workflow|@standard-schema|@tanstack/react-table|@tanstack/table-core|@tanstack/react-store|@tanstack/store)/)',
31
31
  ],
32
32
  testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
33
33
  passWithNoTests: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.6.8-develop.6911.1.f853dd04a9",
3
+ "version": "0.6.8-develop.6913.1.721e521f55",
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.6.8-develop.6911.1.f853dd04a9",
69
- "@open-mercato/ui": "0.6.8-develop.6911.1.f853dd04a9",
68
+ "@open-mercato/core": "0.6.8-develop.6913.1.721e521f55",
69
+ "@open-mercato/ui": "0.6.8-develop.6913.1.721e521f55",
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.6.8-develop.6911.1.f853dd04a9",
79
+ "@open-mercato/shared": "0.6.8-develop.6913.1.721e521f55",
80
80
  "react": "^19.0.0",
81
81
  "react-dom": "^19.0.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@open-mercato/shared": "0.6.8-develop.6911.1.f853dd04a9",
84
+ "@open-mercato/shared": "0.6.8-develop.6913.1.721e521f55",
85
85
  "@types/jest": "^30.0.0",
86
86
  "@types/react": "^19.2.17",
87
87
  "@types/react-dom": "^19.2.3",
@@ -3,7 +3,7 @@
3
3
  import * as React from 'react'
4
4
  import { useRouter, useSearchParams } from 'next/navigation.js'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
- import type { ColumnDef } from '@tanstack/react-table'
6
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
7
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
8
8
  import { DataTable } from '@open-mercato/ui/backend/DataTable'
9
9
  import { EmptyState } from '@open-mercato/ui/backend/EmptyState'
@@ -4,7 +4,8 @@ import * as React from 'react'
4
4
  import { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'
5
5
  import Link from 'next/link'
6
6
  import { useRouter } from 'next/navigation.js'
7
- import type { ColumnDef, SortingState } from '@tanstack/react-table'
7
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
8
+ import type { SortingState } from '@tanstack/react-table'
8
9
  import { Pencil, Trash2 } from 'lucide-react'
9
10
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
10
11
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
@@ -3,7 +3,7 @@
3
3
  import * as React from 'react'
4
4
  import { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'
5
5
  import Link from 'next/link'
6
- import type { ColumnDef } from '@tanstack/react-table'
6
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
7
7
  import { Pencil, Plus, ShieldAlert, Trash2 } from 'lucide-react'
8
8
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
9
9
  import { DataTable } from '@open-mercato/ui/backend/DataTable'
@@ -3,7 +3,8 @@
3
3
  import * as React from 'react'
4
4
  import { extensionPoints } from '@open-mercato/enterprise/modules/security/extension-points'
5
5
  import { useRouter } from 'next/navigation.js'
6
- import type { ColumnDef, SortingState } from '@tanstack/react-table'
6
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
7
+ import type { SortingState } from '@tanstack/react-table'
7
8
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
8
9
  import { DataTable } from '@open-mercato/ui/backend/DataTable'
9
10
  import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React from 'react'
4
4
  import { useRouter } from 'next/navigation'
5
- import type { ColumnDef } from '@tanstack/react-table'
5
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
6
6
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
7
  import { DataTable } from '@open-mercato/ui/backend/DataTable'
8
8
  import { RowActions } from '@open-mercato/ui/backend/RowActions'