@open-mercato/core 0.6.8-develop.6983.1.e4f794099e → 0.6.8-develop.6985.1.fb93574faa

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 (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/notification/index.js +6 -0
  3. package/dist/generated/entities/notification/index.js.map +2 -2
  4. package/dist/generated/entities/notification_preference/index.js +19 -0
  5. package/dist/generated/entities/notification_preference/index.js.map +7 -0
  6. package/dist/generated/entities/notification_type/index.js +21 -0
  7. package/dist/generated/entities/notification_type/index.js.map +7 -0
  8. package/dist/generated/entities/notification_type_override/index.js +17 -0
  9. package/dist/generated/entities/notification_type_override/index.js.map +7 -0
  10. package/dist/generated/entities/push_notification_delivery/index.js +41 -0
  11. package/dist/generated/entities/push_notification_delivery/index.js.map +7 -0
  12. package/dist/generated/entities/user_device/index.js +35 -0
  13. package/dist/generated/entities/user_device/index.js.map +7 -0
  14. package/dist/generated/entities.ids.generated.js +12 -1
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +72 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/api.js +2 -1
  19. package/dist/helpers/integration/api.js.map +2 -2
  20. package/dist/helpers/integration/appRoot.js +9 -0
  21. package/dist/helpers/integration/appRoot.js.map +7 -0
  22. package/dist/helpers/integration/dbFixtures.js +1 -4
  23. package/dist/helpers/integration/dbFixtures.js.map +2 -2
  24. package/dist/helpers/integration/pushFake.js +126 -0
  25. package/dist/helpers/integration/pushFake.js.map +7 -0
  26. package/dist/modules/auth/notifications.js +16 -0
  27. package/dist/modules/auth/notifications.js.map +2 -2
  28. package/dist/modules/business_rules/notifications.js +2 -0
  29. package/dist/modules/business_rules/notifications.js.map +2 -2
  30. package/dist/modules/catalog/notifications.js +2 -0
  31. package/dist/modules/catalog/notifications.js.map +2 -2
  32. package/dist/modules/communication_channels/acl.js +9 -0
  33. package/dist/modules/communication_channels/acl.js.map +2 -2
  34. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +6 -2
  37. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +2 -2
  38. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +6 -2
  39. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +2 -2
  40. package/dist/modules/communication_channels/api/get/channels/route.js +2 -1
  41. package/dist/modules/communication_channels/api/get/channels/route.js.map +2 -2
  42. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +10 -5
  43. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  44. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +29 -1
  45. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  46. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +157 -0
  47. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +7 -0
  48. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +8 -1
  49. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
  50. package/dist/modules/communication_channels/commands/admin-delete-channel.js +141 -0
  51. package/dist/modules/communication_channels/commands/admin-delete-channel.js.map +7 -0
  52. package/dist/modules/communication_channels/commands/connect-credential-channel.js +25 -8
  53. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
  54. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +2 -2
  55. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  56. package/dist/modules/communication_channels/commands/push-register.js +1 -1
  57. package/dist/modules/communication_channels/commands/push-register.js.map +2 -2
  58. package/dist/modules/communication_channels/commands/push-unregister.js +4 -3
  59. package/dist/modules/communication_channels/commands/push-unregister.js.map +2 -2
  60. package/dist/modules/communication_channels/data/entities.js +4 -0
  61. package/dist/modules/communication_channels/data/entities.js.map +2 -2
  62. package/dist/modules/communication_channels/data/validators.js +2 -1
  63. package/dist/modules/communication_channels/data/validators.js.map +2 -2
  64. package/dist/modules/communication_channels/lib/access-control.js +11 -1
  65. package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
  66. package/dist/modules/communication_channels/lib/connect-channel.js +20 -12
  67. package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
  68. package/dist/modules/communication_channels/lib/push-adapter.js +68 -0
  69. package/dist/modules/communication_channels/lib/push-adapter.js.map +7 -0
  70. package/dist/modules/communication_channels/lib/push-capabilities.js +31 -0
  71. package/dist/modules/communication_channels/lib/push-capabilities.js.map +7 -0
  72. package/dist/modules/communication_channels/lib/push-connect-error.js +20 -0
  73. package/dist/modules/communication_channels/lib/push-connect-error.js.map +7 -0
  74. package/dist/modules/communication_channels/lib/push-credential-errors.js +41 -0
  75. package/dist/modules/communication_channels/lib/push-credential-errors.js.map +7 -0
  76. package/dist/modules/communication_channels/lib/push-envelope.js +46 -0
  77. package/dist/modules/communication_channels/lib/push-envelope.js.map +7 -0
  78. package/dist/modules/communication_channels/lib/refcounted-client-cache.js +64 -0
  79. package/dist/modules/communication_channels/lib/refcounted-client-cache.js.map +7 -0
  80. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js +13 -0
  81. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js.map +7 -0
  82. package/dist/modules/communication_channels/notifications.js +4 -0
  83. package/dist/modules/communication_channels/notifications.js.map +2 -2
  84. package/dist/modules/communication_channels/setup.js +2 -0
  85. package/dist/modules/communication_channels/setup.js.map +2 -2
  86. package/dist/modules/configs/lib/upgrade-actions.js +21 -0
  87. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  88. package/dist/modules/customer_accounts/notifications.js +12 -0
  89. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  90. package/dist/modules/customers/notifications.js +4 -0
  91. package/dist/modules/customers/notifications.js.map +2 -2
  92. package/dist/modules/devices/acl.js +11 -0
  93. package/dist/modules/devices/acl.js.map +7 -0
  94. package/dist/modules/devices/api/[id]/route.js +167 -0
  95. package/dist/modules/devices/api/[id]/route.js.map +7 -0
  96. package/dist/modules/devices/api/admin/devices/[id]/route.js +219 -0
  97. package/dist/modules/devices/api/admin/devices/[id]/route.js.map +7 -0
  98. package/dist/modules/devices/api/admin/devices/route.js +140 -0
  99. package/dist/modules/devices/api/admin/devices/route.js.map +7 -0
  100. package/dist/modules/devices/api/auth.js +9 -0
  101. package/dist/modules/devices/api/auth.js.map +7 -0
  102. package/dist/modules/devices/api/deviceList.js +53 -0
  103. package/dist/modules/devices/api/deviceList.js.map +7 -0
  104. package/dist/modules/devices/api/deviceOps.js +118 -0
  105. package/dist/modules/devices/api/deviceOps.js.map +7 -0
  106. package/dist/modules/devices/api/openapi.js +19 -0
  107. package/dist/modules/devices/api/openapi.js.map +7 -0
  108. package/dist/modules/devices/api/route.js +143 -0
  109. package/dist/modules/devices/api/route.js.map +7 -0
  110. package/dist/modules/devices/backend/devices/[id]/page.js +135 -0
  111. package/dist/modules/devices/backend/devices/[id]/page.js.map +7 -0
  112. package/dist/modules/devices/backend/devices/[id]/page.meta.js +16 -0
  113. package/dist/modules/devices/backend/devices/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/devices/backend/devices/create/page.js +68 -0
  115. package/dist/modules/devices/backend/devices/create/page.js.map +7 -0
  116. package/dist/modules/devices/backend/devices/create/page.meta.js +16 -0
  117. package/dist/modules/devices/backend/devices/create/page.meta.js.map +7 -0
  118. package/dist/modules/devices/backend/devices/page.js +235 -0
  119. package/dist/modules/devices/backend/devices/page.js.map +7 -0
  120. package/dist/modules/devices/backend/devices/page.meta.js +23 -0
  121. package/dist/modules/devices/backend/devices/page.meta.js.map +7 -0
  122. package/dist/modules/devices/commands/deactivate.js +107 -0
  123. package/dist/modules/devices/commands/deactivate.js.map +7 -0
  124. package/dist/modules/devices/commands/index.js +4 -0
  125. package/dist/modules/devices/commands/index.js.map +7 -0
  126. package/dist/modules/devices/commands/register.js +183 -0
  127. package/dist/modules/devices/commands/register.js.map +7 -0
  128. package/dist/modules/devices/commands/shared.js +74 -0
  129. package/dist/modules/devices/commands/shared.js.map +7 -0
  130. package/dist/modules/devices/commands/update.js +126 -0
  131. package/dist/modules/devices/commands/update.js.map +7 -0
  132. package/dist/modules/devices/data/entities.js +83 -0
  133. package/dist/modules/devices/data/entities.js.map +7 -0
  134. package/dist/modules/devices/data/validators.js +49 -0
  135. package/dist/modules/devices/data/validators.js.map +7 -0
  136. package/dist/modules/devices/di.js +11 -0
  137. package/dist/modules/devices/di.js.map +7 -0
  138. package/dist/modules/devices/encryption.js +12 -0
  139. package/dist/modules/devices/encryption.js.map +7 -0
  140. package/dist/modules/devices/events.js +19 -0
  141. package/dist/modules/devices/events.js.map +7 -0
  142. package/dist/modules/devices/index.js +17 -0
  143. package/dist/modules/devices/index.js.map +7 -0
  144. package/dist/modules/devices/lib/operationMetadata.js +20 -0
  145. package/dist/modules/devices/lib/operationMetadata.js.map +7 -0
  146. package/dist/modules/devices/migrations/Migration20260602120000.js +15 -0
  147. package/dist/modules/devices/migrations/Migration20260602120000.js.map +7 -0
  148. package/dist/modules/devices/migrations/Migration20260630120000.js +15 -0
  149. package/dist/modules/devices/migrations/Migration20260630120000.js.map +7 -0
  150. package/dist/modules/devices/migrations/Migration20260701120000.js +13 -0
  151. package/dist/modules/devices/migrations/Migration20260701120000.js.map +7 -0
  152. package/dist/modules/devices/migrations/Migration20260721120000.js +15 -0
  153. package/dist/modules/devices/migrations/Migration20260721120000.js.map +7 -0
  154. package/dist/modules/devices/setup.js +13 -0
  155. package/dist/modules/devices/setup.js.map +7 -0
  156. package/dist/modules/entities/cli.js +8 -3
  157. package/dist/modules/entities/cli.js.map +2 -2
  158. package/dist/modules/entities/migrations/Migration20260722120000.js +28 -0
  159. package/dist/modules/entities/migrations/Migration20260722120000.js.map +7 -0
  160. package/dist/modules/inbox_ops/notifications.js +2 -0
  161. package/dist/modules/inbox_ops/notifications.js.map +2 -2
  162. package/dist/modules/messages/notifications.js +2 -0
  163. package/dist/modules/messages/notifications.js.map +2 -2
  164. package/dist/modules/notifications/acl.js +3 -1
  165. package/dist/modules/notifications/acl.js.map +2 -2
  166. package/dist/modules/notifications/api/admin/preferences/route.js +167 -0
  167. package/dist/modules/notifications/api/admin/preferences/route.js.map +7 -0
  168. package/dist/modules/notifications/api/channels/route.js +54 -0
  169. package/dist/modules/notifications/api/channels/route.js.map +7 -0
  170. package/dist/modules/notifications/api/preferences/route.js +126 -0
  171. package/dist/modules/notifications/api/preferences/route.js.map +7 -0
  172. package/dist/modules/notifications/api/route.js +8 -1
  173. package/dist/modules/notifications/api/route.js.map +2 -2
  174. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js +187 -0
  175. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js.map +7 -0
  176. package/dist/modules/notifications/api/types/route.js +225 -0
  177. package/dist/modules/notifications/api/types/route.js.map +7 -0
  178. package/dist/modules/notifications/api/unread-count/route.js +5 -1
  179. package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
  180. package/dist/modules/notifications/backend/notifications/user-preferences/page.js +10 -0
  181. package/dist/modules/notifications/backend/notifications/user-preferences/page.js.map +7 -0
  182. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js +28 -0
  183. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js.map +7 -0
  184. package/dist/modules/notifications/backend/profile/notification-preferences/page.js +10 -0
  185. package/dist/modules/notifications/backend/profile/notification-preferences/page.js.map +7 -0
  186. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +33 -0
  187. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +7 -0
  188. package/dist/modules/notifications/data/entities.js +128 -1
  189. package/dist/modules/notifications/data/entities.js.map +2 -2
  190. package/dist/modules/notifications/data/validators.js +95 -2
  191. package/dist/modules/notifications/data/validators.js.map +2 -2
  192. package/dist/modules/notifications/di.js +7 -0
  193. package/dist/modules/notifications/di.js.map +2 -2
  194. package/dist/modules/notifications/events.js +17 -0
  195. package/dist/modules/notifications/events.js.map +2 -2
  196. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js +190 -0
  197. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js.map +7 -0
  198. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js +124 -0
  199. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js.map +7 -0
  200. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +166 -1
  201. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  202. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js +172 -0
  203. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js.map +7 -0
  204. package/dist/modules/notifications/generators.js +63 -0
  205. package/dist/modules/notifications/generators.js.map +7 -0
  206. package/dist/modules/notifications/lib/delivery-strategies-registry.js +19 -0
  207. package/dist/modules/notifications/lib/delivery-strategies-registry.js.map +7 -0
  208. package/dist/modules/notifications/lib/deliveryStrategies.js.map +2 -2
  209. package/dist/modules/notifications/lib/derive-category.js +8 -0
  210. package/dist/modules/notifications/lib/derive-category.js.map +7 -0
  211. package/dist/modules/notifications/lib/notification-channel-registry.js +35 -0
  212. package/dist/modules/notifications/lib/notification-channel-registry.js.map +7 -0
  213. package/dist/modules/notifications/lib/notification-type-registry.js +87 -0
  214. package/dist/modules/notifications/lib/notification-type-registry.js.map +7 -0
  215. package/dist/modules/notifications/lib/notificationCopy.js +14 -0
  216. package/dist/modules/notifications/lib/notificationCopy.js.map +7 -0
  217. package/dist/modules/notifications/lib/notificationFactory.js +4 -1
  218. package/dist/modules/notifications/lib/notificationFactory.js.map +2 -2
  219. package/dist/modules/notifications/lib/notificationMapper.js +2 -0
  220. package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
  221. package/dist/modules/notifications/lib/notificationPreferenceService.js +83 -0
  222. package/dist/modules/notifications/lib/notificationPreferenceService.js.map +7 -0
  223. package/dist/modules/notifications/lib/notificationService.js +83 -28
  224. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  225. package/dist/modules/notifications/lib/notificationVisibility.js +24 -0
  226. package/dist/modules/notifications/lib/notificationVisibility.js.map +7 -0
  227. package/dist/modules/notifications/lib/routeHelpers.js +11 -1
  228. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  229. package/dist/modules/notifications/lib/shouldDeliver.js +46 -0
  230. package/dist/modules/notifications/lib/shouldDeliver.js.map +7 -0
  231. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js +54 -0
  232. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js.map +7 -0
  233. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js +12 -0
  234. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js.map +7 -0
  235. package/dist/modules/notifications/lib/typeCatalogue.js +71 -0
  236. package/dist/modules/notifications/lib/typeCatalogue.js.map +7 -0
  237. package/dist/modules/notifications/lib/typeChannelSettings.js +12 -0
  238. package/dist/modules/notifications/lib/typeChannelSettings.js.map +7 -0
  239. package/dist/modules/notifications/lib/typeOverrides.js +19 -0
  240. package/dist/modules/notifications/lib/typeOverrides.js.map +7 -0
  241. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js +14 -0
  242. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js.map +7 -0
  243. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js +15 -0
  244. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js.map +7 -0
  245. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js +13 -0
  246. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js.map +7 -0
  247. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js +15 -0
  248. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js.map +7 -0
  249. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js +13 -0
  250. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js.map +7 -0
  251. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js +14 -0
  252. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js.map +7 -0
  253. package/dist/modules/notifications/notification-channels.js +26 -0
  254. package/dist/modules/notifications/notification-channels.js.map +7 -0
  255. package/dist/modules/notifications/notifications.delivery-strategies.js +12 -0
  256. package/dist/modules/notifications/notifications.delivery-strategies.js.map +7 -0
  257. package/dist/modules/notifications/setup.js +16 -1
  258. package/dist/modules/notifications/setup.js.map +2 -2
  259. package/dist/modules/notifications/subscribers/deliver-notification.js +66 -57
  260. package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
  261. package/dist/modules/notifications/subscribers/sync-notification-types.js +16 -0
  262. package/dist/modules/notifications/subscribers/sync-notification-types.js.map +7 -0
  263. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js +23 -0
  264. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js.map +7 -0
  265. package/dist/modules/notifications/widgets/injection-table.js +14 -0
  266. package/dist/modules/notifications/widgets/injection-table.js.map +7 -0
  267. package/dist/modules/push_notifications/acl.js +13 -0
  268. package/dist/modules/push_notifications/acl.js.map +7 -0
  269. package/dist/modules/push_notifications/api/custom-send/route.js +141 -0
  270. package/dist/modules/push_notifications/api/custom-send/route.js.map +7 -0
  271. package/dist/modules/push_notifications/api/deliveries/[id]/route.js +110 -0
  272. package/dist/modules/push_notifications/api/deliveries/[id]/route.js.map +7 -0
  273. package/dist/modules/push_notifications/api/deliveries/route.js +56 -0
  274. package/dist/modules/push_notifications/api/deliveries/route.js.map +7 -0
  275. package/dist/modules/push_notifications/api/openapi.js +19 -0
  276. package/dist/modules/push_notifications/api/openapi.js.map +7 -0
  277. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js +103 -0
  278. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js.map +7 -0
  279. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js +13 -0
  280. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js.map +7 -0
  281. package/dist/modules/push_notifications/backend/push_notifications/page.js +213 -0
  282. package/dist/modules/push_notifications/backend/push_notifications/page.js.map +7 -0
  283. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js +23 -0
  284. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js.map +7 -0
  285. package/dist/modules/push_notifications/backend/push_notifications/send/page.js +245 -0
  286. package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +7 -0
  287. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js +26 -0
  288. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js.map +7 -0
  289. package/dist/modules/push_notifications/data/entities.js +92 -0
  290. package/dist/modules/push_notifications/data/entities.js.map +7 -0
  291. package/dist/modules/push_notifications/data/extensions.js +23 -0
  292. package/dist/modules/push_notifications/data/extensions.js.map +7 -0
  293. package/dist/modules/push_notifications/data/validators.js +89 -0
  294. package/dist/modules/push_notifications/data/validators.js.map +7 -0
  295. package/dist/modules/push_notifications/di.js +17 -0
  296. package/dist/modules/push_notifications/di.js.map +7 -0
  297. package/dist/modules/push_notifications/events.js +24 -0
  298. package/dist/modules/push_notifications/events.js.map +7 -0
  299. package/dist/modules/push_notifications/index.js +16 -0
  300. package/dist/modules/push_notifications/index.js.map +7 -0
  301. package/dist/modules/push_notifications/lib/fake-provider-recorder.js +80 -0
  302. package/dist/modules/push_notifications/lib/fake-provider-recorder.js.map +7 -0
  303. package/dist/modules/push_notifications/lib/push-delivery-strategy.js +54 -0
  304. package/dist/modules/push_notifications/lib/push-delivery-strategy.js.map +7 -0
  305. package/dist/modules/push_notifications/lib/push-delivery.js +297 -0
  306. package/dist/modules/push_notifications/lib/push-delivery.js.map +7 -0
  307. package/dist/modules/push_notifications/lib/push-fanout.js +138 -0
  308. package/dist/modules/push_notifications/lib/push-fanout.js.map +7 -0
  309. package/dist/modules/push_notifications/lib/push-health.js +32 -0
  310. package/dist/modules/push_notifications/lib/push-health.js.map +7 -0
  311. package/dist/modules/push_notifications/lib/push-reaper.js +87 -0
  312. package/dist/modules/push_notifications/lib/push-reaper.js.map +7 -0
  313. package/dist/modules/push_notifications/lib/push-receipt-reaper.js +136 -0
  314. package/dist/modules/push_notifications/lib/push-receipt-reaper.js.map +7 -0
  315. package/dist/modules/push_notifications/lib/push-stub-adapter.js +63 -0
  316. package/dist/modules/push_notifications/lib/push-stub-adapter.js.map +7 -0
  317. package/dist/modules/push_notifications/lib/queue.js +53 -0
  318. package/dist/modules/push_notifications/lib/queue.js.map +7 -0
  319. package/dist/modules/push_notifications/lib/reclaim-window.js +13 -0
  320. package/dist/modules/push_notifications/lib/reclaim-window.js.map +7 -0
  321. package/dist/modules/push_notifications/lib/send-custom-push.js +48 -0
  322. package/dist/modules/push_notifications/lib/send-custom-push.js.map +7 -0
  323. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js +16 -0
  324. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js.map +7 -0
  325. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js +13 -0
  326. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js.map +7 -0
  327. package/dist/modules/push_notifications/notifications.delivery-strategies.js +8 -0
  328. package/dist/modules/push_notifications/notifications.delivery-strategies.js.map +7 -0
  329. package/dist/modules/push_notifications/notifications.js +40 -0
  330. package/dist/modules/push_notifications/notifications.js.map +7 -0
  331. package/dist/modules/push_notifications/setup.js +52 -0
  332. package/dist/modules/push_notifications/setup.js.map +7 -0
  333. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js +57 -0
  334. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js.map +7 -0
  335. package/dist/modules/push_notifications/workers/send-push.worker.js +27 -0
  336. package/dist/modules/push_notifications/workers/send-push.worker.js.map +7 -0
  337. package/dist/modules/sales/notifications.js +8 -0
  338. package/dist/modules/sales/notifications.js.map +2 -2
  339. package/dist/modules/staff/notifications.js +6 -0
  340. package/dist/modules/staff/notifications.js.map +2 -2
  341. package/dist/modules/workflows/notifications.js +2 -0
  342. package/dist/modules/workflows/notifications.js.map +2 -2
  343. package/generated/entities/notification/index.ts +3 -0
  344. package/generated/entities/notification_preference/index.ts +8 -0
  345. package/generated/entities/notification_type/index.ts +9 -0
  346. package/generated/entities/notification_type_override/index.ts +7 -0
  347. package/generated/entities/push_notification_delivery/index.ts +19 -0
  348. package/generated/entities/user_device/index.ts +16 -0
  349. package/generated/entities.ids.generated.ts +12 -1
  350. package/generated/entity-fields-registry.ts +72 -0
  351. package/package.json +7 -7
  352. package/src/helpers/integration/api.ts +2 -1
  353. package/src/helpers/integration/appRoot.ts +13 -0
  354. package/src/helpers/integration/dbFixtures.ts +1 -5
  355. package/src/helpers/integration/pushFake.ts +219 -0
  356. package/src/modules/auth/i18n/de.json +1 -0
  357. package/src/modules/auth/i18n/en.json +1 -0
  358. package/src/modules/auth/i18n/es.json +1 -0
  359. package/src/modules/auth/i18n/ko.json +1 -0
  360. package/src/modules/auth/i18n/pl.json +1 -0
  361. package/src/modules/auth/notifications.ts +16 -0
  362. package/src/modules/business_rules/i18n/de.json +2 -1
  363. package/src/modules/business_rules/i18n/en.json +2 -1
  364. package/src/modules/business_rules/i18n/es.json +2 -1
  365. package/src/modules/business_rules/i18n/ko.json +2 -1
  366. package/src/modules/business_rules/i18n/pl.json +2 -1
  367. package/src/modules/business_rules/notifications.ts +2 -0
  368. package/src/modules/catalog/i18n/de.json +2 -1
  369. package/src/modules/catalog/i18n/en.json +2 -1
  370. package/src/modules/catalog/i18n/es.json +2 -1
  371. package/src/modules/catalog/i18n/ko.json +2 -1
  372. package/src/modules/catalog/i18n/pl.json +2 -1
  373. package/src/modules/catalog/notifications.ts +2 -0
  374. package/src/modules/communication_channels/acl.ts +9 -0
  375. package/src/modules/communication_channels/api/delete/admin/channels/[id]/route.ts +168 -0
  376. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +6 -2
  377. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +6 -2
  378. package/src/modules/communication_channels/api/get/channels/route.ts +2 -1
  379. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +16 -5
  380. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +37 -1
  381. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +175 -0
  382. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +11 -1
  383. package/src/modules/communication_channels/commands/admin-delete-channel.ts +203 -0
  384. package/src/modules/communication_channels/commands/connect-credential-channel.ts +63 -15
  385. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +10 -2
  386. package/src/modules/communication_channels/commands/push-register.ts +7 -1
  387. package/src/modules/communication_channels/commands/push-unregister.ts +14 -3
  388. package/src/modules/communication_channels/data/entities.ts +10 -0
  389. package/src/modules/communication_channels/data/validators.ts +1 -0
  390. package/src/modules/communication_channels/i18n/de.json +35 -1
  391. package/src/modules/communication_channels/i18n/en.json +35 -1
  392. package/src/modules/communication_channels/i18n/es.json +35 -1
  393. package/src/modules/communication_channels/i18n/ko.json +35 -1
  394. package/src/modules/communication_channels/i18n/pl.json +35 -1
  395. package/src/modules/communication_channels/lib/access-control.ts +42 -0
  396. package/src/modules/communication_channels/lib/adapter.ts +25 -0
  397. package/src/modules/communication_channels/lib/connect-channel.ts +61 -26
  398. package/src/modules/communication_channels/lib/push-adapter.ts +139 -0
  399. package/src/modules/communication_channels/lib/push-capabilities.ts +42 -0
  400. package/src/modules/communication_channels/lib/push-connect-error.ts +52 -0
  401. package/src/modules/communication_channels/lib/push-credential-errors.ts +58 -0
  402. package/src/modules/communication_channels/lib/push-envelope.ts +104 -0
  403. package/src/modules/communication_channels/lib/refcounted-client-cache.ts +123 -0
  404. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +9 -0
  405. package/src/modules/communication_channels/migrations/Migration20260703123630_communication_channels.ts +20 -0
  406. package/src/modules/communication_channels/notifications.ts +4 -0
  407. package/src/modules/communication_channels/setup.ts +2 -0
  408. package/src/modules/configs/i18n/de.json +5 -1
  409. package/src/modules/configs/i18n/en.json +5 -1
  410. package/src/modules/configs/i18n/es.json +5 -1
  411. package/src/modules/configs/i18n/ko.json +5 -1
  412. package/src/modules/configs/i18n/pl.json +5 -1
  413. package/src/modules/configs/lib/upgrade-actions.ts +21 -0
  414. package/src/modules/customer_accounts/i18n/de.json +2 -1
  415. package/src/modules/customer_accounts/i18n/en.json +2 -1
  416. package/src/modules/customer_accounts/i18n/es.json +2 -1
  417. package/src/modules/customer_accounts/i18n/ko.json +2 -1
  418. package/src/modules/customer_accounts/i18n/pl.json +2 -1
  419. package/src/modules/customer_accounts/notifications.ts +12 -0
  420. package/src/modules/customers/i18n/de.json +2 -1
  421. package/src/modules/customers/i18n/en.json +2 -1
  422. package/src/modules/customers/i18n/es.json +2 -1
  423. package/src/modules/customers/i18n/ko.json +2 -1
  424. package/src/modules/customers/i18n/pl.json +2 -1
  425. package/src/modules/customers/notifications.ts +4 -0
  426. package/src/modules/devices/AGENTS.md +101 -0
  427. package/src/modules/devices/acl.ts +7 -0
  428. package/src/modules/devices/api/[id]/route.ts +180 -0
  429. package/src/modules/devices/api/admin/devices/[id]/route.ts +235 -0
  430. package/src/modules/devices/api/admin/devices/route.ts +160 -0
  431. package/src/modules/devices/api/auth.ts +13 -0
  432. package/src/modules/devices/api/deviceList.ts +54 -0
  433. package/src/modules/devices/api/deviceOps.ts +186 -0
  434. package/src/modules/devices/api/openapi.ts +23 -0
  435. package/src/modules/devices/api/route.ts +165 -0
  436. package/src/modules/devices/backend/devices/[id]/page.meta.ts +12 -0
  437. package/src/modules/devices/backend/devices/[id]/page.tsx +164 -0
  438. package/src/modules/devices/backend/devices/create/page.meta.ts +12 -0
  439. package/src/modules/devices/backend/devices/create/page.tsx +81 -0
  440. package/src/modules/devices/backend/devices/page.meta.ts +21 -0
  441. package/src/modules/devices/backend/devices/page.tsx +259 -0
  442. package/src/modules/devices/commands/deactivate.ts +126 -0
  443. package/src/modules/devices/commands/index.ts +3 -0
  444. package/src/modules/devices/commands/register.ts +199 -0
  445. package/src/modules/devices/commands/shared.ts +119 -0
  446. package/src/modules/devices/commands/update.ts +142 -0
  447. package/src/modules/devices/data/entities.ts +79 -0
  448. package/src/modules/devices/data/validators.ts +65 -0
  449. package/src/modules/devices/di.ts +9 -0
  450. package/src/modules/devices/encryption.ts +15 -0
  451. package/src/modules/devices/events.ts +16 -0
  452. package/src/modules/devices/i18n/de.json +45 -0
  453. package/src/modules/devices/i18n/en.json +45 -0
  454. package/src/modules/devices/i18n/es.json +45 -0
  455. package/src/modules/devices/i18n/ko.json +45 -0
  456. package/src/modules/devices/i18n/pl.json +45 -0
  457. package/src/modules/devices/index.ts +15 -0
  458. package/src/modules/devices/lib/operationMetadata.ts +35 -0
  459. package/src/modules/devices/migrations/.snapshot-open-mercato.json +200 -0
  460. package/src/modules/devices/migrations/Migration20260602120000.ts +15 -0
  461. package/src/modules/devices/migrations/Migration20260630120000.ts +20 -0
  462. package/src/modules/devices/migrations/Migration20260701120000.ts +13 -0
  463. package/src/modules/devices/migrations/Migration20260721120000.ts +18 -0
  464. package/src/modules/devices/setup.ts +11 -0
  465. package/src/modules/entities/cli.ts +16 -2
  466. package/src/modules/entities/migrations/Migration20260722120000.ts +38 -0
  467. package/src/modules/inbox_ops/i18n/de.json +2 -1
  468. package/src/modules/inbox_ops/i18n/en.json +2 -1
  469. package/src/modules/inbox_ops/i18n/es.json +2 -1
  470. package/src/modules/inbox_ops/i18n/ko.json +2 -1
  471. package/src/modules/inbox_ops/i18n/pl.json +2 -1
  472. package/src/modules/inbox_ops/notifications.ts +2 -0
  473. package/src/modules/messages/i18n/de.json +2 -1
  474. package/src/modules/messages/i18n/en.json +2 -1
  475. package/src/modules/messages/i18n/es.json +2 -1
  476. package/src/modules/messages/i18n/ko.json +2 -1
  477. package/src/modules/messages/i18n/pl.json +2 -1
  478. package/src/modules/messages/notifications.ts +2 -0
  479. package/src/modules/notifications/AGENTS.md +54 -0
  480. package/src/modules/notifications/acl.ts +2 -0
  481. package/src/modules/notifications/api/admin/preferences/route.ts +193 -0
  482. package/src/modules/notifications/api/channels/route.ts +54 -0
  483. package/src/modules/notifications/api/preferences/route.ts +131 -0
  484. package/src/modules/notifications/api/route.ts +8 -1
  485. package/src/modules/notifications/api/types/[id]/channels/[channel]/route.ts +226 -0
  486. package/src/modules/notifications/api/types/route.ts +239 -0
  487. package/src/modules/notifications/api/unread-count/route.ts +5 -1
  488. package/src/modules/notifications/backend/notifications/user-preferences/page.meta.ts +26 -0
  489. package/src/modules/notifications/backend/notifications/user-preferences/page.tsx +12 -0
  490. package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +31 -0
  491. package/src/modules/notifications/backend/profile/notification-preferences/page.tsx +12 -0
  492. package/src/modules/notifications/data/entities.ts +144 -0
  493. package/src/modules/notifications/data/validators.ts +116 -0
  494. package/src/modules/notifications/di.ts +7 -0
  495. package/src/modules/notifications/events.ts +23 -0
  496. package/src/modules/notifications/frontend/NotificationPreferenceMatrix.tsx +287 -0
  497. package/src/modules/notifications/frontend/NotificationPreferencesPageClient.tsx +155 -0
  498. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +273 -2
  499. package/src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx +214 -0
  500. package/src/modules/notifications/generators.ts +82 -0
  501. package/src/modules/notifications/i18n/de.json +43 -0
  502. package/src/modules/notifications/i18n/en.json +43 -0
  503. package/src/modules/notifications/i18n/es.json +43 -0
  504. package/src/modules/notifications/i18n/ko.json +43 -0
  505. package/src/modules/notifications/i18n/pl.json +44 -1
  506. package/src/modules/notifications/lib/delivery-strategies-registry.ts +30 -0
  507. package/src/modules/notifications/lib/deliveryStrategies.ts +40 -4
  508. package/src/modules/notifications/lib/derive-category.ts +15 -0
  509. package/src/modules/notifications/lib/notification-channel-registry.ts +59 -0
  510. package/src/modules/notifications/lib/notification-type-registry.ts +182 -0
  511. package/src/modules/notifications/lib/notificationCopy.ts +34 -0
  512. package/src/modules/notifications/lib/notificationFactory.ts +5 -1
  513. package/src/modules/notifications/lib/notificationMapper.ts +2 -0
  514. package/src/modules/notifications/lib/notificationPreferenceService.ts +138 -0
  515. package/src/modules/notifications/lib/notificationService.ts +129 -25
  516. package/src/modules/notifications/lib/notificationVisibility.ts +48 -0
  517. package/src/modules/notifications/lib/routeHelpers.ts +14 -1
  518. package/src/modules/notifications/lib/shouldDeliver.ts +112 -0
  519. package/src/modules/notifications/lib/strategies/email-delivery-strategy.ts +63 -0
  520. package/src/modules/notifications/lib/strategies/in-app-delivery-strategy.ts +21 -0
  521. package/src/modules/notifications/lib/typeCatalogue.ts +141 -0
  522. package/src/modules/notifications/lib/typeChannelSettings.ts +29 -0
  523. package/src/modules/notifications/lib/typeOverrides.ts +46 -0
  524. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +874 -100
  525. package/src/modules/notifications/migrations/Migration20260625122947_notifications.ts +14 -0
  526. package/src/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.ts +15 -0
  527. package/src/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.ts +13 -0
  528. package/src/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.ts +15 -0
  529. package/src/modules/notifications/migrations/Migration20260706120000_notifications_channels.ts +13 -0
  530. package/src/modules/notifications/migrations/Migration20260716123121_notifications.ts +14 -0
  531. package/src/modules/notifications/notification-channels.ts +30 -0
  532. package/src/modules/notifications/notifications.delivery-strategies.ts +15 -0
  533. package/src/modules/notifications/setup.ts +23 -1
  534. package/src/modules/notifications/subscribers/deliver-notification.ts +105 -71
  535. package/src/modules/notifications/subscribers/sync-notification-types.ts +18 -0
  536. package/src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts +35 -0
  537. package/src/modules/notifications/widgets/injection-table.ts +18 -0
  538. package/src/modules/push_notifications/AGENTS.md +92 -0
  539. package/src/modules/push_notifications/acl.ts +9 -0
  540. package/src/modules/push_notifications/api/custom-send/route.ts +175 -0
  541. package/src/modules/push_notifications/api/deliveries/[id]/route.ts +122 -0
  542. package/src/modules/push_notifications/api/deliveries/route.ts +58 -0
  543. package/src/modules/push_notifications/api/openapi.ts +22 -0
  544. package/src/modules/push_notifications/backend/push_notifications/[id]/page.meta.ts +9 -0
  545. package/src/modules/push_notifications/backend/push_notifications/[id]/page.tsx +149 -0
  546. package/src/modules/push_notifications/backend/push_notifications/page.meta.ts +21 -0
  547. package/src/modules/push_notifications/backend/push_notifications/page.tsx +239 -0
  548. package/src/modules/push_notifications/backend/push_notifications/send/page.meta.ts +24 -0
  549. package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +327 -0
  550. package/src/modules/push_notifications/data/entities.ts +111 -0
  551. package/src/modules/push_notifications/data/extensions.ts +29 -0
  552. package/src/modules/push_notifications/data/validators.ts +102 -0
  553. package/src/modules/push_notifications/di.ts +20 -0
  554. package/src/modules/push_notifications/events.ts +21 -0
  555. package/src/modules/push_notifications/i18n/de.json +79 -0
  556. package/src/modules/push_notifications/i18n/en.json +79 -0
  557. package/src/modules/push_notifications/i18n/es.json +79 -0
  558. package/src/modules/push_notifications/i18n/ko.json +79 -0
  559. package/src/modules/push_notifications/i18n/pl.json +79 -0
  560. package/src/modules/push_notifications/index.ts +15 -0
  561. package/src/modules/push_notifications/lib/fake-provider-recorder.ts +154 -0
  562. package/src/modules/push_notifications/lib/push-delivery-strategy.ts +90 -0
  563. package/src/modules/push_notifications/lib/push-delivery.ts +455 -0
  564. package/src/modules/push_notifications/lib/push-fanout.ts +237 -0
  565. package/src/modules/push_notifications/lib/push-health.ts +47 -0
  566. package/src/modules/push_notifications/lib/push-reaper.ts +140 -0
  567. package/src/modules/push_notifications/lib/push-receipt-reaper.ts +210 -0
  568. package/src/modules/push_notifications/lib/push-stub-adapter.ts +80 -0
  569. package/src/modules/push_notifications/lib/queue.ts +74 -0
  570. package/src/modules/push_notifications/lib/reclaim-window.ts +16 -0
  571. package/src/modules/push_notifications/lib/send-custom-push.ts +100 -0
  572. package/src/modules/push_notifications/migrations/.snapshot-open-mercato.json +386 -0
  573. package/src/modules/push_notifications/migrations/Migration20260625150049_push_notifications.ts +16 -0
  574. package/src/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.ts +13 -0
  575. package/src/modules/push_notifications/notifications.delivery-strategies.ts +8 -0
  576. package/src/modules/push_notifications/notifications.ts +50 -0
  577. package/src/modules/push_notifications/setup.ts +92 -0
  578. package/src/modules/push_notifications/workers/reclaim-stuck.worker.ts +78 -0
  579. package/src/modules/push_notifications/workers/send-push.worker.ts +34 -0
  580. package/src/modules/sales/i18n/de.json +1 -0
  581. package/src/modules/sales/i18n/en.json +1 -0
  582. package/src/modules/sales/i18n/es.json +1 -0
  583. package/src/modules/sales/i18n/ko.json +1 -0
  584. package/src/modules/sales/i18n/pl.json +1 -0
  585. package/src/modules/sales/notifications.ts +8 -0
  586. package/src/modules/staff/i18n/de.json +1 -0
  587. package/src/modules/staff/i18n/en.json +1 -0
  588. package/src/modules/staff/i18n/es.json +1 -0
  589. package/src/modules/staff/i18n/ko.json +1 -0
  590. package/src/modules/staff/i18n/pl.json +1 -0
  591. package/src/modules/staff/notifications.ts +6 -0
  592. package/src/modules/workflows/i18n/de.json +1 -0
  593. package/src/modules/workflows/i18n/en.json +1 -0
  594. package/src/modules/workflows/i18n/es.json +1 -0
  595. package/src/modules/workflows/i18n/ko.json +1 -0
  596. package/src/modules/workflows/i18n/pl.json +1 -0
  597. package/src/modules/workflows/notifications.ts +2 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/frontend/NotificationSettingsPageClient.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\n\ntype NotificationDeliveryConfig = {\n appUrl?: string\n panelPath: string\n strategies: {\n database: { enabled: boolean }\n email: { enabled: boolean; from?: string; replyTo?: string; subjectPrefix?: string }\n custom?: Record<string, { enabled?: boolean; config?: unknown }>\n }\n}\n\ntype SettingsResponse = {\n settings?: NotificationDeliveryConfig\n error?: string\n}\n\nconst emptySettings: NotificationDeliveryConfig = {\n panelPath: '/backend/notifications',\n strategies: {\n database: { enabled: true },\n email: { enabled: true },\n custom: {},\n },\n}\n\nconst SETTINGS_CONTEXT_ID = 'notifications-settings'\n\nexport function NotificationSettingsPageClient() {\n const t = useT()\n const [settings, setSettings] = React.useState<NotificationDeliveryConfig | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: SETTINGS_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const fetchSettings = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const body = await readApiResultOrThrow<SettingsResponse>(\n '/api/notifications/settings',\n undefined,\n { errorMessage: t('notifications.settings.loadError', 'Failed to load notification settings'), allowNullResult: true },\n )\n if (body?.settings) {\n setSettings(body.settings)\n } else {\n setSettings(emptySettings)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : t('notifications.settings.loadError', 'Failed to load notification settings')\n setError(message)\n flash(message, 'error')\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n fetchSettings()\n }, [fetchSettings])\n\n const updateSettings = (patch: Partial<NotificationDeliveryConfig>) => {\n setSettings((prev) => (prev ? { ...prev, ...patch } : prev))\n }\n\n const updateStrategy = (\n strategy: keyof NotificationDeliveryConfig['strategies'],\n patch: Partial<NotificationDeliveryConfig['strategies'][keyof NotificationDeliveryConfig['strategies']]>,\n ) => {\n setSettings((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n strategies: {\n ...prev.strategies,\n [strategy]: {\n ...prev.strategies[strategy],\n ...patch,\n },\n },\n }\n })\n }\n\n const handleSave = async () => {\n if (!settings) return\n setSaving(true)\n try {\n const response = await runMutation({\n operation: () =>\n apiCall<SettingsResponse>('/api/notifications/settings', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(settings),\n }),\n context: { formId: SETTINGS_CONTEXT_ID, resourceKind: 'notifications.settings', retryLastMutation },\n mutationPayload: { settings },\n })\n if (!response.ok) {\n const message = response.result?.error || t('notifications.settings.saveError', 'Failed to save notification settings')\n throw new Error(message)\n }\n if (response.result?.settings) {\n setSettings(response.result.settings)\n }\n flash(t('notifications.settings.saveSuccess', 'Notification settings saved'), 'success')\n } catch (err) {\n const message = err instanceof Error ? err.message : t('notifications.settings.saveError', 'Failed to save notification settings')\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }\n\n if (loading || !settings) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" />\n {t('notifications.settings.loading', 'Loading notification settings...')}\n </div>\n )\n }\n\n return (\n <div className=\"flex flex-col gap-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{t('notifications.settings.pageTitle', 'Notification Delivery')}</h1>\n <p className=\"text-muted-foreground text-sm\">\n {t('notifications.settings.pageDescription', 'Configure delivery strategies for in-app notifications.')}\n </p>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('notifications.settings.core.title', 'Core delivery')}</CardTitle>\n <CardDescription>{t('notifications.settings.core.description', 'Control the default notification center and panel link used by external channels.')}</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-app-url\">{t('notifications.settings.core.appUrl', 'Application URL')}</Label>\n <Input\n id=\"notifications-app-url\"\n value={settings.appUrl ?? ''}\n placeholder=\"https://app.open-mercato.com\"\n onChange={(event) => updateSettings({ appUrl: event.target.value || undefined })}\n />\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.core.appUrlHint', 'Used to build absolute links in email notifications.')}</p>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-panel-path\">{t('notifications.settings.core.panelPath', 'Notification panel path')}</Label>\n <Input\n id=\"notifications-panel-path\"\n value={settings.panelPath}\n onChange={(event) => updateSettings({ panelPath: event.target.value })}\n />\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.core.panelPathHint', 'Relative path for the read-only notification panel.')}</p>\n </div>\n <div className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{t('notifications.settings.core.databaseLabel', 'In-app notifications')}</p>\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.core.databaseHint', 'Store notifications in the database for the panel and bell.')}</p>\n </div>\n <Switch\n checked={settings.strategies.database.enabled}\n disabled\n onCheckedChange={(checked) => updateStrategy('database', { enabled: checked })}\n />\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('notifications.settings.email.title', 'Email strategy')}</CardTitle>\n <CardDescription>{t('notifications.settings.email.description', 'Send notification copies via Resend using React templates.')}</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"flex items-center justify-between rounded-lg border p-3 md:col-span-2\">\n <div>\n <p className=\"text-sm font-medium\">{t('notifications.settings.email.enabledLabel', 'Enable email delivery')}</p>\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.email.enabledHint', 'Email actions are read-only and link back to the notification center.')}</p>\n </div>\n <Switch\n checked={settings.strategies.email.enabled}\n onCheckedChange={(checked) => updateStrategy('email', { enabled: checked })}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-email-from\">{t('notifications.settings.email.from', 'From address')}</Label>\n <Input\n id=\"notifications-email-from\"\n value={settings.strategies.email.from ?? ''}\n placeholder=\"notifications@open-mercato.com\"\n onChange={(event) => updateStrategy('email', { from: event.target.value || undefined })}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-email-reply\">{t('notifications.settings.email.replyTo', 'Reply-to')}</Label>\n <Input\n id=\"notifications-email-reply\"\n value={settings.strategies.email.replyTo ?? ''}\n placeholder=\"support@open-mercato.com\"\n onChange={(event) => updateStrategy('email', { replyTo: event.target.value || undefined })}\n />\n </div>\n <div className=\"space-y-2 md:col-span-2\">\n <Label htmlFor=\"notifications-email-subject-prefix\">{t('notifications.settings.email.subjectPrefix', 'Subject prefix')}</Label>\n <Input\n id=\"notifications-email-subject-prefix\"\n value={settings.strategies.email.subjectPrefix ?? ''}\n placeholder=\"[Open Mercato]\"\n onChange={(event) => updateStrategy('email', { subjectPrefix: event.target.value || undefined })}\n />\n </div>\n </CardContent>\n </Card>\n\n <div className=\"flex items-center gap-3\">\n <Button type=\"button\" onClick={handleSave} disabled={saving}>\n {saving ? t('notifications.settings.saving', 'Saving...') : t('notifications.settings.save', 'Save settings')}\n </Button>\n {error && <span className=\"text-sm text-destructive\">{error}</span>}\n </div>\n </div>\n )\n}\n\nexport default NotificationSettingsPageClient\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash, type FlashKind } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\n\ntype NotificationTypeCatalogueItem = {\n id: string\n labelKey: string\n descriptionKey?: string | null\n // Effective \"required\" flag (tenant override ?? code-declared).\n nonOptOut?: boolean\n // Effective channel eligibility for this tenant (stored override ?? code-declared; null = every channel).\n channels: string[] | null\n // Raw tenant-stored override (null = inherit the code-declared set).\n storedChannels: string[] | null\n // Raw tenant-stored nonOptOut override (null = inherit the code-declared flag).\n storedNonOptOut: boolean | null\n // Optimistic-lock version of the tenant's override row (null = no override stored yet).\n updatedAt: string | null\n}\n\ntype TypesResponse = { items?: NotificationTypeCatalogueItem[] }\ntype ChannelsResponse = { items?: Array<{ id: string; labelKey: string; descriptionKey?: string | null }> }\ntype PatchTypeResponse = { ok?: boolean; item?: NotificationTypeCatalogueItem; error?: string }\n\ntype NotificationDeliveryConfig = {\n appUrl?: string\n panelPath: string\n strategies: {\n database: { enabled: boolean }\n email: { enabled: boolean; from?: string; replyTo?: string; subjectPrefix?: string }\n custom?: Record<string, { enabled?: boolean; config?: unknown }>\n }\n}\n\ntype SettingsResponse = {\n settings?: NotificationDeliveryConfig\n error?: string\n}\n\nconst emptySettings: NotificationDeliveryConfig = {\n panelPath: '/backend/notifications',\n strategies: {\n database: { enabled: true },\n email: { enabled: true },\n custom: {},\n },\n}\n\nconst SETTINGS_CONTEXT_ID = 'notifications-settings'\n\nexport function NotificationSettingsPageClient() {\n const t = useT()\n const [settings, setSettings] = React.useState<NotificationDeliveryConfig | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: SETTINGS_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const [types, setTypes] = React.useState<NotificationTypeCatalogueItem[]>([])\n const [channels, setChannels] = React.useState<Array<{ id: string; labelKey: string }>>([])\n const [savingTypeCell, setSavingTypeCell] = React.useState<string | null>(null)\n\n const fetchCatalogue = React.useCallback(async () => {\n try {\n const [typesBody, channelsBody] = await Promise.all([\n readApiResultOrThrow<TypesResponse>('/api/notifications/types', undefined, {\n errorMessage: t('notifications.settings.types.loadError', 'Failed to load notification types'),\n allowNullResult: true,\n }),\n readApiResultOrThrow<ChannelsResponse>('/api/notifications/channels', undefined, {\n errorMessage: t('notifications.settings.types.loadError', 'Failed to load notification types'),\n allowNullResult: true,\n }),\n ])\n setTypes(typesBody?.items ?? [])\n setChannels((channelsBody?.items ?? []).map((item) => ({ id: item.id, labelKey: item.labelKey })))\n } catch (err) {\n const message = err instanceof Error ? err.message : t('notifications.settings.types.loadError', 'Failed to load notification types')\n flash(message, 'error')\n }\n }, [t])\n\n React.useEffect(() => {\n fetchCatalogue()\n }, [fetchCatalogue])\n\n const saveTypeCell = async (\n type: NotificationTypeCatalogueItem,\n cellKey: string,\n mutationPayload: Record<string, unknown>,\n operation: () => Promise<Awaited<ReturnType<typeof apiCall<PatchTypeResponse>>>>,\n resolveSavedFlash?: (saved: NotificationTypeCatalogueItem) => { message: string; kind: FlashKind } | null,\n ) => {\n setSavingTypeCell(cellKey)\n try {\n const response = await runMutation({\n operation,\n context: {\n formId: SETTINGS_CONTEXT_ID,\n resourceKind: 'notifications.settings',\n resourceId: type.id,\n retryLastMutation,\n },\n mutationPayload,\n })\n if (!response.ok || !response.result?.ok) {\n if (surfaceRecordConflict({ status: response.status, body: response.result }, t, { onRefresh: fetchCatalogue })) {\n await fetchCatalogue()\n return\n }\n const message = response.result?.error || t('notifications.settings.types.saveError', 'Failed to save notification type settings')\n throw new Error(message)\n }\n const saved = response.result.item\n setTypes((prev) =>\n prev.map((item) =>\n item.id === type.id && saved\n ? {\n ...item,\n channels: saved.channels,\n storedChannels: saved.storedChannels,\n nonOptOut: saved.nonOptOut,\n storedNonOptOut: saved.storedNonOptOut,\n updatedAt: saved.updatedAt,\n }\n : item,\n ),\n )\n const savedFlash = saved ? resolveSavedFlash?.(saved) ?? null : null\n flash(\n savedFlash?.message ?? t('notifications.settings.types.saveSuccess', 'Notification type settings saved'),\n savedFlash?.kind ?? 'success',\n )\n } catch (err) {\n const message = err instanceof Error ? err.message : t('notifications.settings.types.saveError', 'Failed to save notification type settings')\n flash(message, 'error')\n } finally {\n setSavingTypeCell(null)\n }\n }\n\n /**\n * `nonOptOut` is a single scalar the request replaces wholesale, so it keeps the\n * optimistic-lock header: a concurrent operator save flips the override row's `updatedAt`\n * and the server 409s rather than letting a stale blind write revert their edit.\n */\n const patchType = (\n type: NotificationTypeCatalogueItem,\n cellKey: string,\n payload: { channels?: string[] | null; nonOptOut?: boolean | null },\n ) =>\n saveTypeCell(type, cellKey, { id: type.id, ...payload }, () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(type.updatedAt), () =>\n apiCall<PatchTypeResponse>('/api/notifications/types', {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: type.id, ...payload }),\n }),\n ),\n )\n\n /**\n * optimistic-lock-exempt: notification type channel add/remove. Addressing the single\n * channel lets the server derive the next eligibility set from the CURRENT stored state\n * under a row lock, so two operators toggling different channels both land instead of the\n * later one clobbering the earlier with a client-computed array. There is no shared\n * aggregate left for a version check to protect \u2014 the same reason the sibling preference\n * matrix and the customers/catalog add-remove endpoints are exempt.\n */\n const toggleTypeChannel = (\n type: NotificationTypeCatalogueItem,\n cellKey: string,\n channel: string,\n enabled: boolean,\n ) =>\n saveTypeCell(\n type,\n cellKey,\n { id: type.id, channel, enabled },\n () =>\n apiCall<PatchTypeResponse>(\n `/api/notifications/types/${encodeURIComponent(type.id)}/channels/${encodeURIComponent(channel)}`,\n { method: enabled ? 'PUT' : 'DELETE' },\n ),\n // Unchecking the last channel clears the override instead of storing an empty set (which would\n // black-hole the type), so the code-declared default reapplies and the box the operator just\n // unticked can come back ticked. The write succeeded, so say what happened rather than letting a\n // green \"saved\" sit next to a checkbox that visibly ignored the click.\n (saved) =>\n !enabled && saved.storedChannels === null && (saved.channels === null || saved.channels.includes(channel))\n ? {\n message: t(\n 'notifications.settings.types.lastChannelRestored',\n 'A notification type must keep at least one channel, so the module default was restored.',\n ),\n kind: 'warning' as FlashKind,\n }\n : null,\n )\n\n const handleTypeChannelToggle = async (\n type: NotificationTypeCatalogueItem,\n channelId: string,\n enabled: boolean,\n ) => {\n // The server resolves the base set (stored override ?? code declaration ?? every\n // registered channel) and clears the override when the last channel is unchecked, so the\n // client no longer computes \u2014 or can stale-clobber \u2014 the whole array.\n await toggleTypeChannel(type, `${type.id}::${channelId}`, channelId, enabled)\n }\n\n const handleTypeNonOptOutToggle = async (type: NotificationTypeCatalogueItem, required: boolean) => {\n await patchType(type, `${type.id}::nonOptOut`, { nonOptOut: required })\n }\n\n const fetchSettings = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const body = await readApiResultOrThrow<SettingsResponse>(\n '/api/notifications/settings',\n undefined,\n { errorMessage: t('notifications.settings.loadError', 'Failed to load notification settings'), allowNullResult: true },\n )\n if (body?.settings) {\n setSettings(body.settings)\n } else {\n setSettings(emptySettings)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : t('notifications.settings.loadError', 'Failed to load notification settings')\n setError(message)\n flash(message, 'error')\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n fetchSettings()\n }, [fetchSettings])\n\n const updateSettings = (patch: Partial<NotificationDeliveryConfig>) => {\n setSettings((prev) => (prev ? { ...prev, ...patch } : prev))\n }\n\n const updateStrategy = (\n strategy: keyof NotificationDeliveryConfig['strategies'],\n patch: Partial<NotificationDeliveryConfig['strategies'][keyof NotificationDeliveryConfig['strategies']]>,\n ) => {\n setSettings((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n strategies: {\n ...prev.strategies,\n [strategy]: {\n ...prev.strategies[strategy],\n ...patch,\n },\n },\n }\n })\n }\n\n const handleSave = async () => {\n if (!settings) return\n setSaving(true)\n try {\n const response = await runMutation({\n operation: () =>\n apiCall<SettingsResponse>('/api/notifications/settings', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(settings),\n }),\n context: { formId: SETTINGS_CONTEXT_ID, resourceKind: 'notifications.settings', retryLastMutation },\n mutationPayload: { settings },\n })\n if (!response.ok) {\n const message = response.result?.error || t('notifications.settings.saveError', 'Failed to save notification settings')\n throw new Error(message)\n }\n if (response.result?.settings) {\n setSettings(response.result.settings)\n }\n flash(t('notifications.settings.saveSuccess', 'Notification settings saved'), 'success')\n } catch (err) {\n const message = err instanceof Error ? err.message : t('notifications.settings.saveError', 'Failed to save notification settings')\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }\n\n if (loading || !settings) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" />\n {t('notifications.settings.loading', 'Loading notification settings...')}\n </div>\n )\n }\n\n return (\n <div className=\"flex flex-col gap-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{t('notifications.settings.pageTitle', 'Notification Delivery')}</h1>\n <p className=\"text-muted-foreground text-sm\">\n {t('notifications.settings.pageDescription', 'Configure delivery strategies for in-app notifications.')}\n </p>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('notifications.settings.core.title', 'Core delivery')}</CardTitle>\n <CardDescription>{t('notifications.settings.core.description', 'Control the default notification center and panel link used by external channels.')}</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-app-url\">{t('notifications.settings.core.appUrl', 'Application URL')}</Label>\n <Input\n id=\"notifications-app-url\"\n value={settings.appUrl ?? ''}\n placeholder=\"https://app.open-mercato.com\"\n onChange={(event) => updateSettings({ appUrl: event.target.value || undefined })}\n />\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.core.appUrlHint', 'Used to build absolute links in email notifications.')}</p>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-panel-path\">{t('notifications.settings.core.panelPath', 'Notification panel path')}</Label>\n <Input\n id=\"notifications-panel-path\"\n value={settings.panelPath}\n onChange={(event) => updateSettings({ panelPath: event.target.value })}\n />\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.core.panelPathHint', 'Relative path for the read-only notification panel.')}</p>\n </div>\n <div className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{t('notifications.settings.core.databaseLabel', 'In-app notifications')}</p>\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.core.databaseHint', 'Store notifications in the database for the panel and bell.')}</p>\n </div>\n <Switch\n checked={settings.strategies.database.enabled}\n disabled\n onCheckedChange={(checked) => updateStrategy('database', { enabled: checked })}\n />\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('notifications.settings.email.title', 'Email strategy')}</CardTitle>\n <CardDescription>{t('notifications.settings.email.description', 'Send notification copies via Resend using React templates.')}</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"flex items-center justify-between rounded-lg border p-3 md:col-span-2\">\n <div>\n <p className=\"text-sm font-medium\">{t('notifications.settings.email.enabledLabel', 'Enable email delivery')}</p>\n <p className=\"text-xs text-muted-foreground\">{t('notifications.settings.email.enabledHint', 'Email actions are read-only and link back to the notification center.')}</p>\n </div>\n <Switch\n checked={settings.strategies.email.enabled}\n onCheckedChange={(checked) => updateStrategy('email', { enabled: checked })}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-email-from\">{t('notifications.settings.email.from', 'From address')}</Label>\n <Input\n id=\"notifications-email-from\"\n value={settings.strategies.email.from ?? ''}\n placeholder=\"notifications@open-mercato.com\"\n onChange={(event) => updateStrategy('email', { from: event.target.value || undefined })}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"notifications-email-reply\">{t('notifications.settings.email.replyTo', 'Reply-to')}</Label>\n <Input\n id=\"notifications-email-reply\"\n value={settings.strategies.email.replyTo ?? ''}\n placeholder=\"support@open-mercato.com\"\n onChange={(event) => updateStrategy('email', { replyTo: event.target.value || undefined })}\n />\n </div>\n <div className=\"space-y-2 md:col-span-2\">\n <Label htmlFor=\"notifications-email-subject-prefix\">{t('notifications.settings.email.subjectPrefix', 'Subject prefix')}</Label>\n <Input\n id=\"notifications-email-subject-prefix\"\n value={settings.strategies.email.subjectPrefix ?? ''}\n placeholder=\"[Open Mercato]\"\n onChange={(event) => updateStrategy('email', { subjectPrefix: event.target.value || undefined })}\n />\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('notifications.settings.types.title', 'Delivery channels per type')}</CardTitle>\n <CardDescription>\n {t(\n 'notifications.settings.types.description',\n 'Delivery channels per notification type. Turning a channel off disables it completely for that type in your tenant \u2014 it never delivers and users cannot enable it in their preferences. Turning it on lets users opt in or out per type; required (non-opt-out) types always deliver on enabled channels. Your setting overrides the module default.',\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {types.length === 0 || channels.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('notifications.settings.types.empty', 'No notification types are registered yet.')}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border bg-muted/50 text-left\">\n <th className=\"px-4 py-3 font-medium\">\n {t('notifications.settings.types.typeColumn', 'Notification type')}\n </th>\n {channels.map((channel) => (\n <th key={channel.id} className=\"px-4 py-3 font-medium\">\n {t(channel.labelKey, channel.id)}\n </th>\n ))}\n <th className=\"px-4 py-3 font-medium\">\n <div>{t('notifications.settings.types.requiredColumn', 'Required')}</div>\n <div className=\"text-xs font-normal text-muted-foreground\">\n {t('notifications.settings.types.requiredHint', 'Users cannot opt out when on.')}\n </div>\n </th>\n </tr>\n </thead>\n <tbody>\n {types.map((type) => (\n <tr key={type.id} className=\"border-b border-border last:border-0\">\n <td className=\"px-4 py-3\">\n <div className=\"font-medium\">{t(type.labelKey, type.id)}</div>\n {type.descriptionKey ? (\n <div className=\"text-xs text-muted-foreground\">{t(type.descriptionKey, '')}</div>\n ) : null}\n </td>\n {channels.map((channel) => {\n const cellKey = `${type.id}::${channel.id}`\n const channelEnabled = type.channels === null || type.channels.includes(channel.id)\n return (\n <td key={channel.id} className=\"px-4 py-3\">\n {/* The saving spinner is absolutely positioned so it never joins the cell's\n layout flow \u2014 an in-flow sibling would widen the column mid-toggle and\n make the whole table jump for the duration of the save. */}\n <span className=\"relative inline-flex items-center\">\n <Switch\n checked={channelEnabled}\n disabled={savingTypeCell !== null}\n aria-label={`${t(type.labelKey, type.id)} \u2013 ${t(channel.labelKey, channel.id)}`}\n onCheckedChange={(checked) =>\n handleTypeChannelToggle(type, channel.id, checked)\n }\n />\n {savingTypeCell === cellKey ? (\n <Spinner size=\"sm\" className=\"absolute left-full ml-2\" />\n ) : null}\n </span>\n </td>\n )\n })}\n <td className=\"px-4 py-3\">\n <span className=\"relative inline-flex\">\n <Switch\n checked={type.nonOptOut === true}\n disabled={savingTypeCell !== null}\n aria-label={`${t(type.labelKey, type.id)} \u2013 ${t('notifications.settings.types.requiredColumn', 'Required')}`}\n onCheckedChange={(checked) => handleTypeNonOptOutToggle(type, checked)}\n />\n {savingTypeCell === `${type.id}::nonOptOut` ? (\n <Spinner size=\"sm\" className=\"absolute left-full ml-2\" />\n ) : null}\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </CardContent>\n </Card>\n\n <div className=\"flex items-center gap-3\">\n <Button type=\"button\" onClick={handleSave} disabled={saving}>\n {saving ? t('notifications.settings.saving', 'Saving...') : t('notifications.settings.save', 'Save settings')}\n </Button>\n {error && <span className=\"text-sm text-destructive\">{error}</span>}\n </div>\n </div>\n )\n}\n\nexport default NotificationSettingsPageClient\n"],
