@open-mercato/core 0.4.2-canary-da2b080494 → 0.4.2-canary-19353c5970

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 (433) 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/profile/route.js +157 -0
  18. package/dist/modules/auth/api/profile/route.js.map +7 -0
  19. package/dist/modules/auth/api/reset/confirm.js +25 -2
  20. package/dist/modules/auth/api/reset/confirm.js.map +2 -2
  21. package/dist/modules/auth/api/reset.js +23 -0
  22. package/dist/modules/auth/api/reset.js.map +2 -2
  23. package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
  24. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  25. package/dist/modules/auth/api/users/route.js +4 -2
  26. package/dist/modules/auth/api/users/route.js.map +2 -2
  27. package/dist/modules/auth/backend/auth/profile/page.js +141 -0
  28. package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
  29. package/dist/modules/auth/backend/auth/profile/page.meta.js +13 -0
  30. package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
  31. package/dist/modules/auth/backend/roles/[id]/edit/page.js +4 -1
  32. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  33. package/dist/modules/auth/backend/roles/page.js +3 -3
  34. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  35. package/dist/modules/auth/backend/users/[id]/edit/page.js +18 -3
  36. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  37. package/dist/modules/auth/backend/users/create/page.js +15 -2
  38. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  39. package/dist/modules/auth/backend/users/page.js +3 -3
  40. package/dist/modules/auth/backend/users/page.js.map +2 -2
  41. package/dist/modules/auth/cli.js +25 -11
  42. package/dist/modules/auth/cli.js.map +2 -2
  43. package/dist/modules/auth/commands/users.js +59 -2
  44. package/dist/modules/auth/commands/users.js.map +2 -2
  45. package/dist/modules/auth/data/validators.js +4 -2
  46. package/dist/modules/auth/data/validators.js.map +2 -2
  47. package/dist/modules/auth/frontend/reset/[token]/page.js +20 -10
  48. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  49. package/dist/modules/auth/lib/setup-app.js +23 -2
  50. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  51. package/dist/modules/auth/notifications.js +112 -0
  52. package/dist/modules/auth/notifications.js.map +7 -0
  53. package/dist/modules/auth/services/authService.js +3 -3
  54. package/dist/modules/auth/services/authService.js.map +2 -2
  55. package/dist/modules/business_rules/api/execute/route.js +7 -1
  56. package/dist/modules/business_rules/api/execute/route.js.map +2 -2
  57. package/dist/modules/business_rules/backend/rules/page.js +4 -0
  58. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  59. package/dist/modules/business_rules/backend/sets/page.js +3 -0
  60. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  61. package/dist/modules/business_rules/lib/rule-engine.js +33 -3
  62. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  63. package/dist/modules/business_rules/notifications.js +28 -0
  64. package/dist/modules/business_rules/notifications.js.map +7 -0
  65. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
  66. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
  67. package/dist/modules/catalog/components/PriceKindSettings.js +2 -0
  68. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  69. package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -2
  70. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  71. package/dist/modules/catalog/components/products/ProductsDataTable.js +2 -0
  72. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  73. package/dist/modules/catalog/notifications.js +28 -0
  74. package/dist/modules/catalog/notifications.js.map +7 -0
  75. package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
  76. package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
  77. package/dist/modules/configs/cli.js +6 -0
  78. package/dist/modules/configs/cli.js.map +2 -2
  79. package/dist/modules/configs/components/CachePanel.js +4 -4
  80. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  81. package/dist/modules/configs/lib/system-status.js +48 -1
  82. package/dist/modules/configs/lib/system-status.js.map +2 -2
  83. package/dist/modules/configs/lib/upgrade-actions.js +18 -0
  84. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  85. package/dist/modules/currencies/backend/currencies/page.js +3 -0
  86. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  87. package/dist/modules/currencies/backend/exchange-rates/page.js +2 -0
  88. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  89. package/dist/modules/customers/backend/customers/companies/page.js +3 -0
  90. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  91. package/dist/modules/customers/backend/customers/deals/page.js +3 -0
  92. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  93. package/dist/modules/customers/backend/customers/people/page.js +3 -0
  94. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  95. package/dist/modules/customers/commands/deals.js +31 -0
  96. package/dist/modules/customers/commands/deals.js.map +2 -2
  97. package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
  98. package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
  99. package/dist/modules/customers/notifications.js +48 -0
  100. package/dist/modules/customers/notifications.js.map +7 -0
  101. package/dist/modules/dashboards/cli.js +44 -5
  102. package/dist/modules/dashboards/cli.js.map +2 -2
  103. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +16 -11
  104. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +3 -3
  105. package/dist/modules/dashboards/lib/role-widgets.js +58 -0
  106. package/dist/modules/dashboards/lib/role-widgets.js.map +7 -0
  107. package/dist/modules/dashboards/services/widgetDataService.js +139 -3
  108. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  109. package/dist/modules/dictionaries/components/DictionaryTable.js +2 -0
  110. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  111. package/dist/modules/directory/backend/directory/organizations/page.js +2 -2
  112. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  113. package/dist/modules/directory/backend/directory/tenants/page.js +2 -2
  114. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  115. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +2 -2
  116. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  117. package/dist/modules/entities/components/SystemEntitiesTable.js +1 -1
  118. package/dist/modules/entities/components/SystemEntitiesTable.js.map +2 -2
  119. package/dist/modules/entities/components/UserEntitiesTable.js +2 -2
  120. package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
  121. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +3 -3
  122. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  123. package/dist/modules/feature_toggles/components/OverridesTable.js +1 -1
  124. package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
  125. package/dist/modules/notifications/acl.js +11 -0
  126. package/dist/modules/notifications/acl.js.map +7 -0
  127. package/dist/modules/notifications/api/[id]/action/route.js +74 -0
  128. package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
  129. package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
  130. package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
  131. package/dist/modules/notifications/api/[id]/read/route.js +15 -0
  132. package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
  133. package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
  134. package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
  135. package/dist/modules/notifications/api/batch/route.js +17 -0
  136. package/dist/modules/notifications/api/batch/route.js.map +7 -0
  137. package/dist/modules/notifications/api/feature/route.js +17 -0
  138. package/dist/modules/notifications/api/feature/route.js.map +7 -0
  139. package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
  140. package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
  141. package/dist/modules/notifications/api/openapi.js +76 -0
  142. package/dist/modules/notifications/api/openapi.js.map +7 -0
  143. package/dist/modules/notifications/api/role/route.js +17 -0
  144. package/dist/modules/notifications/api/role/route.js.map +7 -0
  145. package/dist/modules/notifications/api/route.js +85 -0
  146. package/dist/modules/notifications/api/route.js.map +7 -0
  147. package/dist/modules/notifications/api/settings/route.js +155 -0
  148. package/dist/modules/notifications/api/settings/route.js.map +7 -0
  149. package/dist/modules/notifications/api/unread-count/route.js +38 -0
  150. package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
  151. package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
  152. package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
  153. package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
  154. package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
  155. package/dist/modules/notifications/cli.js +16 -0
  156. package/dist/modules/notifications/cli.js.map +7 -0
  157. package/dist/modules/notifications/data/entities.js +112 -0
  158. package/dist/modules/notifications/data/entities.js.map +7 -0
  159. package/dist/modules/notifications/data/validators.js +98 -0
  160. package/dist/modules/notifications/data/validators.js.map +7 -0
  161. package/dist/modules/notifications/di.js +13 -0
  162. package/dist/modules/notifications/di.js.map +7 -0
  163. package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
  164. package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
  165. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
  166. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
  167. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +220 -0
  168. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
  169. package/dist/modules/notifications/index.js +14 -0
  170. package/dist/modules/notifications/index.js.map +7 -0
  171. package/dist/modules/notifications/lib/deliveryConfig.js +107 -0
  172. package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
  173. package/dist/modules/notifications/lib/deliveryStrategies.js +14 -0
  174. package/dist/modules/notifications/lib/deliveryStrategies.js.map +7 -0
  175. package/dist/modules/notifications/lib/events.js +12 -0
  176. package/dist/modules/notifications/lib/events.js.map +7 -0
  177. package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
  178. package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
  179. package/dist/modules/notifications/lib/notificationFactory.js +54 -0
  180. package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
  181. package/dist/modules/notifications/lib/notificationMapper.js +34 -0
  182. package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
  183. package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
  184. package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
  185. package/dist/modules/notifications/lib/notificationService.js +279 -0
  186. package/dist/modules/notifications/lib/notificationService.js.map +7 -0
  187. package/dist/modules/notifications/lib/routeHelpers.js +101 -0
  188. package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
  189. package/dist/modules/notifications/lib/safeHref.js +24 -0
  190. package/dist/modules/notifications/lib/safeHref.js.map +7 -0
  191. package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
  192. package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
  193. package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
  194. package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
  195. package/dist/modules/notifications/subscribers/deliver-notification.js +165 -0
  196. package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
  197. package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
  198. package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
  199. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +2 -2
  200. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  201. package/dist/modules/query_index/components/QueryIndexesTable.js +7 -1
  202. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  203. package/dist/modules/resources/backend/resources/resource-types/page.js +2 -2
  204. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  205. package/dist/modules/resources/backend/resources/resources/page.js +2 -2
  206. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  207. package/dist/modules/sales/backend/sales/channels/offers/page.js +2 -0
  208. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  209. package/dist/modules/sales/backend/sales/channels/page.js +2 -0
  210. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  211. package/dist/modules/sales/commands/documents.js +53 -0
  212. package/dist/modules/sales/commands/documents.js.map +2 -2
  213. package/dist/modules/sales/commands/payments.js +26 -0
  214. package/dist/modules/sales/commands/payments.js.map +2 -2
  215. package/dist/modules/sales/components/AdjustmentKindSettings.js +2 -2
  216. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  217. package/dist/modules/sales/components/PaymentMethodsSettings.js +2 -2
  218. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  219. package/dist/modules/sales/components/ShippingMethodsSettings.js +2 -2
  220. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  221. package/dist/modules/sales/components/TaxRatesSettings.js +2 -2
  222. package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
  223. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +2 -0
  224. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  225. package/dist/modules/sales/components/documents/AdjustmentsSection.js +2 -0
  226. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  227. package/dist/modules/sales/components/documents/PaymentsSection.js +2 -1
  228. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  229. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -0
  230. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  231. package/dist/modules/sales/notifications.client.js +51 -0
  232. package/dist/modules/sales/notifications.client.js.map +7 -0
  233. package/dist/modules/sales/notifications.js +88 -0
  234. package/dist/modules/sales/notifications.js.map +7 -0
  235. package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
  236. package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
  237. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
  238. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
  239. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
  240. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
  241. package/dist/modules/sales/widgets/notifications/index.js +7 -0
  242. package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
  243. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
  244. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
  245. package/dist/modules/staff/backend/staff/team-members/page.js +1 -1
  246. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  247. package/dist/modules/staff/backend/staff/team-roles/page.js +2 -2
  248. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  249. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +2 -2
  250. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  251. package/dist/modules/staff/backend/staff/teams/page.js +2 -2
  252. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  253. package/dist/modules/staff/commands/leave-requests.js +79 -0
  254. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  255. package/dist/modules/staff/notifications.js +75 -0
  256. package/dist/modules/staff/notifications.js.map +7 -0
  257. package/dist/modules/workflows/backend/definitions/page.js +5 -0
  258. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  259. package/dist/modules/workflows/backend/instances/page.js +3 -0
  260. package/dist/modules/workflows/backend/instances/page.js.map +2 -2
  261. package/dist/modules/workflows/backend/tasks/page.js +3 -0
  262. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  263. package/dist/modules/workflows/lib/transition-handler.js +14 -6
  264. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  265. package/dist/modules/workflows/notifications.js +28 -0
  266. package/dist/modules/workflows/notifications.js.map +7 -0
  267. package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
  268. package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
  269. package/generated/entities/notification/index.ts +27 -0
  270. package/generated/entities.ids.generated.ts +5 -1
  271. package/generated/entity-fields-registry.ts +2 -0
  272. package/package.json +2 -2
  273. package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
  274. package/src/modules/api_keys/backend/api-keys/page.tsx +1 -1
  275. package/src/modules/attachments/components/AttachmentLibrary.tsx +4 -0
  276. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +2 -0
  277. package/src/modules/auth/README.md +1 -1
  278. package/src/modules/auth/__tests__/cli-setup-acl.test.ts +1 -1
  279. package/src/modules/auth/api/admin/nav.ts +10 -6
  280. package/src/modules/auth/api/profile/route.ts +163 -0
  281. package/src/modules/auth/api/reset/confirm.ts +25 -2
  282. package/src/modules/auth/api/reset.ts +23 -0
  283. package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
  284. package/src/modules/auth/api/users/route.ts +5 -2
  285. package/src/modules/auth/backend/auth/profile/page.meta.ts +9 -0
  286. package/src/modules/auth/backend/auth/profile/page.tsx +174 -0
  287. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +4 -1
  288. package/src/modules/auth/backend/roles/page.tsx +3 -3
  289. package/src/modules/auth/backend/users/[id]/edit/page.tsx +22 -3
  290. package/src/modules/auth/backend/users/create/page.tsx +19 -2
  291. package/src/modules/auth/backend/users/page.tsx +3 -3
  292. package/src/modules/auth/cli.ts +38 -11
  293. package/src/modules/auth/commands/users.ts +73 -2
  294. package/src/modules/auth/data/validators.ts +5 -2
  295. package/src/modules/auth/frontend/reset/[token]/page.tsx +24 -11
  296. package/src/modules/auth/i18n/de.json +43 -1
  297. package/src/modules/auth/i18n/en.json +43 -1
  298. package/src/modules/auth/i18n/es.json +43 -1
  299. package/src/modules/auth/i18n/pl.json +43 -1
  300. package/src/modules/auth/lib/setup-app.ts +29 -2
  301. package/src/modules/auth/notifications.ts +109 -0
  302. package/src/modules/auth/services/authService.ts +4 -4
  303. package/src/modules/business_rules/api/execute/route.ts +8 -1
  304. package/src/modules/business_rules/backend/rules/page.tsx +4 -0
  305. package/src/modules/business_rules/backend/sets/page.tsx +3 -0
  306. package/src/modules/business_rules/i18n/en.json +3 -1
  307. package/src/modules/business_rules/lib/__tests__/rule-engine.test.ts +51 -0
  308. package/src/modules/business_rules/lib/rule-engine.ts +57 -3
  309. package/src/modules/business_rules/notifications.ts +25 -0
  310. package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
  311. package/src/modules/catalog/components/PriceKindSettings.tsx +2 -0
  312. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +2 -2
  313. package/src/modules/catalog/components/products/ProductsDataTable.tsx +2 -0
  314. package/src/modules/catalog/i18n/en.json +3 -1
  315. package/src/modules/catalog/notifications.ts +25 -0
  316. package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
  317. package/src/modules/configs/cli.ts +6 -0
  318. package/src/modules/configs/components/CachePanel.tsx +4 -4
  319. package/src/modules/configs/i18n/en.json +12 -2
  320. package/src/modules/configs/i18n/pl.json +12 -2
  321. package/src/modules/configs/lib/system-status.ts +48 -1
  322. package/src/modules/configs/lib/system-status.types.ts +1 -0
  323. package/src/modules/configs/lib/upgrade-actions.ts +18 -0
  324. package/src/modules/currencies/backend/currencies/page.tsx +3 -0
  325. package/src/modules/currencies/backend/exchange-rates/page.tsx +2 -0
  326. package/src/modules/customers/backend/customers/companies/page.tsx +3 -0
  327. package/src/modules/customers/backend/customers/deals/page.tsx +3 -0
  328. package/src/modules/customers/backend/customers/people/page.tsx +3 -0
  329. package/src/modules/customers/commands/deals.ts +39 -0
  330. package/src/modules/customers/components/CustomerTodosTable.tsx +1 -0
  331. package/src/modules/customers/i18n/en.json +5 -1
  332. package/src/modules/customers/notifications.ts +44 -0
  333. package/src/modules/dashboards/cli.ts +55 -5
  334. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +22 -11
  335. package/src/modules/dashboards/lib/role-widgets.ts +80 -0
  336. package/src/modules/dashboards/services/widgetDataService.ts +164 -4
  337. package/src/modules/dictionaries/components/DictionaryTable.tsx +2 -0
  338. package/src/modules/directory/backend/directory/organizations/page.tsx +2 -2
  339. package/src/modules/directory/backend/directory/tenants/page.tsx +2 -2
  340. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +2 -2
  341. package/src/modules/entities/components/SystemEntitiesTable.tsx +1 -1
  342. package/src/modules/entities/components/UserEntitiesTable.tsx +2 -2
  343. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +3 -4
  344. package/src/modules/feature_toggles/components/OverridesTable.tsx +1 -1
  345. package/src/modules/notifications/__tests__/deliver-notification.test.ts +195 -0
  346. package/src/modules/notifications/__tests__/deliveryStrategies.test.ts +19 -0
  347. package/src/modules/notifications/__tests__/notificationService.test.ts +208 -0
  348. package/src/modules/notifications/acl.ts +7 -0
  349. package/src/modules/notifications/api/[id]/action/route.ts +75 -0
  350. package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
  351. package/src/modules/notifications/api/[id]/read/route.ts +12 -0
  352. package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
  353. package/src/modules/notifications/api/batch/route.ts +14 -0
  354. package/src/modules/notifications/api/feature/route.ts +14 -0
  355. package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
  356. package/src/modules/notifications/api/openapi.ts +76 -0
  357. package/src/modules/notifications/api/role/route.ts +14 -0
  358. package/src/modules/notifications/api/route.ts +92 -0
  359. package/src/modules/notifications/api/settings/route.ts +157 -0
  360. package/src/modules/notifications/api/unread-count/route.ts +38 -0
  361. package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
  362. package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
  363. package/src/modules/notifications/cli.ts +18 -0
  364. package/src/modules/notifications/data/entities.ts +99 -0
  365. package/src/modules/notifications/data/validators.ts +115 -0
  366. package/src/modules/notifications/di.ts +11 -0
  367. package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
  368. package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
  369. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +233 -0
  370. package/src/modules/notifications/i18n/de.json +50 -0
  371. package/src/modules/notifications/i18n/en.json +50 -0
  372. package/src/modules/notifications/i18n/es.json +50 -0
  373. package/src/modules/notifications/i18n/pl.json +50 -0
  374. package/src/modules/notifications/index.ts +12 -0
  375. package/src/modules/notifications/lib/deliveryConfig.ts +153 -0
  376. package/src/modules/notifications/lib/deliveryStrategies.ts +50 -0
  377. package/src/modules/notifications/lib/events.ts +48 -0
  378. package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
  379. package/src/modules/notifications/lib/notificationFactory.ts +76 -0
  380. package/src/modules/notifications/lib/notificationMapper.ts +33 -0
  381. package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
  382. package/src/modules/notifications/lib/notificationService.ts +414 -0
  383. package/src/modules/notifications/lib/routeHelpers.ts +151 -0
  384. package/src/modules/notifications/lib/safeHref.ts +29 -0
  385. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
  386. package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
  387. package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
  388. package/src/modules/notifications/subscribers/deliver-notification.ts +204 -0
  389. package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
  390. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +2 -2
  391. package/src/modules/query_index/components/QueryIndexesTable.tsx +8 -2
  392. package/src/modules/resources/backend/resources/resource-types/page.tsx +2 -2
  393. package/src/modules/resources/backend/resources/resources/page.tsx +2 -2
  394. package/src/modules/sales/backend/sales/channels/offers/page.tsx +2 -0
  395. package/src/modules/sales/backend/sales/channels/page.tsx +2 -0
  396. package/src/modules/sales/commands/documents.ts +65 -0
  397. package/src/modules/sales/commands/payments.ts +33 -0
  398. package/src/modules/sales/components/AdjustmentKindSettings.tsx +2 -2
  399. package/src/modules/sales/components/PaymentMethodsSettings.tsx +2 -2
  400. package/src/modules/sales/components/ShippingMethodsSettings.tsx +2 -2
  401. package/src/modules/sales/components/TaxRatesSettings.tsx +2 -2
  402. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +2 -0
  403. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +2 -0
  404. package/src/modules/sales/components/documents/PaymentsSection.tsx +2 -1
  405. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -0
  406. package/src/modules/sales/i18n/de.json +20 -0
  407. package/src/modules/sales/i18n/en.json +25 -1
  408. package/src/modules/sales/i18n/es.json +20 -0
  409. package/src/modules/sales/i18n/pl.json +20 -0
  410. package/src/modules/sales/notifications.client.ts +65 -0
  411. package/src/modules/sales/notifications.ts +82 -0
  412. package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
  413. package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
  414. package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
  415. package/src/modules/sales/widgets/notifications/index.ts +2 -0
  416. package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
  417. package/src/modules/staff/backend/staff/team-members/page.tsx +1 -1
  418. package/src/modules/staff/backend/staff/team-roles/page.tsx +2 -2
  419. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +2 -2
  420. package/src/modules/staff/backend/staff/teams/page.tsx +2 -2
  421. package/src/modules/staff/commands/leave-requests.ts +94 -0
  422. package/src/modules/staff/i18n/de.json +4 -0
  423. package/src/modules/staff/i18n/en.json +9 -1
  424. package/src/modules/staff/i18n/es.json +4 -0
  425. package/src/modules/staff/i18n/pl.json +4 -0
  426. package/src/modules/staff/notifications.ts +71 -0
  427. package/src/modules/workflows/backend/definitions/page.tsx +5 -0
  428. package/src/modules/workflows/backend/instances/page.tsx +4 -1
  429. package/src/modules/workflows/backend/tasks/page.tsx +4 -1
  430. package/src/modules/workflows/i18n/en.json +3 -1
  431. package/src/modules/workflows/lib/transition-handler.ts +18 -6
  432. package/src/modules/workflows/notifications.ts +25 -0
  433. package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
