@open-mercato/core 0.4.2-canary-da2b080494 → 0.4.2-canary-19703ca707

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 (449) hide show
  1. package/dist/generated/entities/notification/index.js +57 -0
  2. package/dist/generated/entities/notification/index.js.map +7 -0
  3. package/dist/generated/entities.ids.generated.js +5 -1
  4. package/dist/generated/entities.ids.generated.js.map +2 -2
  5. package/dist/generated/entity-fields-registry.js +2 -0
  6. package/dist/generated/entity-fields-registry.js.map +2 -2
  7. package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
  8. package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
  9. package/dist/modules/api_keys/backend/api-keys/page.js +1 -1
  10. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  11. package/dist/modules/attachments/components/AttachmentLibrary.js +4 -0
  12. package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
  13. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +2 -0
  14. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  15. package/dist/modules/auth/api/admin/nav.js +4 -3
  16. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  17. package/dist/modules/auth/api/login.js +25 -6
  18. package/dist/modules/auth/api/login.js.map +2 -2
  19. package/dist/modules/auth/api/profile/route.js +157 -0
  20. package/dist/modules/auth/api/profile/route.js.map +7 -0
  21. package/dist/modules/auth/api/reset/confirm.js +25 -2
  22. package/dist/modules/auth/api/reset/confirm.js.map +2 -2
  23. package/dist/modules/auth/api/reset.js +23 -0
  24. package/dist/modules/auth/api/reset.js.map +2 -2
  25. package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
  26. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  27. package/dist/modules/auth/api/users/route.js +4 -2
  28. package/dist/modules/auth/api/users/route.js.map +2 -2
  29. package/dist/modules/auth/backend/auth/profile/page.js +141 -0
  30. package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
  31. package/dist/modules/auth/backend/auth/profile/page.meta.js +13 -0
  32. package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
  33. package/dist/modules/auth/backend/roles/[id]/edit/page.js +4 -1
  34. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  35. package/dist/modules/auth/backend/roles/page.js +3 -3
  36. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  37. package/dist/modules/auth/backend/users/[id]/edit/page.js +18 -3
  38. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  39. package/dist/modules/auth/backend/users/create/page.js +15 -2
  40. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  41. package/dist/modules/auth/backend/users/page.js +3 -3
  42. package/dist/modules/auth/backend/users/page.js.map +2 -2
  43. package/dist/modules/auth/cli.js +25 -11
  44. package/dist/modules/auth/cli.js.map +2 -2
  45. package/dist/modules/auth/commands/users.js +59 -2
  46. package/dist/modules/auth/commands/users.js.map +2 -2
  47. package/dist/modules/auth/data/validators.js +6 -3
  48. package/dist/modules/auth/data/validators.js.map +2 -2
  49. package/dist/modules/auth/frontend/login.js +85 -1
  50. package/dist/modules/auth/frontend/login.js.map +2 -2
  51. package/dist/modules/auth/frontend/reset/[token]/page.js +20 -10
  52. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  53. package/dist/modules/auth/lib/setup-app.js +42 -8
  54. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  55. package/dist/modules/auth/notifications.js +112 -0
  56. package/dist/modules/auth/notifications.js.map +7 -0
  57. package/dist/modules/auth/services/authService.js +24 -3
  58. package/dist/modules/auth/services/authService.js.map +2 -2
  59. package/dist/modules/business_rules/api/execute/route.js +7 -1
  60. package/dist/modules/business_rules/api/execute/route.js.map +2 -2
  61. package/dist/modules/business_rules/backend/rules/page.js +4 -0
  62. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  63. package/dist/modules/business_rules/backend/sets/page.js +3 -0
  64. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  65. package/dist/modules/business_rules/cli.js +2 -1
  66. package/dist/modules/business_rules/cli.js.map +2 -2
  67. package/dist/modules/business_rules/lib/rule-engine.js +33 -3
  68. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  69. package/dist/modules/business_rules/notifications.js +28 -0
  70. package/dist/modules/business_rules/notifications.js.map +7 -0
  71. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
  72. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
  73. package/dist/modules/catalog/components/PriceKindSettings.js +2 -0
  74. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  75. package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -2
  76. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  77. package/dist/modules/catalog/components/products/ProductsDataTable.js +2 -0
  78. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  79. package/dist/modules/catalog/notifications.js +28 -0
  80. package/dist/modules/catalog/notifications.js.map +7 -0
  81. package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
  82. package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
  83. package/dist/modules/configs/cli.js +6 -0
  84. package/dist/modules/configs/cli.js.map +2 -2
  85. package/dist/modules/configs/components/CachePanel.js +4 -4
  86. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  87. package/dist/modules/configs/lib/system-status.js +48 -1
  88. package/dist/modules/configs/lib/system-status.js.map +2 -2
  89. package/dist/modules/configs/lib/upgrade-actions.js +18 -0
  90. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  91. package/dist/modules/currencies/backend/currencies/page.js +3 -0
  92. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  93. package/dist/modules/currencies/backend/exchange-rates/page.js +2 -0
  94. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  95. package/dist/modules/customers/backend/customers/companies/page.js +3 -0
  96. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  97. package/dist/modules/customers/backend/customers/deals/page.js +3 -0
  98. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  99. package/dist/modules/customers/backend/customers/people/page.js +3 -0
  100. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  101. package/dist/modules/customers/commands/deals.js +31 -0
  102. package/dist/modules/customers/commands/deals.js.map +2 -2
  103. package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
  104. package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
  105. package/dist/modules/customers/notifications.js +48 -0
  106. package/dist/modules/customers/notifications.js.map +7 -0
  107. package/dist/modules/dashboards/cli.js +44 -5
  108. package/dist/modules/dashboards/cli.js.map +2 -2
  109. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +16 -11
  110. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +3 -3
  111. package/dist/modules/dashboards/lib/role-widgets.js +58 -0
  112. package/dist/modules/dashboards/lib/role-widgets.js.map +7 -0
  113. package/dist/modules/dashboards/services/widgetDataService.js +139 -3
  114. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  115. package/dist/modules/dictionaries/components/DictionaryTable.js +2 -0
  116. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  117. package/dist/modules/directory/api/get/tenants/lookup.js +68 -0
  118. package/dist/modules/directory/api/get/tenants/lookup.js.map +7 -0
  119. package/dist/modules/directory/backend/directory/organizations/page.js +2 -2
  120. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  121. package/dist/modules/directory/backend/directory/tenants/page.js +2 -2
  122. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  123. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +2 -2
  124. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  125. package/dist/modules/entities/components/SystemEntitiesTable.js +1 -1
  126. package/dist/modules/entities/components/SystemEntitiesTable.js.map +2 -2
  127. package/dist/modules/entities/components/UserEntitiesTable.js +2 -2
  128. package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
  129. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +3 -3
  130. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  131. package/dist/modules/feature_toggles/components/OverridesTable.js +1 -1
  132. package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
  133. package/dist/modules/notifications/acl.js +11 -0
  134. package/dist/modules/notifications/acl.js.map +7 -0
  135. package/dist/modules/notifications/api/[id]/action/route.js +74 -0
  136. package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
  137. package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
  138. package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
  139. package/dist/modules/notifications/api/[id]/read/route.js +15 -0
  140. package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
  141. package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
  142. package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
  143. package/dist/modules/notifications/api/batch/route.js +17 -0
  144. package/dist/modules/notifications/api/batch/route.js.map +7 -0
  145. package/dist/modules/notifications/api/feature/route.js +17 -0
  146. package/dist/modules/notifications/api/feature/route.js.map +7 -0
  147. package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
  148. package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
  149. package/dist/modules/notifications/api/openapi.js +76 -0
  150. package/dist/modules/notifications/api/openapi.js.map +7 -0
  151. package/dist/modules/notifications/api/role/route.js +17 -0
  152. package/dist/modules/notifications/api/role/route.js.map +7 -0
  153. package/dist/modules/notifications/api/route.js +85 -0
  154. package/dist/modules/notifications/api/route.js.map +7 -0
  155. package/dist/modules/notifications/api/settings/route.js +155 -0
  156. package/dist/modules/notifications/api/settings/route.js.map +7 -0
  157. package/dist/modules/notifications/api/unread-count/route.js +38 -0
  158. package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
  159. package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
  160. package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
  161. package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
  162. package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
  163. package/dist/modules/notifications/cli.js +16 -0
  164. package/dist/modules/notifications/cli.js.map +7 -0
  165. package/dist/modules/notifications/data/entities.js +112 -0
  166. package/dist/modules/notifications/data/entities.js.map +7 -0
  167. package/dist/modules/notifications/data/validators.js +98 -0
  168. package/dist/modules/notifications/data/validators.js.map +7 -0
  169. package/dist/modules/notifications/di.js +13 -0
  170. package/dist/modules/notifications/di.js.map +7 -0
  171. package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
  172. package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
  173. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
  174. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
  175. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +220 -0
  176. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
  177. package/dist/modules/notifications/index.js +14 -0
  178. package/dist/modules/notifications/index.js.map +7 -0
  179. package/dist/modules/notifications/lib/deliveryConfig.js +107 -0
  180. package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
  181. package/dist/modules/notifications/lib/deliveryStrategies.js +14 -0
  182. package/dist/modules/notifications/lib/deliveryStrategies.js.map +7 -0
  183. package/dist/modules/notifications/lib/events.js +12 -0
  184. package/dist/modules/notifications/lib/events.js.map +7 -0
  185. package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
  186. package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
  187. package/dist/modules/notifications/lib/notificationFactory.js +54 -0
  188. package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
  189. package/dist/modules/notifications/lib/notificationMapper.js +34 -0
  190. package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
  191. package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
  192. package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
  193. package/dist/modules/notifications/lib/notificationService.js +279 -0
  194. package/dist/modules/notifications/lib/notificationService.js.map +7 -0
  195. package/dist/modules/notifications/lib/routeHelpers.js +101 -0
  196. package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
  197. package/dist/modules/notifications/lib/safeHref.js +24 -0
  198. package/dist/modules/notifications/lib/safeHref.js.map +7 -0
  199. package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
  200. package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
  201. package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
  202. package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
  203. package/dist/modules/notifications/subscribers/deliver-notification.js +165 -0
  204. package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
  205. package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
  206. package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
  207. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +2 -2
  208. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  209. package/dist/modules/query_index/components/QueryIndexesTable.js +7 -1
  210. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  211. package/dist/modules/resources/backend/resources/resource-types/page.js +2 -2
  212. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  213. package/dist/modules/resources/backend/resources/resources/page.js +2 -2
  214. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  215. package/dist/modules/sales/backend/sales/channels/offers/page.js +2 -0
  216. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  217. package/dist/modules/sales/backend/sales/channels/page.js +2 -0
  218. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  219. package/dist/modules/sales/commands/documents.js +53 -0
  220. package/dist/modules/sales/commands/documents.js.map +2 -2
  221. package/dist/modules/sales/commands/payments.js +26 -0
  222. package/dist/modules/sales/commands/payments.js.map +2 -2
  223. package/dist/modules/sales/components/AdjustmentKindSettings.js +2 -2
  224. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  225. package/dist/modules/sales/components/PaymentMethodsSettings.js +2 -2
  226. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  227. package/dist/modules/sales/components/ShippingMethodsSettings.js +2 -2
  228. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  229. package/dist/modules/sales/components/TaxRatesSettings.js +2 -2
  230. package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
  231. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +2 -0
  232. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  233. package/dist/modules/sales/components/documents/AdjustmentsSection.js +2 -0
  234. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  235. package/dist/modules/sales/components/documents/PaymentsSection.js +2 -1
  236. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  237. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -0
  238. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  239. package/dist/modules/sales/notifications.client.js +51 -0
  240. package/dist/modules/sales/notifications.client.js.map +7 -0
  241. package/dist/modules/sales/notifications.js +88 -0
  242. package/dist/modules/sales/notifications.js.map +7 -0
  243. package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
  244. package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
  245. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
  246. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
  247. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
  248. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
  249. package/dist/modules/sales/widgets/notifications/index.js +7 -0
  250. package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
  251. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
  252. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
  253. package/dist/modules/staff/backend/staff/team-members/page.js +1 -1
  254. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  255. package/dist/modules/staff/backend/staff/team-roles/page.js +2 -2
  256. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  257. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +2 -2
  258. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  259. package/dist/modules/staff/backend/staff/teams/page.js +2 -2
  260. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  261. package/dist/modules/staff/commands/leave-requests.js +79 -0
  262. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  263. package/dist/modules/staff/notifications.js +75 -0
  264. package/dist/modules/staff/notifications.js.map +7 -0
  265. package/dist/modules/workflows/backend/definitions/page.js +5 -0
  266. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  267. package/dist/modules/workflows/backend/instances/page.js +3 -0
  268. package/dist/modules/workflows/backend/instances/page.js.map +2 -2
  269. package/dist/modules/workflows/backend/tasks/page.js +3 -0
  270. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  271. package/dist/modules/workflows/cli.js +12 -12
  272. package/dist/modules/workflows/cli.js.map +2 -2
  273. package/dist/modules/workflows/lib/transition-handler.js +14 -6
  274. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  275. package/dist/modules/workflows/notifications.js +28 -0
  276. package/dist/modules/workflows/notifications.js.map +7 -0
  277. package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
  278. package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
  279. package/generated/entities/notification/index.ts +27 -0
  280. package/generated/entities.ids.generated.ts +5 -1
  281. package/generated/entity-fields-registry.ts +2 -0
  282. package/package.json +2 -2
  283. package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
  284. package/src/modules/api_keys/backend/api-keys/page.tsx +1 -1
  285. package/src/modules/attachments/components/AttachmentLibrary.tsx +4 -0
  286. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +2 -0
  287. package/src/modules/auth/README.md +1 -1
  288. package/src/modules/auth/__tests__/cli-setup-acl.test.ts +1 -1
  289. package/src/modules/auth/api/__tests__/login.test.ts +2 -0
  290. package/src/modules/auth/api/admin/nav.ts +10 -6
  291. package/src/modules/auth/api/login.ts +26 -7
  292. package/src/modules/auth/api/profile/route.ts +163 -0
  293. package/src/modules/auth/api/reset/confirm.ts +25 -2
  294. package/src/modules/auth/api/reset.ts +23 -0
  295. package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
  296. package/src/modules/auth/api/users/route.ts +5 -2
  297. package/src/modules/auth/backend/auth/profile/page.meta.ts +9 -0
  298. package/src/modules/auth/backend/auth/profile/page.tsx +174 -0
  299. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +4 -1
  300. package/src/modules/auth/backend/roles/page.tsx +3 -3
  301. package/src/modules/auth/backend/users/[id]/edit/page.tsx +22 -3
  302. package/src/modules/auth/backend/users/create/page.tsx +19 -2
  303. package/src/modules/auth/backend/users/page.tsx +3 -3
  304. package/src/modules/auth/cli.ts +38 -11
  305. package/src/modules/auth/commands/users.ts +73 -2
  306. package/src/modules/auth/data/validators.ts +6 -2
  307. package/src/modules/auth/frontend/login.tsx +106 -2
  308. package/src/modules/auth/frontend/reset/[token]/page.tsx +24 -11
  309. package/src/modules/auth/i18n/de.json +48 -1
  310. package/src/modules/auth/i18n/en.json +48 -1
  311. package/src/modules/auth/i18n/es.json +48 -1
  312. package/src/modules/auth/i18n/pl.json +48 -1
  313. package/src/modules/auth/lib/setup-app.ts +58 -9
  314. package/src/modules/auth/notifications.ts +109 -0
  315. package/src/modules/auth/services/authService.ts +27 -4
  316. package/src/modules/business_rules/api/execute/route.ts +8 -1
  317. package/src/modules/business_rules/backend/rules/page.tsx +4 -0
  318. package/src/modules/business_rules/backend/sets/page.tsx +3 -0
  319. package/src/modules/business_rules/cli.ts +2 -1
  320. package/src/modules/business_rules/i18n/en.json +3 -1
  321. package/src/modules/business_rules/lib/__tests__/rule-engine.test.ts +51 -0
  322. package/src/modules/business_rules/lib/rule-engine.ts +57 -3
  323. package/src/modules/business_rules/notifications.ts +25 -0
  324. package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
  325. package/src/modules/catalog/components/PriceKindSettings.tsx +2 -0
  326. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +2 -2
  327. package/src/modules/catalog/components/products/ProductsDataTable.tsx +2 -0
  328. package/src/modules/catalog/i18n/en.json +3 -1
  329. package/src/modules/catalog/notifications.ts +25 -0
  330. package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
  331. package/src/modules/configs/cli.ts +6 -0
  332. package/src/modules/configs/components/CachePanel.tsx +4 -4
  333. package/src/modules/configs/i18n/en.json +12 -2
  334. package/src/modules/configs/i18n/pl.json +12 -2
  335. package/src/modules/configs/lib/system-status.ts +48 -1
  336. package/src/modules/configs/lib/system-status.types.ts +1 -0
  337. package/src/modules/configs/lib/upgrade-actions.ts +18 -0
  338. package/src/modules/currencies/backend/currencies/page.tsx +3 -0
  339. package/src/modules/currencies/backend/exchange-rates/page.tsx +2 -0
  340. package/src/modules/customers/backend/customers/companies/page.tsx +3 -0
  341. package/src/modules/customers/backend/customers/deals/page.tsx +3 -0
  342. package/src/modules/customers/backend/customers/people/page.tsx +3 -0
  343. package/src/modules/customers/commands/deals.ts +39 -0
  344. package/src/modules/customers/components/CustomerTodosTable.tsx +1 -0
  345. package/src/modules/customers/i18n/en.json +5 -1
  346. package/src/modules/customers/notifications.ts +44 -0
  347. package/src/modules/dashboards/cli.ts +55 -5
  348. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +22 -11
  349. package/src/modules/dashboards/lib/role-widgets.ts +80 -0
  350. package/src/modules/dashboards/services/widgetDataService.ts +164 -4
  351. package/src/modules/dictionaries/components/DictionaryTable.tsx +2 -0
  352. package/src/modules/directory/api/get/tenants/lookup.ts +73 -0
  353. package/src/modules/directory/backend/directory/organizations/page.tsx +2 -2
  354. package/src/modules/directory/backend/directory/tenants/page.tsx +2 -2
  355. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +2 -2
  356. package/src/modules/entities/components/SystemEntitiesTable.tsx +1 -1
  357. package/src/modules/entities/components/UserEntitiesTable.tsx +2 -2
  358. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +3 -4
  359. package/src/modules/feature_toggles/components/OverridesTable.tsx +1 -1
  360. package/src/modules/notifications/__tests__/deliver-notification.test.ts +195 -0
  361. package/src/modules/notifications/__tests__/deliveryStrategies.test.ts +19 -0
  362. package/src/modules/notifications/__tests__/notificationService.test.ts +208 -0
  363. package/src/modules/notifications/acl.ts +7 -0
  364. package/src/modules/notifications/api/[id]/action/route.ts +75 -0
  365. package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
  366. package/src/modules/notifications/api/[id]/read/route.ts +12 -0
  367. package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
  368. package/src/modules/notifications/api/batch/route.ts +14 -0
  369. package/src/modules/notifications/api/feature/route.ts +14 -0
  370. package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
  371. package/src/modules/notifications/api/openapi.ts +76 -0
  372. package/src/modules/notifications/api/role/route.ts +14 -0
  373. package/src/modules/notifications/api/route.ts +92 -0
  374. package/src/modules/notifications/api/settings/route.ts +157 -0
  375. package/src/modules/notifications/api/unread-count/route.ts +38 -0
  376. package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
  377. package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
  378. package/src/modules/notifications/cli.ts +18 -0
  379. package/src/modules/notifications/data/entities.ts +99 -0
  380. package/src/modules/notifications/data/validators.ts +115 -0
  381. package/src/modules/notifications/di.ts +11 -0
  382. package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
  383. package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
  384. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +233 -0
  385. package/src/modules/notifications/i18n/de.json +50 -0
  386. package/src/modules/notifications/i18n/en.json +50 -0
  387. package/src/modules/notifications/i18n/es.json +50 -0
  388. package/src/modules/notifications/i18n/pl.json +50 -0
  389. package/src/modules/notifications/index.ts +12 -0
  390. package/src/modules/notifications/lib/deliveryConfig.ts +153 -0
  391. package/src/modules/notifications/lib/deliveryStrategies.ts +50 -0
  392. package/src/modules/notifications/lib/events.ts +48 -0
  393. package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
  394. package/src/modules/notifications/lib/notificationFactory.ts +76 -0
  395. package/src/modules/notifications/lib/notificationMapper.ts +33 -0
  396. package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
  397. package/src/modules/notifications/lib/notificationService.ts +414 -0
  398. package/src/modules/notifications/lib/routeHelpers.ts +151 -0
  399. package/src/modules/notifications/lib/safeHref.ts +29 -0
  400. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
  401. package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
  402. package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
  403. package/src/modules/notifications/subscribers/deliver-notification.ts +204 -0
  404. package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
  405. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +2 -2
  406. package/src/modules/query_index/components/QueryIndexesTable.tsx +8 -2
  407. package/src/modules/resources/backend/resources/resource-types/page.tsx +2 -2
  408. package/src/modules/resources/backend/resources/resources/page.tsx +2 -2
  409. package/src/modules/sales/backend/sales/channels/offers/page.tsx +2 -0
  410. package/src/modules/sales/backend/sales/channels/page.tsx +2 -0
  411. package/src/modules/sales/commands/documents.ts +65 -0
  412. package/src/modules/sales/commands/payments.ts +33 -0
  413. package/src/modules/sales/components/AdjustmentKindSettings.tsx +2 -2
  414. package/src/modules/sales/components/PaymentMethodsSettings.tsx +2 -2
  415. package/src/modules/sales/components/ShippingMethodsSettings.tsx +2 -2
  416. package/src/modules/sales/components/TaxRatesSettings.tsx +2 -2
  417. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +2 -0
  418. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +2 -0
  419. package/src/modules/sales/components/documents/PaymentsSection.tsx +2 -1
  420. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -0
  421. package/src/modules/sales/i18n/de.json +20 -0
  422. package/src/modules/sales/i18n/en.json +25 -1
  423. package/src/modules/sales/i18n/es.json +20 -0
  424. package/src/modules/sales/i18n/pl.json +20 -0
  425. package/src/modules/sales/notifications.client.ts +65 -0
  426. package/src/modules/sales/notifications.ts +82 -0
  427. package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
  428. package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
  429. package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
  430. package/src/modules/sales/widgets/notifications/index.ts +2 -0
  431. package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
  432. package/src/modules/staff/backend/staff/team-members/page.tsx +1 -1
  433. package/src/modules/staff/backend/staff/team-roles/page.tsx +2 -2
  434. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +2 -2
  435. package/src/modules/staff/backend/staff/teams/page.tsx +2 -2
  436. package/src/modules/staff/commands/leave-requests.ts +94 -0
  437. package/src/modules/staff/i18n/de.json +4 -0
  438. package/src/modules/staff/i18n/en.json +9 -1
  439. package/src/modules/staff/i18n/es.json +4 -0
  440. package/src/modules/staff/i18n/pl.json +4 -0
  441. package/src/modules/staff/notifications.ts +71 -0
  442. package/src/modules/workflows/backend/definitions/page.tsx +5 -0
  443. package/src/modules/workflows/backend/instances/page.tsx +4 -1
  444. package/src/modules/workflows/backend/tasks/page.tsx +4 -1
  445. package/src/modules/workflows/cli.ts +12 -12
  446. package/src/modules/workflows/i18n/en.json +3 -1
  447. package/src/modules/workflows/lib/transition-handler.ts +18 -6
  448. package/src/modules/workflows/notifications.ts +25 -0
  449. package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
