@open-mercato/core 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6121.1.5797a901b3

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 (317) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/catalog_product/index.js +52 -0
  3. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  4. package/dist/generated/entities/catalog_product_variant/index.js +4 -0
  5. package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +28 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/api/products/route.js +103 -33
  9. package/dist/modules/catalog/api/products/route.js.map +2 -2
  10. package/dist/modules/catalog/api/variants/route.js +4 -0
  11. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
  17. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  18. package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
  19. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +251 -14
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/variants.js +72 -1
  23. package/dist/modules/catalog/commands/variants.js.map +2 -2
  24. package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
  25. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
  26. package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
  27. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  28. package/dist/modules/catalog/components/products/productForm.js +164 -2
  29. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  30. package/dist/modules/catalog/components/products/variantForm.js +2 -0
  31. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  32. package/dist/modules/catalog/data/entities.js +93 -0
  33. package/dist/modules/catalog/data/entities.js.map +2 -2
  34. package/dist/modules/catalog/data/types.js +28 -0
  35. package/dist/modules/catalog/data/types.js.map +2 -2
  36. package/dist/modules/catalog/data/validators.js +100 -5
  37. package/dist/modules/catalog/data/validators.js.map +2 -2
  38. package/dist/modules/catalog/lib/gtin.js +61 -0
  39. package/dist/modules/catalog/lib/gtin.js.map +7 -0
  40. package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
  41. package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
  42. package/dist/modules/catalog/translations.js +1 -1
  43. package/dist/modules/catalog/translations.js.map +2 -2
  44. package/dist/modules/configs/api/cache/route.js +60 -10
  45. package/dist/modules/configs/api/cache/route.js.map +3 -3
  46. package/dist/modules/configs/components/CachePanel.js +52 -29
  47. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  48. package/dist/modules/currencies/api/currencies/options/route.js +18 -4
  49. package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
  50. package/dist/modules/currencies/services/rateFetchingService.js +35 -18
  51. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
  55. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  56. package/dist/modules/customers/backend/calendar/page.js +10 -0
  57. package/dist/modules/customers/backend/calendar/page.js.map +7 -0
  58. package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
  59. package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
  60. package/dist/modules/customers/cli.js +1 -0
  61. package/dist/modules/customers/cli.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
  63. package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
  65. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
  66. package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
  67. package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
  68. package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
  69. package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
  70. package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
  71. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
  72. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
  73. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
  74. package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
  75. package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
  76. package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
  77. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
  78. package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
  79. package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
  80. package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
  81. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
  82. package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
  83. package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
  85. package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
  87. package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
  88. package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
  89. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
  90. package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
  91. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
  92. package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
  93. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
  94. package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
  95. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
  96. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
  97. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
  98. package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
  99. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
  100. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
  101. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
  102. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
  103. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
  104. package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
  105. package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
  107. package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
  109. package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
  110. package/dist/modules/customers/components/calendar/types.js +30 -0
  111. package/dist/modules/customers/components/calendar/types.js.map +7 -0
  112. package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
  113. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
  114. package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
  115. package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/categories.js +28 -0
  117. package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
  119. package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
  120. package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
  121. package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
  122. package/dist/modules/customers/lib/calendar/grid.js +48 -0
  123. package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
  124. package/dist/modules/customers/lib/calendar/layout.js +39 -0
  125. package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
  126. package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
  127. package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
  128. package/dist/modules/customers/lib/calendar/preferences.js +71 -0
  129. package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
  130. package/dist/modules/customers/lib/calendar/range.js +46 -0
  131. package/dist/modules/customers/lib/calendar/range.js.map +7 -0
  132. package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
  133. package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
  134. package/dist/modules/dashboards/services/widgetDataService.js +5 -5
  135. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  136. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
  137. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  138. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
  139. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  140. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
  141. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  142. package/dist/modules/dictionaries/api/route.js +33 -1
  143. package/dist/modules/dictionaries/api/route.js.map +2 -2
  144. package/dist/modules/feature_toggles/lib/queries.js +36 -52
  145. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  146. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  147. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  151. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  153. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  154. package/dist/modules/planner/api/access.js +9 -0
  155. package/dist/modules/planner/api/access.js.map +2 -2
  156. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  157. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  158. package/dist/modules/planner/api/availability-weekly.js +22 -1
  159. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  160. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  161. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  164. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  165. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  167. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  168. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  169. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  172. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  173. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  175. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  178. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  179. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  180. package/dist/modules/translations/api/context.js +7 -0
  181. package/dist/modules/translations/api/context.js.map +2 -2
  182. package/dist/modules/translations/api/put/locales.js +33 -1
  183. package/dist/modules/translations/api/put/locales.js.map +2 -2
  184. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  185. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  186. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  187. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  188. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  189. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  192. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  193. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  194. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  195. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  196. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  197. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  198. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  199. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  200. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  201. package/generated/entities/catalog_product/index.ts +26 -0
  202. package/generated/entities/catalog_product_variant/index.ts +2 -0
  203. package/generated/entity-fields-registry.ts +28 -0
  204. package/package.json +7 -7
  205. package/src/modules/catalog/api/products/route.ts +123 -39
  206. package/src/modules/catalog/api/variants/route.ts +4 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  209. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  210. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  211. package/src/modules/catalog/commands/products.ts +307 -2
  212. package/src/modules/catalog/commands/variants.ts +90 -1
  213. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  214. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  215. package/src/modules/catalog/components/products/productForm.ts +262 -0
  216. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  217. package/src/modules/catalog/data/entities.ts +97 -0
  218. package/src/modules/catalog/data/types.ts +36 -0
  219. package/src/modules/catalog/data/validators.ts +159 -3
  220. package/src/modules/catalog/i18n/de.json +64 -0
  221. package/src/modules/catalog/i18n/en.json +64 -0
  222. package/src/modules/catalog/i18n/es.json +64 -0
  223. package/src/modules/catalog/i18n/pl.json +64 -0
  224. package/src/modules/catalog/lib/gtin.ts +66 -0
  225. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  226. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  227. package/src/modules/catalog/translations.ts +1 -1
  228. package/src/modules/configs/api/cache/route.ts +72 -10
  229. package/src/modules/configs/components/CachePanel.tsx +59 -29
  230. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  231. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  232. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  233. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  234. package/src/modules/customer_accounts/i18n/de.json +1 -1
  235. package/src/modules/customer_accounts/i18n/en.json +1 -1
  236. package/src/modules/customer_accounts/i18n/es.json +1 -1
  237. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  238. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  239. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  240. package/src/modules/customers/cli.ts +1 -0
  241. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  242. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  243. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  244. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  245. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  246. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  247. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  248. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  249. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  250. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  251. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  252. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  253. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  254. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  255. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  256. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  257. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  258. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  259. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  260. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  261. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  262. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  263. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  264. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  265. package/src/modules/customers/components/calendar/types.ts +163 -0
  266. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  267. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  268. package/src/modules/customers/i18n/de.json +195 -0
  269. package/src/modules/customers/i18n/en.json +195 -0
  270. package/src/modules/customers/i18n/es.json +195 -0
  271. package/src/modules/customers/i18n/pl.json +195 -0
  272. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  273. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  274. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  275. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  276. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  277. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  278. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  279. package/src/modules/customers/lib/calendar/range.ts +45 -0
  280. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  281. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  282. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  283. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  284. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  285. package/src/modules/dictionaries/api/route.ts +36 -1
  286. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  287. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  288. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  289. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  290. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  291. package/src/modules/planner/api/access.ts +9 -0
  292. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  293. package/src/modules/planner/api/availability-weekly.ts +22 -1
  294. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  295. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  296. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  297. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  298. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  299. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  300. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  301. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  302. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  303. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  304. package/src/modules/translations/api/context.ts +7 -0
  305. package/src/modules/translations/api/put/locales.ts +35 -1
  306. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  307. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  308. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  309. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  310. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  311. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  312. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  313. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  314. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  315. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  316. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  317. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -12,6 +12,11 @@ import { salesEditingSettingsSchema, salesSettingsUpsertSchema } from '../../../
12
12
  import { loadSalesSettings } from '../../../commands/settings'
13
13
  import { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'
14
14
  import { withScopedPayload } from '../../utils'
15
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
16
+ import {
17
+ runCrudMutationGuardAfterSuccess,
18
+ validateCrudMutationGuard,
19
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
15
20
  import { ensureSalesDictionary } from '../../../lib/dictionaries'
16
21
  import { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
17
22
 
@@ -114,23 +119,52 @@ export async function GET(req: Request) {
114
119
  export async function PUT(req: Request) {
115
120
  try {
116
121
  const { ctx, translate, organizationId, tenantId, em } = await resolveSettingsContext(req)
117
- const payload = await req.json().catch(() => ({}))
122
+ const payload = (await readJsonSafe<Record<string, unknown>>(req, {})) ?? {}
118
123
  const scoped = withScopedPayload(payload, ctx, translate)
119
124
  const parsed = salesEditingSettingsSchema.parse(scoped)
120
125
 
126
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
127
+ tenantId,
128
+ organizationId,
129
+ userId: ctx.auth!.sub,
130
+ resourceKind: 'sales.settings',
131
+ resourceId: organizationId,
132
+ operation: 'update',
133
+ requestMethod: req.method,
134
+ requestHeaders: req.headers,
135
+ mutationPayload: parsed,
136
+ })
137
+ if (guardResult && !guardResult.ok) {
138
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
139
+ }
140
+
121
141
  const current = await loadSalesSettings(em, { tenantId, organizationId })
122
142
  const commandInput = salesSettingsUpsertSchema.parse({
123
143
  ...parsed,
124
144
  orderNumberFormat: parsed.orderNumberFormat ?? current?.orderNumberFormat ?? DEFAULT_ORDER_NUMBER_FORMAT,
125
145
  quoteNumberFormat: parsed.quoteNumberFormat ?? current?.quoteNumberFormat ?? DEFAULT_QUOTE_NUMBER_FORMAT,
126
- orderNextNumber: payload?.orderNextNumber,
127
- quoteNextNumber: payload?.quoteNextNumber,
146
+ orderNextNumber: payload.orderNextNumber,
147
+ quoteNextNumber: payload.quoteNextNumber,
128
148
  })
129
149
 
130
150
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
131
151
  const response = await commandBus.execute('sales.settings.save', { input: commandInput, ctx })
132
152
  const result = (response as { result?: { orderCustomerEditableStatuses?: string[] | null; orderAddressEditableStatuses?: string[] | null } }).result
133
153
 
154
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
155
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
156
+ tenantId,
157
+ organizationId,
158
+ userId: ctx.auth!.sub,
159
+ resourceKind: 'sales.settings',
160
+ resourceId: organizationId,
161
+ operation: 'update',
162
+ requestMethod: req.method,
163
+ requestHeaders: req.headers,
164
+ metadata: guardResult.metadata ?? null,
165
+ })
166
+ }
167
+
134
168
  const orderStatuses = await loadStatusOptions(em, tenantId, organizationId)
135
169
 
136
170
  return NextResponse.json({
@@ -10,6 +10,11 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
10
  import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
11
11
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
12
  import { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'
13
+ import {
14
+ resolveUserFeatures,
15
+ runStaffMutationGuardAfterSuccess,
16
+ runStaffMutationGuards,
17
+ } from '../../guards'
13
18
 
14
19
  export const metadata = {
15
20
  POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },
@@ -39,11 +44,51 @@ export async function POST(req: Request) {
39
44
  const { ctx, translate } = await buildContext(req)
40
45
  const body = await req.json().catch(() => ({}))
41
46
  const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)
47
+
48
+ const auth = ctx.auth
49
+ const tenantId = auth?.tenantId ?? ''
50
+ const organizationId = ctx.selectedOrganizationId ?? null
51
+ const guardResult = await runStaffMutationGuards(
52
+ ctx.container,
53
+ {
54
+ tenantId,
55
+ organizationId,
56
+ userId: auth?.sub ?? '',
57
+ resourceKind: 'staff.leave_request',
58
+ resourceId: input.id,
59
+ operation: 'update',
60
+ requestMethod: req.method,
61
+ requestHeaders: req.headers,
62
+ mutationPayload: input,
63
+ },
64
+ resolveUserFeatures(auth),
65
+ )
66
+ if (!guardResult.ok) {
67
+ return NextResponse.json(
68
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
69
+ { status: guardResult.errorStatus ?? 422 },
70
+ )
71
+ }
72
+
42
73
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
43
74
  const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(
44
75
  'staff.leave-requests.accept',
45
76
  { input, ctx },
46
77
  )
78
+
79
+ if (guardResult.afterSuccessCallbacks.length) {
80
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
81
+ tenantId,
82
+ organizationId,
83
+ userId: auth?.sub ?? '',
84
+ resourceKind: 'staff.leave_request',
85
+ resourceId: result?.requestId ?? input.id,
86
+ operation: 'update',
87
+ requestMethod: req.method,
88
+ requestHeaders: req.headers,
89
+ })
90
+ }
91
+
47
92
  const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })
