@open-mercato/core 0.8.1-develop.7458.1.3847638f67 → 0.9.1-develop.7459.1.99e3accd30

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 (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/availability/api/policies/route.js +6 -6
  3. package/dist/modules/availability/api/policies/route.js.map +2 -2
  4. package/dist/modules/availability/backend/availability/check/page.js +4 -3
  5. package/dist/modules/availability/backend/availability/check/page.js.map +2 -2
  6. package/dist/modules/availability/backend/availability/policies/create/page.js +3 -0
  7. package/dist/modules/availability/backend/availability/policies/create/page.js.map +2 -2
  8. package/dist/modules/availability/lib/organizationScope.js +24 -1
  9. package/dist/modules/availability/lib/organizationScope.js.map +2 -2
  10. package/dist/modules/catalog/api/categories/bulk-create/route.js +16 -2
  11. package/dist/modules/catalog/api/categories/bulk-create/route.js.map +2 -2
  12. package/dist/modules/catalog/api/products/bulk-create/route.js +16 -2
  13. package/dist/modules/catalog/api/products/bulk-create/route.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1 -0
  15. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  16. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +2 -2
  17. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
  18. package/dist/modules/customer_accounts/api/admin/users/[id]/sessions/[sessionId].js +78 -0
  19. package/dist/modules/customer_accounts/api/admin/users/[id]/sessions/[sessionId].js.map +7 -0
  20. package/dist/modules/customer_groups/api/customer-groups/price-kinds/route.js +90 -0
  21. package/dist/modules/customer_groups/api/customer-groups/price-kinds/route.js.map +7 -0
  22. package/dist/modules/customer_groups/backend/customer-groups/page.js +5 -1
  23. package/dist/modules/customer_groups/backend/customer-groups/page.js.map +2 -2
  24. package/dist/modules/customer_groups/components/CustomerGroupTermsPriceKindField.js +2 -2
  25. package/dist/modules/customer_groups/components/CustomerGroupTermsPriceKindField.js.map +2 -2
  26. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +3 -1
  27. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
  28. package/dist/modules/customer_groups/lib/priceKindScope.js +35 -1
  29. package/dist/modules/customer_groups/lib/priceKindScope.js.map +2 -2
  30. package/dist/modules/customer_groups/widgets/injection/person-groups-explain-terms.js +1 -1
  31. package/dist/modules/customer_groups/widgets/injection/person-groups-explain-terms.js.map +2 -2
  32. package/dist/modules/customers/commands/companies.js +14 -1
  33. package/dist/modules/customers/commands/companies.js.map +2 -2
  34. package/dist/modules/dictionaries/components/DictionariesManager.js +98 -87
  35. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  36. package/dist/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.js +3 -3
  37. package/dist/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.js.map +2 -2
  38. package/dist/modules/forms/backend/forms/[id]/submissions/components/formSummary.js +9 -0
  39. package/dist/modules/forms/backend/forms/[id]/submissions/components/formSummary.js.map +7 -0
  40. package/dist/modules/forms/backend/forms/[id]/submissions/page.js +3 -6
  41. package/dist/modules/forms/backend/forms/[id]/submissions/page.js.map +2 -2
  42. package/dist/modules/forms/commands/distribution.js +7 -3
  43. package/dist/modules/forms/commands/distribution.js.map +2 -2
  44. package/dist/modules/forms/commands/form-version.js +9 -4
  45. package/dist/modules/forms/commands/form-version.js.map +2 -2
  46. package/dist/modules/forms/commands/form.js +9 -4
  47. package/dist/modules/forms/commands/form.js.map +2 -2
  48. package/dist/modules/forms/commands/invitation.js +7 -3
  49. package/dist/modules/forms/commands/invitation.js.map +2 -2
  50. package/dist/modules/forms/commands/submission.js +13 -6
  51. package/dist/modules/forms/commands/submission.js.map +2 -2
  52. package/package.json +8 -8
  53. package/src/modules/availability/api/policies/route.ts +10 -11
  54. package/src/modules/availability/backend/availability/check/page.tsx +38 -35
  55. package/src/modules/availability/backend/availability/policies/create/page.tsx +3 -0
  56. package/src/modules/availability/i18n/de.json +1 -0
  57. package/src/modules/availability/i18n/en.json +1 -0
  58. package/src/modules/availability/i18n/es.json +1 -0
  59. package/src/modules/availability/i18n/ko.json +1 -0
  60. package/src/modules/availability/i18n/pl.json +1 -0
  61. package/src/modules/availability/lib/organizationScope.ts +37 -0
  62. package/src/modules/catalog/api/categories/bulk-create/route.ts +16 -2
  63. package/src/modules/catalog/api/products/bulk-create/route.ts +16 -2
  64. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +1 -0
  65. package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +2 -2
  66. package/src/modules/customer_accounts/AGENTS.md +1 -0
  67. package/src/modules/customer_accounts/api/admin/users/[id]/sessions/[sessionId].ts +89 -0
  68. package/src/modules/customer_groups/api/customer-groups/price-kinds/route.ts +99 -0
  69. package/src/modules/customer_groups/backend/customer-groups/page.tsx +7 -1
  70. package/src/modules/customer_groups/components/CustomerGroupTermsPriceKindField.tsx +5 -4
  71. package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +8 -4
  72. package/src/modules/customer_groups/lib/priceKindScope.ts +43 -0
  73. package/src/modules/customer_groups/widgets/injection/person-groups-explain-terms.tsx +2 -2
  74. package/src/modules/customers/commands/companies.ts +16 -1
  75. package/src/modules/dictionaries/components/DictionariesManager.tsx +8 -1
  76. package/src/modules/eudr/i18n/de.json +1 -0
  77. package/src/modules/eudr/i18n/en.json +1 -0
  78. package/src/modules/eudr/i18n/es.json +1 -0
  79. package/src/modules/eudr/i18n/ko.json +1 -0
  80. package/src/modules/eudr/i18n/pl.json +1 -0
  81. package/src/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.tsx +6 -5
  82. package/src/modules/forms/backend/forms/[id]/submissions/components/formSummary.ts +19 -0
  83. package/src/modules/forms/backend/forms/[id]/submissions/page.tsx +4 -16
  84. package/src/modules/forms/commands/distribution.ts +7 -3
  85. package/src/modules/forms/commands/form-version.ts +9 -4
  86. package/src/modules/forms/commands/form.ts +9 -4
  87. package/src/modules/forms/commands/invitation.ts +7 -3
  88. package/src/modules/forms/commands/submission.ts +13 -6
  89. package/src/modules/forms/i18n/de.json +21 -0
  90. package/src/modules/forms/i18n/en.json +21 -0
  91. package/src/modules/forms/i18n/es.json +21 -0
  92. package/src/modules/forms/i18n/ko.json +21 -0
  93. package/src/modules/forms/i18n/pl.json +21 -0
  94. package/src/modules/notifications/AGENTS.md +1 -1
  95. package/src/modules/push_notifications/i18n/de.json +1 -0
  96. package/src/modules/push_notifications/i18n/en.json +1 -0
  97. package/src/modules/push_notifications/i18n/es.json +1 -0
  98. package/src/modules/push_notifications/i18n/ko.json +1 -0
  99. package/src/modules/push_notifications/i18n/pl.json +1 -0
  100. package/src/modules/warranty_claims/i18n/de.json +1 -0
  101. package/src/modules/warranty_claims/i18n/en.json +1 -0
  102. package/src/modules/warranty_claims/i18n/es.json +1 -0
  103. package/src/modules/warranty_claims/i18n/ko.json +1 -0
  104. package/src/modules/warranty_claims/i18n/pl.json +1 -0
  105. package/src/modules/wms/i18n/de.json +1 -0
  106. package/src/modules/wms/i18n/en.json +1 -0
  107. package/src/modules/wms/i18n/es.json +1 -0
  108. package/src/modules/wms/i18n/ko.json +1 -0
  109. package/src/modules/wms/i18n/pl.json +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/forms/backend/forms/%5Bid%5D/submissions/components/SubmissionDrawer.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ArrowRight, History, Lock, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { apiCall } 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 { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/forms/extension-points'\nimport { colorForSavedByRole } from './RowBadges'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\n\nexport const FORMS_DRAWER_REFRESH_EVENT = 'forms:submission-drawer:refresh'\n\nexport const SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT = extensionPoints.hosts.submissionDrawerHeaderActions.spotId\nexport const SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT = extensionPoints.hosts.submissionDrawerAccessAudit.spotId\nexport const SUBMISSION_DRAWER_FOOTER_SPOT = extensionPoints.hosts.submissionDrawerFooter.spotId\nexport const SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT = extensionPoints.hosts.submissionDrawerAnonymizeAction.spotId\n\nexport type DrawerSubmission = {\n id: string\n status: 'draft' | 'submitted' | 'reopened' | 'archived' | 'anonymized'\n formVersionNumber: number\n subjectType: string\n subjectId: string\n anonymizedAt: string | null\n pdfSnapshotAttachmentId: string | null\n}\n\nexport type DrawerRevision = {\n id: string\n revisionNumber: number\n savedAt: string | null\n savedBy: string\n savedByRole: string | null\n changedFieldKeys: string[]\n anonymizedAt: string | null\n}\n\nexport type DrawerActor = {\n id: string\n userId: string\n role: string\n assignedAt: string | null\n revokedAt: string | null\n}\n\nexport type DrawerSubmissionDetail = {\n submission: DrawerSubmission\n revision: DrawerRevision\n decoded_data: Record<string, unknown>\n actors: DrawerActor[]\n}\n\nexport type DrawerInjectionContext = {\n submissionId: string\n formId: string\n status: DrawerSubmission['status']\n isAnonymized: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nexport type SubmissionDrawerProps = {\n formId: string\n submissionId: string\n formVersionRoles: string[]\n onClose: () => void\n onMutated?: () => void\n}\n\nexport function SubmissionDrawer({\n formId,\n submissionId,\n formVersionRoles,\n onClose,\n onMutated,\n}: SubmissionDrawerProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({ contextId: 'forms.submission.drawer' })\n const [detail, setDetail] = React.useState<DrawerSubmissionDetail | null>(null)\n const [revisions, setRevisions] = React.useState<DrawerRevision[]>([])\n const [activeRevisionId, setActiveRevisionId] = React.useState<string | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [actorOpen, setActorOpen] = React.useState(false)\n const triggerRef = React.useRef<HTMLDivElement | null>(null)\n\n const reload = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n try {\n const [detailResp, revisionsResp] = await Promise.all([\n apiCall<DrawerSubmissionDetail>(`/api/forms/submissions/${encodeURIComponent(submissionId)}`),\n apiCall<{ items: DrawerRevision[] }>(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/revisions`,\n ),\n ])\n if (!detailResp.ok || !detailResp.result) {\n throw new Error(t('forms.errors.submission_not_found'))\n }\n if (!revisionsResp.ok || !revisionsResp.result) {\n throw new Error(t('forms.errors.internal'))\n }\n setDetail(detailResp.result)\n setRevisions(revisionsResp.result.items)\n setActiveRevisionId(detailResp.result.revision.id)\n } catch (err) {\n setError(err instanceof Error ? err.message : t('forms.errors.internal'))\n } finally {\n setIsLoading(false)\n }\n }, [submissionId, t])\n\n React.useEffect(() => {\n void reload()\n }, [reload])\n\n React.useEffect(() => {\n function handleRefresh() {\n onMutated?.()\n void reload()\n }\n window.addEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n return () => window.removeEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n }, [onMutated, reload])\n\n const handleReopen = React.useCallback(async () => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.drawer.reopen.confirm', { fallback: 'Reopen this submission?' }),\n description: t('forms.drawer.reopen.body', {\n fallback: 'Reopening allows further edits and creates a new revision when changes are saved.',\n }),\n })\n if (!ok) return\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/reopen`,\n { method: 'POST' },\n ),\n context: { submissionId: detail.submission.id },\n mutationPayload: { submissionId: detail.submission.id },\n })\n if (!resp.ok) {\n flash(t('forms.drawer.reopen.failed'), 'error')\n return\n }\n flash(t('forms.drawer.reopen.success'), 'success')\n onMutated?.()\n void reload()\n }, [confirm, detail, onMutated, reload, runMutation, t])\n\n const handleRevokeActor = React.useCallback(\n async (actor: DrawerActor) => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.actor.revoke.confirm', { fallback: 'Revoke this actor?' }),\n variant: 'destructive',\n })\n if (!ok) return\n // optimistic-lock-exempt: assignment row. `forms_form_submission_actor` is\n // a junction between a submission and a user; revoking stamps `revoked_at`\n // and is idempotent, so there is no user-editable field two actors can\n // clobber. Junction/assignment tables are exempt from the `updated_at`\n // requirement per packages/core/AGENTS.md \u00A7 Database Entities.\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/actors/${encodeURIComponent(actor.id)}`,\n { method: 'DELETE' },\n ),\n context: { submissionId: detail.submission.id, actorId: actor.id },\n mutationPayload: { submissionId: detail.submission.id, actorId: actor.id },\n })\n if (!resp.ok) {\n flash(t('forms.actor.revoke.failed'), 'error')\n return\n }\n flash(t('forms.actor.revoke.success'), 'success')\n onMutated?.()\n void reload()\n },\n [confirm, detail, onMutated, reload, runMutation, t],\n )\n\n if (isLoading) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.loading', { fallback: 'Loading\u2026' })}>\n <div className=\"flex items-center justify-center py-12\">\n <Spinner className=\"h-5 w-5\" />\n </div>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n if (error || !detail) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.error', { fallback: 'Error' })}>\n <Alert status=\"error\" style=\"light\">\n <AlertDescription>{error ?? t('forms.drawer.not_available')}</AlertDescription>\n </Alert>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n const isAnonymized = !!detail.submission.anonymizedAt\n const canReopen = detail.submission.status === 'submitted' && !isAnonymized\n const activeRevision = revisions.find((rev) => rev.id === activeRevisionId) ?? detail.revision\n const isViewingLatest = activeRevisionId === detail.revision.id\n const injectionContext: DrawerInjectionContext = {\n submissionId: detail.submission.id,\n formId,\n status: detail.submission.status,\n isAnonymized,\n retryLastMutation,\n }\n\n return (\n <DrawerShell\n ref={triggerRef}\n onClose={onClose}\n title={`${t('forms.drawer.title', { fallback: 'Submission' })} \u2014 v${detail.submission.formVersionNumber}`}\n headerExtras={\n <div className=\"flex items-center gap-2\">\n <Tag variant={isAnonymized ? 'error' : 'success'} dot>\n {detail.submission.status}\n </Tag>\n {canReopen ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleReopen}>\n {t('forms.drawer.actions.reopen', { fallback: 'Reopen' })}\n </Button>\n ) : null}\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT}\n context={injectionContext}\n />\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT}\n context={injectionContext}\n />\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-[200px_1fr_240px]\">\n <RevisionTimeline\n revisions={revisions}\n activeRevisionId={activeRevisionId ?? null}\n onSelect={setActiveRevisionId}\n />\n\n <section className=\"flex flex-col gap-3\">\n {!isViewingLatest ? (\n <div className=\"flex items-center justify-between rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n <span>\n {t('forms.drawer.replay_footer', {\n fallback: 'Viewing as of rev {n}',\n n: String(activeRevision.revisionNumber),\n })}\n </span>\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n onClick={() => setActiveRevisionId(detail.revision.id)}\n >\n {t('forms.drawer.jump_to_latest', { fallback: 'Jump to latest' })}\n </Button>\n </div>\n ) : null}\n\n {activeRevision.anonymizedAt ? (\n <Alert status=\"information\" style=\"light\">\n <AlertDescription>\n {t('forms.drawer.anonymized_revision', {\n fallback: 'This revision has been anonymized \u2014 content unavailable.',\n })}\n </AlertDescription>\n </Alert>\n ) : (\n <DataReadOnly data={detail.decoded_data} />\n )}\n\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT}\n context={injectionContext}\n />\n </section>\n\n <ActorPanel\n actors={detail.actors.filter((actor) => !actor.revokedAt)}\n formVersionRoles={formVersionRoles}\n submissionId={detail.submission.id}\n onAssigned={() => {\n setActorOpen(false)\n onMutated?.()\n void reload()\n }}\n onRevoke={handleRevokeActor}\n isOpen={actorOpen}\n onOpen={() => setActorOpen(true)}\n onClose={() => setActorOpen(false)}\n />\n </div>\n\n <footer className=\"mt-4 border-t border-border pt-3 text-xs text-muted-foreground\">\n <p>\n {t('forms.drawer.audit_note', {\n fallback: 'This view is being written to the access audit log \u2014 purpose: view.',\n })}\n </p>\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_FOOTER_SPOT}\n context={injectionContext}\n />\n </footer>\n\n {ConfirmDialogElement}\n </DrawerShell>\n )\n}\n\nconst DrawerShell = React.forwardRef<HTMLDivElement, {\n onClose: () => void\n title: string\n headerExtras?: React.ReactNode\n children: React.ReactNode\n}>(function DrawerShell({ onClose, title, headerExtras, children }, ref) {\n const t = useT()\n return (\n <Drawer open onOpenChange={(open) => { if (!open) onClose() }}>\n <DrawerContent\n ref={ref}\n className=\"max-w-3xl\"\n closeAriaLabel={t('forms.drawer.actions.close')}\n >\n <DrawerHeader className=\"items-center\">\n <DrawerTitle>{title}</DrawerTitle>\n {headerExtras ? <div className=\"flex items-center gap-2\">{headerExtras}</div> : null}\n </DrawerHeader>\n <DrawerBody>{children}</DrawerBody>\n </DrawerContent>\n </Drawer>\n )\n})\n\nfunction RevisionTimeline({\n revisions,\n activeRevisionId,\n onSelect,\n}: {\n revisions: DrawerRevision[]\n activeRevisionId: string | null\n onSelect: (id: string) => void\n}) {\n const t = useT()\n return (\n <nav\n aria-label={t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n className=\"flex flex-col gap-2\"\n >\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n </h3>\n <ol className=\"flex flex-col gap-1\">\n {revisions.map((rev) => {\n const tone = colorForSavedByRole(rev.savedByRole)\n const active = rev.id === activeRevisionId\n return (\n <li key={rev.id}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSelect(rev.id)}\n className={`h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-xs ${\n active\n ? 'border-primary bg-primary/10'\n : 'border-border hover:bg-muted'\n }`}\n aria-current={active ? 'true' : undefined}\n >\n <span\n aria-hidden=\"true\"\n className={`h-2 w-2 rounded-full ${\n tone === 'primary'\n ? 'bg-primary'\n : tone === 'success'\n ? 'bg-status-success-solid'\n : tone === 'info'\n ? 'bg-status-info-solid'\n : tone === 'warning'\n ? 'bg-status-warning-solid'\n : 'bg-muted-foreground'\n }`}\n />\n <span className=\"font-mono\">#{rev.revisionNumber}</span>\n <span className=\"truncate text-muted-foreground\">\n {rev.savedByRole ?? 'system'}\n </span>\n {rev.anonymizedAt ? (\n <Lock className=\"ml-auto h-3 w-3 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n </Button>\n </li>\n )\n })}\n </ol>\n </nav>\n )\n}\n\nfunction DataReadOnly({ data }: { data: Record<string, unknown> }) {\n const entries = Object.entries(data)\n const t = useT()\n if (entries.length === 0) {\n return (\n <p className=\"rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground\">\n {t('forms.drawer.body.empty', { fallback: 'No answers recorded yet.' })}\n </p>\n )\n }\n return (\n <dl className=\"grid grid-cols-1 gap-2 rounded-md border border-border bg-card p-3 text-sm\">\n {entries.map(([key, value]) => (\n <div key={key} className=\"grid grid-cols-1 gap-1 sm:grid-cols-3 sm:items-baseline\">\n <dt className=\"font-mono text-xs text-muted-foreground\">{key}</dt>\n <dd className=\"col-span-1 text-foreground sm:col-span-2\">\n {typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value)\n : JSON.stringify(value)}\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n\nfunction ActorPanel({\n actors,\n formVersionRoles,\n submissionId,\n isOpen,\n onOpen,\n onClose,\n onAssigned,\n onRevoke,\n}: {\n actors: DrawerActor[]\n formVersionRoles: string[]\n submissionId: string\n isOpen: boolean\n onOpen: () => void\n onClose: () => void\n onAssigned: () => void\n onRevoke: (actor: DrawerActor) => void\n}) {\n const t = useT()\n const { runMutation } = useGuardedMutation({ contextId: 'forms.submission.actor.assign' })\n const [userId, setUserId] = React.useState('')\n const [role, setRole] = React.useState(formVersionRoles[0] ?? '')\n const [submitting, setSubmitting] = React.useState(false)\n\n const handleAssign = React.useCallback(async () => {\n if (!userId || !role) return\n setSubmitting(true)\n try {\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/actors`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ user_id: userId, role }),\n },\n ),\n context: { submissionId, userId, role },\n mutationPayload: { submissionId, userId, role },\n })\n if (!resp.ok) {\n flash(t('forms.actor.assign.failed'), 'error')\n return\n }\n flash(t('forms.actor.assign.success'), 'success')\n setUserId('')\n onAssigned()\n } finally {\n setSubmitting(false)\n }\n }, [onAssigned, role, runMutation, submissionId, t, userId])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleAssign()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n onClose()\n }\n },\n [handleAssign, onClose],\n )\n\n return (\n <aside\n aria-label={t('forms.actor.panel.title', { fallback: 'Actors' })}\n className=\"flex flex-col gap-2\"\n >\n <header className=\"flex items-center justify-between\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.actor.panel.title', { fallback: 'Actors' })}\n </h3>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onOpen}>\n {t('forms.actor.assign', { fallback: 'Assign' })}\n </Button>\n </header>\n <ul className=\"flex flex-col gap-1\">\n {actors.map((actor) => (\n <li\n key={actor.id}\n className=\"flex items-center justify-between rounded-md border border-border bg-card px-2 py-1.5 text-xs\"\n >\n <div className=\"flex flex-col\">\n <span className=\"font-mono text-muted-foreground\">{actor.userId.slice(0, 8)}\u2026</span>\n <Tag variant=\"info\" dot>\n {actor.role}\n </Tag>\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={t('forms.actor.revoke', { fallback: 'Revoke actor' })}\n onClick={() => onRevoke(actor)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" aria-hidden=\"true\" />\n </IconButton>\n </li>\n ))}\n </ul>\n {isOpen ? (\n <div\n className=\"rounded-md border border-border bg-muted/30 p-3\"\n onKeyDown={onKeyDown}\n >\n <FormField label={t('forms.actor.user_id', { fallback: 'User ID' })} required>\n <Input\n value={userId}\n onChange={(event) => setUserId(event.target.value)}\n placeholder=\"UUID\"\n />\n </FormField>\n <FormField label={t('forms.actor.role', { fallback: 'Role' })} required>\n <Select value={role} onValueChange={setRole}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {formVersionRoles.map((entry) => (\n <SelectItem key={entry} value={entry}>\n {entry}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={onClose}>\n {t('forms.actor.cancel', { fallback: 'Cancel' })}\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={submitting || !userId || !role} onClick={handleAssign}>\n {t('forms.actor.assign', { fallback: 'Assign' })}{' '}\n <ArrowRight className=\"ml-1 h-3 w-3\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n ) : null}\n </aside>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ArrowRight, History, Lock, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { apiCall } 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 { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/forms/extension-points'\nimport { colorForSavedByRole } from './RowBadges'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\n\nexport const FORMS_DRAWER_REFRESH_EVENT = 'forms:submission-drawer:refresh'\n\nexport const SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT = extensionPoints.hosts.submissionDrawerHeaderActions.spotId\nexport const SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT = extensionPoints.hosts.submissionDrawerAccessAudit.spotId\nexport const SUBMISSION_DRAWER_FOOTER_SPOT = extensionPoints.hosts.submissionDrawerFooter.spotId\nexport const SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT = extensionPoints.hosts.submissionDrawerAnonymizeAction.spotId\n\nexport type DrawerSubmission = {\n id: string\n status: 'draft' | 'submitted' | 'reopened' | 'archived' | 'anonymized'\n formVersionNumber?: number\n subjectType: string\n subjectId: string\n anonymizedAt: string | null\n pdfSnapshotAttachmentId: string | null\n}\n\nexport type DrawerRevision = {\n id: string\n revisionNumber: number\n savedAt: string | null\n savedBy: string\n savedByRole: string | null\n changedFieldKeys: string[]\n anonymizedAt: string | null\n}\n\nexport type DrawerActor = {\n id: string\n userId: string\n role: string\n assignedAt: string | null\n revokedAt: string | null\n}\n\nexport type DrawerSubmissionDetail = {\n submission: DrawerSubmission\n revision: DrawerRevision\n decoded_data: Record<string, unknown>\n actors: DrawerActor[]\n formVersion?: { id: string; versionNumber: number; roles: string[] }\n}\n\nexport type DrawerInjectionContext = {\n submissionId: string\n formId: string\n status: DrawerSubmission['status']\n isAnonymized: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nexport type SubmissionDrawerProps = {\n formId: string\n submissionId: string\n formVersionRoles: string[]\n onClose: () => void\n onMutated?: () => void\n}\n\nexport function SubmissionDrawer({\n formId,\n submissionId,\n formVersionRoles,\n onClose,\n onMutated,\n}: SubmissionDrawerProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({ contextId: 'forms.submission.drawer' })\n const [detail, setDetail] = React.useState<DrawerSubmissionDetail | null>(null)\n const [revisions, setRevisions] = React.useState<DrawerRevision[]>([])\n const [activeRevisionId, setActiveRevisionId] = React.useState<string | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [actorOpen, setActorOpen] = React.useState(false)\n const triggerRef = React.useRef<HTMLDivElement | null>(null)\n\n const reload = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n try {\n const [detailResp, revisionsResp] = await Promise.all([\n apiCall<DrawerSubmissionDetail>(`/api/forms/submissions/${encodeURIComponent(submissionId)}`),\n apiCall<{ revisions?: DrawerRevision[] }>(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/revisions`,\n ),\n ])\n if (!detailResp.ok || !detailResp.result) {\n throw new Error(t('forms.errors.submission_not_found'))\n }\n if (!revisionsResp.ok || !revisionsResp.result) {\n throw new Error(t('forms.errors.internal'))\n }\n setDetail(detailResp.result)\n setRevisions(revisionsResp.result.revisions ?? [])\n setActiveRevisionId(detailResp.result.revision.id)\n } catch (err) {\n setError(err instanceof Error ? err.message : t('forms.errors.internal'))\n } finally {\n setIsLoading(false)\n }\n }, [submissionId, t])\n\n React.useEffect(() => {\n void reload()\n }, [reload])\n\n React.useEffect(() => {\n function handleRefresh() {\n onMutated?.()\n void reload()\n }\n window.addEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n return () => window.removeEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n }, [onMutated, reload])\n\n const handleReopen = React.useCallback(async () => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.drawer.reopen.confirm', { fallback: 'Reopen this submission?' }),\n description: t('forms.drawer.reopen.body', {\n fallback: 'Reopening allows further edits and creates a new revision when changes are saved.',\n }),\n })\n if (!ok) return\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/reopen`,\n { method: 'POST' },\n ),\n context: { submissionId: detail.submission.id },\n mutationPayload: { submissionId: detail.submission.id },\n })\n if (!resp.ok) {\n flash(t('forms.drawer.reopen.failed'), 'error')\n return\n }\n flash(t('forms.drawer.reopen.success'), 'success')\n onMutated?.()\n void reload()\n }, [confirm, detail, onMutated, reload, runMutation, t])\n\n const handleRevokeActor = React.useCallback(\n async (actor: DrawerActor) => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.actor.revoke.confirm', { fallback: 'Revoke this actor?' }),\n variant: 'destructive',\n })\n if (!ok) return\n // optimistic-lock-exempt: assignment row. `forms_form_submission_actor` is\n // a junction between a submission and a user; revoking stamps `revoked_at`\n // and is idempotent, so there is no user-editable field two actors can\n // clobber. Junction/assignment tables are exempt from the `updated_at`\n // requirement per packages/core/AGENTS.md \u00A7 Database Entities.\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/actors/${encodeURIComponent(actor.id)}`,\n { method: 'DELETE' },\n ),\n context: { submissionId: detail.submission.id, actorId: actor.id },\n mutationPayload: { submissionId: detail.submission.id, actorId: actor.id },\n })\n if (!resp.ok) {\n flash(t('forms.actor.revoke.failed'), 'error')\n return\n }\n flash(t('forms.actor.revoke.success'), 'success')\n onMutated?.()\n void reload()\n },\n [confirm, detail, onMutated, reload, runMutation, t],\n )\n\n if (isLoading) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.loading', { fallback: 'Loading\u2026' })}>\n <div className=\"flex items-center justify-center py-12\">\n <Spinner className=\"h-5 w-5\" />\n </div>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n if (error || !detail) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.error', { fallback: 'Error' })}>\n <Alert status=\"error\" style=\"light\">\n <AlertDescription>{error ?? t('forms.drawer.not_available')}</AlertDescription>\n </Alert>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n const isAnonymized = !!detail.submission.anonymizedAt\n const canReopen = detail.submission.status === 'submitted' && !isAnonymized\n const activeRevision = revisions.find((rev) => rev.id === activeRevisionId) ?? detail.revision\n const isViewingLatest = activeRevisionId === detail.revision.id\n const injectionContext: DrawerInjectionContext = {\n submissionId: detail.submission.id,\n formId,\n status: detail.submission.status,\n isAnonymized,\n retryLastMutation,\n }\n\n return (\n <DrawerShell\n ref={triggerRef}\n onClose={onClose}\n title={detail.formVersion ? `${t('forms.drawer.title', { fallback: 'Submission' })} \u2014 v${detail.formVersion.versionNumber}` : t('forms.drawer.title', { fallback: 'Submission' })}\n headerExtras={\n <div className=\"flex items-center gap-2\">\n <Tag variant={isAnonymized ? 'error' : 'success'} dot>\n {detail.submission.status}\n </Tag>\n {canReopen ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleReopen}>\n {t('forms.drawer.actions.reopen', { fallback: 'Reopen' })}\n </Button>\n ) : null}\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT}\n context={injectionContext}\n />\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT}\n context={injectionContext}\n />\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-[200px_1fr_240px]\">\n <RevisionTimeline\n revisions={revisions}\n activeRevisionId={activeRevisionId ?? null}\n onSelect={setActiveRevisionId}\n />\n\n <section className=\"flex flex-col gap-3\">\n {!isViewingLatest ? (\n <div className=\"flex items-center justify-between rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n <span>\n {t('forms.drawer.replay_footer', {\n fallback: 'Viewing as of rev {n}',\n n: String(activeRevision.revisionNumber),\n })}\n </span>\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n onClick={() => setActiveRevisionId(detail.revision.id)}\n >\n {t('forms.drawer.jump_to_latest', { fallback: 'Jump to latest' })}\n </Button>\n </div>\n ) : null}\n\n {activeRevision.anonymizedAt ? (\n <Alert status=\"information\" style=\"light\">\n <AlertDescription>\n {t('forms.drawer.anonymized_revision', {\n fallback: 'This revision has been anonymized \u2014 content unavailable.',\n })}\n </AlertDescription>\n </Alert>\n ) : (\n <DataReadOnly data={detail.decoded_data} />\n )}\n\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT}\n context={injectionContext}\n />\n </section>\n\n <ActorPanel\n actors={detail.actors.filter((actor) => !actor.revokedAt)}\n formVersionRoles={detail.formVersion?.roles?.length ? detail.formVersion.roles : formVersionRoles}\n submissionId={detail.submission.id}\n onAssigned={() => {\n setActorOpen(false)\n onMutated?.()\n void reload()\n }}\n onRevoke={handleRevokeActor}\n isOpen={actorOpen}\n onOpen={() => setActorOpen(true)}\n onClose={() => setActorOpen(false)}\n />\n </div>\n\n <footer className=\"mt-4 border-t border-border pt-3 text-xs text-muted-foreground\">\n <p>\n {t('forms.drawer.audit_note', {\n fallback: 'This view is being written to the access audit log \u2014 purpose: view.',\n })}\n </p>\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_FOOTER_SPOT}\n context={injectionContext}\n />\n </footer>\n\n {ConfirmDialogElement}\n </DrawerShell>\n )\n}\n\nconst DrawerShell = React.forwardRef<HTMLDivElement, {\n onClose: () => void\n title: string\n headerExtras?: React.ReactNode\n children: React.ReactNode\n}>(function DrawerShell({ onClose, title, headerExtras, children }, ref) {\n const t = useT()\n return (\n <Drawer open onOpenChange={(open) => { if (!open) onClose() }}>\n <DrawerContent\n ref={ref}\n className=\"max-w-3xl\"\n closeAriaLabel={t('forms.drawer.actions.close')}\n >\n <DrawerHeader className=\"items-center\">\n <DrawerTitle>{title}</DrawerTitle>\n {headerExtras ? <div className=\"flex items-center gap-2\">{headerExtras}</div> : null}\n </DrawerHeader>\n <DrawerBody>{children}</DrawerBody>\n </DrawerContent>\n </Drawer>\n )\n})\n\nfunction RevisionTimeline({\n revisions,\n activeRevisionId,\n onSelect,\n}: {\n revisions: DrawerRevision[]\n activeRevisionId: string | null\n onSelect: (id: string) => void\n}) {\n const t = useT()\n return (\n <nav\n aria-label={t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n className=\"flex flex-col gap-2\"\n >\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n </h3>\n <ol className=\"flex flex-col gap-1\">\n {revisions.map((rev) => {\n const tone = colorForSavedByRole(rev.savedByRole)\n const active = rev.id === activeRevisionId\n return (\n <li key={rev.id}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSelect(rev.id)}\n className={`h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-xs ${\n active\n ? 'border-primary bg-primary/10'\n : 'border-border hover:bg-muted'\n }`}\n aria-current={active ? 'true' : undefined}\n >\n <span\n aria-hidden=\"true\"\n className={`h-2 w-2 rounded-full ${\n tone === 'primary'\n ? 'bg-primary'\n : tone === 'success'\n ? 'bg-status-success-solid'\n : tone === 'info'\n ? 'bg-status-info-solid'\n : tone === 'warning'\n ? 'bg-status-warning-solid'\n : 'bg-muted-foreground'\n }`}\n />\n <span className=\"font-mono\">#{rev.revisionNumber}</span>\n <span className=\"truncate text-muted-foreground\">\n {rev.savedByRole ?? 'system'}\n </span>\n {rev.anonymizedAt ? (\n <Lock className=\"ml-auto h-3 w-3 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n </Button>\n </li>\n )\n })}\n </ol>\n </nav>\n )\n}\n\nfunction DataReadOnly({ data }: { data: Record<string, unknown> }) {\n const entries = Object.entries(data)\n const t = useT()\n if (entries.length === 0) {\n return (\n <p className=\"rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground\">\n {t('forms.drawer.body.empty', { fallback: 'No answers recorded yet.' })}\n </p>\n )\n }\n return (\n <dl className=\"grid grid-cols-1 gap-2 rounded-md border border-border bg-card p-3 text-sm\">\n {entries.map(([key, value]) => (\n <div key={key} className=\"grid grid-cols-1 gap-1 sm:grid-cols-3 sm:items-baseline\">\n <dt className=\"font-mono text-xs text-muted-foreground\">{key}</dt>\n <dd className=\"col-span-1 text-foreground sm:col-span-2\">\n {typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value)\n : JSON.stringify(value)}\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n\nfunction ActorPanel({\n actors,\n formVersionRoles,\n submissionId,\n isOpen,\n onOpen,\n onClose,\n onAssigned,\n onRevoke,\n}: {\n actors: DrawerActor[]\n formVersionRoles: string[]\n submissionId: string\n isOpen: boolean\n onOpen: () => void\n onClose: () => void\n onAssigned: () => void\n onRevoke: (actor: DrawerActor) => void\n}) {\n const t = useT()\n const { runMutation } = useGuardedMutation({ contextId: 'forms.submission.actor.assign' })\n const [userId, setUserId] = React.useState('')\n const [role, setRole] = React.useState(formVersionRoles[0] ?? '')\n const [submitting, setSubmitting] = React.useState(false)\n\n const handleAssign = React.useCallback(async () => {\n if (!userId || !role) return\n setSubmitting(true)\n try {\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/actors`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ user_id: userId, role }),\n },\n ),\n context: { submissionId, userId, role },\n mutationPayload: { submissionId, userId, role },\n })\n if (!resp.ok) {\n flash(t('forms.actor.assign.failed'), 'error')\n return\n }\n flash(t('forms.actor.assign.success'), 'success')\n setUserId('')\n onAssigned()\n } finally {\n setSubmitting(false)\n }\n }, [onAssigned, role, runMutation, submissionId, t, userId])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleAssign()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n onClose()\n }\n },\n [handleAssign, onClose],\n )\n\n return (\n <aside\n aria-label={t('forms.actor.panel.title', { fallback: 'Actors' })}\n className=\"flex flex-col gap-2\"\n >\n <header className=\"flex items-center justify-between\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.actor.panel.title', { fallback: 'Actors' })}\n </h3>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onOpen}>\n {t('forms.actor.assign', { fallback: 'Assign' })}\n </Button>\n </header>\n <ul className=\"flex flex-col gap-1\">\n {actors.map((actor) => (\n <li\n key={actor.id}\n className=\"flex items-center justify-between rounded-md border border-border bg-card px-2 py-1.5 text-xs\"\n >\n <div className=\"flex flex-col\">\n <span className=\"font-mono text-muted-foreground\">{actor.userId.slice(0, 8)}\u2026</span>\n <Tag variant=\"info\" dot>\n {actor.role}\n </Tag>\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={t('forms.actor.revoke', { fallback: 'Revoke actor' })}\n onClick={() => onRevoke(actor)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" aria-hidden=\"true\" />\n </IconButton>\n </li>\n ))}\n </ul>\n {isOpen ? (\n <div\n className=\"rounded-md border border-border bg-muted/30 p-3\"\n onKeyDown={onKeyDown}\n >\n <FormField label={t('forms.actor.user_id', { fallback: 'User ID' })} required>\n <Input\n value={userId}\n onChange={(event) => setUserId(event.target.value)}\n placeholder=\"UUID\"\n />\n </FormField>\n <FormField label={t('forms.actor.role', { fallback: 'Role' })} required>\n <Select value={role} onValueChange={setRole}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {formVersionRoles.map((entry) => (\n <SelectItem key={entry} value={entry}>\n {entry}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={onClose}>\n {t('forms.actor.cancel', { fallback: 'Cancel' })}\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={submitting || !userId || !role} onClick={handleAssign}>\n {t('forms.actor.assign', { fallback: 'Assign' })}{' '}\n <ArrowRight className=\"ml-1 h-3 w-3\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n ) : null}\n </aside>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["DrawerShell"]
7
7
  }
@@ -0,0 +1,9 @@
1
+ function resolvePublishedVersionRoles(summary) {
2
+ if (!summary?.versions || !summary.currentPublishedVersionId) return [];
3
+ const published = summary.versions.find((version) => version.id === summary.currentPublishedVersionId);
4
+ return Array.isArray(published?.roles) ? published.roles : [];
5
+ }
6
+ export {
7
+ resolvePublishedVersionRoles
8
+ };
9
+ //# sourceMappingURL=formSummary.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/forms/backend/forms/%5Bid%5D/submissions/components/formSummary.ts"],
4
+ "sourcesContent": ["export type FormSummaryVersion = {\n id: string\n status: string\n versionNumber: number\n roles?: string[]\n}\n\nexport type FormSummaryResponse = {\n id: string\n name: string\n currentPublishedVersionId: string | null\n versions?: FormSummaryVersion[]\n}\n\nexport function resolvePublishedVersionRoles(summary: FormSummaryResponse | null | undefined): string[] {\n if (!summary?.versions || !summary.currentPublishedVersionId) return []\n const published = summary.versions.find((version) => version.id === summary.currentPublishedVersionId)\n return Array.isArray(published?.roles) ? published.roles : []\n}\n"],
5
+ "mappings": "AAcO,SAAS,6BAA6B,SAA2D;AACtG,MAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,0BAA2B,QAAO,CAAC;AACtE,QAAM,YAAY,QAAQ,SAAS,KAAK,CAAC,YAAY,QAAQ,OAAO,QAAQ,yBAAyB;AACrG,SAAO,MAAM,QAAQ,WAAW,KAAK,IAAI,UAAU,QAAQ,CAAC;AAC9D;",
6
+ "names": []
7
+ }
@@ -14,6 +14,7 @@ import {
14
14
  ROW_BADGE_TONE
15
15
  } from "./components/RowBadges.js";