@@ -15,6 +15,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
15
15
  import { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'
16
16
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
17
17
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
18
+ import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
18
19
 
19
20
  const querySchema = z.object({
20
21
  id: z.string().uuid().optional(),
@@ -27,9 +28,11 @@ const querySchema = z.object({
27
28
 
28
29
  const rawBodySchema = z.object({}).passthrough()
29
30
 
31
+ const passwordSchema = buildPasswordSchema()
32
+
30
33
  const userCreateSchema = z.object({
31
34
  email: z.string().email(),
32
- password: z.string().min(6),
35
+ password: passwordSchema,
33
36
  organizationId: z.string().uuid(),
34
37
  roles: z.array(z.string()).optional(),
35
38
  })
@@ -37,7 +40,7 @@ const userCreateSchema = z.object({
37
40
  const userUpdateSchema = z.object({
38
41
  id: z.string().uuid(),
39
42
  email: z.string().email().optional(),
40
- password: z.string().min(6).optional(),
43
+ password: passwordSchema.optional(),
41
44
  organizationId: z.string().uuid().optional(),
42
45
  roles: z.array(z.string()).optional(),
43
46
  })
@@ -0,0 +1,9 @@
1
+ export const metadata = {
2
+ requireAuth: true,
3
+ navHidden: true,
4
+ pageTitle: 'Profile',
5
+ pageTitleKey: 'auth.profile.title',
6
+ breadcrumb: [
7
+ { label: 'Profile', labelKey: 'auth.profile.title' },
8
+ ],
9
+ }
@@ -0,0 +1,174 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import { useRouter } from 'next/navigation'
4
+ import { z } from 'zod'
5
+ import { Save } from 'lucide-react'
6
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
+ import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
8
+ import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
9
+ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
10
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
11
+ import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
12
+ import { Button } from '@open-mercato/ui/primitives/button'
13
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
14
+ import { buildPasswordSchema, formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
15
+
16
+ type ProfileResponse = {
17
+ email?: string | null
18
+ }
19
+
20
+ type ProfileUpdateResponse = {
21
+ ok?: boolean
22
+ email?: string | null
23
+ }
24
+
25
+ type ProfileFormValues = {
26
+ email: string
27
+ password?: string
28
+ confirmPassword?: string
29
+ }
30
+
31
+ export default function AuthProfilePage() {
32
+ const t = useT()
33
+ const router = useRouter()
34
+ const [loading, setLoading] = React.useState(true)
35
+ const [error, setError] = React.useState<string | null>(null)
36
+ const [email, setEmail] = React.useState('')
37
+ const [formKey, setFormKey] = React.useState(0)
38
+ const formId = React.useId()
39
+ const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])
40
+ const passwordRequirements = React.useMemo(
41
+ () => formatPasswordRequirements(passwordPolicy, t),
42
+ [passwordPolicy, t],
43
+ )
44
+ const passwordDescription = React.useMemo(() => (
45
+ passwordRequirements
46
+ ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })
47
+ : undefined
48
+ ), [passwordRequirements, t])
49
+
50
+ React.useEffect(() => {
51
+ let cancelled = false
52
+ async function load() {
53
+ setLoading(true)
54
+ setError(null)
55
+ try {
56
+ const { ok, result } = await apiCall<ProfileResponse>('/api/auth/profile')
57
+ if (!ok) throw new Error('load_failed')
58
+ const resolvedEmail = typeof result?.email === 'string' ? result.email : ''
59
+ if (!cancelled) setEmail(resolvedEmail)
60
+ } catch (err) {
61
+ console.error('Failed to load auth profile', err)
62
+ if (!cancelled) setError(t('auth.profile.form.errors.load', 'Failed to load profile.'))
63
+ } finally {
64
+ if (!cancelled) setLoading(false)
65
+ }
66
+ }
67
+ load()
68
+ return () => { cancelled = true }
69
+ }, [t])
70
+
71
+ const fields = React.useMemo<CrudField[]>(() => [
72
+ { id: 'email', label: t('auth.profile.form.email', 'Email'), type: 'text', required: true },
73
+ {
74
+ id: 'password',
75
+ label: t('auth.profile.form.password', 'New password'),
76
+ type: 'text',
77
+ description: passwordDescription,
78
+ },
79
+ { id: 'confirmPassword', label: t('auth.profile.form.confirmPassword', 'Confirm new password'), type: 'text' },
80
+ ], [passwordDescription, t])
81
+
82
+ const schema = React.useMemo(() => {
83
+ const passwordSchema = buildPasswordSchema({
84
+ policy: passwordPolicy,
85
+ message: t('auth.profile.form.errors.passwordRequirements', 'Password must meet the requirements.'),
86
+ })
87
+ const optionalPasswordSchema = z.union([z.literal(''), passwordSchema]).optional()
88
+ return z.object({
89
+ email: z.string().trim().min(1, t('auth.profile.form.errors.emailRequired', 'Email is required.')),
90
+ password: optionalPasswordSchema,
91
+ confirmPassword: z.string().optional(),
92
+ }).superRefine((values, ctx) => {
93
+ const password = values.password?.trim() ?? ''
94
+ const confirmPassword = values.confirmPassword?.trim() ?? ''
95
+ if ((password || confirmPassword) && password !== confirmPassword) {
96
+ ctx.addIssue({
97
+ code: z.ZodIssueCode.custom,
98
+ message: t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'),
99
+ path: ['confirmPassword'],
100
+ })
101
+ }
102
+ })
103
+ }, [passwordPolicy, t])
104
+
105
+ const handleSubmit = React.useCallback(async (values: ProfileFormValues) => {
106
+ const nextEmail = values.email?.trim() ?? ''
107
+ const password = values.password?.trim() ?? ''
108
+
109
+ if (!password && nextEmail === email) {
110
+ throw createCrudFormError(t('auth.profile.form.errors.noChanges', 'No changes to save.'))
111
+ }
112
+
113
+ const payload: { email: string; password?: string } = { email: nextEmail }
114
+ if (password) payload.password = password
115
+
116
+ const result = await readApiResultOrThrow<ProfileUpdateResponse>(
117
+ '/api/auth/profile',
118
+ {
119
+ method: 'PUT',
120
+ headers: { 'content-type': 'application/json' },
121
+ body: JSON.stringify(payload),
122
+ },
123
+ { errorMessage: t('auth.profile.form.errors.save', 'Failed to update profile.') },
124
+ )
125
+
126
+ const resolvedEmail = typeof result?.email === 'string' ? result.email : nextEmail
127
+ setEmail(resolvedEmail)
128
+ setFormKey((prev) => prev + 1)
129
+ flash(t('auth.profile.form.success', 'Profile updated.'), 'success')
130
+ router.refresh()
131
+ }, [email, router, t])
132
+
133
+ return (
134
+ <Page>
135
+ <PageBody>
136
+ {loading ? (
137
+ <LoadingMessage label={t('auth.profile.form.loading', 'Loading profile...')} />
138
+ ) : error ? (
139
+ <ErrorMessage label={error} />
140
+ ) : (
141
+ <section className="space-y-6 rounded-lg border bg-background p-6">
142
+ <header className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
143
+ <div className="space-y-1">
144
+ <h2 className="text-lg font-semibold">{t('auth.profile.title', 'Profile')}</h2>
145
+ <p className="text-sm text-muted-foreground">
146
+ {t('auth.profile.subtitle', 'Change password')}
147
+ </p>
148
+ </div>
149
+ <Button type="submit" form={formId}>
150
+ <Save className="size-4 mr-2" />
151
+ {t('auth.profile.form.save', 'Save changes')}
152
+ </Button>
153
+ </header>
154
+ <CrudForm<ProfileFormValues>
155
+ key={formKey}
156
+ formId={formId}
157
+ schema={schema}
158
+ fields={fields}
159
+ initialValues={{
160
+ email,
161
+ password: '',
162
+ confirmPassword: '',
163
+ }}
164
+ submitLabel={t('auth.profile.form.save', 'Save changes')}
165
+ onSubmit={handleSubmit}
166
+ embedded
167
+ hideFooterActions
168
+ />
169
+ </section>
170
+ )}
171
+ </PageBody>
172
+ </Page>
173
+ )
174
+ }
@@ -6,7 +6,7 @@ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
6
6
  import { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
7
7
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
8
8
  import { AclEditor, type AclData } from '@open-mercato/core/modules/auth/components/AclEditor'
9
- import { WidgetVisibilityEditor } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'
9
+ import { WidgetVisibilityEditor, type WidgetVisibilityEditorHandle } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'
10
10
  import { E } from '#generated/entities.ids.generated'
11
11
  import { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'
12
12
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -37,6 +37,7 @@ export default function EditRolePage({ params }: { params?: { id?: string } }) {
37
37
  const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })
38
38
  const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)
39
39
  const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)
40
+ const widgetEditorRef = React.useRef<WidgetVisibilityEditorHandle | null>(null)
40
41
 
41
42
  React.useEffect(() => {
42
43
  if (!id) return
@@ -153,6 +154,7 @@ export default function EditRolePage({ params }: { params?: { id?: string } }) {
153
154
  kind="role"
154
155
  targetId={String(id)}
155
156
  tenantId={selectedTenantId ?? (initial?.tenantId ?? null)}
157
+ ref={widgetEditorRef}
156
158
  />
157
159
  )
158
160
  : null),
@@ -191,6 +193,7 @@ export default function EditRolePage({ params }: { params?: { id?: string } }) {
191
193
  await updateCrud('auth/roles/acl', { roleId: id, tenantId: effectiveTenantId, ...aclData }, {
192
194
  errorMessage: t('auth.roles.form.errors.aclUpdate', 'Failed to update role access control'),
193
195
  })
196
+ await widgetEditorRef.current?.save()
194
197
  try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}
195
198
  }}
196
199
  onDelete={async () => {
@@ -117,9 +117,9 @@ export default function RolesListPage() {
117
117
  onSearchChange={(v) => { setSearch(v); setPage(1) }}
118
118
  rowActions={(row) => (
119
119
  <RowActions items={[
120
- { label: t('common.edit', 'Edit'), href: `/backend/roles/${row.id}/edit` },
121
- { label: t('auth.roles.list.actions.showUsers', 'Show users'), href: `/backend/users?roleId=${encodeURIComponent(row.id)}` },
122
- { label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
120
+ { id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/roles/${row.id}/edit` },
121
+ { id: 'show-users', label: t('auth.roles.list.actions.showUsers', 'Show users'), href: `/backend/users?roleId=${encodeURIComponent(row.id)}` },
122
+ { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
123
123
  ]} />
124
124
  )}
125
125
  sortable
@@ -10,8 +10,9 @@ import { AclEditor, type AclData } from '@open-mercato/core/modules/auth/compone
10
10
  import { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'
11
11
  import { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'
12
12
  import { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'
13
- import { WidgetVisibilityEditor } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'
13
+ import { WidgetVisibilityEditor, type WidgetVisibilityEditorHandle } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'
14
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
15
+ import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
15
16
 
16
17
  type EditUserFormValues = {
17
18
  email: string
@@ -108,6 +109,17 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
108
109
  const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })
109
110
  const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})
110
111
  const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)
112
+ const widgetEditorRef = React.useRef<WidgetVisibilityEditorHandle | null>(null)
113
+ const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])
114
+ const passwordRequirements = React.useMemo(
115
+ () => formatPasswordRequirements(passwordPolicy, t),
116
+ [passwordPolicy, t],
117
+ )
118
+ const passwordDescription = React.useMemo(() => (
119
+ passwordRequirements
120
+ ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })
121
+ : undefined
122
+ ), [passwordRequirements, t])
111
123
 
112
124
  React.useEffect(() => {
113
125
  if (!id) {
@@ -201,7 +213,12 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
201
213
  const fields: CrudField[] = React.useMemo(() => {
202
214
  const items: CrudField[] = [
203
215
  { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },
204
- { id: 'password', label: t('auth.users.form.field.password', 'Password'), type: 'text' },
216
+ {
217
+ id: 'password',
218
+ label: t('auth.users.form.field.password', 'Password'),
219
+ type: 'text',
220
+ description: passwordDescription,
221
+ },
205
222
  ]
206
223
  if (actorIsSuperAdmin) {
207
224
  items.push({
@@ -251,7 +268,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
251
268
  })
252
269
  items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })
253
270
  return items
254
- }, [actorIsSuperAdmin, loadRoleOptions, preloadedTenants, selectedOrgId, selectedTenantId, t])
271
+ }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t])
255
272
 
256
273
  const detailFieldIds = React.useMemo(() => {
257
274
  const base: string[] = ['email', 'password', 'organizationId', 'roles']
@@ -292,6 +309,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
292
309
  targetId={String(id)}
293
310
  tenantId={selectedTenantId ?? null}
294
311
  organizationId={initialUser?.organizationId ?? null}
312
+ ref={widgetEditorRef}
295
313
  />
296
314
  ) : null
297
315
  ),
@@ -354,6 +372,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
354
372
  await updateCrud('auth/users/acl', { userId: id, ...aclData }, {
355
373
  errorMessage: t('auth.users.form.errors.aclUpdate', 'Failed to update user access control'),
356
374
  })
375
+ await widgetEditorRef.current?.save()
357
376
  try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}
358
377
  }}
359
378
  onDelete={async () => {
@@ -11,6 +11,7 @@ import { TenantSelect } from '@open-mercato/core/modules/directory/components/Te
11
11
  import { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'
12
12
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
13
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
+ import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
14
15
 
15
16
  type CreateUserFormValues = {
16
17
  email: string
@@ -84,6 +85,16 @@ export default function CreateUserPage() {
84
85
  const [selectedWidgets, setSelectedWidgets] = React.useState<string[]>([])
85
86
  const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)
86
87
  const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)
88
+ const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])
89
+ const passwordRequirements = React.useMemo(
90
+ () => formatPasswordRequirements(passwordPolicy, t),
91
+ [passwordPolicy, t],
92
+ )
93
+ const passwordDescription = React.useMemo(() => (
94
+ passwordRequirements
95
+ ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })
96
+ : undefined
97
+ ), [passwordRequirements, t])
87
98
 
88
99
  React.useEffect(() => {
89
100
  let cancelled = false
@@ -156,7 +167,13 @@ export default function CreateUserPage() {
156
167
  const fields: CrudField[] = React.useMemo(() => {
157
168
  const items: CrudField[] = [
158
169
  { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },
159
- { id: 'password', label: t('auth.users.form.field.password', 'Password'), type: 'text', required: true },
170
+ {
171
+ id: 'password',
172
+ label: t('auth.users.form.field.password', 'Password'),
173
+ type: 'text',
174
+ required: true,
175
+ description: passwordDescription,
176
+ },
160
177
  ]
161
178
  if (actorIsSuperAdmin) {
162
179
  items.push({
@@ -203,7 +220,7 @@ export default function CreateUserPage() {
203
220
  })
204
221
  items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })
205
222
  return items
206
- }, [actorIsSuperAdmin, loadRoleOptions, selectedTenantId, t])
223
+ }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, selectedTenantId, t])
207
224
 
208
225
  const detailFieldIds = React.useMemo(() => {
209
226
  const base: string[] = ['email', 'password', 'organizationId', 'roles']
@@ -383,9 +383,9 @@ export default function UsersListPage() {
383
383
  perspective={{ tableId: 'auth.users.list' }}
384
384
  rowActions={(row) => (
385
385
  <RowActions items={[
386
- { label: t('common.edit', 'Edit'), href: `/backend/users/${row.id}/edit` },
387
- { label: t('auth.users.list.actions.showRoles', 'Show roles'), href: `/backend/roles?userId=${encodeURIComponent(row.id)}` },
388
- { label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
386
+ { id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/users/${row.id}/edit` },
387
+ { id: 'show-roles', label: t('auth.users.list.actions.showRoles', 'Show roles'), href: `/backend/roles?userId=${encodeURIComponent(row.id)}` },
388
+ { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
389
389
  ]} />
390
390
  )}
391
391
  pagination={{ page, pageSize: 50, total, totalPages, onPageChange: setPage }}
@@ -16,6 +16,8 @@ import { decryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'
16
16
  import { env } from 'process'
17
17
  import type { KmsService, TenantDek } from '@open-mercato/shared/lib/encryption/kms'
18
18
  import crypto from 'node:crypto'
19
+ import { formatPasswordRequirements, getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'
20
+ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
19
21
 
20
22
  const addUser: ModuleCli = {
21
23
  command: 'add-user',
@@ -34,6 +36,7 @@ const addUser: ModuleCli = {
34
36
  console.error('Usage: mercato auth add-user --email <email> --password <password> --organizationId <id> [--roles customer,employee]')
35
37
  return
36
38
  }
39
+ if (!ensurePasswordPolicy(password)) return
37
40
  const { resolve } = await createRequestContainer()
38
41
  const em = resolve('em') as any
39
42
  const org =
@@ -102,6 +105,16 @@ function hashSecret(value: string | null | undefined): string | null {
102
105
  return crypto.createHash('sha256').update(normalizeKeyInput(value)).digest('hex').slice(0, 12)
103
106
  }
104
107
 
108
+ function ensurePasswordPolicy(password: string): boolean {
109
+ const policy = getPasswordPolicy()
110
+ const result = validatePassword(password, policy)
111
+ if (result.ok) return true
112
+ const requirements = formatPasswordRequirements(policy, (_key, fallback) => fallback)
113
+ const suffix = requirements ? `: ${requirements}` : ''
114
+ console.error(`Password does not meet the requirements${suffix}.`)
115
+ return false
116
+ }
117
+
105
118
  async function withEncryptionDebugDisabled<T>(fn: () => Promise<T>): Promise<T> {
106
119
  const previous = process.env.TENANT_DATA_ENCRYPTION_DEBUG
107
120
  process.env.TENANT_DATA_ENCRYPTION_DEBUG = 'no'
@@ -392,20 +405,33 @@ const addOrganization: ModuleCli = {
392
405
  const setupApp: ModuleCli = {
393
406
  command: 'setup',
394
407
  async run(rest) {
395
- const args: Record<string, string> = {}
396
- for (let i = 0; i < rest.length; i += 2) {
397
- const k = rest[i]?.replace(/^--/, '')
398
- const v = rest[i + 1]
399
- if (k) args[k] = v
400
- }
401
- const orgName = args.orgName || args.name
402
- const email = args.email
403
- const password = args.password
404
- const rolesCsv = (args.roles ?? 'superadmin,admin,employee').trim()
408
+ const args = parseArgs(rest)
409
+ const orgName = typeof args.orgName === 'string'
410
+ ? args.orgName
411
+ : typeof args.name === 'string'
412
+ ? args.name
413
+ : undefined
414
+ const email = typeof args.email === 'string' ? args.email : undefined
415
+ const password = typeof args.password === 'string' ? args.password : undefined
416
+ const rolesCsv = typeof args.roles === 'string'
417
+ ? args.roles.trim()
418
+ : 'superadmin,admin,employee'
419
+ const skipPasswordPolicyRaw =
420
+ args['skip-password-policy'] ??
421
+ args.skipPasswordPolicy ??
422
+ args['allow-weak-password'] ??
423
+ args.allowWeakPassword
424
+ const skipPasswordPolicy = typeof skipPasswordPolicyRaw === 'boolean'
425
+ ? skipPasswordPolicyRaw
426
+ : parseBooleanToken(typeof skipPasswordPolicyRaw === 'string' ? skipPasswordPolicyRaw : null) ?? false
405
427
  if (!orgName || !email || !password) {
406
- console.error('Usage: mercato auth setup --orgName <name> --email <email> --password <password> [--roles superadmin,admin,employee]')
428
+ console.error('Usage: mercato auth setup --orgName <name> --email <email> --password <password> [--roles superadmin,admin,employee] [--skip-password-policy]')
407
429
  return
408
430
  }
431
+ if (!skipPasswordPolicy && !ensurePasswordPolicy(password)) return
432
+ if (skipPasswordPolicy) {
433
+ console.warn('⚠️ Password policy validation skipped for setup.')
434
+ }
409
435
  const { resolve } = await createRequestContainer()
410
436
  const em = resolve<EntityManager>('em')
411
437
  const roleNames = rolesCsv
@@ -595,6 +621,7 @@ const setPassword: ModuleCli = {
595
621
  console.error('Usage: mercato auth set-password --email <email> --password <newPassword>')
596
622
  return
597
623
  }
624
+ if (!ensurePasswordPolicy(password)) return
598
625
 
599
626
  const { resolve } = await createRequestContainer()
600
627
  const em = resolve('em') as any
@@ -27,6 +27,10 @@ import {
27
27
  import { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'
28
28
  import { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'
29
29
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
30
+ import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
31
+ import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
32
+ import notificationTypes from '@open-mercato/core/modules/auth/notifications'
33
+ import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
30
34
 
31
35
  type SerializedUser = {
32
36
  email: string
@@ -63,9 +67,11 @@ type UserSnapshots = {
63
67
  undo: UserUndoSnapshot
64
68
  }
65
69
 
70
+ const passwordSchema = buildPasswordSchema()
71
+
66
72
  const createSchema = z.object({
67
73
  email: z.string().email(),
68
- password: z.string().min(6),
74
+ password: passwordSchema,
69
75
  organizationId: z.string().uuid(),
70
76
  roles: z.array(z.string()).optional(),
71
77
  })
@@ -73,7 +79,7 @@ const createSchema = z.object({
73
79
  const updateSchema = z.object({
74
80
  id: z.string().uuid(),
75
81
  email: z.string().email().optional(),
76
- password: z.string().min(6).optional(),
82
+ password: passwordSchema.optional(),
77
83
  organizationId: z.string().uuid().optional(),
78
84
  roles: z.array(z.string()).optional(),
79
85
  })
@@ -105,6 +111,46 @@ export const userCrudIndexer: CrudIndexerConfig = {
105
111
  }),
106
112
  }
107
113
 
114
+ async function notifyRoleChanges(
115
+ ctx: CommandRuntimeContext,
116
+ user: User,
117
+ assignedRoles: string[],
118
+ revokedRoles: string[],
119
+ ): Promise<void> {
120
+ const tenantId = user.tenantId ? String(user.tenantId) : null
121
+ if (!tenantId) return
122
+ const organizationId = user.organizationId ? String(user.organizationId) : null
123
+
124
+ try {
125
+ const notificationService = resolveNotificationService(ctx.container)
126
+ if (assignedRoles.length) {
127
+ const assignedType = notificationTypes.find((type) => type.type === 'auth.role.assigned')
128
+ if (assignedType) {
129
+ const notificationInput = buildNotificationFromType(assignedType, {
130
+ recipientUserId: String(user.id),
131
+ sourceEntityType: 'auth:user',
132
+ sourceEntityId: String(user.id),
133
+ })
134
+ await notificationService.create(notificationInput, { tenantId, organizationId })
135
+ }
136
+ }
137
+
138
+ if (revokedRoles.length) {
139
+ const revokedType = notificationTypes.find((type) => type.type === 'auth.role.revoked')
140
+ if (revokedType) {
141
+ const notificationInput = buildNotificationFromType(revokedType, {
142
+ recipientUserId: String(user.id),
143
+ sourceEntityType: 'auth:user',
144
+ sourceEntityId: String(user.id),
145
+ })
146
+ await notificationService.create(notificationInput, { tenantId, organizationId })
147
+ }
148
+ }
149
+ } catch (err) {
150
+ console.error('[auth.users.roles] Failed to create notification:', err)
151
+ }
152
+ }
153
+
108
154
  const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
109
155
  id: 'auth.users.create',
110
156
  async execute(rawInput, ctx) {
@@ -147,8 +193,10 @@ const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
147
193
  throw error
148
194
  }
149
195
 
196
+ let assignedRoles: string[] = []
150
197
  if (Array.isArray(parsed.roles) && parsed.roles.length) {
151
198
  await syncUserRoles(em, user, parsed.roles, tenantId)
199
+ assignedRoles = await loadUserRoleNames(em, String(user.id))
152
200
  }
153
201
 
154
202
  await setCustomFieldsIfAny({
@@ -173,6 +221,10 @@ const createUserCommand: CommandHandler<Record<string, unknown>, User> = {
173
221
  indexer: userCrudIndexer,
174
222
  })
175
223
 
224
+ if (assignedRoles.length) {
225
+ await notifyRoleChanges(ctx, user, assignedRoles, [])
226
+ }
227
+
176
228
  return user
177
229
  },
178
230
  captureAfter: async (_input, result, ctx) => {
@@ -288,6 +340,9 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
288
340
  async execute(rawInput, ctx) {
289
341
  const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)
290
342
  const em = (ctx.container.resolve('em') as EntityManager)
343
+ const rolesBefore = Array.isArray(parsed.roles)
344
+ ? await loadUserRoleNames(em, parsed.id)
345
+ : null
291
346
 
292
347
  if (parsed.email !== undefined) {
293
348
  const emailHash = computeEmailHash(parsed.email)
@@ -377,6 +432,14 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
377
432
  indexer: userCrudIndexer,
378
433
  })
379
434
 
435
+ if (Array.isArray(parsed.roles) && rolesBefore) {
436
+ const rolesAfter = await loadUserRoleNames(em, String(user.id))
437
+ const { assigned, revoked } = diffRoleChanges(rolesBefore, rolesAfter)
438
+ if (assigned.length || revoked.length) {
439
+ await notifyRoleChanges(ctx, user, assigned, revoked)
440
+ }
441
+ }
442
+
380
443
  await invalidateUserCache(ctx, parsed.id)
381
444
 
382
445
  return user
@@ -772,6 +835,14 @@ async function invalidateUserCache(ctx: CommandRuntimeContext, userId: string) {
772
835
  }
773
836
  }
774
837
 
838
+ function diffRoleChanges(before: string[], after: string[]) {
839
+ const beforeSet = new Set(before)
840
+ const afterSet = new Set(after)
841
+ const assigned = after.filter((role) => !beforeSet.has(role))
842
+ const revoked = before.filter((role) => !afterSet.has(role))
843
+ return { assigned, revoked }
844
+ }
845
+
775
846
  function arrayEquals(left: string[] | undefined, right: string[]): boolean {
776
847
  if (!left) return false
777
848
  if (left.length !== right.length) return false
@@ -1,10 +1,14 @@
1
1
  import { z } from 'zod'
2
+ import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
3
+
4
+ const passwordSchema = buildPasswordSchema()
2
5
 
3
6
  // Core auth validators
4
7
  export const userLoginSchema = z.object({
5
8
  email: z.string().email(),
6
9
  password: z.string().min(6),
7
10
  requireRole: z.string().optional(),
11
+ tenantId: z.string().uuid().optional(),
8
12
  })
9
13
 
10
14
  export const requestPasswordResetSchema = z.object({
@@ -13,7 +17,7 @@ export const requestPasswordResetSchema = z.object({
13
17
 
14
18
  export const confirmPasswordResetSchema = z.object({
15
19
  token: z.string().min(10),
16
- password: z.string().min(6),
20
+ password: passwordSchema,
17
21
  })
18
22
 
19
23
  export const sidebarPreferencesInputSchema = z.object({
@@ -29,7 +33,7 @@ export const sidebarPreferencesInputSchema = z.object({
29
33
  // Optional helpers for CLI or admin forms
30
34
  export const userCreateSchema = z.object({
31
35
  email: z.string().email(),
32
- password: z.string().min(6),
36
+ password: passwordSchema,
33
37
  tenantId: z.string().uuid().optional(),
34
38
  organizationId: z.string().uuid(),
35
39
  rolesCsv: z.string().optional(),