48
93
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
49
94
  response.headers.set(
@@ -10,6 +10,11 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
10
  import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
11
11
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
12
  import { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'
13
+ import {
14
+ resolveUserFeatures,
15
+ runStaffMutationGuardAfterSuccess,
16
+ runStaffMutationGuards,
17
+ } from '../../guards'
13
18
 
14
19
  export const metadata = {
15
20
  POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },
@@ -39,11 +44,51 @@ export async function POST(req: Request) {
39
44
  const { ctx, translate } = await buildContext(req)
40
45
  const body = await req.json().catch(() => ({}))
41
46
  const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)
47
+
48
+ const auth = ctx.auth
49
+ const tenantId = auth?.tenantId ?? ''
50
+ const organizationId = ctx.selectedOrganizationId ?? null
51
+ const guardResult = await runStaffMutationGuards(
52
+ ctx.container,
53
+ {
54
+ tenantId,
55
+ organizationId,
56
+ userId: auth?.sub ?? '',
57
+ resourceKind: 'staff.leave_request',
58
+ resourceId: input.id,
59
+ operation: 'update',
60
+ requestMethod: req.method,
61
+ requestHeaders: req.headers,
62
+ mutationPayload: input,
63
+ },
64
+ resolveUserFeatures(auth),
65
+ )
66
+ if (!guardResult.ok) {
67
+ return NextResponse.json(
68
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
69
+ { status: guardResult.errorStatus ?? 422 },
70
+ )
71
+ }
72
+
42
73
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
43
74
  const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(
44
75
  'staff.leave-requests.reject',
45
76
  { input, ctx },
46
77
  )
78
+
79
+ if (guardResult.afterSuccessCallbacks.length) {
80
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
81
+ tenantId,
82
+ organizationId,
83
+ userId: auth?.sub ?? '',
84
+ resourceKind: 'staff.leave_request',
85
+ resourceId: result?.requestId ?? input.id,
86
+ operation: 'update',
87
+ requestMethod: req.method,
88
+ requestHeaders: req.headers,
89
+ })
90
+ }
91
+
47
92
  const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })
48
93
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
49
94
  response.headers.set(
@@ -16,6 +16,11 @@ import {
16
16
  type StaffTeamMemberSelfCreateInput,
17
17
  type StaffTeamMemberCreateInput,
18
18
  } from '../../../data/validators'
19
+ import {
20
+ resolveUserFeatures,
21
+ runStaffMutationGuardAfterSuccess,
22
+ runStaffMutationGuards,
23
+ } from '../../guards'
19
24
 
20
25
  export const metadata = {
21
26
  GET: { requireAuth: true, requireFeatures: ['staff.leave_requests.send'] },
@@ -102,6 +107,30 @@ export async function POST(req: Request) {
102
107
  throw new CrudHttpError(409, { error: translate('staff.teamMembers.self.exists', 'Team member profile already exists.') })
103
108
  }
104
109
 
110
+ const tenantId = auth.tenantId ?? ''
111
+ const organizationId = ctx.selectedOrganizationId ?? null
112
+ const guardResult = await runStaffMutationGuards(
113
+ ctx.container,
114
+ {
115
+ tenantId,
116
+ organizationId,
117
+ userId: auth.sub,
118
+ resourceKind: 'staff.team_member',
119
+ resourceId: auth.sub,
120
+ operation: 'create',
121
+ requestMethod: req.method,
122
+ requestHeaders: req.headers,
123
+ mutationPayload: parsed,
124
+ },
125
+ resolveUserFeatures(auth),
126
+ )
127
+ if (!guardResult.ok) {
128
+ return NextResponse.json(
129
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
130
+ { status: guardResult.errorStatus ?? 422 },
131
+ )
132
+ }
133
+
105
134
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
106
135
  const selfInput: StaffTeamMemberCreateInput = {
107
136
  ...parsed,
@@ -119,6 +148,20 @@ export async function POST(req: Request) {
119
148
  ctx,
120
149
  },
121
150
  )
151
+
152
+ if (guardResult.afterSuccessCallbacks.length) {
153
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
154
+ tenantId,
155
+ organizationId,
156
+ userId: auth.sub,
157
+ resourceKind: 'staff.team_member',
158
+ resourceId: result?.memberId ?? auth.sub,
159
+ operation: 'create',
160
+ requestMethod: req.method,
161
+ requestHeaders: req.headers,
162
+ })
163
+ }
164
+
122
165
  const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 })