5
+ "mappings": ";AA8TM,SACE,KADF;AA5TN,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,SAAS,sBAAsB,mCAAmC;AAC3E,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AACnC,SAAS,aAA6B;AACtC,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,MAAM,aAAa,iBAAiB,YAAY,iBAAiB;AAqC1E,MAAM,gBAA4C;AAAA,EAChD,WAAW;AAAA,EACX,YAAY;AAAA,IACV,UAAU,EAAE,SAAS,KAAK;AAAA,IAC1B,OAAO,EAAE,SAAS,KAAK;AAAA,IACvB,QAAQ,CAAC;AAAA,EACX;AACF;AAEA,MAAM,sBAAsB;AAErB,SAAS,iCAAiC;AAC/C,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA4C,IAAI;AACtF,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAE5D,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAKxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AAED,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA0C,CAAC,CAAC;AAC5E,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAkD,CAAC,CAAC;AAC1F,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAwB,IAAI;AAE9E,QAAM,iBAAiB,MAAM,YAAY,YAAY;AACnD,QAAI;AACF,YAAM,CAAC,WAAW,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,QAClD,qBAAoC,4BAA4B,QAAW;AAAA,UACzE,cAAc,EAAE,0CAA0C,mCAAmC;AAAA,UAC7F,iBAAiB;AAAA,QACnB,CAAC;AAAA,QACD,qBAAuC,+BAA+B,QAAW;AAAA,UAC/E,cAAc,EAAE,0CAA0C,mCAAmC;AAAA,UAC7F,iBAAiB;AAAA,QACnB,CAAC;AAAA,MACH,CAAC;AACD,eAAS,WAAW,SAAS,CAAC,CAAC;AAC/B,mBAAa,cAAc,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;AAAA,IACnG,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,EAAE,0CAA0C,mCAAmC;AACpI,YAAM,SAAS,OAAO;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,mBAAe;AAAA,EACjB,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,eAAe,OACnB,MACA,SACA,iBACA,WACA,sBACG;AACH,sBAAkB,OAAO;AACzB,QAAI;AACF,YAAM,WAAW,MAAM,YAAY;AAAA,QACjC;AAAA,QACA,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY,KAAK;AAAA,UACjB;AAAA,QACF;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,CAAC,SAAS,MAAM,CAAC,SAAS,QAAQ,IAAI;AACxC,YAAI,sBAAsB,EAAE,QAAQ,SAAS,QAAQ,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE,WAAW,eAAe,CAAC,GAAG;AAC/G,gBAAM,eAAe;AACrB;AAAA,QACF;AACA,cAAM,UAAU,SAAS,QAAQ,SAAS,EAAE,0CAA0C,2CAA2C;AACjI,cAAM,IAAI,MAAM,OAAO;AAAA,MACzB;AACA,YAAM,QAAQ,SAAS,OAAO;AAC9B;AAAA,QAAS,CAAC,SACR,KAAK;AAAA,UAAI,CAAC,SACR,KAAK,OAAO,KAAK,MAAM,QACnB;AAAA,YACE,GAAG;AAAA,YACH,UAAU,MAAM;AAAA,YAChB,gBAAgB,MAAM;AAAA,YACtB,WAAW,MAAM;AAAA,YACjB,iBAAiB,MAAM;AAAA,YACvB,WAAW,MAAM;AAAA,UACnB,IACA;AAAA,QACN;AAAA,MACF;AACA,YAAM,aAAa,QAAQ,oBAAoB,KAAK,KAAK,OAAO;AAChE;AAAA,QACE,YAAY,WAAW,EAAE,4CAA4C,kCAAkC;AAAA,QACvG,YAAY,QAAQ;AAAA,MACtB;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,EAAE,0CAA0C,2CAA2C;AAC5I,YAAM,SAAS,OAAO;AAAA,IACxB,UAAE;AACA,wBAAkB,IAAI;AAAA,IACxB;AAAA,EACF;AAOA,QAAM,YAAY,CAChB,MACA,SACA,YAEA;AAAA,IAAa;AAAA,IAAM;AAAA,IAAS,EAAE,IAAI,KAAK,IAAI,GAAG,QAAQ;AAAA,IAAG,MACvD;AAAA,MAA4B,0BAA0B,KAAK,SAAS;AAAA,MAAG,MACrE,QAA2B,4BAA4B;AAAA,QACrD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,KAAK,IAAI,GAAG,QAAQ,CAAC;AAAA,MAClD,CAAC;AAAA,IACH;AAAA,EACF;AAUF,QAAM,oBAAoB,CACxB,MACA,SACA,SACA,YAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA,EAAE,IAAI,KAAK,IAAI,SAAS,QAAQ;AAAA,IAChC,MACE;AAAA,MACE,4BAA4B,mBAAmB,KAAK,EAAE,CAAC,aAAa,mBAAmB,OAAO,CAAC;AAAA,MAC/F,EAAE,QAAQ,UAAU,QAAQ,SAAS;AAAA,IACvC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,CAAC,UACC,CAAC,WAAW,MAAM,mBAAmB,SAAS,MAAM,aAAa,QAAQ,MAAM,SAAS,SAAS,OAAO,KACpG;AAAA,MACE,SAAS;AAAA,QACP;AAAA,QACA;AAAA,MACF;AAAA,MACA,MAAM;AAAA,IACR,IACA;AAAA,EACR;AAEF,QAAM,0BAA0B,OAC9B,MACA,WACA,YACG;AAIH,UAAM,kBAAkB,MAAM,GAAG,KAAK,EAAE,KAAK,SAAS,IAAI,WAAW,OAAO;AAAA,EAC9E;AAEA,QAAM,4BAA4B,OAAO,MAAqC,aAAsB;AAClG,UAAM,UAAU,MAAM,GAAG,KAAK,EAAE,eAAe,EAAE,WAAW,SAAS,CAAC;AAAA,EACxE;AAEA,QAAM,gBAAgB,MAAM,YAAY,YAAY;AAClD,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA,EAAE,cAAc,EAAE,oCAAoC,sCAAsC,GAAG,iBAAiB,KAAK;AAAA,MACvH;AACA,UAAI,MAAM,UAAU;AAClB,oBAAY,KAAK,QAAQ;AAAA,MAC3B,OAAO;AACL,oBAAY,aAAa;AAAA,MAC3B;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,EAAE,oCAAoC,sCAAsC;AACjI,eAAS,OAAO;AAChB,YAAM,SAAS,OAAO;AAAA,IACxB,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,kBAAc;AAAA,EAChB,GAAG,CAAC,aAAa,CAAC;AAElB,QAAM,iBAAiB,CAAC,UAA+C;AACrE,gBAAY,CAAC,SAAU,OAAO,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI,IAAK;AAAA,EAC7D;AAEA,QAAM,iBAAiB,CACrB,UACA,UACG;AACH,gBAAY,CAAC,SAAS;AACpB,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,UACV,GAAG,KAAK;AAAA,UACR,CAAC,QAAQ,GAAG;AAAA,YACV,GAAG,KAAK,WAAW,QAAQ;AAAA,YAC3B,GAAG;AAAA,UACL;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,aAAa,YAAY;AAC7B,QAAI,CAAC,SAAU;AACf,cAAU,IAAI;AACd,QAAI;AACF,YAAM,WAAW,MAAM,YAAY;AAAA,QACjC,WAAW,MACT,QAA0B,+BAA+B;AAAA,UACvD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,QAAQ;AAAA,QAC/B,CAAC;AAAA,QACH,SAAS,EAAE,QAAQ,qBAAqB,cAAc,0BAA0B,kBAAkB;AAAA,QAClG,iBAAiB,EAAE,SAAS;AAAA,MAC9B,CAAC;AACD,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,UAAU,SAAS,QAAQ,SAAS,EAAE,oCAAoC,sCAAsC;AACtH,cAAM,IAAI,MAAM,OAAO;AAAA,MACzB;AACA,UAAI,SAAS,QAAQ,UAAU;AAC7B,oBAAY,SAAS,OAAO,QAAQ;AAAA,MACtC;AACA,YAAM,EAAE,sCAAsC,6BAA6B,GAAG,SAAS;AAAA,IACzF,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,EAAE,oCAAoC,sCAAsC;AACjI,YAAM,SAAS,OAAO;AAAA,IACxB,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,MAAI,WAAW,CAAC,UAAU;AACxB,WACE,qBAAC,SAAI,WAAU,yDACb;AAAA,0BAAC,WAAQ,MAAK,MAAK;AAAA,MAClB,EAAE,kCAAkC,kCAAkC;AAAA,OACzE;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,uBACb;AAAA,yBAAC,SACC;AAAA,0BAAC,QAAG,WAAU,0BAA0B,YAAE,oCAAoC,uBAAuB,GAAE;AAAA,MACvG,oBAAC,OAAE,WAAU,iCACV,YAAE,0CAA0C,yDAAyD,GACxG;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,2BAAC,cACC;AAAA,4BAAC,aAAW,YAAE,qCAAqC,eAAe,GAAE;AAAA,QACpE,oBAAC,mBAAiB,YAAE,2CAA2C,mFAAmF,GAAE;AAAA,SACtJ;AAAA,MACA,qBAAC,eAAY,WAAU,6BACrB;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,yBAAyB,YAAE,sCAAsC,iBAAiB,GAAE;AAAA,UACnG;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,SAAS,UAAU;AAAA,cAC1B,aAAY;AAAA,cACZ,UAAU,CAAC,UAAU,eAAe,EAAE,QAAQ,MAAM,OAAO,SAAS,OAAU,CAAC;AAAA;AAAA,UACjF;AAAA,UACA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,0CAA0C,sDAAsD,GAAE;AAAA,WACpJ;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,4BAA4B,YAAE,yCAAyC,yBAAyB,GAAE;AAAA,UACjH;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,SAAS;AAAA,cAChB,UAAU,CAAC,UAAU,eAAe,EAAE,WAAW,MAAM,OAAO,MAAM,CAAC;AAAA;AAAA,UACvE;AAAA,UACA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,6CAA6C,qDAAqD,GAAE;AAAA,WACtJ;AAAA,QACA,qBAAC,SAAI,WAAU,2DACb;AAAA,+BAAC,SACC;AAAA,gCAAC,OAAE,WAAU,uBAAuB,YAAE,6CAA6C,sBAAsB,GAAE;AAAA,YAC3G,oBAAC,OAAE,WAAU,iCAAiC,YAAE,4CAA4C,6DAA6D,GAAE;AAAA,aAC7J;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,SAAS,WAAW,SAAS;AAAA,cACtC,UAAQ;AAAA,cACR,iBAAiB,CAAC,YAAY,eAAe,YAAY,EAAE,SAAS,QAAQ,CAAC;AAAA;AAAA,UAC/E;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,2BAAC,cACC;AAAA,4BAAC,aAAW,YAAE,sCAAsC,gBAAgB,GAAE;AAAA,QACtE,oBAAC,mBAAiB,YAAE,4CAA4C,4DAA4D,GAAE;AAAA,SAChI;AAAA,MACA,qBAAC,eAAY,WAAU,6BACrB;AAAA,6BAAC,SAAI,WAAU,yEACb;AAAA,+BAAC,SACC;AAAA,gCAAC,OAAE,WAAU,uBAAuB,YAAE,6CAA6C,uBAAuB,GAAE;AAAA,YAC5G,oBAAC,OAAE,WAAU,iCAAiC,YAAE,4CAA4C,uEAAuE,GAAE;AAAA,aACvK;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,SAAS,WAAW,MAAM;AAAA,cACnC,iBAAiB,CAAC,YAAY,eAAe,SAAS,EAAE,SAAS,QAAQ,CAAC;AAAA;AAAA,UAC5E;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,4BAA4B,YAAE,qCAAqC,cAAc,GAAE;AAAA,UAClG;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,SAAS,WAAW,MAAM,QAAQ;AAAA,cACzC,aAAY;AAAA,cACZ,UAAU,CAAC,UAAU,eAAe,SAAS,EAAE,MAAM,MAAM,OAAO,SAAS,OAAU,CAAC;AAAA;AAAA,UACxF;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,6BAA6B,YAAE,wCAAwC,UAAU,GAAE;AAAA,UAClG;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,SAAS,WAAW,MAAM,WAAW;AAAA,cAC5C,aAAY;AAAA,cACZ,UAAU,CAAC,UAAU,eAAe,SAAS,EAAE,SAAS,MAAM,OAAO,SAAS,OAAU,CAAC;AAAA;AAAA,UAC3F;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,SAAM,SAAQ,sCAAsC,YAAE,8CAA8C,gBAAgB,GAAE;AAAA,UACvH;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,SAAS,WAAW,MAAM,iBAAiB;AAAA,cAClD,aAAY;AAAA,cACZ,UAAU,CAAC,UAAU,eAAe,SAAS,EAAE,eAAe,MAAM,OAAO,SAAS,OAAU,CAAC;AAAA;AAAA,UACjG;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,2BAAC,cACC;AAAA,4BAAC,aAAW,YAAE,sCAAsC,4BAA4B,GAAE;AAAA,QAClF,oBAAC,mBACE;AAAA,UACC;AAAA,UACA;AAAA,QACF,GACF;AAAA,SACF;AAAA,MACA,oBAAC,eACE,gBAAM,WAAW,KAAK,SAAS,WAAW,IACzC,oBAAC,OAAE,WAAU,iCACV,YAAE,sCAAsC,2CAA2C,GACtF,IAEA,oBAAC,SAAI,WAAU,mDACb,+BAAC,WAAM,WAAU,kBACf;AAAA,4BAAC,WACC,+BAAC,QAAG,WAAU,gDACZ;AAAA,8BAAC,QAAG,WAAU,yBACX,YAAE,2CAA2C,mBAAmB,GACnE;AAAA,UACC,SAAS,IAAI,CAAC,YACb,oBAAC,QAAoB,WAAU,yBAC5B,YAAE,QAAQ,UAAU,QAAQ,EAAE,KADxB,QAAQ,EAEjB,CACD;AAAA,UACD,qBAAC,QAAG,WAAU,yBACZ;AAAA,gCAAC,SAAK,YAAE,+CAA+C,UAAU,GAAE;AAAA,YACnE,oBAAC,SAAI,WAAU,6CACZ,YAAE,6CAA6C,+BAA+B,GACjF;AAAA,aACF;AAAA,WACF,GACF;AAAA,QACA,oBAAC,WACE,gBAAM,IAAI,CAAC,SACV,qBAAC,QAAiB,WAAU,wCAC1B;AAAA,+BAAC,QAAG,WAAU,aACZ;AAAA,gCAAC,SAAI,WAAU,eAAe,YAAE,KAAK,UAAU,KAAK,EAAE,GAAE;AAAA,YACvD,KAAK,iBACJ,oBAAC,SAAI,WAAU,iCAAiC,YAAE,KAAK,gBAAgB,EAAE,GAAE,IACzE;AAAA,aACN;AAAA,UACC,SAAS,IAAI,CAAC,YAAY;AACzB,kBAAM,UAAU,GAAG,KAAK,EAAE,KAAK,QAAQ,EAAE;AACzC,kBAAM,iBAAiB,KAAK,aAAa,QAAQ,KAAK,SAAS,SAAS,QAAQ,EAAE;AAClF,mBACE,oBAAC,QAAoB,WAAU,aAI7B,+BAAC,UAAK,WAAU,qCACd;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAS;AAAA,kBACT,UAAU,mBAAmB;AAAA,kBAC7B,cAAY,GAAG,EAAE,KAAK,UAAU,KAAK,EAAE,CAAC,WAAM,EAAE,QAAQ,UAAU,QAAQ,EAAE,CAAC;AAAA,kBAC7E,iBAAiB,CAAC,YAChB,wBAAwB,MAAM,QAAQ,IAAI,OAAO;AAAA;AAAA,cAErD;AAAA,cACC,mBAAmB,UAClB,oBAAC,WAAQ,MAAK,MAAK,WAAU,2BAA0B,IACrD;AAAA,eACN,KAhBO,QAAQ,EAiBjB;AAAA,UAEJ,CAAC;AAAA,UACD,oBAAC,QAAG,WAAU,aACZ,+BAAC,UAAK,WAAU,wBACd;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,KAAK,cAAc;AAAA,gBAC5B,UAAU,mBAAmB;AAAA,gBAC7B,cAAY,GAAG,EAAE,KAAK,UAAU,KAAK,EAAE,CAAC,WAAM,EAAE,+CAA+C,UAAU,CAAC;AAAA,gBAC1G,iBAAiB,CAAC,YAAY,0BAA0B,MAAM,OAAO;AAAA;AAAA,YACvE;AAAA,YACC,mBAAmB,GAAG,KAAK,EAAE,gBAC5B,oBAAC,WAAQ,MAAK,MAAK,WAAU,2BAA0B,IACrD;AAAA,aACN,GACF;AAAA,aA3CO,KAAK,EA4Cd,CACD,GACH;AAAA,SACF,GACF,GAEJ;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,UAAO,MAAK,UAAS,SAAS,YAAY,UAAU,QAClD,mBAAS,EAAE,iCAAiC,WAAW,IAAI,EAAE,+BAA+B,eAAe,GAC9G;AAAA,MACC,SAAS,oBAAC,UAAK,WAAU,4BAA4B,iBAAM;AAAA,OAC9D;AAAA,KACF;AAEJ;AAEA,IAAO,yCAAQ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,172 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import { apiCall, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
6
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
7
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
8
+ import { Button } from "@open-mercato/ui/primitives/button";
9
+ import { LoadingMessage } from "@open-mercato/ui/backend/detail";
10
+ import { LookupSelect } from "@open-mercato/ui/backend/inputs";
11
+ import {
12
+ NotificationPreferenceMatrix,
13
+ PREFERENCE_CHANNELS,
14
+ buildPreferenceMap,
15
+ diffPreferenceItems,
16
+ preferenceKey,
17
+ toChannelDef
18
+ } from "./NotificationPreferenceMatrix.js";
19
+ const ADMIN_PREFERENCES_CONTEXT_ID = "notifications-admin-preferences";
20
+ function userLabel(user) {
21
+ return user.name?.trim() || user.email?.trim() || user.id;
22
+ }
23
+ function NotificationUserPreferencesAdminPageClient() {
24
+ const t = useT();
25
+ const [types, setTypes] = React.useState([]);
26
+ const [channels, setChannels] = React.useState(PREFERENCE_CHANNELS);
27
+ const [typesLoading, setTypesLoading] = React.useState(true);
28
+ const [selectedUserId, setSelectedUserId] = React.useState(null);
29
+ const userLabels = React.useRef(/* @__PURE__ */ new Map());
30
+ const [prefs, setPrefs] = React.useState({});
31
+ const initialPrefs = React.useRef({});
32
+ const [prefsLoading, setPrefsLoading] = React.useState(false);
33
+ const [saving, setSaving] = React.useState(false);
34
+ const { runMutation, retryLastMutation } = useGuardedMutation({
35
+ contextId: ADMIN_PREFERENCES_CONTEXT_ID,
36
+ blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
37
+ });
38
+ React.useEffect(() => {
39
+ let cancelled = false;
40
+ (async () => {
41
+ setTypesLoading(true);
42
+ try {
43
+ const [body, channelsBody] = await Promise.all([
44
+ readApiResultOrThrow("/api/notifications/types", void 0, {
45
+ errorMessage: t("notifications.preferences.loadError", "Failed to load notification preferences"),
46
+ allowNullResult: true
47
+ }),
48
+ readApiResultOrThrow("/api/notifications/channels", void 0, {
49
+ errorMessage: t("notifications.preferences.loadError", "Failed to load notification preferences"),
50
+ allowNullResult: true
51
+ })
52
+ ]);
53
+ if (!cancelled) {
54
+ setTypes(body?.items ?? []);
55
+ setChannels(channelsBody?.items?.length ? channelsBody.items.map(toChannelDef) : PREFERENCE_CHANNELS);
56
+ }
57
+ } catch (err) {
58
+ if (!cancelled) flash(err instanceof Error ? err.message : t("notifications.preferences.loadError", "Failed to load notification preferences"), "error");
59
+ } finally {
60
+ if (!cancelled) setTypesLoading(false);
61
+ }
62
+ })();
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, [t]);
67
+ const fetchUsers = React.useCallback(async (query) => {
68
+ const params = new URLSearchParams({ page: "1", pageSize: "10" });
69
+ if (query && query.trim().length) params.set("search", query.trim());
70
+ const call = await apiCall(
71
+ `/api/auth/users?${params.toString()}`,
72
+ { headers: { "x-om-forbidden-redirect": "0" } },
73
+ { fallback: null }
74
+ ).catch(() => null);
75
+ if (!call || !call.ok) return [];
76
+ return (call.result?.items ?? []).flatMap((user) => {
77
+ if (!user || typeof user.id !== "string" || !user.id.trim()) return [];
78
+ const label = userLabel(user);
79
+ userLabels.current.set(user.id, label);
80
+ const email = user.email?.trim() ?? null;
81
+ return [{ id: user.id, title: label, subtitle: email && email !== label ? email : null }];
82
+ });
83
+ }, []);
84
+ const loadPreferencesFor = React.useCallback(async (userId) => {
85
+ setPrefsLoading(true);
86
+ try {
87
+ const body = await readApiResultOrThrow(
88
+ `/api/notifications/admin/preferences?userId=${encodeURIComponent(userId)}`,
89
+ void 0,
90
+ { errorMessage: t("notifications.preferences.loadError", "Failed to load notification preferences"), allowNullResult: true }
91
+ );
92
+ const map = buildPreferenceMap(types, body?.items ?? [], channels);
93
+ initialPrefs.current = map;
94
+ setPrefs(map);
95
+ } catch (err) {
96
+ flash(err instanceof Error ? err.message : t("notifications.preferences.loadError", "Failed to load notification preferences"), "error");
97
+ setSelectedUserId(null);
98
+ } finally {
99
+ setPrefsLoading(false);
100
+ }
101
+ }, [t, types, channels]);
102
+ const handleSelectUser = (userId) => {
103
+ setSelectedUserId(userId);
104
+ if (userId) void loadPreferencesFor(userId);
105
+ };
106
+ const togglePref = (typeId, channel, enabled) => {
107
+ setPrefs((prev) => ({ ...prev, [preferenceKey(typeId, channel)]: enabled }));
108
+ };
109
+ const handleSave = async () => {
110
+ if (!selectedUserId) return;
111
+ setSaving(true);
112
+ try {
113
+ const preferences = diffPreferenceItems(types, initialPrefs.current, prefs, channels);
114
+ if (preferences.length === 0) {
115
+ flash(t("notifications.preferences.saveSuccess", "Notification preferences saved"), "success");
116
+ return;
117
+ }
118
+ const response = await runMutation({
119
+ // optimistic-lock-exempt: idempotent per-cell preference toggle grid — the client sends only
120
+ // changed (type, channel) cells (diffPreferenceItems) and the server upserts them
121
+ // last-write-wins per cell, so there is no lost-update hazard to version-lock.
122
+ operation: () => apiCall("/api/notifications/admin/preferences", {
123
+ method: "PUT",
124
+ headers: { "Content-Type": "application/json" },
125
+ body: JSON.stringify({ userId: selectedUserId, preferences })
126
+ }),
127
+ context: { formId: ADMIN_PREFERENCES_CONTEXT_ID, resourceKind: "notifications.preference", retryLastMutation },
128
+ mutationPayload: { userId: selectedUserId, preferences }
129
+ });
130
+ if (!response.ok) {
131
+ throw new Error(response.result?.error || t("notifications.preferences.saveError", "Failed to save notification preferences"));
132
+ }
133
+ initialPrefs.current = prefs;
134
+ flash(t("notifications.preferences.saveSuccess", "Notification preferences saved"), "success");
135
+ } catch (err) {
136
+ flash(err instanceof Error ? err.message : t("notifications.preferences.saveError", "Failed to save notification preferences"), "error");
137
+ } finally {
138
+ setSaving(false);
139
+ }
140
+ };
141
+ const selectedLabel = selectedUserId ? userLabels.current.get(selectedUserId) ?? selectedUserId : "";
142
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
143
+ /* @__PURE__ */ jsxs("div", { children: [
144
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: t("notifications.preferences.admin.pageTitle", "User Notification Preferences") }),
145
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: t("notifications.preferences.admin.pageDescription", "Search for a user to review and edit their notification channel preferences.") })
146
+ ] }),
147
+ /* @__PURE__ */ jsx(
148
+ LookupSelect,
149
+ {
150
+ value: selectedUserId,
151
+ onChange: handleSelectUser,
152
+ fetchOptions: fetchUsers,
153
+ defaultOpen: true,
154
+ searchPlaceholder: t("notifications.preferences.admin.searchPlaceholder", "Search users by name or email..."),
155
+ emptyLabel: t("notifications.preferences.admin.noUsers", "No users found.")
156
+ }
157
+ ),
158
+ selectedUserId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
159
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium", children: t("notifications.preferences.admin.editingFor", "Preferences for {user}").replace("{user}", selectedLabel) }),
160
+ prefsLoading || typesLoading ? /* @__PURE__ */ jsx(LoadingMessage, { label: t("notifications.preferences.loading", "Loading notification preferences...") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
161
+ /* @__PURE__ */ jsx(NotificationPreferenceMatrix, { types, prefs, onToggle: togglePref, channels }),
162
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleSave, disabled: saving, children: saving ? t("notifications.preferences.saving", "Saving...") : t("notifications.preferences.save", "Save preferences") }) })
163
+ ] })
164
+ ] }) : null
165
+ ] });
166
+ }
167
+ var NotificationUserPreferencesAdminPageClient_default = NotificationUserPreferencesAdminPageClient;
168
+ export {
169
+ NotificationUserPreferencesAdminPageClient,
170
+ NotificationUserPreferencesAdminPageClient_default as default
171
+ };
172
+ //# sourceMappingURL=NotificationUserPreferencesAdminPageClient.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { LookupSelect, type LookupSelectItem } from '@open-mercato/ui/backend/inputs'\nimport {\n NotificationPreferenceMatrix,\n PREFERENCE_CHANNELS,\n buildPreferenceMap,\n diffPreferenceItems,\n preferenceKey,\n toChannelDef,\n type ChannelDef,\n type NotificationTypeItem,\n type PreferenceItem,\n} from './NotificationPreferenceMatrix'\n\ntype TypesResponse = { items?: NotificationTypeItem[] }\ntype PreferencesResponse = { items?: PreferenceItem[] }\ntype ChannelsResponse = { items?: Array<{ id: string; labelKey: string; descriptionKey?: string | null }> }\ntype SaveResponse = { ok?: boolean; error?: string }\n\ntype UserRow = { id: string; name?: string | null; email?: string | null }\ntype UsersResponse = { items?: UserRow[] }\n\nconst ADMIN_PREFERENCES_CONTEXT_ID = 'notifications-admin-preferences'\n\nfunction userLabel(user: UserRow): string {\n return user.name?.trim() || user.email?.trim() || user.id\n}\n\nexport function NotificationUserPreferencesAdminPageClient() {\n const t = useT()\n const [types, setTypes] = React.useState<NotificationTypeItem[]>([])\n const [channels, setChannels] = React.useState<ChannelDef[]>(PREFERENCE_CHANNELS)\n const [typesLoading, setTypesLoading] = React.useState(true)\n const [selectedUserId, setSelectedUserId] = React.useState<string | null>(null)\n // id -> display label, populated as options are fetched so the heading can name the selected user.\n const userLabels = React.useRef<Map<string, string>>(new Map())\n const [prefs, setPrefs] = React.useState<Record<string, boolean>>({})\n // Baseline for the selected user; saves send only entries that differ from this.\n const initialPrefs = React.useRef<Record<string, boolean>>({})\n const [prefsLoading, setPrefsLoading] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: ADMIN_PREFERENCES_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n React.useEffect(() => {\n let cancelled = false\n ;(async () => {\n setTypesLoading(true)\n try {\n const [body, channelsBody] = await Promise.all([\n readApiResultOrThrow<TypesResponse>('/api/notifications/types', undefined, {\n errorMessage: t('notifications.preferences.loadError', 'Failed to load notification preferences'),\n allowNullResult: true,\n }),\n readApiResultOrThrow<ChannelsResponse>('/api/notifications/channels', undefined, {\n errorMessage: t('notifications.preferences.loadError', 'Failed to load notification preferences'),\n allowNullResult: true,\n }),\n ])\n if (!cancelled) {\n setTypes(body?.items ?? [])\n setChannels(channelsBody?.items?.length ? channelsBody.items.map(toChannelDef) : PREFERENCE_CHANNELS)\n }\n } catch (err) {\n if (!cancelled) flash(err instanceof Error ? err.message : t('notifications.preferences.loadError', 'Failed to load notification preferences'), 'error')\n } finally {\n if (!cancelled) setTypesLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [t])\n\n // Async user lookup for LookupSelect. Devices admins may lack auth.users.list; degrade to no\n // options (x-om-forbidden-redirect: 0 + swallow) instead of redirecting the whole page.\n const fetchUsers = React.useCallback(async (query?: string): Promise<LookupSelectItem[]> => {\n const params = new URLSearchParams({ page: '1', pageSize: '10' })\n if (query && query.trim().length) params.set('search', query.trim())\n const call = await apiCall<UsersResponse>(\n `/api/auth/users?${params.toString()}`,\n { headers: { 'x-om-forbidden-redirect': '0' } },\n { fallback: null },\n ).catch(() => null)\n if (!call || !call.ok) return []\n return (call.result?.items ?? []).flatMap((user) => {\n if (!user || typeof user.id !== 'string' || !user.id.trim()) return []\n const label = userLabel(user)\n userLabels.current.set(user.id, label)\n const email = user.email?.trim() ?? null\n return [{ id: user.id, title: label, subtitle: email && email !== label ? email : null }]\n })\n }, [])\n\n const loadPreferencesFor = React.useCallback(async (userId: string) => {\n setPrefsLoading(true)\n try {\n const body = await readApiResultOrThrow<PreferencesResponse>(\n `/api/notifications/admin/preferences?userId=${encodeURIComponent(userId)}`,\n undefined,\n { errorMessage: t('notifications.preferences.loadError', 'Failed to load notification preferences'), allowNullResult: true },\n )\n const map = buildPreferenceMap(types, body?.items ?? [], channels)\n initialPrefs.current = map\n setPrefs(map)\n } catch (err) {\n flash(err instanceof Error ? err.message : t('notifications.preferences.loadError', 'Failed to load notification preferences'), 'error')\n setSelectedUserId(null)\n } finally {\n setPrefsLoading(false)\n }\n }, [t, types, channels])\n\n const handleSelectUser = (userId: string | null) => {\n setSelectedUserId(userId)\n if (userId) void loadPreferencesFor(userId)\n }\n\n const togglePref = (typeId: string, channel: string, enabled: boolean) => {\n setPrefs((prev) => ({ ...prev, [preferenceKey(typeId, channel)]: enabled }))\n }\n\n const handleSave = async () => {\n if (!selectedUserId) return\n setSaving(true)\n try {\n const preferences = diffPreferenceItems(types, initialPrefs.current, prefs, channels)\n // Nothing changed for this user since load/last save \u2014 skip the no-op write.\n if (preferences.length === 0) {\n flash(t('notifications.preferences.saveSuccess', 'Notification preferences saved'), 'success')\n return\n }\n const response = await runMutation({\n // optimistic-lock-exempt: idempotent per-cell preference toggle grid \u2014 the client sends only\n // changed (type, channel) cells (diffPreferenceItems) and the server upserts them\n // last-write-wins per cell, so there is no lost-update hazard to version-lock.\n operation: () =>\n apiCall<SaveResponse>('/api/notifications/admin/preferences', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ userId: selectedUserId, preferences }),\n }),\n context: { formId: ADMIN_PREFERENCES_CONTEXT_ID, resourceKind: 'notifications.preference', retryLastMutation },\n mutationPayload: { userId: selectedUserId, preferences },\n })\n if (!response.ok) {\n throw new Error(response.result?.error || t('notifications.preferences.saveError', 'Failed to save notification preferences'))\n }\n initialPrefs.current = prefs\n flash(t('notifications.preferences.saveSuccess', 'Notification preferences saved'), 'success')\n } catch (err) {\n flash(err instanceof Error ? err.message : t('notifications.preferences.saveError', 'Failed to save notification preferences'), 'error')\n } finally {\n setSaving(false)\n }\n }\n\n const selectedLabel = selectedUserId ? (userLabels.current.get(selectedUserId) ?? selectedUserId) : ''\n\n return (\n <div className=\"flex flex-col gap-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{t('notifications.preferences.admin.pageTitle', 'User Notification Preferences')}</h1>\n <p className=\"text-muted-foreground text-sm\">\n {t('notifications.preferences.admin.pageDescription', \"Search for a user to review and edit their notification channel preferences.\")}\n </p>\n </div>\n\n <LookupSelect\n value={selectedUserId}\n onChange={handleSelectUser}\n fetchOptions={fetchUsers}\n defaultOpen\n searchPlaceholder={t('notifications.preferences.admin.searchPlaceholder', 'Search users by name or email...')}\n emptyLabel={t('notifications.preferences.admin.noUsers', 'No users found.')}\n />\n\n {selectedUserId ? (\n <div className=\"flex flex-col gap-4\">\n <h2 className=\"text-lg font-medium\">\n {t('notifications.preferences.admin.editingFor', 'Preferences for {user}').replace('{user}', selectedLabel)}\n </h2>\n {prefsLoading || typesLoading ? (\n <LoadingMessage label={t('notifications.preferences.loading', 'Loading notification preferences...')} />\n ) : (\n <>\n <NotificationPreferenceMatrix types={types} prefs={prefs} onToggle={togglePref} channels={channels} />\n <div>\n <Button type=\"button\" onClick={handleSave} disabled={saving}>\n {saving ? t('notifications.preferences.saving', 'Saving...') : t('notifications.preferences.save', 'Save preferences')}\n </Button>\n </div>\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default NotificationUserPreferencesAdminPageClient\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,63 @@
1
+ function buildDeliveryStrategiesOutput({
2
+ importSection,
3
+ entriesLiteral
4
+ }) {
5
+ return `// AUTO-GENERATED by mercato generate registry
6
+ ${importSection ? `${importSection}
7
+ ` : ""}type NotificationDeliveryStrategyEntry = { moduleId: string; strategies: unknown[] }
8
+
9
+ export const notificationDeliveryStrategyEntries: NotificationDeliveryStrategyEntry[] = [
10
+ ${entriesLiteral ? ` ${entriesLiteral}
11
+ ` : ""}]
12
+ `;
13
+ }
14
+ const deliveryStrategiesPlugin = {
15
+ id: "notifications.delivery-strategies",
16
+ conventionFile: "notifications.delivery-strategies.ts",
17
+ importPrefix: "NOTIFICATIONS_DELIVERY_STRATEGIES",
18
+ configExpr: (importName, moduleId) => `{ moduleId: '${moduleId}', strategies: ((${importName}.default ?? ${importName}.deliveryStrategies ?? []) as unknown[]) }`,
19
+ outputFileName: "notifications-delivery-strategies.generated.ts",
20
+ buildOutput: buildDeliveryStrategiesOutput,
21
+ bootstrapRegistration: {
22
+ entriesExportName: "notificationDeliveryStrategyEntries",
23
+ registrationImports: [
24
+ `import { registerNotificationDeliveryStrategyEntries } from '@open-mercato/core/modules/notifications/lib/delivery-strategies-registry'`
25
+ ],
26
+ buildCall: (name) => `registerNotificationDeliveryStrategyEntries(${name})`
27
+ }
28
+ };
29
+ function buildChannelsOutput({
30
+ importSection,
31
+ entriesLiteral
32
+ }) {
33
+ return `// AUTO-GENERATED by mercato generate registry
34
+ ${importSection ? `${importSection}
35
+ ` : ""}type NotificationChannelEntry = { moduleId: string; channels: unknown[] }
36
+
37
+ export const notificationChannelEntries: NotificationChannelEntry[] = [
38
+ ${entriesLiteral ? ` ${entriesLiteral}
39
+ ` : ""}]
40
+ `;
41
+ }
42
+ const channelsPlugin = {
43
+ id: "notifications.channels",
44
+ conventionFile: "notification-channels.ts",
45
+ importPrefix: "NOTIFICATION_CHANNELS",
46
+ configExpr: (importName, moduleId) => `{ moduleId: '${moduleId}', channels: ((${importName}.default ?? ${importName}.notificationChannels ?? []) as unknown[]) }`,
47
+ outputFileName: "notification-channels.generated.ts",
48
+ buildOutput: buildChannelsOutput,
49
+ bootstrapRegistration: {
50
+ entriesExportName: "notificationChannelEntries",
51
+ registrationImports: [
52
+ `import { registerNotificationChannelEntries } from '@open-mercato/core/modules/notifications/lib/notification-channel-registry'`
53
+ ],
54
+ buildCall: (name) => `registerNotificationChannelEntries(${name})`
55
+ }
56
+ };
57
+ const generatorPlugins = [deliveryStrategiesPlugin, channelsPlugin];
58
+ var generators_default = generatorPlugins;
59
+ export {
60
+ generators_default as default,
61
+ generatorPlugins
62
+ };
63
+ //# sourceMappingURL=generators.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/notifications/generators.ts"],
4
+ "sourcesContent": ["import type { GeneratorPlugin } from '@open-mercato/shared/modules/generators'\n\nfunction buildDeliveryStrategiesOutput({\n importSection,\n entriesLiteral,\n}: {\n importSection: string\n entriesLiteral: string\n}): string {\n return `// AUTO-GENERATED by mercato generate registry\n${importSection ? `${importSection}\\n` : ''}type NotificationDeliveryStrategyEntry = { moduleId: string; strategies: unknown[] }\n\nexport const notificationDeliveryStrategyEntries: NotificationDeliveryStrategyEntry[] = [\n${entriesLiteral ? ` ${entriesLiteral}\\n` : ''}]\n`\n}\n\n/**\n * Lets any module contribute notification delivery strategies by exporting\n * `notifications.delivery-strategies.ts` (`export const deliveryStrategies = [...]`). The generator\n * aggregates them and wires a bootstrap registration call, so the strategy registry is populated at\n * boot without `bootstrap.ts` knowing about individual modules. Mirrors the security MFA-provider plugin.\n */\nconst deliveryStrategiesPlugin: GeneratorPlugin = {\n id: 'notifications.delivery-strategies',\n conventionFile: 'notifications.delivery-strategies.ts',\n importPrefix: 'NOTIFICATIONS_DELIVERY_STRATEGIES',\n configExpr: (importName: string, moduleId: string) =>\n `{ moduleId: '${moduleId}', strategies: ((${importName}.default ?? ${importName}.deliveryStrategies ?? []) as unknown[]) }`,\n outputFileName: 'notifications-delivery-strategies.generated.ts',\n buildOutput: buildDeliveryStrategiesOutput,\n bootstrapRegistration: {\n entriesExportName: 'notificationDeliveryStrategyEntries',\n registrationImports: [\n `import { registerNotificationDeliveryStrategyEntries } from '@open-mercato/core/modules/notifications/lib/delivery-strategies-registry'`,\n ],\n buildCall: (name: string) => `registerNotificationDeliveryStrategyEntries(${name})`,\n },\n}\n\nfunction buildChannelsOutput({\n importSection,\n entriesLiteral,\n}: {\n importSection: string\n entriesLiteral: string\n}): string {\n return `// AUTO-GENERATED by mercato generate registry\n${importSection ? `${importSection}\\n` : ''}type NotificationChannelEntry = { moduleId: string; channels: unknown[] }\n\nexport const notificationChannelEntries: NotificationChannelEntry[] = [\n${entriesLiteral ? ` ${entriesLiteral}\\n` : ''}]\n`\n}\n\n/**\n * Lets any module contribute notification channels (metadata for the preferences UI + the\n * `/api/notifications/channels` endpoint) by exporting `notification-channels.ts`\n * (`export const notificationChannels = [...]`). The generator aggregates them and wires a bootstrap\n * registration call, so the channel catalogue is populated at boot without `bootstrap.ts` knowing about\n * individual modules. Mirrors the delivery-strategies plugin.\n */\nconst channelsPlugin: GeneratorPlugin = {\n id: 'notifications.channels',\n conventionFile: 'notification-channels.ts',\n importPrefix: 'NOTIFICATION_CHANNELS',\n configExpr: (importName: string, moduleId: string) =>\n `{ moduleId: '${moduleId}', channels: ((${importName}.default ?? ${importName}.notificationChannels ?? []) as unknown[]) }`,\n outputFileName: 'notification-channels.generated.ts',\n buildOutput: buildChannelsOutput,\n bootstrapRegistration: {\n entriesExportName: 'notificationChannelEntries',\n registrationImports: [\n `import { registerNotificationChannelEntries } from '@open-mercato/core/modules/notifications/lib/notification-channel-registry'`,\n ],\n buildCall: (name: string) => `registerNotificationChannelEntries(${name})`,\n },\n}\n\nexport const generatorPlugins: GeneratorPlugin[] = [deliveryStrategiesPlugin, channelsPlugin]\n\nexport default generatorPlugins\n"],
5
+ "mappings": "AAEA,SAAS,8BAA8B;AAAA,EACrC;AAAA,EACA;AACF,GAGW;AACT,SAAO;AAAA,EACP,gBAAgB,GAAG,aAAa;AAAA,IAAO,EAAE;AAAA;AAAA;AAAA,EAGzC,iBAAiB,KAAK,cAAc;AAAA,IAAO,EAAE;AAAA;AAE/C;AAQA,MAAM,2BAA4C;AAAA,EAChD,IAAI;AAAA,EACJ,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,YAAY,CAAC,YAAoB,aAC/B,gBAAgB,QAAQ,oBAAoB,UAAU,eAAe,UAAU;AAAA,EACjF,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,uBAAuB;AAAA,IACrB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,MACnB;AAAA,IACF;AAAA,IACA,WAAW,CAAC,SAAiB,+CAA+C,IAAI;AAAA,EAClF;AACF;AAEA,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EACA;AACF,GAGW;AACT,SAAO;AAAA,EACP,gBAAgB,GAAG,aAAa;AAAA,IAAO,EAAE;AAAA;AAAA;AAAA,EAGzC,iBAAiB,KAAK,cAAc;AAAA,IAAO,EAAE;AAAA;AAE/C;AASA,MAAM,iBAAkC;AAAA,EACtC,IAAI;AAAA,EACJ,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,YAAY,CAAC,YAAoB,aAC/B,gBAAgB,QAAQ,kBAAkB,UAAU,eAAe,UAAU;AAAA,EAC/E,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,uBAAuB;AAAA,IACrB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,MACnB;AAAA,IACF;AAAA,IACA,WAAW,CAAC,SAAiB,sCAAsC,IAAI;AAAA,EACzE;AACF;AAEO,MAAM,mBAAsC,CAAC,0BAA0B,cAAc;AAE5F,IAAO,qBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ import {
2
+ getNotificationDeliveryStrategies,
3
+ registerNotificationDeliveryStrategy
4
+ } from "./deliveryStrategies.js";
5
+ function registerNotificationDeliveryStrategyEntries(entries) {
6
+ const registered = new Set(getNotificationDeliveryStrategies().map((strategy) => strategy.id));
7
+ for (const entry of entries) {
8
+ for (const strategy of entry.strategies ?? []) {
9
+ const candidate = strategy;
10
+ if (!candidate?.id || registered.has(candidate.id)) continue;
11
+ registerNotificationDeliveryStrategy(candidate);
12
+ registered.add(candidate.id);
13
+ }
14
+ }
15
+ }
16
+ export {
17
+ registerNotificationDeliveryStrategyEntries
18
+ };
19
+ //# sourceMappingURL=delivery-strategies-registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/lib/delivery-strategies-registry.ts"],
4
+ "sourcesContent": ["import {\n getNotificationDeliveryStrategies,\n registerNotificationDeliveryStrategy,\n type NotificationDeliveryStrategy,\n} from './deliveryStrategies'\n\nexport type NotificationDeliveryStrategyEntry = {\n moduleId: string\n strategies: unknown[]\n}\n\n/**\n * Bootstrap-time registration of module-contributed notification delivery strategies.\n *\n * Fed from the generated `notifications-delivery-strategies.generated.ts` aggregate (see\n * `notifications/generators.ts`) via `runBootstrapRegistrations()`, mirroring how the security\n * module registers its MFA providers. Idempotent: a strategy whose `id` is already registered is\n * skipped, so a repeated bootstrap (HMR / test re-import) never double-registers.\n */\nexport function registerNotificationDeliveryStrategyEntries(entries: NotificationDeliveryStrategyEntry[]): void {\n const registered = new Set(getNotificationDeliveryStrategies().map((strategy) => strategy.id))\n for (const entry of entries) {\n for (const strategy of entry.strategies ?? []) {\n const candidate = strategy as NotificationDeliveryStrategy\n if (!candidate?.id || registered.has(candidate.id)) continue\n registerNotificationDeliveryStrategy(candidate)\n registered.add(candidate.id)\n }\n }\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAeA,SAAS,4CAA4C,SAAoD;AAC9G,QAAM,aAAa,IAAI,IAAI,kCAAkC,EAAE,IAAI,CAAC,aAAa,SAAS,EAAE,CAAC;AAC7F,aAAW,SAAS,SAAS;AAC3B,eAAW,YAAY,MAAM,cAAc,CAAC,GAAG;AAC7C,YAAM,YAAY;AAClB,UAAI,CAAC,WAAW,MAAM,WAAW,IAAI,UAAU,EAAE,EAAG;AACpD,2CAAqC,SAAS;AAC9C,iBAAW,IAAI,UAAU,EAAE;AAAA,IAC7B;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/lib/deliveryStrategies.ts"],
4
- "sourcesContent": ["import type { Notification } from '../data/entities'\nimport type { NotificationDeliveryConfig } from './deliveryConfig'\n\nexport type NotificationDeliveryStrategyConfig = {\n enabled?: boolean\n config?: unknown\n}\n\nexport type NotificationDeliveryRecipient = {\n email?: string | null\n name?: string | null\n}\n\nexport type NotificationDeliveryContext = {\n notification: Notification\n recipient: NotificationDeliveryRecipient\n title: string\n body: string | null\n panelUrl: string | null\n panelLink: string | null\n actionLinks: Array<{ id: string; label: string; href: string }>\n deliveryConfig: NotificationDeliveryConfig\n config: NotificationDeliveryStrategyConfig\n resolve: <T = unknown>(name: string) => T\n t: (key: string, fallback?: string, variables?: Record<string, string>) => string\n}\n\nexport type NotificationDeliveryStrategy = {\n id: string\n label?: string\n defaultEnabled?: boolean\n deliver: (ctx: NotificationDeliveryContext) => Promise<void> | void\n}\n\ntype RegisteredStrategy = NotificationDeliveryStrategy & { priority: number }\n\nconst registry: RegisteredStrategy[] = []\n\nexport function registerNotificationDeliveryStrategy(\n strategy: NotificationDeliveryStrategy,\n options?: { priority?: number }\n): void {\n const priority = options?.priority ?? 0\n registry.push({ ...strategy, priority })\n registry.sort((a, b) => b.priority - a.priority)\n}\n\nexport function getNotificationDeliveryStrategies(): NotificationDeliveryStrategy[] {\n return registry\n}\n"],
5
- "mappings": "AAoCA,MAAM,WAAiC,CAAC;AAEjC,SAAS,qCACd,UACA,SACM;AACN,QAAM,WAAW,SAAS,YAAY;AACtC,WAAS,KAAK,EAAE,GAAG,UAAU,SAAS,CAAC;AACvC,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACjD;AAEO,SAAS,oCAAoE;AAClF,SAAO;AACT;",
4
+ "sourcesContent": ["import type { Notification } from '../data/entities'\nimport type { NotificationDeliveryConfig } from './deliveryConfig'\n\nexport type NotificationDeliveryStrategyConfig = {\n enabled?: boolean\n config?: unknown\n}\n\nexport type NotificationDeliveryRecipient = {\n email?: string | null\n name?: string | null\n}\n\n/**\n * Channel-agnostic delivery context. Every strategy can rely on these fields regardless of channel.\n */\nexport type NotificationDeliveryContextCore = {\n notification: Notification\n recipient: NotificationDeliveryRecipient\n title: string\n body: string | null\n deliveryConfig: NotificationDeliveryConfig\n config: NotificationDeliveryStrategyConfig\n resolve: <T = unknown>(name: string) => T\n t: (key: string, fallback?: string, variables?: Record<string, string>) => string\n}\n\n/**\n * Email-shaped extras carried on every context for backward compatibility. Only the `email` strategy\n * should read these \u2014 they encode one channel's assumptions (a panel deep-link and pre-rendered\n * action hyperlinks). New channel strategies MUST derive whatever they need from `notification`\n * instead.\n *\n * @deprecated These fields are email-specific and will move behind an email-scoped accessor in a\n * future major version. Do not depend on them from a non-email strategy.\n */\nexport type EmailDeliveryExtras = {\n /** @deprecated Email-specific. Absolute panel URL (no notification id). */\n panelUrl: string | null\n /** @deprecated Email-specific. Absolute deep link to the notification in the panel. */\n panelLink: string | null\n /** @deprecated Email-specific. Pre-resolved absolute action hyperlinks for the email template. */\n actionLinks: Array<{ id: string; label: string; href: string }>\n}\n\n/**\n * Full delivery context handed to `strategy.deliver`. Kept as the flat intersection of the\n * channel-agnostic core and the email extras so existing strategies compile unchanged; the split\n * documents which fields are channel-neutral vs. email-only (see `EmailDeliveryExtras`).\n */\nexport type NotificationDeliveryContext = NotificationDeliveryContextCore & EmailDeliveryExtras\n\nexport type NotificationDeliveryStrategy = {\n id: string\n label?: string\n defaultEnabled?: boolean\n /**\n * Optional technical-deliverability gate evaluated by the dispatcher before `deliver`. Return\n * `false` to skip this channel for the current context (e.g. email disabled by tenant config).\n * This is NOT for per-user opt-out \u2014 that is enforced once at create time via `shouldDeliver`.\n */\n isConfigured?: (ctx: NotificationDeliveryContext) => boolean | Promise<boolean>\n /**\n * Optional per-notification applicability check. Return `false` to skip this strategy entirely for\n * the given notification (e.g. a channel that only handles a certain notification shape).\n */\n supports?: (notification: Notification) => boolean\n deliver: (ctx: NotificationDeliveryContext) => Promise<void> | void\n}\n\ntype RegisteredStrategy = NotificationDeliveryStrategy & { priority: number }\n\nconst registry: RegisteredStrategy[] = []\n\nexport function registerNotificationDeliveryStrategy(\n strategy: NotificationDeliveryStrategy,\n options?: { priority?: number }\n): void {\n const priority = options?.priority ?? 0\n registry.push({ ...strategy, priority })\n registry.sort((a, b) => b.priority - a.priority)\n}\n\nexport function getNotificationDeliveryStrategies(): NotificationDeliveryStrategy[] {\n return registry\n}\n"],
5
+ "mappings": "AAwEA,MAAM,WAAiC,CAAC;AAEjC,SAAS,qCACd,UACA,SACM;AACN,QAAM,WAAW,SAAS,YAAY;AACtC,WAAS,KAAK,EAAE,GAAG,UAAU,SAAS,CAAC;AACvC,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACjD;AAEO,SAAS,oCAAoE;AAClF,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,8 @@
1
+ function deriveCategory(type) {
2
+ const dot = type.indexOf(".");
3
+ return dot === -1 ? type : type.slice(0, dot);
4
+ }
5
+ export {
6
+ deriveCategory
7
+ };
8
+ //# sourceMappingURL=derive-category.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/lib/derive-category.ts"],
4
+ "sourcesContent": ["/**\n * Default grouping key for a notification type when the declaring module does not\n * provide an explicit `category`. Uses the prefix before the first dot (e.g.\n * `sales.order.created` \u2192 `sales`), or the whole id when there is no dot.\n *\n * This intentionally has NO knowledge of specific notification types \u2014 modules\n * that want a non-default grouping declare `category` on their notification type.\n *\n * Applied once, in `syncNotificationTypes`, so the mirrored row always carries a\n * resolved category and read paths never need a fallback branch.\n */\nexport function deriveCategory(type: string): string {\n const dot = type.indexOf('.')\n return dot === -1 ? type : type.slice(0, dot)\n}\n"],
5
+ "mappings": "AAWO,SAAS,eAAe,MAAsB;AACnD,QAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,SAAO,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;AAC9C;",
6
+ "names": []
7
+ }
@@ -0,0 +1,35 @@
1
+ const registry = /* @__PURE__ */ new Map();
2
+ function registerNotificationChannels(channels, options = {}) {
3
+ if (options.replace) registry.clear();
4
+ for (const channel of channels) {
5
+ if (!channel?.id) continue;
6
+ registry.set(channel.id, channel);
7
+ }
8
+ }
9
+ function getNotificationChannel(id) {
10
+ return registry.get(id);
11
+ }
12
+ function getNotificationChannels() {
13
+ return Array.from(registry.values()).sort((a, b) => {
14
+ const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
15
+ const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
16
+ if (orderA !== orderB) return orderA - orderB;
17
+ return a.id.localeCompare(b.id);
18
+ });
19
+ }
20
+ function registerNotificationChannelEntries(entries) {
21
+ for (const entry of entries) {
22
+ for (const channel of entry.channels ?? []) {
23
+ const candidate = channel;
24
+ if (!candidate?.id || registry.has(candidate.id)) continue;
25
+ registry.set(candidate.id, candidate);
26
+ }
27
+ }
28
+ }
29
+ export {
30
+ getNotificationChannel,
31
+ getNotificationChannels,
32
+ registerNotificationChannelEntries,
33
+ registerNotificationChannels
34
+ };
35
+ //# sourceMappingURL=notification-channel-registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/lib/notification-channel-registry.ts"],
4
+ "sourcesContent": ["import type { NotificationChannelDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nconst registry = new Map<string, NotificationChannelDefinition>()\n\nexport type RegisterNotificationChannelsOptions = {\n replace?: boolean\n}\n\n/**\n * In-memory catalogue of module-contributed delivery channels. Fed at app bootstrap from the\n * generated aggregate (see `notifications/generators.ts` \u2192 `channelsPlugin`), mirroring the\n * notification-type registry. Provides labels/metadata for the preferences UI and the\n * `GET /api/notifications/channels` endpoint; the delivery-strategy registry remains the\n * behavior layer (both are keyed by the same channel id).\n */\nexport function registerNotificationChannels(\n channels: NotificationChannelDefinition[],\n options: RegisterNotificationChannelsOptions = {},\n): void {\n if (options.replace) registry.clear()\n for (const channel of channels) {\n if (!channel?.id) continue\n registry.set(channel.id, channel)\n }\n}\n\nexport function getNotificationChannel(id: string): NotificationChannelDefinition | undefined {\n return registry.get(id)\n}\n\nexport function getNotificationChannels(): NotificationChannelDefinition[] {\n return Array.from(registry.values()).sort((a, b) => {\n const orderA = a.order ?? Number.MAX_SAFE_INTEGER\n const orderB = b.order ?? Number.MAX_SAFE_INTEGER\n if (orderA !== orderB) return orderA - orderB\n return a.id.localeCompare(b.id)\n })\n}\n\nexport type NotificationChannelEntry = {\n moduleId: string\n channels: unknown[]\n}\n\n/**\n * Bootstrap-time registration of module-contributed channels, fed from the generated\n * `notification-channels.generated.ts` aggregate via `runBootstrapRegistrations()`. Idempotent:\n * a channel whose `id` is already registered is skipped, so a repeated bootstrap (HMR / test\n * re-import) never double-registers.\n */\nexport function registerNotificationChannelEntries(entries: NotificationChannelEntry[]): void {\n for (const entry of entries) {\n for (const channel of entry.channels ?? []) {\n const candidate = channel as NotificationChannelDefinition\n if (!candidate?.id || registry.has(candidate.id)) continue\n registry.set(candidate.id, candidate)\n }\n }\n}\n"],
5
+ "mappings": "AAEA,MAAM,WAAW,oBAAI,IAA2C;AAazD,SAAS,6BACd,UACA,UAA+C,CAAC,GAC1C;AACN,MAAI,QAAQ,QAAS,UAAS,MAAM;AACpC,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,SAAS,GAAI;AAClB,aAAS,IAAI,QAAQ,IAAI,OAAO;AAAA,EAClC;AACF;AAEO,SAAS,uBAAuB,IAAuD;AAC5F,SAAO,SAAS,IAAI,EAAE;AACxB;AAEO,SAAS,0BAA2D;AACzE,SAAO,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM;AAClD,UAAM,SAAS,EAAE,SAAS,OAAO;AACjC,UAAM,SAAS,EAAE,SAAS,OAAO;AACjC,QAAI,WAAW,OAAQ,QAAO,SAAS;AACvC,WAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,EAChC,CAAC;AACH;AAaO,SAAS,mCAAmC,SAA2C;AAC5F,aAAW,SAAS,SAAS;AAC3B,eAAW,WAAW,MAAM,YAAY,CAAC,GAAG;AAC1C,YAAM,YAAY;AAClB,UAAI,CAAC,WAAW,MAAM,SAAS,IAAI,UAAU,EAAE,EAAG;AAClD,eAAS,IAAI,UAAU,IAAI,SAAS;AAAA,IACtC;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }