@open-mercato/core 0.6.6-develop.6229.1.ebe6706732 → 0.6.6-develop.6245.1.2be3b151f8

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 (137) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/commands/users.js +1 -0
  3. package/dist/modules/auth/commands/users.js.map +2 -2
  4. package/dist/modules/communication_channels/data/enrichers.js +9 -1
  5. package/dist/modules/communication_channels/data/enrichers.js.map +2 -2
  6. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +3 -5
  7. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +2 -2
  8. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +2 -2
  9. package/dist/modules/customers/api/pipeline-stages/reorder/route.js +39 -1
  10. package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
  11. package/dist/modules/customers/api/pipeline-stages/route.js +107 -12
  12. package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
  13. package/dist/modules/customers/api/pipelines/route.js +107 -12
  14. package/dist/modules/customers/api/pipelines/route.js.map +2 -2
  15. package/dist/modules/customers/api/settings/address-format/route.js +33 -1
  16. package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
  17. package/dist/modules/customers/api/tags/assign/route.js +37 -0
  18. package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
  19. package/dist/modules/customers/api/tags/unassign/route.js +37 -0
  20. package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
  21. package/dist/modules/customers/components/calendar/CalendarToolbar.js +4 -6
  22. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +2 -2
  23. package/dist/modules/customers/components/calendar/EventBlock.js +2 -6
  24. package/dist/modules/customers/components/calendar/EventBlock.js.map +2 -2
  25. package/dist/modules/customers/components/calendar/UpcomingCards.js +9 -7
  26. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +2 -2
  27. package/dist/modules/customers/lib/calendar/format.js +27 -0
  28. package/dist/modules/customers/lib/calendar/format.js.map +7 -0
  29. package/dist/modules/entities/api/encryption.js +109 -38
  30. package/dist/modules/entities/api/encryption.js.map +2 -2
  31. package/dist/modules/entities/components/EncryptionManager.js +22 -9
  32. package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
  33. package/dist/modules/integrations/api/[id]/credentials/route.js +23 -1
  34. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  35. package/dist/modules/integrations/api/[id]/route.js +10 -3
  36. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  37. package/dist/modules/integrations/api/[id]/state/route.js +20 -5
  38. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  39. package/dist/modules/integrations/api/[id]/version/route.js +17 -1
  40. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  41. package/dist/modules/integrations/api/route.js +2 -0
  42. package/dist/modules/integrations/api/route.js.map +2 -2
  43. package/dist/modules/integrations/backend/integrations/[id]/page.js +36 -19
  44. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  45. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +13 -9
  46. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  47. package/dist/modules/integrations/backend/integrations/page.js +4 -3
  48. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  49. package/dist/modules/integrations/data/entities.js +2 -2
  50. package/dist/modules/integrations/data/entities.js.map +2 -2
  51. package/dist/modules/integrations/lib/credentials-service.js +32 -0
  52. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  53. package/dist/modules/integrations/lib/state-service.js +4 -2
  54. package/dist/modules/integrations/lib/state-service.js.map +2 -2
  55. package/dist/modules/notifications/lib/notificationService.js +42 -10
  56. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  57. package/dist/modules/payment_gateways/api/status/route.js +91 -6
  58. package/dist/modules/payment_gateways/api/status/route.js.map +2 -2
  59. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +25 -11
  60. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
  61. package/dist/modules/sales/backend/sales/channels/offers/page.js +25 -7
  62. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  63. package/dist/modules/sales/backend/sales/channels/page.js +25 -7
  64. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  65. package/dist/modules/sales/components/AdjustmentKindSettings.js +52 -24
  66. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  67. package/dist/modules/sales/components/DocumentNumberSettings.js +26 -9
  68. package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
  69. package/dist/modules/sales/components/OrderEditingSettings.js +26 -9
  70. package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
  71. package/dist/modules/sales/components/StatusSettings.js +66 -31
  72. package/dist/modules/sales/components/StatusSettings.js.map +2 -2
  73. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +25 -7
  74. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  75. package/dist/modules/shipping_carriers/api/cancel/route.js +38 -0
  76. package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
  77. package/dist/modules/shipping_carriers/api/shipments/route.js +12 -5
  78. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  79. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +46 -13
  80. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
  81. package/dist/modules/staff/lib/timesheets-ui/timerErrors.js +12 -0
  82. package/dist/modules/staff/lib/timesheets-ui/timerErrors.js.map +7 -0
  83. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +43 -8
  84. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +2 -2
  85. package/dist/modules/workflows/backend/definitions/page.js +19 -10
  86. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  87. package/package.json +7 -7
  88. package/src/modules/auth/commands/users.ts +1 -0
  89. package/src/modules/communication_channels/data/enrichers.ts +25 -0
  90. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +7 -6
  91. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +5 -2
  92. package/src/modules/customers/api/pipeline-stages/reorder/route.ts +41 -1
  93. package/src/modules/customers/api/pipeline-stages/route.ts +112 -13
  94. package/src/modules/customers/api/pipelines/route.ts +112 -13
  95. package/src/modules/customers/api/settings/address-format/route.ts +36 -1
  96. package/src/modules/customers/api/tags/assign/route.ts +39 -0
  97. package/src/modules/customers/api/tags/unassign/route.ts +39 -0
  98. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +4 -7
  99. package/src/modules/customers/components/calendar/EventBlock.tsx +2 -6
  100. package/src/modules/customers/components/calendar/UpcomingCards.tsx +9 -7
  101. package/src/modules/customers/i18n/de.json +1 -0
  102. package/src/modules/customers/i18n/en.json +1 -0
  103. package/src/modules/customers/i18n/es.json +1 -0
  104. package/src/modules/customers/i18n/pl.json +1 -0
  105. package/src/modules/customers/lib/calendar/format.ts +25 -0
  106. package/src/modules/entities/api/encryption.ts +122 -39
  107. package/src/modules/entities/components/EncryptionManager.tsx +28 -9
  108. package/src/modules/integrations/api/[id]/credentials/route.ts +23 -0
  109. package/src/modules/integrations/api/[id]/route.ts +8 -1
  110. package/src/modules/integrations/api/[id]/state/route.ts +20 -4
  111. package/src/modules/integrations/api/[id]/version/route.ts +18 -1
  112. package/src/modules/integrations/api/route.ts +3 -0
  113. package/src/modules/integrations/backend/integrations/[id]/page.tsx +39 -20
  114. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +19 -11
  115. package/src/modules/integrations/backend/integrations/page.tsx +8 -5
  116. package/src/modules/integrations/data/entities.ts +2 -2
  117. package/src/modules/integrations/lib/credentials-service.ts +35 -0
  118. package/src/modules/integrations/lib/state-service.ts +3 -0
  119. package/src/modules/notifications/lib/notificationService.ts +74 -11
  120. package/src/modules/payment_gateways/api/status/route.ts +97 -5
  121. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +27 -11
  122. package/src/modules/sales/backend/sales/channels/offers/page.tsx +31 -7
  123. package/src/modules/sales/backend/sales/channels/page.tsx +31 -7
  124. package/src/modules/sales/components/AdjustmentKindSettings.tsx +60 -24
  125. package/src/modules/sales/components/DocumentNumberSettings.tsx +32 -10
  126. package/src/modules/sales/components/OrderEditingSettings.tsx +32 -10
  127. package/src/modules/sales/components/StatusSettings.tsx +74 -33
  128. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +30 -6
  129. package/src/modules/shipping_carriers/api/cancel/route.ts +46 -0
  130. package/src/modules/shipping_carriers/api/shipments/route.ts +21 -6
  131. package/src/modules/staff/i18n/de.json +5 -5
  132. package/src/modules/staff/i18n/es.json +5 -5
  133. package/src/modules/staff/i18n/pl.json +5 -5
  134. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +57 -13
  135. package/src/modules/staff/lib/timesheets-ui/timerErrors.ts +23 -0
  136. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +57 -9
  137. package/src/modules/workflows/backend/definitions/page.tsx +19 -10
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/workflows/backend/definitions/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { Trash2 } from 'lucide-react'\n\ntype WorkflowDefinitionSource = 'code' | 'code_override' | 'user'\n\ntype WorkflowDefinition = {\n id: string\n workflowId: string\n workflowName: string\n description: string | null\n version: number\n definition: Record<string, unknown>\n enabled: boolean\n effectiveFrom: string | null\n effectiveTo: string | null\n metadata: {\n tags?: string[]\n category?: string\n icon?: string\n } | null\n tenantId: string\n organizationId: string\n createdAt: string\n updatedAt: string\n createdBy: string | null\n source?: WorkflowDefinitionSource\n isCodeBased?: boolean\n}\n\ntype DefinitionsResponse = {\n data: WorkflowDefinition[]\n pagination: {\n total: number\n limit: number\n offset: number\n hasMore: boolean\n }\n}\n\ntype CreateDefinitionResponse = {\n data?: {\n id?: string\n }\n error?: string\n}\n\nconst WORKFLOW_ID_MAX_LENGTH = 100\n\nfunction buildDuplicateWorkflowId(sourceWorkflowId: string, attempt: number): string {\n const suffix = attempt === 0 ? '_copy' : `_copy_${attempt + 1}`\n const maxBaseLength = Math.max(1, WORKFLOW_ID_MAX_LENGTH - suffix.length)\n const base = sourceWorkflowId.slice(0, maxBaseLength)\n return `${base}${suffix}`\n}\n\nexport default function WorkflowDefinitionsListPage() {\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(20)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const t = useT()\n const router = useRouter()\n const queryClient = useQueryClient()\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [deleteTarget, setDeleteTarget] = React.useState<{ id: string; name: string; updatedAt: string | null } | null>(null)\n\n const { data, isLoading, error } = useQuery({\n queryKey: ['workflow-definitions', 'list', filterValues, page],\n queryFn: async () => {\n const params = new URLSearchParams()\n const offset = (page - 1) * pageSize\n params.set('limit', pageSize.toString())\n params.set('offset', offset.toString())\n\n if (filterValues.enabled !== undefined && filterValues.enabled !== '') {\n params.set('enabled', filterValues.enabled as string)\n }\n if (filterValues.workflowId) params.set('workflowId', filterValues.workflowId as string)\n if (filterValues.search) params.set('search', filterValues.search as string)\n\n const result = await apiCall<DefinitionsResponse>(\n `/api/workflows/definitions?${params.toString()}`\n )\n\n if (!result.ok) {\n throw new Error('Failed to fetch workflow definitions')\n }\n\n const response = result.result\n if (response?.pagination) {\n setTotal(response.pagination.total || 0)\n const calculatedPages = Math.ceil((response.pagination.total || 0) / pageSize)\n setTotalPages(calculatedPages || 1)\n }\n\n return response?.data || []\n },\n })\n\n const handleDelete = (id: string, workflowName: string, updatedAt: string | null) => {\n setDeleteTarget({ id, name: workflowName, updatedAt })\n }\n\n const confirmDelete = async () => {\n if (!deleteTarget) return\n\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(deleteTarget.updatedAt),\n () => apiCall(`/api/workflows/definitions/${deleteTarget.id}`, {\n method: 'DELETE',\n }),\n )\n\n if (result.ok) {\n flash(t('workflows.messages.deleted'), 'success')\n queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })\n } else {\n const conflictError = Object.assign(new Error(t('workflows.messages.deleteFailed')), {\n status: result.status,\n ...(result.result && typeof result.result === 'object' ? result.result : {}),\n })\n if (!surfaceRecordConflict(conflictError, t)) {\n flash(t('workflows.messages.deleteFailed'), 'error')\n }\n }\n setDeleteTarget(null)\n }\n\n const handleToggleEnabled = async (id: string, currentEnabled: boolean) => {\n const result = await apiCall(`/api/workflows/definitions/${id}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n enabled: !currentEnabled,\n }),\n })\n\n if (result.ok) {\n flash(t('workflows.messages.updated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })\n } else {\n flash(t('workflows.messages.updateFailed'), 'error')\n }\n }\n\n const handleDuplicate = async (definition: WorkflowDefinition) => {\n for (let attempt = 0; attempt < 10; attempt += 1) {\n const duplicateWorkflowId = buildDuplicateWorkflowId(definition.workflowId, attempt)\n const result = await apiCall<CreateDefinitionResponse>('/api/workflows/definitions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n workflowId: duplicateWorkflowId,\n workflowName: definition.workflowName,\n description: definition.description,\n version: definition.version,\n definition: definition.definition,\n metadata: definition.metadata,\n enabled: definition.enabled,\n }),\n })\n\n if (result.ok) {\n flash(t('workflows.messages.workflowDuplicated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })\n return\n }\n\n if (result.status !== 409) {\n break\n }\n }\n\n flash(t('workflows.errors.createFailed'), 'error')\n }\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n const next: FilterValues = {}\n Object.entries(values).forEach(([key, value]) => {\n if (value !== undefined && value !== '') next[key] = value\n })\n setFilterValues(next)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const filters: FilterDef[] = [\n {\n id: 'search',\n type: 'text',\n label: t('workflows.filters.search'),\n placeholder: t('workflows.filters.searchPlaceholder'),\n },\n {\n id: 'enabled',\n type: 'select',\n label: t('workflows.filters.status'),\n options: [\n { label: t('common.all'), value: '' },\n { label: t('common.enabled'), value: 'true' },\n { label: t('common.disabled'), value: 'false' },\n ],\n },\n {\n id: 'workflowId',\n type: 'text',\n label: t('workflows.filters.workflowId'),\n placeholder: t('workflows.filters.workflowIdPlaceholder'),\n },\n ]\n\n const columns: ColumnDef<WorkflowDefinition>[] = [\n {\n id: 'workflowId',\n header: t('workflows.fields.workflowId'),\n accessorKey: 'workflowId',\n meta: { truncate: false },\n cell: ({ row }) => (\n <span className=\"font-mono text-sm\">{row.original.workflowId}</span>\n ),\n },\n {\n id: 'workflowName',\n header: t('workflows.fields.workflowName'),\n accessorKey: 'workflowName',\n meta: { truncate: false },\n cell: ({ row }) => (\n <div>\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium\">{row.original.workflowName}</span>\n {row.original.source === 'code' && (\n <Badge variant=\"secondary\">{t('workflows.source.code')}</Badge>\n )}\n {row.original.source === 'code_override' && (\n <Badge variant=\"outline\">{t('workflows.source.code_override')}</Badge>\n )}\n </div>\n {row.original.description && (\n <div className=\"text-xs text-muted-foreground\">\n {row.original.description}\n </div>\n )}\n {row.original.metadata?.category && (\n <div className=\"text-xs text-muted-foreground mt-0.5\">\n {row.original.metadata.category}\n </div>\n )}\n </div>\n ),\n },\n {\n id: 'version',\n header: t('workflows.fields.version'),\n accessorKey: 'version',\n meta: { truncate: false },\n cell: ({ row }) => (\n <Badge variant=\"secondary\" className=\"font-mono\">\n v{row.original.version}\n </Badge>\n ),\n },\n {\n id: 'enabled',\n header: t('workflows.fields.enabled'),\n accessorKey: 'enabled',\n cell: ({ row }) => (\n <button\n onClick={() => handleToggleEnabled(row.original.id, row.original.enabled)}\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium cursor-pointer ${\n row.original.enabled\n ? 'bg-status-success-bg text-status-success-text hover:bg-status-success-border'\n : 'bg-status-neutral-bg text-status-neutral-text hover:bg-status-neutral-border'\n }`}\n title={t('workflows.actions.toggleEnabled')}\n >\n {row.original.enabled ? t('common.yes') : t('common.no')}\n </button>\n ),\n },\n {\n id: 'tags',\n header: t('workflows.fields.tags'),\n cell: ({ row }) => {\n const tags = row.original.metadata?.tags || []\n if (tags.length === 0) return <span className=\"text-muted-foreground\">-</span>\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.slice(0, 2).map((tag, idx) => (\n <Badge key={idx} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {tags.length > 2 && (\n <Badge variant=\"outline\">+{tags.length - 2}</Badge>\n )}\n </div>\n )\n },\n },\n {\n id: 'createdAt',\n header: t('workflows.fields.createdAt'),\n accessorKey: 'createdAt',\n cell: ({ row }) => {\n const date = new Date(row.original.createdAt)\n return <span className=\"text-sm text-muted-foreground\">{date.toLocaleDateString()}</span>\n },\n },\n {\n id: 'actions',\n header: '',\n cell: ({ row }) => {\n const isCodeOnly = row.original.source === 'code'\n const items = [\n {\n id: 'edit',\n label: isCodeOnly ? t('common.view') : t('common.edit'),\n href: `/backend/definitions/${row.original.id}`,\n },\n ...(!isCodeOnly ? [{\n id: 'edit-visual',\n label: t('workflows.actions.editVisually'),\n href: `/backend/definitions/visual-editor?id=${row.original.id}`,\n }] : []),\n ...(!isCodeOnly ? [{\n id: row.original.enabled ? 'disable' : 'enable',\n label: row.original.enabled ? t('common.disable') : t('common.enable'),\n onSelect: () => handleToggleEnabled(row.original.id, row.original.enabled),\n }] : []),\n ...(!isCodeOnly ? [{\n id: 'duplicate',\n label: t('common.duplicate'),\n onSelect: () => handleDuplicate(row.original),\n }] : []),\n ...(!isCodeOnly ? [{\n id: 'delete',\n label: t('common.delete'),\n onSelect: () => handleDelete(row.original.id, row.original.workflowName, row.original.updatedAt),\n destructive: true,\n }] : []),\n ]\n return <RowActions items={items} />\n },\n },\n ]\n\n if (error) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={t('workflows.messages.loadFailed')}\n description={error.message}\n action={(\n <Button variant=\"outline\" size=\"sm\" onClick={() => queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })}>\n {t('common.retry', 'Retry')}\n </Button>\n )}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('workflows.list.title')}\n actions={(\n <div className=\"flex items-center gap-2\">\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/definitions/visual-editor\">\n {t('workflows.actions.createVisual')}\n </Link>\n </Button>\n <Button asChild>\n <Link href=\"/backend/definitions/create\">\n {t('workflows.actions.create')}\n </Link>\n </Button>\n </div>\n )}\n columns={columns}\n data={data || []}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n onRowClick={(row) => router.push(`/backend/definitions/visual-editor?id=${row.id}`)}\n perspective={{\n tableId: 'workflows.definitions.list',\n }}\n emptyState={(\n <ListEmptyState\n entityName={t('workflows.list.title')}\n createHref=\"/backend/definitions/create\"\n createLabel={t('workflows.actions.create')}\n />\n )}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n />\n <Dialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('workflows.confirm.deleteTitle')}</DialogTitle>\n <DialogDescription>\n {t('workflows.confirm.delete', { name: deleteTarget?.name ?? '' })}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setDeleteTarget(null)}>\n {t('common.cancel')}\n </Button>\n <Button variant=\"destructive\" onClick={confirmDelete}>\n <Trash2/>\n {t('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { Trash2 } from 'lucide-react'\n\ntype WorkflowDefinitionSource = 'code' | 'code_override' | 'user'\n\ntype WorkflowDefinition = {\n id: string\n workflowId: string\n workflowName: string\n description: string | null\n version: number\n definition: Record<string, unknown>\n enabled: boolean\n effectiveFrom: string | null\n effectiveTo: string | null\n metadata: {\n tags?: string[]\n category?: string\n icon?: string\n } | null\n tenantId: string\n organizationId: string\n createdAt: string\n updatedAt: string\n createdBy: string | null\n source?: WorkflowDefinitionSource\n isCodeBased?: boolean\n}\n\ntype DefinitionsResponse = {\n data: WorkflowDefinition[]\n pagination: {\n total: number\n limit: number\n offset: number\n hasMore: boolean\n }\n}\n\ntype CreateDefinitionResponse = {\n data?: {\n id?: string\n }\n error?: string\n}\n\nconst WORKFLOW_ID_MAX_LENGTH = 100\n\nfunction buildDuplicateWorkflowId(sourceWorkflowId: string, attempt: number): string {\n const suffix = attempt === 0 ? '_copy' : `_copy_${attempt + 1}`\n const maxBaseLength = Math.max(1, WORKFLOW_ID_MAX_LENGTH - suffix.length)\n const base = sourceWorkflowId.slice(0, maxBaseLength)\n return `${base}${suffix}`\n}\n\nexport default function WorkflowDefinitionsListPage() {\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(20)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const t = useT()\n const router = useRouter()\n const queryClient = useQueryClient()\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [deleteTarget, setDeleteTarget] = React.useState<{ id: string; name: string; updatedAt: string | null } | null>(null)\n\n const { data, isLoading, error } = useQuery({\n queryKey: ['workflow-definitions', 'list', filterValues, page],\n queryFn: async () => {\n const params = new URLSearchParams()\n const offset = (page - 1) * pageSize\n params.set('limit', pageSize.toString())\n params.set('offset', offset.toString())\n\n if (filterValues.enabled !== undefined && filterValues.enabled !== '') {\n params.set('enabled', filterValues.enabled as string)\n }\n if (filterValues.workflowId) params.set('workflowId', filterValues.workflowId as string)\n if (filterValues.search) params.set('search', filterValues.search as string)\n\n const result = await apiCall<DefinitionsResponse>(\n `/api/workflows/definitions?${params.toString()}`\n )\n\n if (!result.ok) {\n throw new Error('Failed to fetch workflow definitions')\n }\n\n const response = result.result\n if (response?.pagination) {\n setTotal(response.pagination.total || 0)\n const calculatedPages = Math.ceil((response.pagination.total || 0) / pageSize)\n setTotalPages(calculatedPages || 1)\n }\n\n return response?.data || []\n },\n })\n\n const handleDelete = (id: string, workflowName: string, updatedAt: string | null) => {\n setDeleteTarget({ id, name: workflowName, updatedAt })\n }\n\n const confirmDelete = async () => {\n if (!deleteTarget) return\n\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(deleteTarget.updatedAt),\n () => apiCall(`/api/workflows/definitions/${deleteTarget.id}`, {\n method: 'DELETE',\n }),\n )\n\n if (result.ok) {\n flash(t('workflows.messages.deleted'), 'success')\n queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })\n } else {\n const conflictError = Object.assign(new Error(t('workflows.messages.deleteFailed')), {\n status: result.status,\n ...(result.result && typeof result.result === 'object' ? result.result : {}),\n })\n if (!surfaceRecordConflict(conflictError, t)) {\n flash(t('workflows.messages.deleteFailed'), 'error')\n }\n }\n setDeleteTarget(null)\n }\n\n const handleToggleEnabled = async (id: string, currentEnabled: boolean, updatedAt: string | null) => {\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () => apiCall(`/api/workflows/definitions/${id}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n enabled: !currentEnabled,\n }),\n }),\n )\n\n if (result.ok) {\n flash(t('workflows.messages.updated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })\n } else {\n const conflictError = Object.assign(new Error(t('workflows.messages.updateFailed')), {\n status: result.status,\n ...(result.result && typeof result.result === 'object' ? result.result : {}),\n })\n if (!surfaceRecordConflict(conflictError, t)) {\n flash(t('workflows.messages.updateFailed'), 'error')\n }\n }\n }\n\n const handleDuplicate = async (definition: WorkflowDefinition) => {\n for (let attempt = 0; attempt < 10; attempt += 1) {\n const duplicateWorkflowId = buildDuplicateWorkflowId(definition.workflowId, attempt)\n const result = await apiCall<CreateDefinitionResponse>('/api/workflows/definitions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n workflowId: duplicateWorkflowId,\n workflowName: definition.workflowName,\n description: definition.description,\n version: definition.version,\n definition: definition.definition,\n metadata: definition.metadata,\n enabled: definition.enabled,\n }),\n })\n\n if (result.ok) {\n flash(t('workflows.messages.workflowDuplicated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })\n return\n }\n\n if (result.status !== 409) {\n break\n }\n }\n\n flash(t('workflows.errors.createFailed'), 'error')\n }\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n const next: FilterValues = {}\n Object.entries(values).forEach(([key, value]) => {\n if (value !== undefined && value !== '') next[key] = value\n })\n setFilterValues(next)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const filters: FilterDef[] = [\n {\n id: 'search',\n type: 'text',\n label: t('workflows.filters.search'),\n placeholder: t('workflows.filters.searchPlaceholder'),\n },\n {\n id: 'enabled',\n type: 'select',\n label: t('workflows.filters.status'),\n options: [\n { label: t('common.all'), value: '' },\n { label: t('common.enabled'), value: 'true' },\n { label: t('common.disabled'), value: 'false' },\n ],\n },\n {\n id: 'workflowId',\n type: 'text',\n label: t('workflows.filters.workflowId'),\n placeholder: t('workflows.filters.workflowIdPlaceholder'),\n },\n ]\n\n const columns: ColumnDef<WorkflowDefinition>[] = [\n {\n id: 'workflowId',\n header: t('workflows.fields.workflowId'),\n accessorKey: 'workflowId',\n meta: { truncate: false },\n cell: ({ row }) => (\n <span className=\"font-mono text-sm\">{row.original.workflowId}</span>\n ),\n },\n {\n id: 'workflowName',\n header: t('workflows.fields.workflowName'),\n accessorKey: 'workflowName',\n meta: { truncate: false },\n cell: ({ row }) => (\n <div>\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium\">{row.original.workflowName}</span>\n {row.original.source === 'code' && (\n <Badge variant=\"secondary\">{t('workflows.source.code')}</Badge>\n )}\n {row.original.source === 'code_override' && (\n <Badge variant=\"outline\">{t('workflows.source.code_override')}</Badge>\n )}\n </div>\n {row.original.description && (\n <div className=\"text-xs text-muted-foreground\">\n {row.original.description}\n </div>\n )}\n {row.original.metadata?.category && (\n <div className=\"text-xs text-muted-foreground mt-0.5\">\n {row.original.metadata.category}\n </div>\n )}\n </div>\n ),\n },\n {\n id: 'version',\n header: t('workflows.fields.version'),\n accessorKey: 'version',\n meta: { truncate: false },\n cell: ({ row }) => (\n <Badge variant=\"secondary\" className=\"font-mono\">\n v{row.original.version}\n </Badge>\n ),\n },\n {\n id: 'enabled',\n header: t('workflows.fields.enabled'),\n accessorKey: 'enabled',\n cell: ({ row }) => (\n <button\n onClick={() => handleToggleEnabled(row.original.id, row.original.enabled, row.original.updatedAt)}\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium cursor-pointer ${\n row.original.enabled\n ? 'bg-status-success-bg text-status-success-text hover:bg-status-success-border'\n : 'bg-status-neutral-bg text-status-neutral-text hover:bg-status-neutral-border'\n }`}\n title={t('workflows.actions.toggleEnabled')}\n >\n {row.original.enabled ? t('common.yes') : t('common.no')}\n </button>\n ),\n },\n {\n id: 'tags',\n header: t('workflows.fields.tags'),\n cell: ({ row }) => {\n const tags = row.original.metadata?.tags || []\n if (tags.length === 0) return <span className=\"text-muted-foreground\">-</span>\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.slice(0, 2).map((tag, idx) => (\n <Badge key={idx} variant=\"secondary\">\n {tag}\n </Badge>\n ))}\n {tags.length > 2 && (\n <Badge variant=\"outline\">+{tags.length - 2}</Badge>\n )}\n </div>\n )\n },\n },\n {\n id: 'createdAt',\n header: t('workflows.fields.createdAt'),\n accessorKey: 'createdAt',\n cell: ({ row }) => {\n const date = new Date(row.original.createdAt)\n return <span className=\"text-sm text-muted-foreground\">{date.toLocaleDateString()}</span>\n },\n },\n {\n id: 'actions',\n header: '',\n cell: ({ row }) => {\n const isCodeOnly = row.original.source === 'code'\n const items = [\n {\n id: 'edit',\n label: isCodeOnly ? t('common.view') : t('common.edit'),\n href: `/backend/definitions/${row.original.id}`,\n },\n ...(!isCodeOnly ? [{\n id: 'edit-visual',\n label: t('workflows.actions.editVisually'),\n href: `/backend/definitions/visual-editor?id=${row.original.id}`,\n }] : []),\n ...(!isCodeOnly ? [{\n id: row.original.enabled ? 'disable' : 'enable',\n label: row.original.enabled ? t('common.disable') : t('common.enable'),\n onSelect: () => handleToggleEnabled(row.original.id, row.original.enabled, row.original.updatedAt),\n }] : []),\n ...(!isCodeOnly ? [{\n id: 'duplicate',\n label: t('common.duplicate'),\n onSelect: () => handleDuplicate(row.original),\n }] : []),\n ...(!isCodeOnly ? [{\n id: 'delete',\n label: t('common.delete'),\n onSelect: () => handleDelete(row.original.id, row.original.workflowName, row.original.updatedAt),\n destructive: true,\n }] : []),\n ]\n return <RowActions items={items} />\n },\n },\n ]\n\n if (error) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={t('workflows.messages.loadFailed')}\n description={error.message}\n action={(\n <Button variant=\"outline\" size=\"sm\" onClick={() => queryClient.invalidateQueries({ queryKey: ['workflow-definitions'] })}>\n {t('common.retry', 'Retry')}\n </Button>\n )}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('workflows.list.title')}\n actions={(\n <div className=\"flex items-center gap-2\">\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/definitions/visual-editor\">\n {t('workflows.actions.createVisual')}\n </Link>\n </Button>\n <Button asChild>\n <Link href=\"/backend/definitions/create\">\n {t('workflows.actions.create')}\n </Link>\n </Button>\n </div>\n )}\n columns={columns}\n data={data || []}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n onRowClick={(row) => router.push(`/backend/definitions/visual-editor?id=${row.id}`)}\n perspective={{\n tableId: 'workflows.definitions.list',\n }}\n emptyState={(\n <ListEmptyState\n entityName={t('workflows.list.title')}\n createHref=\"/backend/definitions/create\"\n createLabel={t('workflows.actions.create')}\n />\n )}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n />\n <Dialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('workflows.confirm.deleteTitle')}</DialogTitle>\n <DialogDescription>\n {t('workflows.confirm.delete', { name: deleteTarget?.name ?? '' })}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setDeleteTarget(null)}>\n {t('common.cancel')}\n </Button>\n <Button variant=\"destructive\" onClick={confirmDelete}>\n <Trash2/>\n {t('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6229.1.ebe6706732",
3
+ "version": "0.6.6-develop.6245.1.2be3b151f8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6229.1.ebe6706732",
250
- "@open-mercato/shared": "0.6.6-develop.6229.1.ebe6706732",
251
- "@open-mercato/ui": "0.6.6-develop.6229.1.ebe6706732",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6245.1.2be3b151f8",
250
+ "@open-mercato/shared": "0.6.6-develop.6245.1.2be3b151f8",
251
+ "@open-mercato/ui": "0.6.6-develop.6245.1.2be3b151f8",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6229.1.ebe6706732",
257
- "@open-mercato/shared": "0.6.6-develop.6229.1.ebe6706732",
258
- "@open-mercato/ui": "0.6.6-develop.6229.1.ebe6706732",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6245.1.2be3b151f8",
257
+ "@open-mercato/shared": "0.6.6-develop.6245.1.2be3b151f8",
258
+ "@open-mercato/ui": "0.6.6-develop.6245.1.2be3b151f8",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",
@@ -205,6 +205,7 @@ const createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResul
205
205
  )
206
206
  if (!organization) throw new CrudHttpError(400, { error: 'Organization not found' })
207
207
  const tenantId = organization.tenant?.id ? String(organization.tenant.id) : null
208
+ assertTargetTenantInScope(resolveActorTenantScope(ctx), tenantId, 'Organization not found')
208
209
 
209
210
  const emailHash = computeEmailHash(parsed.email)
210
211
  // Email is unique per-tenant, not globally (see Migration20260610120000:
@@ -1,6 +1,7 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import type { EnricherContext, ResponseEnricher } from '@open-mercato/shared/lib/crud/response-enricher'
3
3
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
+ import { sanitizeChannelHtml } from '../lib/sanitize-channel-html'
4
5
  import {
5
6
  CommunicationChannel,
6
7
  ExternalConversation,
@@ -63,6 +64,12 @@ export type ChannelPayloadEnrichment = {
63
64
  channelPayload: Record<string, unknown> | null
64
65
  interactiveState: Record<string, unknown> | null
65
66
  channelMetadata: Record<string, unknown> | null
67
+ /**
68
+ * Server-sanitized email HTML, safe for `dangerouslySetInnerHTML`. Populated
69
+ * for `email/*` payloads that carry an html body, `null` otherwise. Sanitizing
70
+ * here keeps `sanitize-html` off the client render path.
71
+ */
72
+ sanitizedHtml: string | null
66
73
  }
67
74
 
68
75
  export type ChannelContactEnrichment = {
@@ -71,6 +78,23 @@ export type ChannelContactEnrichment = {
71
78
  subject: string | null
72
79
  }
73
80
 
81
+ /**
82
+ * Sanitize channel-supplied email HTML on the server so the client widget never
83
+ * has to pull `sanitize-html` into the browser bundle or run sanitization on the
84
+ * render path. Returns the sanitized HTML for `email/*` payloads that carry an
85
+ * html body, and `null` otherwise. The raw `channelPayload` is left untouched so
86
+ * provider-package widget overrides keep their existing data contract.
87
+ */
88
+ function sanitizeEmailPayloadHtml(
89
+ contentType: string | null | undefined,
90
+ payload: Record<string, unknown> | null | undefined,
91
+ ): string | null {
92
+ if (!contentType || !contentType.startsWith('email/')) return null
93
+ const html = payload?.html
94
+ if (typeof html !== 'string' || html.length === 0) return null
95
+ return sanitizeChannelHtml(html)
96
+ }
97
+
74
98
  // ── _channel + _channelPayload + _channelContact ──────────────────────────────
75
99
 
76
100
  const messageChannelEnricher: ResponseEnricher<
@@ -196,6 +220,7 @@ const messageChannelEnricher: ResponseEnricher<
196
220
  channelPayload: link.channelPayload ?? null,
197
221
  interactiveState: link.interactiveState ?? null,
198
222
  channelMetadata: link.channelMetadata ?? null,
223
+ sanitizedHtml: sanitizeEmailPayloadHtml(link.channelContentType, link.channelPayload),
199
224
  }
200
225
 
201
226
  const conversation = link.externalConversationId
@@ -3,13 +3,15 @@
3
3
  import * as React from 'react'
4
4
  import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
- import { sanitizeChannelHtml } from '../../../lib/sanitize-channel-html'
7
6
 
8
7
  type ChannelPayloadEnrichment = {
9
8
  channelContentType: string | null
10
9
  channelPayload: Record<string, unknown> | null
11
10
  interactiveState: Record<string, unknown> | null
12
11
  channelMetadata: Record<string, unknown> | null
12
+ // Email HTML is sanitized server-side by the channel-payload enricher and
13
+ // delivered here ready to render — the client never imports `sanitize-html`.
14
+ sanitizedHtml: string | null
13
15
  }
14
16
 
15
17
  type MessageWithPayload = Record<string, unknown> & {
@@ -24,11 +26,10 @@ export default function ChannelPayloadRendererWidget({
24
26
  const payload = data?._channelPayload ?? null
25
27
  if (!payload || !payload.channelContentType) return null
26
28
 
27
- const { channelContentType, channelPayload } = payload
29
+ const { channelContentType, channelPayload, sanitizedHtml } = payload
28
30
 
29
- // Email / HTML — sanitize, then render.
30
- if (channelContentType.startsWith('email/') && typeof channelPayload?.html === 'string') {
31
- const sanitized = sanitizeChannelHtml(channelPayload.html as string)
31
+ // Email / HTML — render the HTML the server already sanitized.
32
+ if (channelContentType.startsWith('email/') && typeof sanitizedHtml === 'string') {
32
33
  return (
33
34
  <section
34
35
  className="rounded-md border bg-card p-4 text-sm"
@@ -37,7 +38,7 @@ export default function ChannelPayloadRendererWidget({
37
38
  'Channel payload — email',
38
39
  )}
39
40
  >
40
- <div dangerouslySetInnerHTML={{ __html: sanitized }} />
41
+ <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
41
42
  </section>
42
43
  )
43
44
  }
@@ -6,8 +6,11 @@ import ChannelPayloadRendererWidget from './widget.client'
6
6
  * (Slack Block Kit, WhatsApp interactive, email MIME, etc.) below the body in the
7
7
  * messages detail page (`detail:messages:message:body:after`).
8
8
  *
9
- * For `email/*` content types, HTML is sanitized via `sanitizeChannelHtml` before
10
- * being passed to `dangerouslySetInnerHTML` — see SPEC-045d §4.6.
9
+ * For `email/*` content types, HTML is sanitized server-side by the
10
+ * channel-payload enricher (`data/enrichers.ts`, via `sanitizeChannelHtml`) and
11
+ * delivered as `_channelPayload.sanitizedHtml`, which this widget renders with
12
+ * `dangerouslySetInnerHTML` — keeping `sanitize-html` off the client bundle.
13
+ * See SPEC-045d §4.6.
11
14
  *
12
15
  * Provider packages override this via UMES component replacement (handle
13
16
  * `widget:communication_channels.injection.channel-payload-renderer`) to render
@@ -8,8 +8,14 @@ import { pipelineStageReorderSchema, type PipelineStageReorderInput } from '../.
8
8
  import { withScopedPayload } from '../../utils'
9
9
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
10
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
11
+ import {
12
+ runCrudMutationGuardAfterSuccess,
13
+ validateCrudMutationGuard,
14
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
11
15
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
16
 
17
+ const PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'
18
+
13
19
  export const metadata = {
14
20
  POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
15
21
  }
@@ -29,15 +35,49 @@ export async function POST(req: Request) {
29
35
  organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
30
36
  request: req,
31
37
  }
38
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null
39
+ const tenantId = auth.tenantId ?? null
40
+ if (!organizationId || !tenantId) {
41
+ return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
42
+ }
32
43
 
33
44
  const body = await req.json().catch(() => ({}))
34
45
  const scoped = withScopedPayload(body, ctx, translate)
46
+ const input = pipelineStageReorderSchema.parse(scoped)
47
+
48
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
49
+ tenantId,
50
+ organizationId,
51
+ userId: auth.sub,
52
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
53
+ resourceId: organizationId,
54
+ operation: 'custom',
55
+ requestMethod: req.method,
56
+ requestHeaders: req.headers,
57
+ mutationPayload: input,
58
+ })
59
+ if (guardResult && !guardResult.ok) {
60
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
61
+ }
35
62
 
36
63
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
37
64
  await commandBus.execute<PipelineStageReorderInput, void>(
38
65
  'customers.pipeline-stages.reorder',
39
- { input: pipelineStageReorderSchema.parse(scoped), ctx },
66
+ { input, ctx },
40
67
  )
68
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
69
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
70
+ tenantId,
71
+ organizationId,
72
+ userId: auth.sub,
73
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
74
+ resourceId: organizationId,
75
+ operation: 'custom',
76
+ requestMethod: req.method,
77
+ requestHeaders: req.headers,
78
+ metadata: guardResult.metadata ?? null,
79
+ })
80
+ }
41
81
  return NextResponse.json({ ok: true })
42
82
  } catch (err) {
43
83
  if (isCrudHttpError(err)) {
@@ -18,8 +18,14 @@ import { withScopedPayload } from '../utils'
18
18
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
19
19
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
20
20
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
21
+ import {
22
+ runCrudMutationGuardAfterSuccess,
23
+ validateCrudMutationGuard,
24
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
21
25
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
22
26
 
27
+ const PIPELINE_STAGE_RESOURCE_KIND = 'customers.pipelineStage'
28
+
23
29
  export const metadata = {
24
30
  GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },
25
31
  POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
@@ -29,7 +35,7 @@ export const metadata = {
29
35
 
30
36
  async function buildContext(
31
37
  req: Request
32
- ): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null }> {
38
+ ): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null; translate: (key: string, fallback?: string) => string }> {
33
39
  const container = await createRequestContainer()
34
40
  const auth = await getAuthFromRequest(req)
35
41
  const { translate } = await resolveTranslations()
@@ -45,14 +51,14 @@ async function buildContext(
45
51
  }
46
52
  const organizationId = scope?.selectedId ?? auth.orgId ?? null
47
53
  const tenantId = auth.tenantId ?? null
48
- return { ctx, organizationId, tenantId }
54
+ return { ctx, organizationId, tenantId, translate }
49
55
  }
50
56
 
51
57
  export async function GET(req: Request) {
52
58
  try {
53
- const { ctx, organizationId, tenantId } = await buildContext(req)
59
+ const { ctx, organizationId, tenantId, translate } = await buildContext(req)
54
60
  if (!organizationId || !tenantId) {
55
- return NextResponse.json({ error: 'Organization and tenant context required' }, { status: 400 })
61
+ return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
56
62
  }
57
63
  const url = new URL(req.url)
58
64
  const pipelineId = url.searchParams.get('pipelineId')
@@ -102,16 +108,47 @@ export async function GET(req: Request) {
102
108
 
103
109
  export async function POST(req: Request) {
104
110
  try {
105
- const { ctx } = await buildContext(req)
111
+ const { ctx, organizationId, tenantId, translate } = await buildContext(req)
112
+ if (!organizationId || !tenantId) {
113
+ return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
114
+ }
106
115
  const body = await req.json().catch(() => ({}))
107
- const { translate } = await resolveTranslations()
108
116
  const scoped = withScopedPayload(body, ctx, translate)
117
+ const input = pipelineStageCreateSchema.parse(scoped)
118
+
119
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
120
+ tenantId,
121
+ organizationId,
122
+ userId: ctx.auth!.sub,
123
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
124
+ resourceId: organizationId,
125
+ operation: 'create',
126
+ requestMethod: req.method,
127
+ requestHeaders: req.headers,
128
+ mutationPayload: input,
129
+ })
130
+ if (guardResult && !guardResult.ok) {
131
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
132
+ }
109
133
 
110
134
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
111
135
  const { result, logEntry } = await commandBus.execute<PipelineStageCreateInput, { stageId: string }>(
112
136
  'customers.pipeline-stages.create',
113
- { input: pipelineStageCreateSchema.parse(scoped), ctx },
137
+ { input, ctx },
114
138
  )
139
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
140
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
141
+ tenantId,
142
+ organizationId,
143
+ userId: ctx.auth!.sub,
144
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
145
+ resourceId: result?.stageId ?? organizationId,
146
+ operation: 'create',
147
+ requestMethod: req.method,
148
+ requestHeaders: req.headers,
149
+ metadata: guardResult.metadata ?? null,
150
+ })
151
+ }
115
152
  const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 })
116
153
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
117
154
  response.headers.set(
@@ -139,16 +176,47 @@ export async function POST(req: Request) {
139
176
 
140
177
  export async function PUT(req: Request) {
141
178
  try {
142
- const { ctx } = await buildContext(req)
179
+ const { ctx, organizationId, tenantId, translate } = await buildContext(req)
180
+ if (!organizationId || !tenantId) {
181
+ return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
182
+ }
143
183
  const body = await req.json().catch(() => ({}))
144
- const { translate } = await resolveTranslations()
145
184
  const scoped = withScopedPayload(body, ctx, translate)
185
+ const input = pipelineStageUpdateSchema.parse(scoped)
186
+
187
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
188
+ tenantId,
189
+ organizationId,
190
+ userId: ctx.auth!.sub,
191
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
192
+ resourceId: input.id,
193
+ operation: 'update',
194
+ requestMethod: req.method,
195
+ requestHeaders: req.headers,
196
+ mutationPayload: input,
197
+ })
198
+ if (guardResult && !guardResult.ok) {
199
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
200
+ }
146
201
 
147
202
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
148
203
  const { logEntry } = await commandBus.execute<PipelineStageUpdateInput, void>(
149
204
  'customers.pipeline-stages.update',
150
- { input: pipelineStageUpdateSchema.parse(scoped), ctx },
205
+ { input, ctx },
151
206
  )
207
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
208
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
209
+ tenantId,
210
+ organizationId,
211
+ userId: ctx.auth!.sub,
212
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
213
+ resourceId: input.id,
214
+ operation: 'update',
215
+ requestMethod: req.method,
216
+ requestHeaders: req.headers,
217
+ metadata: guardResult.metadata ?? null,
218
+ })
219
+ }
152
220
  const response = NextResponse.json({ ok: true })