123
166
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
124
167
  response.headers.set(
@@ -13,6 +13,11 @@ import {
13
13
  staffTeamMemberTagAssignmentSchema,
14
14
  type StaffTeamMemberTagAssignmentInput,
15
15
  } from '../../../../data/validators'
16
+ import {
17
+ resolveUserFeatures,
18
+ runStaffMutationGuardAfterSuccess,
19
+ runStaffMutationGuards,
20
+ } from '../../../guards'
16
21
 
17
22
  export const metadata = {
18
23
  POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },
@@ -42,11 +47,51 @@ export async function POST(req: Request) {
42
47
  const { ctx, translate } = await buildContext(req)
43
48
  const body = await req.json().catch(() => ({}))
44
49
  const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)
50
+
51
+ const auth = ctx.auth
52
+ const tenantId = auth?.tenantId ?? ''
53
+ const organizationId = ctx.selectedOrganizationId ?? null
54
+ const guardResult = await runStaffMutationGuards(
55
+ ctx.container,
56
+ {
57
+ tenantId,
58
+ organizationId,
59
+ userId: auth?.sub ?? '',
60
+ resourceKind: 'staff.team_member',
61
+ resourceId: input.memberId,
62
+ operation: 'create',
63
+ requestMethod: req.method,
64
+ requestHeaders: req.headers,
65
+ mutationPayload: input,
66
+ },
67
+ resolveUserFeatures(auth),
68
+ )
69
+ if (!guardResult.ok) {
70
+ return NextResponse.json(
71
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
72
+ { status: guardResult.errorStatus ?? 422 },
73
+ )
74
+ }
75
+
45
76
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
46
77
  const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(
47
78
  'staff.team-members.tags.assign',
48
79
  { input, ctx },
49
80
  )
81
+
82
+ if (guardResult.afterSuccessCallbacks.length) {
83
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
84
+ tenantId,
85
+ organizationId,
86
+ userId: auth?.sub ?? '',
87
+ resourceKind: 'staff.team_member',
88
+ resourceId: result?.memberId ?? input.memberId,
89
+ operation: 'create',
90
+ requestMethod: req.method,
91
+ requestHeaders: req.headers,
92
+ })
93
+ }
94
+
50
95
  const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 })
51
96
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
52
97
  response.headers.set(
@@ -13,6 +13,11 @@ import {
13
13
  staffTeamMemberTagAssignmentSchema,
14
14
  type StaffTeamMemberTagAssignmentInput,
15
15
  } from '../../../../data/validators'
16
+ import {
17
+ resolveUserFeatures,
18
+ runStaffMutationGuardAfterSuccess,
19
+ runStaffMutationGuards,
20
+ } from '../../../guards'
16
21
 
17
22
  export const metadata = {
18
23
  POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },
@@ -42,11 +47,51 @@ export async function POST(req: Request) {
42
47
  const { ctx, translate } = await buildContext(req)
43
48
  const body = await req.json().catch(() => ({}))
44
49
  const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)
50
+
51
+ const auth = ctx.auth
52
+ const tenantId = auth?.tenantId ?? ''
53
+ const organizationId = ctx.selectedOrganizationId ?? null
54
+ const guardResult = await runStaffMutationGuards(
55
+ ctx.container,
56
+ {
57
+ tenantId,
58
+ organizationId,
59
+ userId: auth?.sub ?? '',
60
+ resourceKind: 'staff.team_member',
61
+ resourceId: input.memberId,
62
+ operation: 'delete',
63
+ requestMethod: req.method,
64
+ requestHeaders: req.headers,
65
+ mutationPayload: input,
66
+ },
67
+ resolveUserFeatures(auth),
68
+ )
69
+ if (!guardResult.ok) {
70
+ return NextResponse.json(
71
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
72
+ { status: guardResult.errorStatus ?? 422 },
73
+ )
74
+ }
75
+
45
76
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
46
77
  const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(
47
78
  'staff.team-members.tags.unassign',
48
79
  { input, ctx },
49
80
  )
81
+
82
+ if (guardResult.afterSuccessCallbacks.length) {
83
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
84
+ tenantId,
85
+ organizationId,
86
+ userId: auth?.sub ?? '',
87
+ resourceKind: 'staff.team_member',
88
+ resourceId: result?.memberId ?? input.memberId,
89
+ operation: 'delete',
90
+ requestMethod: req.method,
91
+ requestHeaders: req.headers,
92
+ })
93
+ }
94
+
50
95
  const response = NextResponse.json({ id: result?.memberId ?? null })