16
16
  import { SubmissionDrawer } from "./components/SubmissionDrawer.js";
17
+ import { resolvePublishedVersionRoles } from "./components/formSummary.js";
17
18
  function formatDate(value) {
18
19
  if (!value) return "";
19
20
  const date = new Date(value);
@@ -96,11 +97,7 @@ function FormSubmissionInboxPage({ params }) {
96
97
  };
97
98
  }, [filterValues, formId, page, reloadToken, scopeVersion, search, t]);
98
99
  const reload = React.useCallback(() => setReloadToken((token) => token + 1), []);
99
- const formVersionRoles = React.useMemo(() => {
100
- if (!formSummary?.versions) return [];
101
- const published = formSummary.versions.find((v) => v.id === formSummary.form.currentPublishedVersionId);
102
- return published?.roles ?? [];
103
- }, [formSummary]);
100
+ const formVersionRoles = React.useMemo(() => resolvePublishedVersionRoles(formSummary), [formSummary]);
104
101
  const columns = React.useMemo(
105
102
  () => [
106
103
  {
@@ -168,7 +165,7 @@ function FormSubmissionInboxPage({ params }) {
168
165
  /* @__PURE__ */ jsx(
169
166
  DataTable,
170
167
  {
171
- title: formSummary?.form?.name ? `${formSummary.form.name} \u2014 ${t("forms.inbox.title", { fallback: "Submissions" })}` : t("forms.inbox.title", { fallback: "Submissions" }),
168
+ title: formSummary?.name ? `${formSummary.name} \u2014 ${t("forms.inbox.title", { fallback: "Submissions" })}` : t("forms.inbox.title", { fallback: "Submissions" }),
172
169
  columns,
173
170
  data: rows,
174
171
  isLoading,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/forms/backend/forms/%5Bid%5D/submissions/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { File as FileIcon, Lock, Users } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport {\n fromSubmission,\n ROW_BADGE_TONE,\n type SubmissionInboxRow,\n} from './components/RowBadges'\nimport { SubmissionDrawer } from './components/SubmissionDrawer'\n\ntype InboxResponse = {\n items: Array<SubmissionInboxRow & {\n subjectType: string\n subjectId: string\n submittedAt: string | null\n updatedAt: string\n locale: string | null\n }>\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\ntype FormSummaryResponse = {\n form: {\n id: string\n name: string\n currentPublishedVersionId: string | null\n }\n versions?: Array<{ id: string; status: string; roles: string[]; versionNumber: number }>\n}\n\nfunction formatDate(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString()\n}\n\nexport default function FormSubmissionInboxPage({ params }: { params?: { id?: string } }) {\n const formId = params?.id ?? ''\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [rows, setRows] = React.useState<InboxResponse['items']>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [openSubmissionId, setOpenSubmissionId] = React.useState<string | null>(null)\n const [formSummary, setFormSummary] = React.useState<FormSummaryResponse | null>(null)\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('forms.inbox.filter.status', { fallback: 'Status' }),\n type: 'select',\n options: [\n { value: 'draft', label: t('forms.submission.status.draft', { fallback: 'Draft' }) },\n { value: 'submitted', label: t('forms.submission.status.submitted', { fallback: 'Submitted' }) },\n { value: 'reopened', label: t('forms.submission.status.reopened', { fallback: 'Reopened' }) },\n { value: 'archived', label: t('forms.submission.status.archived', { fallback: 'Archived' }) },\n { value: 'anonymized', label: t('forms.submission.status.anonymized', { fallback: 'Anonymized' }) },\n ],\n multi: true,\n },\n ],\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search.trim()) params.set('q', search.trim())\n const statusValue = filterValues.status\n if (Array.isArray(statusValue) && statusValue.length > 0) {\n params.set('status', statusValue.join(','))\n } else if (typeof statusValue === 'string' && statusValue) {\n params.set('status', statusValue)\n }\n try {\n const [listResp, summaryResp] = await Promise.all([\n apiCall<InboxResponse>(`/api/forms/${encodeURIComponent(formId)}/submissions?${params.toString()}`),\n apiCall<FormSummaryResponse>(`/api/forms/${encodeURIComponent(formId)}`),\n ])\n if (cancelled) return\n if (!listResp.ok || !listResp.result) {\n flash(t('forms.errors.internal'), 'error')\n return\n }\n setRows(listResp.result.items)\n setTotal(listResp.result.total)\n setTotalPages(listResp.result.totalPages || 1)\n if (summaryResp.ok && summaryResp.result) {\n setFormSummary(summaryResp.result)\n }\n } catch {\n if (!cancelled) {\n flash(t('forms.errors.internal'), 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [filterValues, formId, page, reloadToken, scopeVersion, search, t])\n\n const reload = React.useCallback(() => setReloadToken((token) => token + 1), [])\n\n const formVersionRoles = React.useMemo(() => {\n if (!formSummary?.versions) return []\n const published = formSummary.versions.find((v) => v.id === formSummary.form.currentPublishedVersionId)\n return published?.roles ?? []\n }, [formSummary])\n\n const columns = React.useMemo<ColumnDef<InboxResponse['items'][number]>[]>(\n () => [\n {\n header: t('forms.inbox.columns.subject', { fallback: 'Subject' }),\n accessorKey: 'subjectId',\n cell: ({ row }) => {\n const isAnonymized = !!row.original.anonymizedAt\n return (\n <span className={`font-mono text-xs ${isAnonymized ? 'italic text-muted-foreground' : 'text-foreground'}`}>\n {row.original.subjectType}: {row.original.subjectId.slice(0, 8)}\u2026\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n header: t('forms.inbox.columns.status', { fallback: 'Status' }),\n accessorKey: 'status',\n cell: ({ row }) => {\n const badges = fromSubmission(row.original)\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {badges.map((badge, index) => {\n if (badge.kind === 'status') {\n return (\n <Tag key={`status-${index}`} variant={ROW_BADGE_TONE[badge.status]} dot>\n {t(`forms.submission.status.${badge.status}`, { fallback: badge.status })}\n </Tag>\n )\n }\n if (badge.kind === 'version') {\n return (\n <Tag key={`version-${index}`} variant=\"neutral\">\n v{badge.versionNumber}\n </Tag>\n )\n }\n if (badge.kind === 'revision_count') {\n return (\n <Tag key={`rev-${index}`} variant=\"neutral\">\n {badge.count} rev\n </Tag>\n )\n }\n if (badge.kind === 'multi_role') {\n return (\n <Tag key={`multirole-${index}`} variant=\"info\" aria-label={t('forms.inbox.badges.multiple_roles')}>\n <Users className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'pdf_available') {\n return (\n <Tag key={`pdf-${index}`} variant=\"success\" aria-label={t('forms.inbox.badges.pdf_available')}>\n <FileIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'anonymized') {\n return (\n <Tag key={`anon-${index}`} variant=\"error\" aria-label={t('forms.submission.status.anonymized')}>\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n return null\n })}\n </div>\n )\n },\n },\n {\n header: t('forms.inbox.columns.submitted_at', { fallback: 'Submitted' }),\n accessorKey: 'submittedAt',\n cell: ({ row }) => formatDate(row.original.submittedAt) || '\u2014',\n },\n {\n header: t('forms.inbox.columns.updated_at', { fallback: 'Updated' }),\n accessorKey: 'updatedAt',\n cell: ({ row }) => formatDate(row.original.updatedAt),\n },\n ],\n [t],\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={\n formSummary?.form?.name\n ? `${formSummary.form.name} \u2014 ${t('forms.inbox.title', { fallback: 'Submissions' })}`\n : t('forms.inbox.title', { fallback: 'Submissions' })\n }\n columns={columns}\n data={rows}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder={t('forms.inbox.search.placeholder', { fallback: 'Search by subject id' })}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={setFilterValues}\n onFiltersClear={() => setFilterValues({})}\n pagination={{\n page,\n pageSize: 20,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n onRowClick={(row) => setOpenSubmissionId(row.id)}\n emptyState={t('forms.inbox.empty', { fallback: 'No submissions yet.' })}\n />\n {openSubmissionId ? (\n <SubmissionDrawer\n formId={formId}\n submissionId={openSubmissionId}\n formVersionRoles={formVersionRoles}\n onClose={() => setOpenSubmissionId(null)}\n onMutated={reload}\n />\n ) : null}\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { File as FileIcon, Lock, Users } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport {\n fromSubmission,\n ROW_BADGE_TONE,\n type SubmissionInboxRow,\n} from './components/RowBadges'\nimport { SubmissionDrawer } from './components/SubmissionDrawer'\nimport { resolvePublishedVersionRoles, type FormSummaryResponse } from './components/formSummary'\n\ntype InboxResponse = {\n items: Array<SubmissionInboxRow & {\n subjectType: string\n subjectId: string\n submittedAt: string | null\n updatedAt: string\n locale: string | null\n }>\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nfunction formatDate(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString()\n}\n\nexport default function FormSubmissionInboxPage({ params }: { params?: { id?: string } }) {\n const formId = params?.id ?? ''\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [rows, setRows] = React.useState<InboxResponse['items']>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [openSubmissionId, setOpenSubmissionId] = React.useState<string | null>(null)\n const [formSummary, setFormSummary] = React.useState<FormSummaryResponse | null>(null)\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('forms.inbox.filter.status', { fallback: 'Status' }),\n type: 'select',\n options: [\n { value: 'draft', label: t('forms.submission.status.draft', { fallback: 'Draft' }) },\n { value: 'submitted', label: t('forms.submission.status.submitted', { fallback: 'Submitted' }) },\n { value: 'reopened', label: t('forms.submission.status.reopened', { fallback: 'Reopened' }) },\n { value: 'archived', label: t('forms.submission.status.archived', { fallback: 'Archived' }) },\n { value: 'anonymized', label: t('forms.submission.status.anonymized', { fallback: 'Anonymized' }) },\n ],\n multi: true,\n },\n ],\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search.trim()) params.set('q', search.trim())\n const statusValue = filterValues.status\n if (Array.isArray(statusValue) && statusValue.length > 0) {\n params.set('status', statusValue.join(','))\n } else if (typeof statusValue === 'string' && statusValue) {\n params.set('status', statusValue)\n }\n try {\n const [listResp, summaryResp] = await Promise.all([\n apiCall<InboxResponse>(`/api/forms/${encodeURIComponent(formId)}/submissions?${params.toString()}`),\n apiCall<FormSummaryResponse>(`/api/forms/${encodeURIComponent(formId)}`),\n ])\n if (cancelled) return\n if (!listResp.ok || !listResp.result) {\n flash(t('forms.errors.internal'), 'error')\n return\n }\n setRows(listResp.result.items)\n setTotal(listResp.result.total)\n setTotalPages(listResp.result.totalPages || 1)\n if (summaryResp.ok && summaryResp.result) {\n setFormSummary(summaryResp.result)\n }\n } catch {\n if (!cancelled) {\n flash(t('forms.errors.internal'), 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [filterValues, formId, page, reloadToken, scopeVersion, search, t])\n\n const reload = React.useCallback(() => setReloadToken((token) => token + 1), [])\n\n const formVersionRoles = React.useMemo(() => resolvePublishedVersionRoles(formSummary), [formSummary])\n\n const columns = React.useMemo<ColumnDef<InboxResponse['items'][number]>[]>(\n () => [\n {\n header: t('forms.inbox.columns.subject', { fallback: 'Subject' }),\n accessorKey: 'subjectId',\n cell: ({ row }) => {\n const isAnonymized = !!row.original.anonymizedAt\n return (\n <span className={`font-mono text-xs ${isAnonymized ? 'italic text-muted-foreground' : 'text-foreground'}`}>\n {row.original.subjectType}: {row.original.subjectId.slice(0, 8)}\u2026\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n header: t('forms.inbox.columns.status', { fallback: 'Status' }),\n accessorKey: 'status',\n cell: ({ row }) => {\n const badges = fromSubmission(row.original)\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {badges.map((badge, index) => {\n if (badge.kind === 'status') {\n return (\n <Tag key={`status-${index}`} variant={ROW_BADGE_TONE[badge.status]} dot>\n {t(`forms.submission.status.${badge.status}`, { fallback: badge.status })}\n </Tag>\n )\n }\n if (badge.kind === 'version') {\n return (\n <Tag key={`version-${index}`} variant=\"neutral\">\n v{badge.versionNumber}\n </Tag>\n )\n }\n if (badge.kind === 'revision_count') {\n return (\n <Tag key={`rev-${index}`} variant=\"neutral\">\n {badge.count} rev\n </Tag>\n )\n }\n if (badge.kind === 'multi_role') {\n return (\n <Tag key={`multirole-${index}`} variant=\"info\" aria-label={t('forms.inbox.badges.multiple_roles')}>\n <Users className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'pdf_available') {\n return (\n <Tag key={`pdf-${index}`} variant=\"success\" aria-label={t('forms.inbox.badges.pdf_available')}>\n <FileIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'anonymized') {\n return (\n <Tag key={`anon-${index}`} variant=\"error\" aria-label={t('forms.submission.status.anonymized')}>\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n return null\n })}\n </div>\n )\n },\n },\n {\n header: t('forms.inbox.columns.submitted_at', { fallback: 'Submitted' }),\n accessorKey: 'submittedAt',\n cell: ({ row }) => formatDate(row.original.submittedAt) || '\u2014',\n },\n {\n header: t('forms.inbox.columns.updated_at', { fallback: 'Updated' }),\n accessorKey: 'updatedAt',\n cell: ({ row }) => formatDate(row.original.updatedAt),\n },\n ],\n [t],\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={\n formSummary?.name\n ? `${formSummary.name} \u2014 ${t('forms.inbox.title', { fallback: 'Submissions' })}`\n : t('forms.inbox.title', { fallback: 'Submissions' })\n }\n columns={columns}\n data={rows}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder={t('forms.inbox.search.placeholder', { fallback: 'Search by subject id' })}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={setFilterValues}\n onFiltersClear={() => setFilterValues({})}\n pagination={{\n page,\n pageSize: 20,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n onRowClick={(row) => setOpenSubmissionId(row.id)}\n emptyState={t('forms.inbox.empty', { fallback: 'No submissions yet.' })}\n />\n {openSubmissionId ? (\n <SubmissionDrawer\n formId={formId}\n submissionId={openSubmissionId}\n formVersionRoles={formVersionRoles}\n onClose={() => setOpenSubmissionId(null)}\n onMutated={reload}\n />\n ) : null}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": ";AAqIY,SAiBQ,KAjBR;AAnIZ,YAAY,WAAW;AACvB,SAAS,QAAQ,UAAU,MAAM,aAAa;AAC9C,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAE1B,SAAS,WAAW;AACpB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,wBAAwB;AACjC,SAAS,oCAA8D;AAgBvE,SAAS,WAAW,OAA8B;AAChD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,SAAO,KAAK,eAAe;AAC7B;AAEe,SAAR,wBAAyC,EAAE,OAAO,GAAiC;AACxF,QAAM,SAAS,QAAQ,MAAM;AAC7B,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AAEjD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAiC,CAAC,CAAC;AACjE,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC;AAC1C,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAuB,CAAC,CAAC;AACvE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAwB,IAAI;AAClF,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAqC,IAAI;AAErF,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,6BAA6B,EAAE,UAAU,SAAS,CAAC;AAAA,QAC5D,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,SAAS,OAAO,EAAE,iCAAiC,EAAE,UAAU,QAAQ,CAAC,EAAE;AAAA,UACnF,EAAE,OAAO,aAAa,OAAO,EAAE,qCAAqC,EAAE,UAAU,YAAY,CAAC,EAAE;AAAA,UAC/F,EAAE,OAAO,YAAY,OAAO,EAAE,oCAAoC,EAAE,UAAU,WAAW,CAAC,EAAE;AAAA,UAC5F,EAAE,OAAO,YAAY,OAAO,EAAE,oCAAoC,EAAE,UAAU,WAAW,CAAC,EAAE;AAAA,UAC5F,EAAE,OAAO,cAAc,OAAO,EAAE,sCAAsC,EAAE,UAAU,aAAa,CAAC,EAAE;AAAA,QACpG;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,mBAAa,IAAI;AACjB,YAAMA,UAAS,IAAI,gBAAgB;AACnC,MAAAA,QAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,MAAAA,QAAO,IAAI,YAAY,IAAI;AAC3B,UAAI,OAAO,KAAK,EAAG,CAAAA,QAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAChD,YAAM,cAAc,aAAa;AACjC,UAAI,MAAM,QAAQ,WAAW,KAAK,YAAY,SAAS,GAAG;AACxD,QAAAA,QAAO,IAAI,UAAU,YAAY,KAAK,GAAG,CAAC;AAAA,MAC5C,WAAW,OAAO,gBAAgB,YAAY,aAAa;AACzD,QAAAA,QAAO,IAAI,UAAU,WAAW;AAAA,MAClC;AACA,UAAI;AACF,cAAM,CAAC,UAAU,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,UAChD,QAAuB,cAAc,mBAAmB,MAAM,CAAC,gBAAgBA,QAAO,SAAS,CAAC,EAAE;AAAA,UAClG,QAA6B,cAAc,mBAAmB,MAAM,CAAC,EAAE;AAAA,QACzE,CAAC;AACD,YAAI,UAAW;AACf,YAAI,CAAC,SAAS,MAAM,CAAC,SAAS,QAAQ;AACpC,gBAAM,EAAE,uBAAuB,GAAG,OAAO;AACzC;AAAA,QACF;AACA,gBAAQ,SAAS,OAAO,KAAK;AAC7B,iBAAS,SAAS,OAAO,KAAK;AAC9B,sBAAc,SAAS,OAAO,cAAc,CAAC;AAC7C,YAAI,YAAY,MAAM,YAAY,QAAQ;AACxC,yBAAe,YAAY,MAAM;AAAA,QACnC;AAAA,MACF,QAAQ;AACN,YAAI,CAAC,WAAW;AACd,gBAAM,EAAE,uBAAuB,GAAG,OAAO;AAAA,QAC3C;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,cAAa,KAAK;AAAA,MACpC;AAAA,IACF;AACA,SAAK,KAAK;AACV,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,cAAc,QAAQ,MAAM,aAAa,cAAc,QAAQ,CAAC,CAAC;AAErE,QAAM,SAAS,MAAM,YAAY,MAAM,eAAe,CAAC,UAAU,QAAQ,CAAC,GAAG,CAAC,CAAC;AAE/E,QAAM,mBAAmB,MAAM,QAAQ,MAAM,6BAA6B,WAAW,GAAG,CAAC,WAAW,CAAC;AAErG,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,QAAQ,EAAE,+BAA+B,EAAE,UAAU,UAAU,CAAC;AAAA,QAChE,aAAa;AAAA,QACb,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,eAAe,CAAC,CAAC,IAAI,SAAS;AACpC,iBACE,qBAAC,UAAK,WAAW,qBAAqB,eAAe,iCAAiC,iBAAiB,IACpG;AAAA,gBAAI,SAAS;AAAA,YAAY;AAAA,YAAG,IAAI,SAAS,UAAU,MAAM,GAAG,CAAC;AAAA,YAAE;AAAA,aAClE;AAAA,QAEJ;AAAA,QACA,MAAM,EAAE,UAAU,MAAM,UAAU,IAAI;AAAA,MACxC;AAAA,MACA;AAAA,QACE,QAAQ,EAAE,8BAA8B,EAAE,UAAU,SAAS,CAAC;AAAA,QAC9D,aAAa;AAAA,QACb,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,SAAS,eAAe,IAAI,QAAQ;AAC1C,iBACE,oBAAC,SAAI,WAAU,qCACZ,iBAAO,IAAI,CAAC,OAAO,UAAU;AAC5B,gBAAI,MAAM,SAAS,UAAU;AAC3B,qBACE,oBAAC,OAA4B,SAAS,eAAe,MAAM,MAAM,GAAG,KAAG,MACpE,YAAE,2BAA2B,MAAM,MAAM,IAAI,EAAE,UAAU,MAAM,OAAO,CAAC,KADhE,UAAU,KAAK,EAEzB;AAAA,YAEJ;AACA,gBAAI,MAAM,SAAS,WAAW;AAC5B,qBACE,qBAAC,OAA6B,SAAQ,WAAU;AAAA;AAAA,gBAC5C,MAAM;AAAA,mBADA,WAAW,KAAK,EAE1B;AAAA,YAEJ;AACA,gBAAI,MAAM,SAAS,kBAAkB;AACnC,qBACE,qBAAC,OAAyB,SAAQ,WAC/B;AAAA,sBAAM;AAAA,gBAAM;AAAA,mBADL,OAAO,KAAK,EAEtB;AAAA,YAEJ;AACA,gBAAI,MAAM,SAAS,cAAc;AAC/B,qBACE,oBAAC,OAA+B,SAAQ,QAAO,cAAY,EAAE,mCAAmC,GAC9F,8BAAC,SAAM,WAAU,WAAU,eAAY,QAAO,KADtC,aAAa,KAAK,EAE5B;AAAA,YAEJ;AACA,gBAAI,MAAM,SAAS,iBAAiB;AAClC,qBACE,oBAAC,OAAyB,SAAQ,WAAU,cAAY,EAAE,kCAAkC,GAC1F,8BAAC,YAAS,WAAU,WAAU,eAAY,QAAO,KADzC,OAAO,KAAK,EAEtB;AAAA,YAEJ;AACA,gBAAI,MAAM,SAAS,cAAc;AAC/B,qBACE,oBAAC,OAA0B,SAAQ,SAAQ,cAAY,EAAE,oCAAoC,GAC3F,8BAAC,QAAK,WAAU,WAAU,eAAY,QAAO,KADrC,QAAQ,KAAK,EAEvB;AAAA,YAEJ;AACA,mBAAO;AAAA,UACT,CAAC,GACH;AAAA,QAEJ;AAAA,MACF;AAAA,MACA;AAAA,QACE,QAAQ,EAAE,oCAAoC,EAAE,UAAU,YAAY,CAAC;AAAA,QACvE,aAAa;AAAA,QACb,MAAM,CAAC,EAAE,IAAI,MAAM,WAAW,IAAI,SAAS,WAAW,KAAK;AAAA,MAC7D;AAAA,MACA;AAAA,QACE,QAAQ,EAAE,kCAAkC,EAAE,UAAU,UAAU,CAAC;AAAA,QACnE,aAAa;AAAA,QACb,MAAM,CAAC,EAAE,IAAI,MAAM,WAAW,IAAI,SAAS,SAAS;AAAA,MACtD;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE,oBAAC,QACC,+BAAC,YACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OACE,aAAa,OACT,GAAG,YAAY,IAAI,WAAM,EAAE,qBAAqB,EAAE,UAAU,cAAc,CAAC,CAAC,KAC5E,EAAE,qBAAqB,EAAE,UAAU,cAAc,CAAC;AAAA,QAExD;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,mBAAmB,EAAE,kCAAkC,EAAE,UAAU,uBAAuB,CAAC;AAAA,QAC3F;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAChB,gBAAgB,MAAM,gBAAgB,CAAC,CAAC;AAAA,QACxC,YAAY;AAAA,UACV;AAAA,UACA,UAAU;AAAA,UACV;AAAA,UACA;AAAA,UACA,cAAc;AAAA,QAChB;AAAA,QACA,YAAY,CAAC,QAAQ,oBAAoB,IAAI,EAAE;AAAA,QAC/C,YAAY,EAAE,qBAAqB,EAAE,UAAU,sBAAsB,CAAC;AAAA;AAAA,IACxE;AAAA,IACC,mBACC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA,SAAS,MAAM,oBAAoB,IAAI;AAAA,QACvC,WAAW;AAAA;AAAA,IACb,IACE;AAAA,KACN,GACF;AAEJ;",
6
6
  "names": ["params"]
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { registerCommand } from "@open-mercato/shared/lib/commands";
2
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
2
3
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
4
  import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
4
5
  import { FormDistribution } from "../data/entities.js";
@@ -160,9 +161,10 @@ const createDistributionCommand = {
160
161
  return distribution ? serializeDistributionSnapshot(distribution) : null;
161
162
  },
162
163
  buildLog: async ({ result, snapshots }) => {
164
+ const { translate } = await resolveTranslations();
163
165
  const after = snapshots.after;
164
166
  return {
165
- actionLabel: "forms.audit.distribution.create",
167
+ actionLabel: translate("forms.audit.distribution.create", "Create distribution"),
166
168
  resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,
167
169
  resourceId: result.distributionId,
168
170
  tenantId: after?.tenantId ?? null,
@@ -284,10 +286,11 @@ const updateDistributionCommand = {
284
286
  return distribution ? serializeDistributionSnapshot(distribution) : null;
285
287
  },
286
288
  buildLog: async ({ result, snapshots }) => {
289
+ const { translate } = await resolveTranslations();
287
290
  const before = snapshots.before;
288
291
  const after = snapshots.after;
289
292
  return {
290
- actionLabel: "forms.audit.distribution.update",
293
+ actionLabel: translate("forms.audit.distribution.update", "Update distribution"),
291
294
  resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,
292
295
  resourceId: result.distributionId,
293
296
  tenantId: after?.tenantId ?? before?.tenantId ?? null,
@@ -372,10 +375,11 @@ const closeDistributionCommand = {
372
375
  return distribution ? serializeDistributionSnapshot(distribution) : null;
373
376
  },
374
377
  buildLog: async ({ result, snapshots }) => {
378
+ const { translate } = await resolveTranslations();
375
379
  const before = snapshots.before;
376
380
  const after = snapshots.after;
377
381
  return {
378
- actionLabel: "forms.audit.distribution.close",
382
+ actionLabel: translate("forms.audit.distribution.close", "Close distribution"),
379
383
  resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,
380
384
  resourceId: result.distributionId,
381
385
  tenantId: after?.tenantId ?? before?.tenantId ?? null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/forms/commands/distribution.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { FormDistribution } from '../data/entities'\nimport {\n distributionCloseCommandSchema,\n distributionCreateCommandSchema,\n distributionUpdateCommandSchema,\n type FormDistributionCloseCommandInput,\n type FormDistributionCreateCommandInput,\n type FormDistributionUpdateCommandInput,\n} from '../data/validators'\nimport { generatePublicSlug } from '../services/distribution-token'\nimport {\n FORMS_CACHE_TAGS,\n emitForms,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n findFormInScope,\n invalidateFormsCacheTags,\n resolveActorUserId,\n resolveEntityManager,\n} from './shared'\n\nexport const FORM_DISTRIBUTION_RESOURCE_KIND = 'forms.distribution'\n\n// ----------------------------------------------------------------------------\n// Snapshot + undo payloads\n// ----------------------------------------------------------------------------\n\nexport type FormDistributionSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n formId: string\n pinnedVersionId: string | null\n mode: 'open' | 'personal'\n publicSlug: string | null\n status: 'active' | 'paused' | 'closed'\n title: string | null\n defaultLocale: string\n requireCustomerAuth: boolean\n allowMultipleSubmissions: boolean\n maxResponses: number | null\n responseCount: number\n opensAt: string | null\n closesAt: string | null\n redirectUrl: string | null\n settings: Record<string, unknown> | null\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\ntype FormDistributionUndoPayload = {\n before?: FormDistributionSnapshot | null\n after?: FormDistributionSnapshot | null\n}\n\nexport function serializeDistributionSnapshot(distribution: FormDistribution): FormDistributionSnapshot {\n return {\n id: distribution.id,\n organizationId: distribution.organizationId,\n tenantId: distribution.tenantId,\n formId: distribution.formId,\n pinnedVersionId: distribution.pinnedVersionId ?? null,\n mode: distribution.mode,\n publicSlug: distribution.publicSlug ?? null,\n status: distribution.status,\n title: distribution.title ?? null,\n defaultLocale: distribution.defaultLocale,\n requireCustomerAuth: distribution.requireCustomerAuth,\n allowMultipleSubmissions: distribution.allowMultipleSubmissions,\n maxResponses: distribution.maxResponses ?? null,\n responseCount: distribution.responseCount,\n opensAt: distribution.opensAt ? distribution.opensAt.toISOString() : null,\n closesAt: distribution.closesAt ? distribution.closesAt.toISOString() : null,\n redirectUrl: distribution.redirectUrl ?? null,\n settings: distribution.settings ?? null,\n createdBy: distribution.createdBy,\n createdAt: distribution.createdAt.toISOString(),\n updatedAt: distribution.updatedAt.toISOString(),\n }\n}\n\n// ----------------------------------------------------------------------------\n// Cache tags + helpers\n// ----------------------------------------------------------------------------\n\nconst DISTRIBUTION_CACHE_TAGS = {\n distribution: (distributionId: string) => `forms.distribution:${distributionId}`,\n distributionList: (formId: string) => `forms.distribution.list:${formId}`,\n} as const\n\nfunction isUniqueViolation(error: unknown, constraint: string): boolean {\n if (!error || typeof error !== 'object') return false\n const candidate = error as Record<string, unknown>\n const code = candidate.code\n const detail = typeof candidate.detail === 'string' ? candidate.detail : ''\n const constraintName = typeof candidate.constraint === 'string' ? candidate.constraint : ''\n return code === '23505' && (constraintName === constraint || detail.includes(constraint))\n}\n\nasync function findDistributionInScope(\n em: EntityManager,\n distributionId: string,\n tenantId: string,\n organizationId: string,\n): Promise<FormDistribution> {\n const distribution = await em.findOne(FormDistribution, {\n id: distributionId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (!distribution) {\n throw new CrudHttpError(404, { error: 'forms.errors.distribution_not_found' })\n }\n return distribution\n}\n\nfunction parseIsoDate(value: string | null | undefined): Date | null {\n if (value === undefined || value === null) return null\n return new Date(value)\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.create\n// ----------------------------------------------------------------------------\n\nconst createDistributionCommand: CommandHandler<FormDistributionCreateCommandInput, { distributionId: string }> = {\n id: 'forms.distribution.create',\n async execute(rawInput, ctx) {\n const parsed = distributionCreateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const actorUserId = resolveActorUserId(ctx)\n if (!actorUserId) {\n throw new CrudHttpError(401, { error: 'forms.errors.unauthorized' })\n }\n\n const em = resolveEntityManager(ctx).fork()\n await findFormInScope(em, parsed.formId, parsed.tenantId, parsed.organizationId)\n\n const now = new Date()\n const buildDistribution = (publicSlug: string | null): FormDistribution =>\n em.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug,\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n\n let distribution = buildDistribution(parsed.mode === 'open' ? generatePublicSlug() : null)\n em.persist(distribution)\n\n try {\n await em.flush()\n } catch (error) {\n if (parsed.mode === 'open' && isUniqueViolation(error, 'forms_distribution_org_public_slug_unique')) {\n const retryEm = resolveEntityManager(ctx).fork()\n distribution = retryEm.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug: generatePublicSlug(),\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n retryEm.persist(distribution)\n await retryEm.flush()\n } else {\n throw error\n }\n }\n\n await emitForms('forms.distribution.created', {\n distributionId: distribution.id,\n organizationId: distribution.organizationId,\n formId: distribution.formId,\n })\n\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n FORMS_CACHE_TAGS.form(distribution.formId),\n ])\n\n return { distributionId: distribution.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: 'forms.audit.distribution.create',\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: { undo: { after: after ?? null } satisfies FormDistributionUndoPayload },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: after.id })\n if (!distribution) return\n em.remove(distribution)\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: after.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(after.formId),\n DISTRIBUTION_CACHE_TAGS.distribution(after.id),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.update\n// ----------------------------------------------------------------------------\n\nconst updateDistributionCommand: CommandHandler<FormDistributionUpdateCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.update',\n async prepare(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n const previousStatus = distribution.status\n let touched = false\n\n if (parsed.status !== undefined && parsed.status !== distribution.status) {\n distribution.status = parsed.status\n touched = true\n }\n if (parsed.title !== undefined) {\n const next = parsed.title?.trim() ?? null\n if (next !== distribution.title) {\n distribution.title = next\n touched = true\n }\n }\n if (parsed.maxResponses !== undefined) {\n const next = parsed.maxResponses ?? null\n if (next !== distribution.maxResponses) {\n distribution.maxResponses = next\n touched = true\n }\n }\n if (parsed.opensAt !== undefined) {\n distribution.opensAt = parseIsoDate(parsed.opensAt)\n touched = true\n }\n if (parsed.closesAt !== undefined) {\n distribution.closesAt = parseIsoDate(parsed.closesAt)\n touched = true\n }\n if (parsed.redirectUrl !== undefined) {\n const next = parsed.redirectUrl ?? null\n if (next !== distribution.redirectUrl) {\n distribution.redirectUrl = next\n touched = true\n }\n }\n if (parsed.allowMultipleSubmissions !== undefined && parsed.allowMultipleSubmissions !== distribution.allowMultipleSubmissions) {\n distribution.allowMultipleSubmissions = parsed.allowMultipleSubmissions\n touched = true\n }\n if (parsed.requireCustomerAuth !== undefined && parsed.requireCustomerAuth !== distribution.requireCustomerAuth) {\n distribution.requireCustomerAuth = parsed.requireCustomerAuth\n touched = true\n }\n if (parsed.settings !== undefined) {\n distribution.settings = parsed.settings ?? null\n touched = true\n }\n\n if (touched) {\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n if (distribution.status === 'closed' && previousStatus !== 'closed') {\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: 'forms.audit.distribution.update',\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.title = before.title\n distribution.maxResponses = before.maxResponses\n distribution.opensAt = before.opensAt ? new Date(before.opensAt) : null\n distribution.closesAt = before.closesAt ? new Date(before.closesAt) : null\n distribution.redirectUrl = before.redirectUrl\n distribution.allowMultipleSubmissions = before.allowMultipleSubmissions\n distribution.requireCustomerAuth = before.requireCustomerAuth\n distribution.settings = before.settings\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.close\n// ----------------------------------------------------------------------------\n\nconst closeDistributionCommand: CommandHandler<FormDistributionCloseCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.close',\n async prepare(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n if (distribution.status !== 'closed') {\n distribution.status = 'closed'\n distribution.updatedAt = new Date()\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: 'forms.audit.distribution.close',\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\nregisterCommand(createDistributionCommand)\nregisterCommand(updateDistributionCommand)\nregisterCommand(closeDistributionCommand)\n\nexport {\n DISTRIBUTION_CACHE_TAGS,\n createDistributionCommand,\n updateDistributionCommand,\n closeDistributionCommand,\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { FormDistribution } from '../data/entities'\nimport {\n distributionCloseCommandSchema,\n distributionCreateCommandSchema,\n distributionUpdateCommandSchema,\n type FormDistributionCloseCommandInput,\n type FormDistributionCreateCommandInput,\n type FormDistributionUpdateCommandInput,\n} from '../data/validators'\nimport { generatePublicSlug } from '../services/distribution-token'\nimport {\n FORMS_CACHE_TAGS,\n emitForms,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n findFormInScope,\n invalidateFormsCacheTags,\n resolveActorUserId,\n resolveEntityManager,\n} from './shared'\n\nexport const FORM_DISTRIBUTION_RESOURCE_KIND = 'forms.distribution'\n\n// ----------------------------------------------------------------------------\n// Snapshot + undo payloads\n// ----------------------------------------------------------------------------\n\nexport type FormDistributionSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n formId: string\n pinnedVersionId: string | null\n mode: 'open' | 'personal'\n publicSlug: string | null\n status: 'active' | 'paused' | 'closed'\n title: string | null\n defaultLocale: string\n requireCustomerAuth: boolean\n allowMultipleSubmissions: boolean\n maxResponses: number | null\n responseCount: number\n opensAt: string | null\n closesAt: string | null\n redirectUrl: string | null\n settings: Record<string, unknown> | null\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\ntype FormDistributionUndoPayload = {\n before?: FormDistributionSnapshot | null\n after?: FormDistributionSnapshot | null\n}\n\nexport function serializeDistributionSnapshot(distribution: FormDistribution): FormDistributionSnapshot {\n return {\n id: distribution.id,\n organizationId: distribution.organizationId,\n tenantId: distribution.tenantId,\n formId: distribution.formId,\n pinnedVersionId: distribution.pinnedVersionId ?? null,\n mode: distribution.mode,\n publicSlug: distribution.publicSlug ?? null,\n status: distribution.status,\n title: distribution.title ?? null,\n defaultLocale: distribution.defaultLocale,\n requireCustomerAuth: distribution.requireCustomerAuth,\n allowMultipleSubmissions: distribution.allowMultipleSubmissions,\n maxResponses: distribution.maxResponses ?? null,\n responseCount: distribution.responseCount,\n opensAt: distribution.opensAt ? distribution.opensAt.toISOString() : null,\n closesAt: distribution.closesAt ? distribution.closesAt.toISOString() : null,\n redirectUrl: distribution.redirectUrl ?? null,\n settings: distribution.settings ?? null,\n createdBy: distribution.createdBy,\n createdAt: distribution.createdAt.toISOString(),\n updatedAt: distribution.updatedAt.toISOString(),\n }\n}\n\n// ----------------------------------------------------------------------------\n// Cache tags + helpers\n// ----------------------------------------------------------------------------\n\nconst DISTRIBUTION_CACHE_TAGS = {\n distribution: (distributionId: string) => `forms.distribution:${distributionId}`,\n distributionList: (formId: string) => `forms.distribution.list:${formId}`,\n} as const\n\nfunction isUniqueViolation(error: unknown, constraint: string): boolean {\n if (!error || typeof error !== 'object') return false\n const candidate = error as Record<string, unknown>\n const code = candidate.code\n const detail = typeof candidate.detail === 'string' ? candidate.detail : ''\n const constraintName = typeof candidate.constraint === 'string' ? candidate.constraint : ''\n return code === '23505' && (constraintName === constraint || detail.includes(constraint))\n}\n\nasync function findDistributionInScope(\n em: EntityManager,\n distributionId: string,\n tenantId: string,\n organizationId: string,\n): Promise<FormDistribution> {\n const distribution = await em.findOne(FormDistribution, {\n id: distributionId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (!distribution) {\n throw new CrudHttpError(404, { error: 'forms.errors.distribution_not_found' })\n }\n return distribution\n}\n\nfunction parseIsoDate(value: string | null | undefined): Date | null {\n if (value === undefined || value === null) return null\n return new Date(value)\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.create\n// ----------------------------------------------------------------------------\n\nconst createDistributionCommand: CommandHandler<FormDistributionCreateCommandInput, { distributionId: string }> = {\n id: 'forms.distribution.create',\n async execute(rawInput, ctx) {\n const parsed = distributionCreateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const actorUserId = resolveActorUserId(ctx)\n if (!actorUserId) {\n throw new CrudHttpError(401, { error: 'forms.errors.unauthorized' })\n }\n\n const em = resolveEntityManager(ctx).fork()\n await findFormInScope(em, parsed.formId, parsed.tenantId, parsed.organizationId)\n\n const now = new Date()\n const buildDistribution = (publicSlug: string | null): FormDistribution =>\n em.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug,\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n\n let distribution = buildDistribution(parsed.mode === 'open' ? generatePublicSlug() : null)\n em.persist(distribution)\n\n try {\n await em.flush()\n } catch (error) {\n if (parsed.mode === 'open' && isUniqueViolation(error, 'forms_distribution_org_public_slug_unique')) {\n const retryEm = resolveEntityManager(ctx).fork()\n distribution = retryEm.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug: generatePublicSlug(),\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n retryEm.persist(distribution)\n await retryEm.flush()\n } else {\n throw error\n }\n }\n\n await emitForms('forms.distribution.created', {\n distributionId: distribution.id,\n organizationId: distribution.organizationId,\n formId: distribution.formId,\n })\n\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n FORMS_CACHE_TAGS.form(distribution.formId),\n ])\n\n return { distributionId: distribution.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: translate('forms.audit.distribution.create', 'Create distribution'),\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: { undo: { after: after ?? null } satisfies FormDistributionUndoPayload },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: after.id })\n if (!distribution) return\n em.remove(distribution)\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: after.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(after.formId),\n DISTRIBUTION_CACHE_TAGS.distribution(after.id),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.update\n// ----------------------------------------------------------------------------\n\nconst updateDistributionCommand: CommandHandler<FormDistributionUpdateCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.update',\n async prepare(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n const previousStatus = distribution.status\n let touched = false\n\n if (parsed.status !== undefined && parsed.status !== distribution.status) {\n distribution.status = parsed.status\n touched = true\n }\n if (parsed.title !== undefined) {\n const next = parsed.title?.trim() ?? null\n if (next !== distribution.title) {\n distribution.title = next\n touched = true\n }\n }\n if (parsed.maxResponses !== undefined) {\n const next = parsed.maxResponses ?? null\n if (next !== distribution.maxResponses) {\n distribution.maxResponses = next\n touched = true\n }\n }\n if (parsed.opensAt !== undefined) {\n distribution.opensAt = parseIsoDate(parsed.opensAt)\n touched = true\n }\n if (parsed.closesAt !== undefined) {\n distribution.closesAt = parseIsoDate(parsed.closesAt)\n touched = true\n }\n if (parsed.redirectUrl !== undefined) {\n const next = parsed.redirectUrl ?? null\n if (next !== distribution.redirectUrl) {\n distribution.redirectUrl = next\n touched = true\n }\n }\n if (parsed.allowMultipleSubmissions !== undefined && parsed.allowMultipleSubmissions !== distribution.allowMultipleSubmissions) {\n distribution.allowMultipleSubmissions = parsed.allowMultipleSubmissions\n touched = true\n }\n if (parsed.requireCustomerAuth !== undefined && parsed.requireCustomerAuth !== distribution.requireCustomerAuth) {\n distribution.requireCustomerAuth = parsed.requireCustomerAuth\n touched = true\n }\n if (parsed.settings !== undefined) {\n distribution.settings = parsed.settings ?? null\n touched = true\n }\n\n if (touched) {\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n if (distribution.status === 'closed' && previousStatus !== 'closed') {\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: translate('forms.audit.distribution.update', 'Update distribution'),\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.title = before.title\n distribution.maxResponses = before.maxResponses\n distribution.opensAt = before.opensAt ? new Date(before.opensAt) : null\n distribution.closesAt = before.closesAt ? new Date(before.closesAt) : null\n distribution.redirectUrl = before.redirectUrl\n distribution.allowMultipleSubmissions = before.allowMultipleSubmissions\n distribution.requireCustomerAuth = before.requireCustomerAuth\n distribution.settings = before.settings\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.close\n// ----------------------------------------------------------------------------\n\nconst closeDistributionCommand: CommandHandler<FormDistributionCloseCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.close',\n async prepare(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n if (distribution.status !== 'closed') {\n distribution.status = 'closed'\n distribution.updatedAt = new Date()\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: translate('forms.audit.distribution.close', 'Close distribution'),\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\nregisterCommand(createDistributionCommand)\nregisterCommand(updateDistributionCommand)\nregisterCommand(closeDistributionCommand)\n\nexport {\n DISTRIBUTION_CACHE_TAGS,\n createDistributionCommand,\n updateDistributionCommand,\n closeDistributionCommand,\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { registerCommand } from "@open-mercato/shared/lib/commands";
2
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
2
3
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
4
  import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
4
5
  import { Form, FormVersion } from "../data/entities.js";
@@ -173,9 +174,10 @@ const forkDraftCommand = {
173
174
  return version ? serializeFormVersionSnapshot(version) : null;
174
175
  },
175
176
  buildLog: async ({ result, snapshots }) => {
177
+ const { translate } = await resolveTranslations();
176
178
  const after = snapshots.after;
177
179
  return {
178
- actionLabel: "forms.audit.form_version.fork_draft",
180
+ actionLabel: translate("forms.audit.form_version.fork_draft", "Create draft form version"),
179
181
  resourceKind: FORM_VERSION_RESOURCE_KIND,
180
182
  resourceId: result.versionId,
181
183
  parentResourceKind: "forms.form",
@@ -300,10 +302,11 @@ const updateDraftCommand = {
300
302
  return version ? serializeFormVersionSnapshot(version) : null;
301
303
  },
302
304
  buildLog: async ({ result, snapshots }) => {
305
+ const { translate } = await resolveTranslations();
303
306
  const before = snapshots.before;
304
307
  const after = snapshots.after;
305
308
  return {
306
- actionLabel: "forms.audit.form_version.update_draft",
309
+ actionLabel: translate("forms.audit.form_version.update_draft", "Update draft form version"),
307
310
  resourceKind: FORM_VERSION_RESOURCE_KIND,
308
311
  resourceId: result.versionId,
309
312
  parentResourceKind: "forms.form",
@@ -469,11 +472,12 @@ const publishVersionCommand = {
469
472
  return version ? serializeFormVersionSnapshot(version) : null;
470
473
  },
471
474
  buildLog: async ({ result, snapshots, ctx }) => {
475
+ const { translate } = await resolveTranslations();
472
476
  const before = snapshots.before;
473
477
  const after = snapshots.after;
474
478
  const meta = ctx.__formsPublishMeta;
475
479
  return {
476
- actionLabel: "forms.audit.form_version.publish",
480
+ actionLabel: translate("forms.audit.form_version.publish", "Publish form version"),
477
481
  resourceKind: FORM_VERSION_RESOURCE_KIND,
478
482
  resourceId: result.versionId,
479
483
  parentResourceKind: "forms.form",
@@ -587,10 +591,11 @@ const archiveVersionCommand = {
587
591
  return version ? serializeFormVersionSnapshot(version) : null;
588
592
  },
589
593
  buildLog: async ({ result, snapshots }) => {
594
+ const { translate } = await resolveTranslations();
590
595
  const before = snapshots.before;
591
596
  const after = snapshots.after;
592
597
  return {
593
- actionLabel: "forms.audit.form_version.archive",
598
+ actionLabel: translate("forms.audit.form_version.archive", "Archive form version"),
594
599
  resourceKind: FORM_VERSION_RESOURCE_KIND,
595
600
  resourceId: result.versionId,
596
601
  parentResourceKind: "forms.form",