@@ -297,12 +297,16 @@ export async function GET(req: Request) {
297
297
  const groupsWithRole = rolePreference ? applySidebarPreference(groups, rolePreference) : groups
298
298
  const baseForUser = adoptSidebarDefaults(groupsWithRole)
299
299
 
300
- const preference = await loadSidebarPreference(em, {
301
- userId: auth.sub,
302
- tenantId: auth.tenantId ?? null,
303
- organizationId: auth.orgId ?? null,
304
- locale,
305
- })
300
+ // For API key auth, use userId (the actual user) if available; otherwise skip user preferences
301
+ const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
302
+ const preference = effectiveUserId
303
+ ? await loadSidebarPreference(em, {
304
+ userId: effectiveUserId,
305
+ tenantId: auth.tenantId ?? null,
306
+ organizationId: auth.orgId ?? null,
307
+ locale,
308
+ })
309
+ : null
306
310
 
307
311
  const withPreference = applySidebarPreference(baseForUser, preference)
308
312
 
@@ -0,0 +1,163 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
4
+ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
7
+ import { signJwt } from '@open-mercato/shared/lib/auth/jwt'
8
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
+ import { AuthService } from '@open-mercato/core/modules/auth/services/authService'
11
+ import { User } from '@open-mercato/core/modules/auth/data/entities'
12
+ import type { EntityManager } from '@mikro-orm/postgresql'
13
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
14
+ import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
15
+
16
+ const profileResponseSchema = z.object({
17
+ email: z.string().email(),
18
+ })
19
+
20
+ const passwordSchema = buildPasswordSchema()
21
+
22
+ const updateSchema = z.object({
23
+ email: z.string().email().optional(),
24
+ password: passwordSchema.optional(),
25
+ }).refine((data) => Boolean(data.email || data.password), {
26
+ message: 'Provide an email or password.',
27
+ path: ['email'],
28
+ })
29
+
30
+ const profileUpdateResponseSchema = z.object({
31
+ ok: z.literal(true),
32
+ email: z.string().email(),
33
+ })
34
+
35
+ export const metadata = {
36
+ GET: { requireAuth: true },
37
+ PUT: { requireAuth: true },
38
+ }
39
+
40
+ function buildCommandContext(container: Awaited<ReturnType<typeof createRequestContainer>>, auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>, req: Request): CommandRuntimeContext {
41
+ return {
42
+ container,
43
+ auth,
44
+ organizationScope: null,
45
+ selectedOrganizationId: auth.orgId ?? null,
46
+ organizationIds: auth.orgId ? [auth.orgId] : null,
47
+ request: req,
48
+ }
49
+ }
50
+
51
+ export async function GET(req: Request) {
52
+ const { translate } = await resolveTranslations()
53
+ const auth = await getAuthFromRequest(req)
54
+ if (!auth?.sub) {
55
+ return NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
56
+ }
57
+ try {
58
+ const container = await createRequestContainer()
59
+ const em = (container.resolve('em') as EntityManager)
60
+ const user = await findOneWithDecryption(
61
+ em,
62
+ User,
63
+ { id: auth.sub, deletedAt: null },
64
+ undefined,
65
+ { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
66
+ )
67
+ if (!user) {
68
+ return NextResponse.json({ error: translate('auth.users.form.errors.notFound', 'User not found') }, { status: 404 })
69
+ }
70
+ return NextResponse.json({ email: String(user.email) })
71
+ } catch (err) {
72
+ console.error('auth.profile.load failed', err)
73
+ return NextResponse.json({ error: translate('auth.profile.form.errors.load', 'Failed to load profile.') }, { status: 400 })
74
+ }
75
+ }
76
+
77
+ export async function PUT(req: Request) {
78
+ const { translate } = await resolveTranslations()
79
+ const auth = await getAuthFromRequest(req)
80
+ if (!auth?.sub) {
81
+ return NextResponse.json({ error: translate('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })
82
+ }
83
+ try {
84
+ const body = await req.json().catch(() => ({}))
85
+ const parsed = updateSchema.safeParse(body)
86
+ if (!parsed.success) {
87
+ return NextResponse.json(
88
+ {
89
+ error: translate('auth.profile.form.errors.invalid', 'Invalid profile update.'),
90
+ issues: parsed.error.issues,
91
+ },
92
+ { status: 400 },
93
+ )
94
+ }
95
+ const container = await createRequestContainer()
96
+ const commandBus = (container.resolve('commandBus') as CommandBus)
97
+ const ctx = buildCommandContext(container, auth, req)
98
+ const { result } = await commandBus.execute<{ id: string; email?: string; password?: string }, User>(
99
+ 'auth.users.update',
100
+ {
101
+ input: {
102
+ id: auth.sub,
103
+ email: parsed.data.email,
104
+ password: parsed.data.password,
105
+ },
106
+ ctx,
107
+ },
108
+ )
109
+ const authService = container.resolve('authService') as AuthService
110
+ const roles = await authService.getUserRoles(result, result.tenantId ? String(result.tenantId) : null)
111
+ const jwt = signJwt({
112
+ sub: String(result.id),
113
+ tenantId: result.tenantId ? String(result.tenantId) : null,
114
+ orgId: result.organizationId ? String(result.organizationId) : null,
115
+ email: result.email,
116
+ roles,
117
+ })
118
+ const res = NextResponse.json({ ok: true, email: String(result.email) })
119
+ res.cookies.set('auth_token', jwt, {
120
+ httpOnly: true,
121
+ path: '/',
122
+ sameSite: 'lax',
123
+ secure: process.env.NODE_ENV === 'production',
124
+ maxAge: 60 * 60 * 8,
125
+ })
126
+ return res
127
+ } catch (err) {
128
+ if (err instanceof CrudHttpError) {
129
+ return NextResponse.json(err.body, { status: err.status })
130
+ }
131
+ console.error('auth.profile.update failed', err)
132
+ return NextResponse.json({ error: translate('auth.profile.form.errors.save', 'Failed to update profile.') }, { status: 400 })
133
+ }
134
+ }
135
+
136
+ export const openApi: OpenApiRouteDoc = {
137
+ tag: 'Authentication & Accounts',
138
+ summary: 'Profile settings',
139
+ methods: {
140
+ GET: {
141
+ summary: 'Get current profile',
142
+ description: 'Returns the email address for the signed-in user.',
143
+ responses: [
144
+ { status: 200, description: 'Profile payload', schema: profileResponseSchema },
145
+ { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
146
+ { status: 404, description: 'User not found', schema: z.object({ error: z.string() }) },
147
+ ],
148
+ },
149
+ PUT: {
150
+ summary: 'Update current profile',
151
+ description: 'Updates the email address or password for the signed-in user.',
152
+ requestBody: {
153
+ contentType: 'application/json',
154
+ schema: updateSchema,
155
+ },
156
+ responses: [
157
+ { status: 200, description: 'Profile updated', schema: profileUpdateResponseSchema },
158
+ { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },
159
+ { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
160
+ ],
161
+ },
162
+ },
163
+ }
@@ -3,6 +3,9 @@ import { NextResponse } from 'next/server'
3
3
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import { AuthService } from '@open-mercato/core/modules/auth/services/authService'
6
+ import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
7
+ import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
8
+ import notificationTypes from '@open-mercato/core/modules/auth/notifications'
6
9
  import { z } from 'zod'
7
10
 
8
11
  // validation via confirmPasswordResetSchema
@@ -15,8 +18,28 @@ export async function POST(req: Request) {
15
18
  if (!parsed.success) return NextResponse.json({ ok: false, error: 'Invalid request' }, { status: 400 })
16
19
  const c = await createRequestContainer()
17
20
  const auth = c.resolve<AuthService>('authService')
18
- const ok = await auth.confirmPasswordReset(parsed.data.token, parsed.data.password)
19
- if (!ok) return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })
21
+ const user = await auth.confirmPasswordReset(parsed.data.token, parsed.data.password)
22
+ if (!user) return NextResponse.json({ ok: false, error: 'Invalid or expired token' }, { status: 400 })
23
+ try {
24
+ const tenantId = user.tenantId ? String(user.tenantId) : null
25
+ if (tenantId) {
26
+ const notificationService = resolveNotificationService(c)
27
+ const typeDef = notificationTypes.find((type) => type.type === 'auth.password_reset.completed')
28
+ if (typeDef) {
29
+ const notificationInput = buildNotificationFromType(typeDef, {
30
+ recipientUserId: String(user.id),
31
+ sourceEntityType: 'auth:user',
32
+ sourceEntityId: String(user.id),
33
+ })
34
+ await notificationService.create(notificationInput, {
35
+ tenantId,
36
+ organizationId: user.organizationId ? String(user.organizationId) : null,
37
+ })
38
+ }
39
+ }
40
+ } catch (err) {
41
+ console.error('[auth.reset.confirm] Failed to create notification:', err)
42
+ }
20
43
  return NextResponse.json({ ok: true, redirect: '/login' })
21
44
  }
22
45
 
@@ -6,6 +6,9 @@ import { AuthService } from '@open-mercato/core/modules/auth/services/authServic
6
6
  import { sendEmail } from '@open-mercato/shared/lib/email/send'
7
7
  import ResetPasswordEmail from '@open-mercato/core/modules/auth/emails/ResetPasswordEmail'
8
8
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
+ import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
10
+ import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
11
+ import notificationTypes from '@open-mercato/core/modules/auth/notifications'
9
12
  import { z } from 'zod'
10
13
 
11
14
  // validation via requestPasswordResetSchema
@@ -35,6 +38,26 @@ export async function POST(req: Request) {
35
38
  }
36
39
 
37
40
  await sendEmail({ to: user.email, subject, react: ResetPasswordEmail({ resetUrl, copy }) })
41
+ try {
42
+ const tenantId = user.tenantId ? String(user.tenantId) : null
43
+ if (tenantId) {
44
+ const notificationService = resolveNotificationService(c)
45
+ const typeDef = notificationTypes.find((type) => type.type === 'auth.password_reset.requested')
46
+ if (typeDef) {
47
+ const notificationInput = buildNotificationFromType(typeDef, {
48
+ recipientUserId: String(user.id),
49
+ sourceEntityType: 'auth:user',
50
+ sourceEntityId: String(user.id),
51
+ })
52
+ await notificationService.create(notificationInput, {
53
+ tenantId,
54
+ organizationId: user.organizationId ? String(user.organizationId) : null,
55
+ })
56
+ }
57
+ }
58
+ } catch (err) {
59
+ console.error('[auth.reset] Failed to create notification:', err)
60
+ }
38
61
  return NextResponse.json({ ok: true })
39
62
  }
40
63
 
@@ -64,12 +64,16 @@ export async function GET(req: Request) {
64
64
  { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
65
65
  ) ?? false
66
66
 
67
- const settings = await loadSidebarPreference(em, {
68
- userId: auth.sub,
69
- tenantId: auth.tenantId ?? null,
70
- organizationId: auth.orgId ?? null,
71
- locale,
72
- })
67
+ // For API key auth, use userId (the actual user) if available
68
+ const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
69
+ const settings = effectiveUserId
70
+ ? await loadSidebarPreference(em, {
71
+ userId: effectiveUserId,
72
+ tenantId: auth.tenantId ?? null,
73
+ organizationId: auth.orgId ?? null,
74
+ locale,
75
+ })
76
+ : null
73
77
 
74
78
  let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []
75
79
  if (canApplyToRoles) {
@@ -92,11 +96,11 @@ export async function GET(req: Request) {
92
96
  return NextResponse.json({
93
97
  locale,
94
98
  settings: {
95
- version: settings.version ?? SIDEBAR_PREFERENCES_VERSION,
96
- groupOrder: settings.groupOrder ?? [],
97
- groupLabels: settings.groupLabels ?? {},
98
- itemLabels: settings.itemLabels ?? {},
99
- hiddenItems: settings.hiddenItems ?? [],
99
+ version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,
100
+ groupOrder: settings?.groupOrder ?? [],
101
+ groupLabels: settings?.groupLabels ?? {},
102
+ itemLabels: settings?.itemLabels ?? {},
103
+ hiddenItems: settings?.hiddenItems ?? [],
100
104
  },
101
105
  canApplyToRoles,
102
106
  roles: rolesPayload,
@@ -106,6 +110,11 @@ export async function GET(req: Request) {
106
110
  export async function PUT(req: Request) {
107
111
  const auth = await getAuthFromRequest(req)
108
112
  if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
113
+ // For API key auth, use userId (the actual user) if available
114
+ const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
115
+ if (!effectiveUserId) {
116
+ return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })
117
+ }
109
118
 
110
119
  let parsedBody: unknown
111
120
  try {
@@ -182,7 +191,7 @@ export async function PUT(req: Request) {
182
191
  }
183
192
 
184
193
  const settings = await saveSidebarPreference(em, {
185
- userId: auth.sub,
194
+ userId: effectiveUserId,
186
195
  tenantId: auth.tenantId ?? null,
187
196
  organizationId: auth.orgId ?? null,
188
197
  locale,
@@ -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 () => {