51
96
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
52
97
  response.headers.set(
@@ -1,9 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { sql } from 'kysely'
4
- import { resolveTranslationsRouteContext, requireTranslationFeatures } from '@open-mercato/core/modules/translations/api/context'
4
+ import { resolveTranslationsRouteContext, requireTranslationFeatures, resolveTranslationsActorId } from '@open-mercato/core/modules/translations/api/context'
5
5
  import { translationBodySchema, entityTypeParamSchema, entityIdParamSchema } from '@open-mercato/core/modules/translations/data/validators'
6
6
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
+ import {
8
+ runCrudMutationGuardAfterSuccess,
9
+ validateCrudMutationGuard,
10
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
7
11
  import { CommandBus } from '@open-mercato/shared/lib/commands'
8
12
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
9
13
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -80,6 +84,22 @@ export async function PUT(req: Request, ctx: { params?: { entityType?: string; e
80
84
  }
81
85
  const translations = translationBodySchema.parse(rawBody)
82
86
 
87
+ const guardUserId = resolveTranslationsActorId(context.auth)
88
+ const guardResult = await validateCrudMutationGuard(context.container, {
89
+ tenantId: context.tenantId,
90
+ organizationId: context.organizationId,
91
+ userId: guardUserId,
92
+ resourceKind: 'translations.translation',
93
+ resourceId: `${entityType}:${entityId}`,
94
+ operation: 'update',
95
+ requestMethod: req.method,
96
+ requestHeaders: req.headers,
97
+ mutationPayload: { entityType, entityId, translations },
98
+ })
99
+ if (guardResult && !guardResult.ok) {
100
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
101
+ }
102
+
83
103
  const commandBus = context.container.resolve('commandBus') as CommandBus
84
104
  const { result, logEntry } = await commandBus.execute<
85
105
  { entityType: string; entityId: string; translations: typeof translations; organizationId: string | null; tenantId: string },
@@ -95,6 +115,20 @@ export async function PUT(req: Request, ctx: { params?: { entityType?: string; e
95
115
  ctx: context.commandCtx,
96
116
  })
97
117
 
118
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
119
+ await runCrudMutationGuardAfterSuccess(context.container, {
120
+ tenantId: context.tenantId,
121
+ organizationId: context.organizationId,
122
+ userId: guardUserId,
123
+ resourceKind: 'translations.translation',
124
+ resourceId: `${entityType}:${entityId}`,
125
+ operation: 'update',
126
+ requestMethod: req.method,
127
+ requestHeaders: req.headers,
128
+ metadata: guardResult.metadata ?? null,
129
+ })
130
+ }
131
+
98
132
  const row = await (context.db as any)
99
133
  .selectFrom('entity_translations')
100
134
  .selectAll()
@@ -150,6 +184,22 @@ export async function DELETE(req: Request, ctx: { params?: { entityType?: string
150
184
  entityId: ctx.params?.entityId,
151
185
  })
152
186
 
187
+ const guardUserId = resolveTranslationsActorId(context.auth)
188
+ const guardResult = await validateCrudMutationGuard(context.container, {
189
+ tenantId: context.tenantId,
190
+ organizationId: context.organizationId,
191
+ userId: guardUserId,
192
+ resourceKind: 'translations.translation',
193
+ resourceId: `${entityType}:${entityId}`,
194
+ operation: 'delete',
195
+ requestMethod: req.method,
196
+ requestHeaders: req.headers,
197
+ mutationPayload: null,
198
+ })
199
+ if (guardResult && !guardResult.ok) {
200
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
201
+ }
202
+
153
203
  const commandBus = context.container.resolve('commandBus') as CommandBus
154
204
  const { logEntry } = await commandBus.execute<
155
205
  { entityType: string; entityId: string; organizationId: string | null; tenantId: string },
@@ -164,6 +214,20 @@ export async function DELETE(req: Request, ctx: { params?: { entityType?: string
164
214
  ctx: context.commandCtx,
165
215
  })
166
216
 
217
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
218
+ await runCrudMutationGuardAfterSuccess(context.container, {
219
+ tenantId: context.tenantId,
220
+ organizationId: context.organizationId,
221
+ userId: guardUserId,
222
+ resourceKind: 'translations.translation',
223
+ resourceId: `${entityType}:${entityId}`,
224
+ operation: 'delete',
225
+ requestMethod: req.method,
226
+ requestHeaders: req.headers,
227
+ metadata: guardResult.metadata ?? null,
228
+ })
229
+ }
230
+
167
231
  const response = new NextResponse(null, { status: 204 })
168
232
 
169
233
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
@@ -17,6 +17,13 @@ export type TranslationsRouteContext = {
17
17
  commandCtx: CommandRuntimeContext
18
18
  }
19
19
 
20
+ export function resolveTranslationsActorId(auth: TranslationsRouteContext['auth']): string {
21
+ if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub
22
+ if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId
23
+ if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId
24
+ return 'system'
25
+ }
26
+
20
27
  export async function resolveTranslationsRouteContext(req: Request): Promise<TranslationsRouteContext> {
21
28
  const container = await createRequestContainer()
22
29
  const auth = await getAuthFromRequest(req)
@@ -1,7 +1,11 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
- import { resolveTranslationsRouteContext } from '@open-mercato/core/modules/translations/api/context'
3
+ import { resolveTranslationsRouteContext, resolveTranslationsActorId } from '@open-mercato/core/modules/translations/api/context'
4
4
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import {
6
+ runCrudMutationGuardAfterSuccess,
7
+ validateCrudMutationGuard,
8
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
5
9
  import { isValidIso639 } from '@open-mercato/shared/lib/i18n/iso639'
6
10
  import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'
7
11
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -23,9 +27,39 @@ async function PUT(req: Request) {
23
27
  const body = bodySchema.parse(await req.json())
24
28
  const uniqueLocales = [...new Set(body.locales.map((l) => l.toLowerCase().trim()))]
25
29
 
30
+ const guardUserId = resolveTranslationsActorId(context.auth)
31
+ const guardResult = await validateCrudMutationGuard(context.container, {
32
+ tenantId: context.tenantId,
33
+ organizationId: context.organizationId,
34
+ userId: guardUserId,
35
+ resourceKind: 'translations.locales',
36
+ resourceId: 'supported_locales',
37
+ operation: 'custom',
38
+ requestMethod: req.method,
39
+ requestHeaders: req.headers,
40
+ mutationPayload: { locales: uniqueLocales },
41
+ })
42
+ if (guardResult && !guardResult.ok) {
43
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
44
+ }
45
+
26
46
  const configService = context.container.resolve('moduleConfigService') as ModuleConfigService
27
47
  await configService.setValue('translations', 'supported_locales', uniqueLocales)
28
48
 
49
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
50
+ await runCrudMutationGuardAfterSuccess(context.container, {
51
+ tenantId: context.tenantId,
52
+ organizationId: context.organizationId,
53
+ userId: guardUserId,
54
+ resourceKind: 'translations.locales',
55
+ resourceId: 'supported_locales',
56
+ operation: 'custom',
57
+ requestMethod: req.method,
58
+ requestHeaders: req.headers,
59
+ metadata: guardResult.metadata ?? null,
60
+ })
61
+ }
62
+
29
63
  return NextResponse.json({ locales: uniqueLocales })
30
64
  } catch (err) {
31
65
  if (isCrudHttpError(err)) {
@@ -6,10 +6,10 @@ import { NodeEditDialog } from '../../../components/NodeEditDialog'
6
6
  import { EdgeEditDialog } from '../../../components/EdgeEditDialog'
7
7
  import { NodeEditDialogCrudForm } from '../../../components/NodeEditDialogCrudForm'
8
8
  import { EdgeEditDialogCrudForm } from '../../../components/EdgeEditDialogCrudForm'
9
- import { Node, Edge, addEdge, Connection, applyNodeChanges, applyEdgeChanges, NodeChange, EdgeChange } from '@xyflow/react'
9
+ import type { Node, Edge, Connection } from '@xyflow/react'
10
10
  import { useState, useCallback, useEffect } from 'react'
11
11
  import { useRouter, useSearchParams } from 'next/navigation'
12
- import { graphToDefinition, definitionToGraph, validateWorkflowGraph, generateStepId, generateTransitionId, ValidationError } from '../../../lib/graph-utils'
12
+ import { graphToDefinition, definitionToGraph, validateWorkflowGraph, generateStepId, generateTransitionId, appendWorkflowEdge, ValidationError } from '../../../lib/graph-utils'
13
13
  import { performDeleteEdgeFlow, performDeleteNodeFlow } from '../../../lib/visual-editor-delete-flow'
14
14
  import { workflowDefinitionDataSchema } from '../../../data/validators'
15
15
  import { Page } from '@open-mercato/ui/backend/Page'
@@ -169,16 +169,18 @@ export default function VisualEditorPage() {
169
169
  loadDefinition()
170
170
  }, [definitionId])
171
171
 
172
- // Handle node changes from ReactFlow
173
- const handleNodesChange = useCallback((changes: NodeChange[]) => {
172
+ // Handle node changes from ReactFlow. The lazy graph applies React Flow's
173
+ // change reducers internally (#3169) and hands back the resolved nodes, so
174
+ // this page never imports the @xyflow/react runtime.
175
+ const handleNodesChange = useCallback((nextNodes: Node[]) => {
174
176
  if (isCodeOnly) return
175
- setNodes((nds) => applyNodeChanges(changes, nds))
177
+ setNodes(nextNodes)
176
178
  }, [isCodeOnly])
177
179
 
178
- // Handle edge changes from ReactFlow
179
- const handleEdgesChange = useCallback((changes: EdgeChange[]) => {
180
+ // Handle edge changes from ReactFlow (resolved edges from the lazy graph).
181
+ const handleEdgesChange = useCallback((nextEdges: Edge[]) => {
180
182
  if (isCodeOnly) return
181
- setEdges((eds) => applyEdgeChanges(changes, eds))
183
+ setEdges(nextEdges)
182
184
  }, [isCodeOnly])
183
185
 
184
186
  // Handle adding new node from palette
@@ -285,7 +287,7 @@ export default function VisualEditorPage() {
285
287
  },
286
288
  }
287
289
 
288
- setEdges((eds) => addEdge(newEdge, eds))
290
+ setEdges((eds) => appendWorkflowEdge(eds, newEdge))
289
291
  }, [])
290
292
 
291
293
  // Validate workflow
@@ -18,7 +18,7 @@ import { WorkflowLegend } from '../../../components/WorkflowLegend'
18
18
  import { MobileInstanceOverview } from '../../../components/mobile/MobileInstanceOverview'
19
19
  import { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'
20
20
  import { definitionToGraph } from '../../../lib/graph-utils'
21
- import { Node } from '@xyflow/react'
21
+ import type { Node } from '@xyflow/react'
22
22
  import { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'
23
23
 
24
24
  export default function WorkflowInstanceDetailPage({ params }: { params?: { id?: string } }) {
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import {Edge} from '@xyflow/react'
3
+ import type {Edge} from '@xyflow/react'
4
4
  import {useEffect, useState} from 'react'
5
5
  import {
6
6
  Dialog,
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { Edge } from '@xyflow/react'
3
+ import type { Edge } from '@xyflow/react'
4
4
  import { useState, useEffect, useCallback, useMemo } from 'react'
5
5
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@open-mercato/ui/primitives/dialog'
6
6
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import {Node} from '@xyflow/react'
3
+ import type {Node} from '@xyflow/react'
4
4
  import {useEffect, useState} from 'react'
5
5
  import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from '@open-mercato/ui/primitives/dialog'
6
6
  import {Button} from '@open-mercato/ui/primitives/button'
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { Node } from '@xyflow/react'
3
+ import type { Node } from '@xyflow/react'
4
4
  import { useState, useEffect, useCallback, useMemo } from 'react'
5
5
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@open-mercato/ui/primitives/dialog'
6
6
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -8,8 +8,8 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
8
8
  export interface WorkflowGraphProps {
9
9
  initialNodes?: Node[]
10
10
  initialEdges?: Edge[]
11
- onNodesChange?: (changes: any[]) => void
12
- onEdgesChange?: (changes: any[]) => void
11
+ onNodesChange?: (nodes: Node[]) => void
12
+ onEdgesChange?: (edges: Edge[]) => void
13
13
  onNodeClick?: (event: React.MouseEvent, node: Node) => void
14
14
  onEdgeClick?: (event: React.MouseEvent, edge: Edge) => void
15
15
  onConnect?: (connection: Connection) => void