153
221
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
154
222
  response.headers.set(
@@ -176,16 +244,47 @@ export async function PUT(req: Request) {
176
244
 
177
245
  export async function DELETE(req: Request) {
178
246
  try {
179
- const { ctx } = await buildContext(req)
247
+ const { ctx, organizationId, tenantId, translate } = await buildContext(req)
248
+ if (!organizationId || !tenantId) {
249
+ return NextResponse.json({ error: translate('customers.errors.context_required', 'Organization and tenant context required') }, { status: 400 })
250
+ }
180
251
  const body = await req.json().catch(() => ({}))
181
- const { translate } = await resolveTranslations()
182
252
  const scoped = withScopedPayload(body, ctx, translate)
253
+ const input = pipelineStageDeleteSchema.parse(scoped)
254
+
255
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
256
+ tenantId,
257
+ organizationId,
258
+ userId: ctx.auth!.sub,
259
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
260
+ resourceId: input.id,
261
+ operation: 'delete',
262
+ requestMethod: req.method,
263
+ requestHeaders: req.headers,
264
+ mutationPayload: input,
265
+ })
266
+ if (guardResult && !guardResult.ok) {
267
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
268
+ }
183
269
 
184
270
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
185
271
  await commandBus.execute<PipelineStageDeleteInput, void>(
186
272
  'customers.pipeline-stages.delete',
187
- { input: pipelineStageDeleteSchema.parse(scoped), ctx },
273
+ { input, ctx },
188
274
  )
275
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
276
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
277
+ tenantId,
278
+ organizationId,
279
+ userId: ctx.auth!.sub,
280
+ resourceKind: PIPELINE_STAGE_RESOURCE_KIND,
281
+ resourceId: input.id,
282
+ operation: 'delete',
283
+ requestMethod: req.method,
284
+ requestHeaders: req.headers,
285
+ metadata: guardResult.metadata ?? null,
286
+ })
287
+ }
189
288
  return NextResponse.json({ ok: true })
190
289
  } catch (err) {
191
290
  if (isCrudHttpError(err)) {