@open-mercato/core 0.4.2-canary-07dbc98202 → 0.4.2-canary-1000cb714f

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 (235) hide show
  1. package/dist/generated/entities.ids.generated.js +59 -63
  2. package/dist/generated/entities.ids.generated.js.map +2 -2
  3. package/dist/generated/entity-fields-registry.js +0 -2
  4. package/dist/generated/entity-fields-registry.js.map +2 -2
  5. package/dist/modules/api_docs/frontend/docs/api/page.js +2 -3
  6. package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
  7. package/dist/modules/auth/api/admin/nav.js +3 -4
  8. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  9. package/dist/modules/auth/api/reset/confirm.js +2 -25
  10. package/dist/modules/auth/api/reset/confirm.js.map +2 -2
  11. package/dist/modules/auth/api/reset.js +0 -23
  12. package/dist/modules/auth/api/reset.js.map +2 -2
  13. package/dist/modules/auth/api/sidebar/preferences/route.js +9 -14
  14. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  15. package/dist/modules/auth/commands/users.js +0 -55
  16. package/dist/modules/auth/commands/users.js.map +2 -2
  17. package/dist/modules/auth/lib/setup-app.js +0 -1
  18. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  19. package/dist/modules/auth/services/authService.js +3 -3
  20. package/dist/modules/auth/services/authService.js.map +2 -2
  21. package/dist/modules/configs/cli.js +0 -6
  22. package/dist/modules/configs/cli.js.map +2 -2
  23. package/dist/modules/customers/commands/deals.js +0 -31
  24. package/dist/modules/customers/commands/deals.js.map +2 -2
  25. package/dist/modules/sales/commands/documents.js +0 -53
  26. package/dist/modules/sales/commands/documents.js.map +2 -2
  27. package/dist/modules/sales/commands/payments.js +0 -26
  28. package/dist/modules/sales/commands/payments.js.map +2 -2
  29. package/dist/modules/staff/commands/leave-requests.js +0 -79
  30. package/dist/modules/staff/commands/leave-requests.js.map +2 -2
  31. package/generated/entities.ids.generated.ts +59 -63
  32. package/generated/entity-fields-registry.ts +0 -2
  33. package/package.json +2 -2
  34. package/src/modules/api_docs/frontend/docs/api/page.tsx +2 -3
  35. package/src/modules/auth/api/admin/nav.ts +6 -10
  36. package/src/modules/auth/api/reset/confirm.ts +2 -25
  37. package/src/modules/auth/api/reset.ts +0 -23
  38. package/src/modules/auth/api/sidebar/preferences/route.ts +12 -21
  39. package/src/modules/auth/commands/users.ts +0 -68
  40. package/src/modules/auth/i18n/de.json +1 -29
  41. package/src/modules/auth/i18n/en.json +1 -29
  42. package/src/modules/auth/i18n/es.json +1 -29
  43. package/src/modules/auth/i18n/pl.json +1 -29
  44. package/src/modules/auth/lib/setup-app.ts +0 -1
  45. package/src/modules/auth/services/authService.ts +4 -4
  46. package/src/modules/business_rules/i18n/en.json +1 -3
  47. package/src/modules/catalog/i18n/en.json +1 -3
  48. package/src/modules/configs/cli.ts +0 -6
  49. package/src/modules/customers/commands/deals.ts +0 -39
  50. package/src/modules/customers/i18n/en.json +1 -5
  51. package/src/modules/sales/commands/documents.ts +0 -65
  52. package/src/modules/sales/commands/payments.ts +0 -33
  53. package/src/modules/sales/i18n/de.json +0 -20
  54. package/src/modules/sales/i18n/en.json +1 -25
  55. package/src/modules/sales/i18n/es.json +0 -20
  56. package/src/modules/sales/i18n/pl.json +0 -20
  57. package/src/modules/staff/commands/leave-requests.ts +0 -94
  58. package/src/modules/staff/i18n/de.json +0 -4
  59. package/src/modules/staff/i18n/en.json +1 -9
  60. package/src/modules/staff/i18n/es.json +0 -4
  61. package/src/modules/staff/i18n/pl.json +0 -4
  62. package/src/modules/workflows/i18n/en.json +1 -3
  63. package/dist/generated/entities/notification/index.js +0 -57
  64. package/dist/generated/entities/notification/index.js.map +0 -7
  65. package/dist/modules/auth/api/profile/route.js +0 -155
  66. package/dist/modules/auth/api/profile/route.js.map +0 -7
  67. package/dist/modules/auth/backend/auth/profile/page.js +0 -99
  68. package/dist/modules/auth/backend/auth/profile/page.js.map +0 -7
  69. package/dist/modules/auth/backend/auth/profile/page.meta.js +0 -12
  70. package/dist/modules/auth/backend/auth/profile/page.meta.js.map +0 -7
  71. package/dist/modules/auth/notifications.js +0 -112
  72. package/dist/modules/auth/notifications.js.map +0 -7
  73. package/dist/modules/business_rules/notifications.js +0 -28
  74. package/dist/modules/business_rules/notifications.js.map +0 -7
  75. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +0 -37
  76. package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +0 -7
  77. package/dist/modules/catalog/notifications.js +0 -28
  78. package/dist/modules/catalog/notifications.js.map +0 -7
  79. package/dist/modules/catalog/subscribers/low-stock-notification.js +0 -38
  80. package/dist/modules/catalog/subscribers/low-stock-notification.js.map +0 -7
  81. package/dist/modules/customers/notifications.js +0 -48
  82. package/dist/modules/customers/notifications.js.map +0 -7
  83. package/dist/modules/notifications/acl.js +0 -11
  84. package/dist/modules/notifications/acl.js.map +0 -7
  85. package/dist/modules/notifications/api/[id]/action/route.js +0 -74
  86. package/dist/modules/notifications/api/[id]/action/route.js.map +0 -7
  87. package/dist/modules/notifications/api/[id]/dismiss/route.js +0 -15
  88. package/dist/modules/notifications/api/[id]/dismiss/route.js.map +0 -7
  89. package/dist/modules/notifications/api/[id]/read/route.js +0 -15
  90. package/dist/modules/notifications/api/[id]/read/route.js.map +0 -7
  91. package/dist/modules/notifications/api/[id]/restore/route.js +0 -53
  92. package/dist/modules/notifications/api/[id]/restore/route.js.map +0 -7
  93. package/dist/modules/notifications/api/batch/route.js +0 -17
  94. package/dist/modules/notifications/api/batch/route.js.map +0 -7
  95. package/dist/modules/notifications/api/feature/route.js +0 -17
  96. package/dist/modules/notifications/api/feature/route.js.map +0 -7
  97. package/dist/modules/notifications/api/mark-all-read/route.js +0 -35
  98. package/dist/modules/notifications/api/mark-all-read/route.js.map +0 -7
  99. package/dist/modules/notifications/api/openapi.js +0 -76
  100. package/dist/modules/notifications/api/openapi.js.map +0 -7
  101. package/dist/modules/notifications/api/role/route.js +0 -17
  102. package/dist/modules/notifications/api/role/route.js.map +0 -7
  103. package/dist/modules/notifications/api/route.js +0 -85
  104. package/dist/modules/notifications/api/route.js.map +0 -7
  105. package/dist/modules/notifications/api/settings/route.js +0 -155
  106. package/dist/modules/notifications/api/settings/route.js.map +0 -7
  107. package/dist/modules/notifications/api/unread-count/route.js +0 -38
  108. package/dist/modules/notifications/api/unread-count/route.js.map +0 -7
  109. package/dist/modules/notifications/backend/config/notifications/page.js +0 -10
  110. package/dist/modules/notifications/backend/config/notifications/page.js.map +0 -7
  111. package/dist/modules/notifications/backend/config/notifications/page.meta.js +0 -24
  112. package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +0 -7
  113. package/dist/modules/notifications/cli.js +0 -16
  114. package/dist/modules/notifications/cli.js.map +0 -7
  115. package/dist/modules/notifications/data/entities.js +0 -112
  116. package/dist/modules/notifications/data/entities.js.map +0 -7
  117. package/dist/modules/notifications/data/validators.js +0 -94
  118. package/dist/modules/notifications/data/validators.js.map +0 -7
  119. package/dist/modules/notifications/di.js +0 -13
  120. package/dist/modules/notifications/di.js.map +0 -7
  121. package/dist/modules/notifications/emails/NotificationEmail.js +0 -58
  122. package/dist/modules/notifications/emails/NotificationEmail.js.map +0 -7
  123. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +0 -44
  124. package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +0 -7
  125. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +0 -219
  126. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +0 -7
  127. package/dist/modules/notifications/index.js +0 -14
  128. package/dist/modules/notifications/index.js.map +0 -7
  129. package/dist/modules/notifications/lib/deliveryConfig.js +0 -105
  130. package/dist/modules/notifications/lib/deliveryConfig.js.map +0 -7
  131. package/dist/modules/notifications/lib/events.js +0 -12
  132. package/dist/modules/notifications/lib/events.js.map +0 -7
  133. package/dist/modules/notifications/lib/notificationBuilder.js +0 -66
  134. package/dist/modules/notifications/lib/notificationBuilder.js.map +0 -7
  135. package/dist/modules/notifications/lib/notificationFactory.js +0 -54
  136. package/dist/modules/notifications/lib/notificationFactory.js.map +0 -7
  137. package/dist/modules/notifications/lib/notificationMapper.js +0 -34
  138. package/dist/modules/notifications/lib/notificationMapper.js.map +0 -7
  139. package/dist/modules/notifications/lib/notificationRecipients.js +0 -35
  140. package/dist/modules/notifications/lib/notificationRecipients.js.map +0 -7
  141. package/dist/modules/notifications/lib/notificationService.js +0 -279
  142. package/dist/modules/notifications/lib/notificationService.js.map +0 -7
  143. package/dist/modules/notifications/lib/routeHelpers.js +0 -101
  144. package/dist/modules/notifications/lib/routeHelpers.js.map +0 -7
  145. package/dist/modules/notifications/lib/safeHref.js +0 -24
  146. package/dist/modules/notifications/lib/safeHref.js.map +0 -7
  147. package/dist/modules/notifications/migrations/Migration20260123000001.js +0 -70
  148. package/dist/modules/notifications/migrations/Migration20260123000001.js.map +0 -7
  149. package/dist/modules/notifications/migrations/Migration20260126150000.js +0 -37
  150. package/dist/modules/notifications/migrations/Migration20260126150000.js.map +0 -7
  151. package/dist/modules/notifications/subscribers/deliver-notification.js +0 -139
  152. package/dist/modules/notifications/subscribers/deliver-notification.js.map +0 -7
  153. package/dist/modules/notifications/workers/create-notification.worker.js +0 -70
  154. package/dist/modules/notifications/workers/create-notification.worker.js.map +0 -7
  155. package/dist/modules/sales/notifications.client.js +0 -51
  156. package/dist/modules/sales/notifications.client.js.map +0 -7
  157. package/dist/modules/sales/notifications.js +0 -88
  158. package/dist/modules/sales/notifications.js.map +0 -7
  159. package/dist/modules/sales/subscribers/quote-expiring-notification.js +0 -38
  160. package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +0 -7
  161. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +0 -137
  162. package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +0 -7
  163. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +0 -137
  164. package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +0 -7
  165. package/dist/modules/sales/widgets/notifications/index.js +0 -7
  166. package/dist/modules/sales/widgets/notifications/index.js.map +0 -7
  167. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +0 -60
  168. package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +0 -7
  169. package/dist/modules/staff/notifications.js +0 -75
  170. package/dist/modules/staff/notifications.js.map +0 -7
  171. package/dist/modules/workflows/notifications.js +0 -28
  172. package/dist/modules/workflows/notifications.js.map +0 -7
  173. package/dist/modules/workflows/subscribers/task-assigned-notification.js +0 -38
  174. package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +0 -7
  175. package/generated/entities/notification/index.ts +0 -27
  176. package/src/modules/auth/api/profile/route.ts +0 -160
  177. package/src/modules/auth/backend/auth/profile/page.meta.ts +0 -8
  178. package/src/modules/auth/backend/auth/profile/page.tsx +0 -127
  179. package/src/modules/auth/notifications.ts +0 -109
  180. package/src/modules/business_rules/notifications.ts +0 -25
  181. package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +0 -50
  182. package/src/modules/catalog/notifications.ts +0 -25
  183. package/src/modules/catalog/subscribers/low-stock-notification.ts +0 -52
  184. package/src/modules/customers/notifications.ts +0 -44
  185. package/src/modules/notifications/acl.ts +0 -7
  186. package/src/modules/notifications/api/[id]/action/route.ts +0 -75
  187. package/src/modules/notifications/api/[id]/dismiss/route.ts +0 -12
  188. package/src/modules/notifications/api/[id]/read/route.ts +0 -12
  189. package/src/modules/notifications/api/[id]/restore/route.ts +0 -53
  190. package/src/modules/notifications/api/batch/route.ts +0 -14
  191. package/src/modules/notifications/api/feature/route.ts +0 -14
  192. package/src/modules/notifications/api/mark-all-read/route.ts +0 -34
  193. package/src/modules/notifications/api/openapi.ts +0 -76
  194. package/src/modules/notifications/api/role/route.ts +0 -14
  195. package/src/modules/notifications/api/route.ts +0 -92
  196. package/src/modules/notifications/api/settings/route.ts +0 -157
  197. package/src/modules/notifications/api/unread-count/route.ts +0 -38
  198. package/src/modules/notifications/backend/config/notifications/page.meta.ts +0 -22
  199. package/src/modules/notifications/backend/config/notifications/page.tsx +0 -12
  200. package/src/modules/notifications/cli.ts +0 -18
  201. package/src/modules/notifications/data/entities.ts +0 -99
  202. package/src/modules/notifications/data/validators.ts +0 -110
  203. package/src/modules/notifications/di.ts +0 -11
  204. package/src/modules/notifications/emails/NotificationEmail.tsx +0 -98
  205. package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +0 -42
  206. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +0 -231
  207. package/src/modules/notifications/i18n/de.json +0 -50
  208. package/src/modules/notifications/i18n/en.json +0 -50
  209. package/src/modules/notifications/i18n/es.json +0 -50
  210. package/src/modules/notifications/i18n/pl.json +0 -50
  211. package/src/modules/notifications/index.ts +0 -12
  212. package/src/modules/notifications/lib/deliveryConfig.ts +0 -145
  213. package/src/modules/notifications/lib/events.ts +0 -48
  214. package/src/modules/notifications/lib/notificationBuilder.ts +0 -121
  215. package/src/modules/notifications/lib/notificationFactory.ts +0 -76
  216. package/src/modules/notifications/lib/notificationMapper.ts +0 -33
  217. package/src/modules/notifications/lib/notificationRecipients.ts +0 -83
  218. package/src/modules/notifications/lib/notificationService.ts +0 -414
  219. package/src/modules/notifications/lib/routeHelpers.ts +0 -151
  220. package/src/modules/notifications/lib/safeHref.ts +0 -29
  221. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +0 -300
  222. package/src/modules/notifications/migrations/Migration20260123000001.ts +0 -73
  223. package/src/modules/notifications/migrations/Migration20260126150000.ts +0 -39
  224. package/src/modules/notifications/subscribers/deliver-notification.ts +0 -175
  225. package/src/modules/notifications/workers/create-notification.worker.ts +0 -122
  226. package/src/modules/sales/notifications.client.ts +0 -65
  227. package/src/modules/sales/notifications.ts +0 -82
  228. package/src/modules/sales/subscribers/quote-expiring-notification.ts +0 -53
  229. package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +0 -156
  230. package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +0 -156
  231. package/src/modules/sales/widgets/notifications/index.ts +0 -2
  232. package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +0 -81
  233. package/src/modules/staff/notifications.ts +0 -71
  234. package/src/modules/workflows/notifications.ts +0 -25
  235. package/src/modules/workflows/subscribers/task-assigned-notification.ts +0 -53
@@ -1,139 +0,0 @@
1
- import { Notification } from "../data/entities.js";
2
- import { NOTIFICATION_EVENTS } from "../lib/events.js";
3
- import { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig, resolveNotificationPanelUrl } from "../lib/deliveryConfig.js";
4
- import { sendEmail } from "@open-mercato/shared/lib/email/send";
5
- import NotificationEmail from "../emails/NotificationEmail.js";
6
- import { loadDictionary } from "@open-mercato/shared/lib/i18n/server";
7
- import { createFallbackTranslator } from "@open-mercato/shared/lib/i18n/translate";
8
- import { defaultLocale } from "@open-mercato/shared/lib/i18n/config";
9
- import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
10
- import { User } from "../../auth/data/entities.js";
11
- const metadata = {
12
- event: NOTIFICATION_EVENTS.CREATED,
13
- persistent: true,
14
- id: "notifications:deliver"
15
- };
16
- const DEBUG = process.env.NOTIFICATIONS_DEBUG === "true";
17
- function debug(...args) {
18
- if (DEBUG) {
19
- console.log("[notifications]", ...args);
20
- }
21
- }
22
- const buildPanelLink = (panelUrl, notificationId) => {
23
- if (panelUrl.startsWith("http://") || panelUrl.startsWith("https://")) {
24
- const url = new URL(panelUrl);
25
- url.searchParams.set("notificationId", notificationId);
26
- return url.toString();
27
- }
28
- const separator = panelUrl.includes("?") ? "&" : "?";
29
- return `${panelUrl}${separator}notificationId=${encodeURIComponent(notificationId)}`;
30
- };
31
- const resolveNotificationCopy = async (notification) => {
32
- const dict = await loadDictionary(defaultLocale);
33
- const t = createFallbackTranslator(dict);
34
- const title = notification.titleKey ? t(notification.titleKey, notification.title ?? notification.titleKey, notification.titleVariables ?? void 0) : notification.title;
35
- const body = notification.bodyKey ? t(notification.bodyKey, notification.body ?? notification.bodyKey ?? "", notification.bodyVariables ?? void 0) : notification.body ?? null;
36
- return { title, body, t };
37
- };
38
- const resolveRecipient = async (em, notification, encryptionService) => {
39
- const where = {
40
- id: notification.recipientUserId,
41
- tenantId: notification.tenantId,
42
- deletedAt: null
43
- };
44
- if (notification.organizationId) {
45
- where.organizationId = notification.organizationId;
46
- }
47
- const record = await findOneWithDecryption(
48
- em,
49
- User,
50
- where,
51
- void 0,
52
- {
53
- tenantId: notification.tenantId,
54
- organizationId: notification.organizationId ?? null,
55
- encryptionService: encryptionService ?? null
56
- }
57
- );
58
- if (!record) return null;
59
- return {
60
- email: typeof record.email === "string" ? record.email : null,
61
- name: typeof record.name === "string" ? record.name : null
62
- };
63
- };
64
- async function handle(payload, ctx) {
65
- debug("deliver notification event", payload);
66
- const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, { defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG });
67
- if (!deliveryConfig.strategies.email.enabled) {
68
- debug("email delivery disabled");
69
- return;
70
- }
71
- const em = ctx.resolve("em");
72
- const notification = await em.findOne(Notification, {
73
- id: payload.notificationId,
74
- tenantId: payload.tenantId,
75
- organizationId: payload.organizationId ?? null
76
- });
77
- if (!notification) {
78
- debug("notification not found", payload.notificationId);
79
- return;
80
- }
81
- let encryptionService = null;
82
- try {
83
- encryptionService = ctx.resolve("tenantEncryptionService");
84
- } catch {
85
- encryptionService = null;
86
- }
87
- const recipient = await resolveRecipient(em, notification, encryptionService);
88
- if (!recipient?.email) {
89
- debug("recipient has no email", notification.recipientUserId);
90
- }
91
- const { title, body, t } = await resolveNotificationCopy(notification);
92
- const panelUrl = resolveNotificationPanelUrl(deliveryConfig);
93
- if (!panelUrl) {
94
- debug("missing panelUrl; check appUrl/panelPath settings");
95
- return;
96
- }
97
- const panelLink = buildPanelLink(panelUrl, notification.id);
98
- const actionLinks = (notification.actionData?.actions ?? []).map((action) => ({
99
- id: action.id,
100
- label: action.labelKey ? t(action.labelKey, action.label) : action.label,
101
- href: panelLink
102
- }));
103
- if (deliveryConfig.strategies.email.enabled && recipient?.email) {
104
- const subjectPrefix = deliveryConfig.strategies.email.subjectPrefix?.trim();
105
- const subject = subjectPrefix ? `${subjectPrefix} ${title}` : title;
106
- const copy = {
107
- preview: t("notifications.delivery.email.preview", "New notification"),
108
- heading: t("notifications.delivery.email.heading", "You have a new notification"),
109
- bodyIntro: t("notifications.delivery.email.bodyIntro", "Review the notification details and take any required actions."),
110
- actionNotice: t("notifications.delivery.email.actionNotice", "Actions are available in Open Mercato and are read-only in this email."),
111
- openCta: t("notifications.delivery.email.openCta", "Open notification center"),
112
- footer: t("notifications.delivery.email.footer", "Open Mercato notifications")
113
- };
114
- try {
115
- debug("sending email", { to: recipient.email, from: deliveryConfig.strategies.email.from, subject });
116
- await sendEmail({
117
- to: recipient.email,
118
- subject,
119
- from: deliveryConfig.strategies.email.from,
120
- replyTo: deliveryConfig.strategies.email.replyTo,
121
- react: NotificationEmail({
122
- title,
123
- body,
124
- actions: actionLinks,
125
- panelUrl: panelLink,
126
- copy
127
- })
128
- });
129
- } catch (error) {
130
- console.error("[notifications] email delivery failed", error);
131
- }
132
- }
133
- return;
134
- }
135
- export {
136
- handle as default,
137
- metadata
138
- };
139
- //# sourceMappingURL=deliver-notification.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/notifications/subscribers/deliver-notification.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Notification } from '../data/entities'\nimport { NOTIFICATION_EVENTS } from '../lib/events'\nimport { DEFAULT_NOTIFICATION_DELIVERY_CONFIG, resolveNotificationDeliveryConfig, resolveNotificationPanelUrl } from '../lib/deliveryConfig'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport NotificationEmail from '../emails/NotificationEmail'\nimport { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { defaultLocale } from '@open-mercato/shared/lib/i18n/config'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { User } from '../../auth/data/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\n\nexport const metadata = {\n event: NOTIFICATION_EVENTS.CREATED,\n persistent: true,\n id: 'notifications:deliver',\n}\n\nconst DEBUG = process.env.NOTIFICATIONS_DEBUG === 'true'\n\nfunction debug(...args: unknown[]): void {\n if (DEBUG) {\n console.log('[notifications]', ...args)\n }\n}\n\ntype NotificationCreatedPayload = {\n notificationId: string\n recipientUserId: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nconst buildPanelLink = (panelUrl: string, notificationId: string) => {\n if (panelUrl.startsWith('http://') || panelUrl.startsWith('https://')) {\n const url = new URL(panelUrl)\n url.searchParams.set('notificationId', notificationId)\n return url.toString()\n }\n const separator = panelUrl.includes('?') ? '&' : '?'\n return `${panelUrl}${separator}notificationId=${encodeURIComponent(notificationId)}`\n}\n\nconst resolveNotificationCopy = async (\n notification: Notification\n) => {\n const dict = await loadDictionary(defaultLocale)\n const t = createFallbackTranslator(dict)\n\n const title = notification.titleKey\n ? t(notification.titleKey, notification.title ?? notification.titleKey, notification.titleVariables ?? undefined)\n : notification.title\n\n const body = notification.bodyKey\n ? t(notification.bodyKey, notification.body ?? notification.bodyKey ?? '', notification.bodyVariables ?? undefined)\n : notification.body ?? null\n\n return { title, body, t }\n}\n\nconst resolveRecipient = async (\n em: EntityManager,\n notification: Notification,\n encryptionService?: TenantDataEncryptionService | null,\n) => {\n const where: Partial<User> & { deletedAt?: null } = {\n id: notification.recipientUserId,\n tenantId: notification.tenantId,\n deletedAt: null,\n }\n if (notification.organizationId) {\n where.organizationId = notification.organizationId\n }\n const record = await findOneWithDecryption(\n em,\n User,\n where,\n undefined,\n {\n tenantId: notification.tenantId,\n organizationId: notification.organizationId ?? null,\n encryptionService: encryptionService ?? null,\n },\n )\n if (!record) return null\n return {\n email: typeof record.email === 'string' ? record.email : null,\n name: typeof record.name === 'string' ? record.name : null,\n }\n}\n\n\nexport default async function handle(payload: NotificationCreatedPayload, ctx: ResolverContext) {\n debug('deliver notification event', payload)\n const deliveryConfig = await resolveNotificationDeliveryConfig(ctx, { defaultValue: DEFAULT_NOTIFICATION_DELIVERY_CONFIG })\n if (!deliveryConfig.strategies.email.enabled) {\n debug('email delivery disabled')\n return\n }\n\n const em = ctx.resolve('em') as EntityManager\n const notification = await em.findOne(Notification, {\n id: payload.notificationId,\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n })\n if (!notification) {\n debug('notification not found', payload.notificationId)\n return\n }\n\n let encryptionService: TenantDataEncryptionService | null = null\n try {\n encryptionService = ctx.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n } catch {\n encryptionService = null\n }\n\n const recipient = await resolveRecipient(em, notification, encryptionService)\n if (!recipient?.email) {\n debug('recipient has no email', notification.recipientUserId)\n }\n const { title, body, t } = await resolveNotificationCopy(notification)\n const panelUrl = resolveNotificationPanelUrl(deliveryConfig)\n if (!panelUrl) {\n debug('missing panelUrl; check appUrl/panelPath settings')\n return\n }\n\n const panelLink = buildPanelLink(panelUrl, notification.id)\n const actionLinks = (notification.actionData?.actions ?? []).map((action) => ({\n id: action.id,\n label: action.labelKey ? t(action.labelKey, action.label) : action.label,\n href: panelLink,\n }))\n\n if (deliveryConfig.strategies.email.enabled && recipient?.email) {\n const subjectPrefix = deliveryConfig.strategies.email.subjectPrefix?.trim()\n const subject = subjectPrefix ? `${subjectPrefix} ${title}` : title\n const copy = {\n preview: t('notifications.delivery.email.preview', 'New notification'),\n heading: t('notifications.delivery.email.heading', 'You have a new notification'),\n bodyIntro: t('notifications.delivery.email.bodyIntro', 'Review the notification details and take any required actions.'),\n actionNotice: t('notifications.delivery.email.actionNotice', 'Actions are available in Open Mercato and are read-only in this email.'),\n openCta: t('notifications.delivery.email.openCta', 'Open notification center'),\n footer: t('notifications.delivery.email.footer', 'Open Mercato notifications'),\n }\n\n try {\n debug('sending email', { to: recipient.email, from: deliveryConfig.strategies.email.from, subject })\n await sendEmail({\n to: recipient.email,\n subject,\n from: deliveryConfig.strategies.email.from,\n replyTo: deliveryConfig.strategies.email.replyTo,\n react: NotificationEmail({\n title,\n body,\n actions: actionLinks,\n panelUrl: panelLink,\n copy,\n }),\n })\n } catch (error) {\n console.error('[notifications] email delivery failed', error)\n }\n }\n\n return\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,70 +0,0 @@
1
- import { buildNotificationEntity, emitNotificationCreated, emitNotificationCreatedBatch } from "../lib/notificationFactory.js";
2
- import { getRecipientUserIdsForFeature, getRecipientUserIdsForRole } from "../lib/notificationRecipients.js";
3
- function getKnex(em) {
4
- return em.getConnection().getKnex();
5
- }
6
- const NOTIFICATIONS_QUEUE_NAME = "notifications";
7
- const metadata = {
8
- queue: NOTIFICATIONS_QUEUE_NAME,
9
- id: "notifications:create",
10
- concurrency: 5
11
- };
12
- async function handle(job, ctx) {
13
- const { payload } = job;
14
- if (payload.type === "create") {
15
- const em = ctx.resolve("em").fork();
16
- const eventBus = ctx.resolve("eventBus");
17
- const { input, tenantId, organizationId } = payload;
18
- const { recipientUserId, ...content } = input;
19
- const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId });
20
- await em.persistAndFlush(notification);
21
- await emitNotificationCreated(eventBus, notification, { tenantId, organizationId });
22
- } else if (payload.type === "create-role") {
23
- const em = ctx.resolve("em").fork();
24
- const eventBus = ctx.resolve("eventBus");
25
- const { input, tenantId, organizationId } = payload;
26
- const knex = getKnex(em);
27
- const recipientUserIds = await getRecipientUserIdsForRole(knex, tenantId, input.roleId);
28
- if (recipientUserIds.length === 0) {
29
- return;
30
- }
31
- const { roleId: _roleId, ...content } = input;
32
- const notifications = [];
33
- for (const recipientUserId of recipientUserIds) {
34
- const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId });
35
- notifications.push(notification);
36
- }
37
- await em.persistAndFlush(notifications);
38
- await emitNotificationCreatedBatch(eventBus, notifications, { tenantId, organizationId });
39
- } else if (payload.type === "create-feature") {
40
- const em = ctx.resolve("em").fork();
41
- const eventBus = ctx.resolve("eventBus");
42
- const { input, tenantId, organizationId } = payload;
43
- const knex = getKnex(em);
44
- const recipientUserIds = await getRecipientUserIdsForFeature(knex, tenantId, input.requiredFeature);
45
- if (recipientUserIds.length === 0) {
46
- return;
47
- }
48
- const notifications = [];
49
- const { requiredFeature: _requiredFeature, ...content } = input;
50
- for (const recipientUserId of recipientUserIds) {
51
- const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId });
52
- notifications.push(notification);
53
- }
54
- await em.persistAndFlush(notifications);
55
- await emitNotificationCreatedBatch(eventBus, notifications, { tenantId, organizationId });
56
- } else if (payload.type === "cleanup-expired") {
57
- const em = ctx.resolve("em").fork();
58
- const knex = getKnex(em);
59
- await knex("notifications").where("expires_at", "<", knex.fn.now()).whereNotIn("status", ["actioned", "dismissed"]).update({
60
- status: "dismissed",
61
- dismissed_at: knex.fn.now()
62
- });
63
- }
64
- }
65
- export {
66
- NOTIFICATIONS_QUEUE_NAME,
67
- handle as default,
68
- metadata
69
- };
70
- //# sourceMappingURL=create-notification.worker.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/notifications/workers/create-notification.worker.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport type { Knex } from 'knex'\nimport { Notification } from '../data/entities'\nimport type { CreateNotificationInput, CreateRoleNotificationInput, CreateFeatureNotificationInput } from '../data/validators'\nimport { buildNotificationEntity, emitNotificationCreated, emitNotificationCreatedBatch } from '../lib/notificationFactory'\nimport { getRecipientUserIdsForFeature, getRecipientUserIdsForRole } from '../lib/notificationRecipients'\n\nfunction getKnex(em: EntityManager): Knex {\n return (em.getConnection() as unknown as { getKnex: () => Knex }).getKnex()\n}\n\nexport const NOTIFICATIONS_QUEUE_NAME = 'notifications'\n\nexport type CreateNotificationJob = {\n type: 'create'\n input: CreateNotificationInput\n tenantId: string\n organizationId?: string | null\n}\n\nexport type CreateRoleNotificationJob = {\n type: 'create-role'\n input: CreateRoleNotificationInput\n tenantId: string\n organizationId?: string | null\n}\n\nexport type CreateFeatureNotificationJob = {\n type: 'create-feature'\n input: CreateFeatureNotificationInput\n tenantId: string\n organizationId?: string | null\n}\n\nexport type CleanupExpiredJob = {\n type: 'cleanup-expired'\n}\n\nexport type NotificationJob = CreateNotificationJob | CreateRoleNotificationJob | CreateFeatureNotificationJob | CleanupExpiredJob\n\nexport const metadata = {\n queue: NOTIFICATIONS_QUEUE_NAME,\n id: 'notifications:create',\n concurrency: 5,\n}\n\ntype HandlerContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(\n job: { payload: NotificationJob },\n ctx: HandlerContext\n): Promise<void> {\n const { payload } = job\n\n if (payload.type === 'create') {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const eventBus = ctx.resolve('eventBus') as { emit: (event: string, payload: unknown) => Promise<void> }\n const { input, tenantId, organizationId } = payload\n const { recipientUserId, ...content } = input\n const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId })\n\n await em.persistAndFlush(notification)\n\n await emitNotificationCreated(eventBus, notification, { tenantId, organizationId })\n } else if (payload.type === 'create-role') {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const eventBus = ctx.resolve('eventBus') as { emit: (event: string, payload: unknown) => Promise<void> }\n const { input, tenantId, organizationId } = payload\n\n const knex = getKnex(em)\n const recipientUserIds = await getRecipientUserIdsForRole(knex, tenantId, input.roleId)\n if (recipientUserIds.length === 0) {\n return\n }\n\n const { roleId: _roleId, ...content } = input\n const notifications: Notification[] = []\n for (const recipientUserId of recipientUserIds) {\n const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId })\n notifications.push(notification)\n }\n\n await em.persistAndFlush(notifications)\n\n await emitNotificationCreatedBatch(eventBus, notifications, { tenantId, organizationId })\n } else if (payload.type === 'create-feature') {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const eventBus = ctx.resolve('eventBus') as { emit: (event: string, payload: unknown) => Promise<void> }\n const { input, tenantId, organizationId } = payload\n\n const knex = getKnex(em)\n const recipientUserIds = await getRecipientUserIdsForFeature(knex, tenantId, input.requiredFeature)\n\n if (recipientUserIds.length === 0) {\n return\n }\n\n const notifications: Notification[] = []\n const { requiredFeature: _requiredFeature, ...content } = input\n for (const recipientUserId of recipientUserIds) {\n const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId })\n notifications.push(notification)\n }\n\n await em.persistAndFlush(notifications)\n\n await emitNotificationCreatedBatch(eventBus, notifications, { tenantId, organizationId })\n } else if (payload.type === 'cleanup-expired') {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const knex = getKnex(em)\n\n await knex('notifications')\n .where('expires_at', '<', knex.fn.now())\n .whereNotIn('status', ['actioned', 'dismissed'])\n .update({\n status: 'dismissed',\n dismissed_at: knex.fn.now(),\n })\n }\n}\n"],
5
- "mappings": "AAIA,SAAS,yBAAyB,yBAAyB,oCAAoC;AAC/F,SAAS,+BAA+B,kCAAkC;AAE1E,SAAS,QAAQ,IAAyB;AACxC,SAAQ,GAAG,cAAc,EAAyC,QAAQ;AAC5E;AAEO,MAAM,2BAA2B;AA6BjC,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAMA,eAAO,OACL,KACA,KACe;AACf,QAAM,EAAE,QAAQ,IAAI;AAEpB,MAAI,QAAQ,SAAS,UAAU;AAC7B,UAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,UAAM,WAAW,IAAI,QAAQ,UAAU;AACvC,UAAM,EAAE,OAAO,UAAU,eAAe,IAAI;AAC5C,UAAM,EAAE,iBAAiB,GAAG,QAAQ,IAAI;AACxC,UAAM,eAAe,wBAAwB,IAAI,SAAS,iBAAiB,EAAE,UAAU,eAAe,CAAC;AAEvG,UAAM,GAAG,gBAAgB,YAAY;AAErC,UAAM,wBAAwB,UAAU,cAAc,EAAE,UAAU,eAAe,CAAC;AAAA,EACpF,WAAW,QAAQ,SAAS,eAAe;AACzC,UAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,UAAM,WAAW,IAAI,QAAQ,UAAU;AACvC,UAAM,EAAE,OAAO,UAAU,eAAe,IAAI;AAE5C,UAAM,OAAO,QAAQ,EAAE;AACvB,UAAM,mBAAmB,MAAM,2BAA2B,MAAM,UAAU,MAAM,MAAM;AACtF,QAAI,iBAAiB,WAAW,GAAG;AACjC;AAAA,IACF;AAEA,UAAM,EAAE,QAAQ,SAAS,GAAG,QAAQ,IAAI;AACxC,UAAM,gBAAgC,CAAC;AACvC,eAAW,mBAAmB,kBAAkB;AAC9C,YAAM,eAAe,wBAAwB,IAAI,SAAS,iBAAiB,EAAE,UAAU,eAAe,CAAC;AACvG,oBAAc,KAAK,YAAY;AAAA,IACjC;AAEA,UAAM,GAAG,gBAAgB,aAAa;AAEtC,UAAM,6BAA6B,UAAU,eAAe,EAAE,UAAU,eAAe,CAAC;AAAA,EAC1F,WAAW,QAAQ,SAAS,kBAAkB;AAC5C,UAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,UAAM,WAAW,IAAI,QAAQ,UAAU;AACvC,UAAM,EAAE,OAAO,UAAU,eAAe,IAAI;AAE5C,UAAM,OAAO,QAAQ,EAAE;AACvB,UAAM,mBAAmB,MAAM,8BAA8B,MAAM,UAAU,MAAM,eAAe;AAElG,QAAI,iBAAiB,WAAW,GAAG;AACjC;AAAA,IACF;AAEA,UAAM,gBAAgC,CAAC;AACvC,UAAM,EAAE,iBAAiB,kBAAkB,GAAG,QAAQ,IAAI;AAC1D,eAAW,mBAAmB,kBAAkB;AAC9C,YAAM,eAAe,wBAAwB,IAAI,SAAS,iBAAiB,EAAE,UAAU,eAAe,CAAC;AACvG,oBAAc,KAAK,YAAY;AAAA,IACjC;AAEA,UAAM,GAAG,gBAAgB,aAAa;AAEtC,UAAM,6BAA6B,UAAU,eAAe,EAAE,UAAU,eAAe,CAAC;AAAA,EAC1F,WAAW,QAAQ,SAAS,mBAAmB;AAC7C,UAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,UAAM,OAAO,QAAQ,EAAE;AAEvB,UAAM,KAAK,eAAe,EACvB,MAAM,cAAc,KAAK,KAAK,GAAG,IAAI,CAAC,EACtC,WAAW,UAAU,CAAC,YAAY,WAAW,CAAC,EAC9C,OAAO;AAAA,MACN,QAAQ;AAAA,MACR,cAAc,KAAK,GAAG,IAAI;AAAA,IAC5B,CAAC;AAAA,EACL;AACF;",
6
- "names": []
7
- }
@@ -1,51 +0,0 @@
1
- "use client";
2
- import { SalesOrderCreatedRenderer } from "./widgets/notifications/SalesOrderCreatedRenderer.js";
3
- import { SalesQuoteCreatedRenderer } from "./widgets/notifications/SalesQuoteCreatedRenderer.js";
4
- const salesNotificationTypes = [
5
- {
6
- type: "sales.order.created",
7
- module: "sales",
8
- titleKey: "sales.notifications.order.created.title",
9
- bodyKey: "sales.notifications.order.created.body",
10
- icon: "shopping-cart",
11
- severity: "info",
12
- actions: [
13
- {
14
- id: "view",
15
- labelKey: "common.view",
16
- variant: "outline",
17
- href: "/backend/sales/orders/{sourceEntityId}",
18
- icon: "external-link"
19
- }
20
- ],
21
- linkHref: "/backend/sales/orders/{sourceEntityId}",
22
- Renderer: SalesOrderCreatedRenderer,
23
- expiresAfterHours: 168
24
- },
25
- {
26
- type: "sales.quote.created",
27
- module: "sales",
28
- titleKey: "sales.notifications.quote.created.title",
29
- bodyKey: "sales.notifications.quote.created.body",
30
- icon: "file-text",
31
- severity: "info",
32
- actions: [
33
- {
34
- id: "view",
35
- labelKey: "common.view",
36
- variant: "outline",
37
- href: "/backend/sales/quotes/{sourceEntityId}",
38
- icon: "external-link"
39
- }
40
- ],
41
- linkHref: "/backend/sales/quotes/{sourceEntityId}",
42
- Renderer: SalesQuoteCreatedRenderer,
43
- expiresAfterHours: 168
44
- }
45
- ];
46
- var notifications_client_default = salesNotificationTypes;
47
- export {
48
- notifications_client_default as default,
49
- salesNotificationTypes
50
- };
51
- //# sourceMappingURL=notifications.client.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/modules/sales/notifications.client.ts"],
4
- "sourcesContent": ["'use client'\n\nimport type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\nimport { SalesOrderCreatedRenderer } from './widgets/notifications/SalesOrderCreatedRenderer'\nimport { SalesQuoteCreatedRenderer } from './widgets/notifications/SalesQuoteCreatedRenderer'\n\n/**\n * Client-side notification type definitions with custom renderers.\n * These should be used in client components where custom rendering is needed.\n *\n * Example usage:\n * ```tsx\n * import { salesNotificationTypes } from '@open-mercato/core/modules/sales/notifications.client'\n *\n * // Use in NotificationPanel or NotificationItem\n * const renderer = salesNotificationTypes.find(t => t.type === notification.type)?.Renderer\n * if (renderer) {\n * return <renderer notification={notification} onAction={...} onDismiss={...} actions={...} />\n * }\n * ```\n */\nexport const salesNotificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'sales.order.created',\n module: 'sales',\n titleKey: 'sales.notifications.order.created.title',\n bodyKey: 'sales.notifications.order.created.body',\n icon: 'shopping-cart',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/sales/orders/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/sales/orders/{sourceEntityId}',\n Renderer: SalesOrderCreatedRenderer,\n expiresAfterHours: 168,\n },\n {\n type: 'sales.quote.created',\n module: 'sales',\n titleKey: 'sales.notifications.quote.created.title',\n bodyKey: 'sales.notifications.quote.created.body',\n icon: 'file-text',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/sales/quotes/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/sales/quotes/{sourceEntityId}',\n Renderer: SalesQuoteCreatedRenderer,\n expiresAfterHours: 168,\n },\n]\n\nexport default salesNotificationTypes\n"],
5
- "mappings": ";AAGA,SAAS,iCAAiC;AAC1C,SAAS,iCAAiC;AAiBnC,MAAM,yBAAuD;AAAA,EAClE;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,mBAAmB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,mBAAmB;AAAA,EACrB;AACF;AAEA,IAAO,+BAAQ;",
6
- "names": []
7
- }
@@ -1,88 +0,0 @@
1
- const notificationTypes = [
2
- {
3
- type: "sales.order.created",
4
- module: "sales",
5
- titleKey: "sales.notifications.order.created.title",
6
- bodyKey: "sales.notifications.order.created.body",
7
- icon: "shopping-cart",
8
- severity: "info",
9
- actions: [
10
- {
11
- id: "view",
12
- labelKey: "common.view",
13
- variant: "outline",
14
- href: "/backend/sales/orders/{sourceEntityId}",
15
- icon: "external-link"
16
- }
17
- ],
18
- linkHref: "/backend/sales/orders/{sourceEntityId}",
19
- expiresAfterHours: 168
20
- // 7 days
21
- },
22
- {
23
- type: "sales.quote.created",
24
- module: "sales",
25
- titleKey: "sales.notifications.quote.created.title",
26
- bodyKey: "sales.notifications.quote.created.body",
27
- icon: "file-text",
28
- severity: "info",
29
- actions: [
30
- {
31
- id: "view",
32
- labelKey: "common.view",
33
- variant: "outline",
34
- href: "/backend/sales/quotes/{sourceEntityId}",
35
- icon: "external-link"
36
- }
37
- ],
38
- linkHref: "/backend/sales/quotes/{sourceEntityId}",
39
- expiresAfterHours: 168
40
- // 7 days
41
- },
42
- {
43
- type: "sales.payment.received",
44
- module: "sales",
45
- titleKey: "sales.notifications.payment.received.title",
46
- bodyKey: "sales.notifications.payment.received.body",
47
- icon: "credit-card",
48
- severity: "success",
49
- actions: [
50
- {
51
- id: "view",
52
- labelKey: "common.view",
53
- variant: "outline",
54
- href: "/backend/sales/orders/{sourceEntityId}",
55
- icon: "external-link"
56
- }
57
- ],
58
- linkHref: "/backend/sales/orders/{sourceEntityId}",
59
- expiresAfterHours: 168
60
- // 7 days
61
- },
62
- {
63
- type: "sales.quote.expiring",
64
- module: "sales",
65
- titleKey: "sales.notifications.quote.expiring.title",
66
- bodyKey: "sales.notifications.quote.expiring.body",
67
- icon: "clock",
68
- severity: "warning",
69
- actions: [
70
- {
71
- id: "view",
72
- labelKey: "common.view",
73
- variant: "outline",
74
- href: "/backend/sales/quotes/{sourceEntityId}",
75
- icon: "external-link"
76
- }
77
- ],
78
- linkHref: "/backend/sales/quotes/{sourceEntityId}",
79
- expiresAfterHours: 72
80
- // 3 days
81
- }
82
- ];
83
- var notifications_default = notificationTypes;
84
- export {
85
- notifications_default as default,
86
- notificationTypes
87
- };
88
- //# sourceMappingURL=notifications.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/modules/sales/notifications.ts"],
4
- "sourcesContent": ["import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\n\nexport const notificationTypes: NotificationTypeDefinition[] = [\n {\n type: 'sales.order.created',\n module: 'sales',\n titleKey: 'sales.notifications.order.created.title',\n bodyKey: 'sales.notifications.order.created.body',\n icon: 'shopping-cart',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/sales/orders/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/sales/orders/{sourceEntityId}',\n expiresAfterHours: 168, // 7 days\n },\n {\n type: 'sales.quote.created',\n module: 'sales',\n titleKey: 'sales.notifications.quote.created.title',\n bodyKey: 'sales.notifications.quote.created.body',\n icon: 'file-text',\n severity: 'info',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/sales/quotes/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/sales/quotes/{sourceEntityId}',\n expiresAfterHours: 168, // 7 days\n },\n {\n type: 'sales.payment.received',\n module: 'sales',\n titleKey: 'sales.notifications.payment.received.title',\n bodyKey: 'sales.notifications.payment.received.body',\n icon: 'credit-card',\n severity: 'success',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/sales/orders/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/sales/orders/{sourceEntityId}',\n expiresAfterHours: 168, // 7 days\n },\n {\n type: 'sales.quote.expiring',\n module: 'sales',\n titleKey: 'sales.notifications.quote.expiring.title',\n bodyKey: 'sales.notifications.quote.expiring.body',\n icon: 'clock',\n severity: 'warning',\n actions: [\n {\n id: 'view',\n labelKey: 'common.view',\n variant: 'outline',\n href: '/backend/sales/quotes/{sourceEntityId}',\n icon: 'external-link',\n },\n ],\n linkHref: '/backend/sales/quotes/{sourceEntityId}',\n expiresAfterHours: 72, // 3 days\n },\n]\n\nexport default notificationTypes\n"],
5
- "mappings": "AAEO,MAAM,oBAAkD;AAAA,EAC7D;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,MACP;AAAA,QACE,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,UAAU;AAAA,IACV,mBAAmB;AAAA;AAAA,EACrB;AACF;AAEA,IAAO,wBAAQ;",
6
- "names": []
7
- }
@@ -1,38 +0,0 @@
1
- import { resolveNotificationService } from "../../notifications/lib/notificationService.js";
2
- import { buildFeatureNotificationFromType } from "../../notifications/lib/notificationBuilder.js";
3
- import { notificationTypes } from "../notifications.js";
4
- const metadata = {
5
- event: "sales.quote.expiring",
6
- persistent: true,
7
- id: "sales:quote-expiring-notification"
8
- };
9
- async function handle(payload, ctx) {
10
- try {
11
- const notificationService = resolveNotificationService(ctx);
12
- const typeDef = notificationTypes.find((type) => type.type === "sales.quote.expiring");
13
- if (!typeDef) return;
14
- const notificationInput = buildFeatureNotificationFromType(typeDef, {
15
- requiredFeature: "sales.quotes.manage",
16
- bodyVariables: {
17
- quoteNumber: payload.quoteNumber,
18
- expiresAt: payload.expiresAt,
19
- daysUntilExpiry: String(payload.daysUntilExpiry),
20
- customerName: payload.customerName ?? ""
21
- },
22
- sourceEntityType: "sales:quote",
23
- sourceEntityId: payload.quoteId,
24
- linkHref: `/backend/sales/quotes/${payload.quoteId}`
25
- });
26
- await notificationService.createForFeature(notificationInput, {
27
- tenantId: payload.tenantId,
28
- organizationId: payload.organizationId ?? null
29
- });
30
- } catch (err) {
31
- console.error("[sales:quote-expiring-notification] Failed to create notification:", err);
32
- }
33
- }
34
- export {
35
- handle as default,
36
- metadata
37
- };
38
- //# sourceMappingURL=quote-expiring-notification.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/sales/subscribers/quote-expiring-notification.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveNotificationService } from '../../notifications/lib/notificationService'\nimport { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'\nimport { notificationTypes } from '../notifications'\n\nexport const metadata = {\n event: 'sales.quote.expiring',\n persistent: true,\n id: 'sales:quote-expiring-notification',\n}\n\ntype QuoteExpiringPayload = {\n quoteId: string\n quoteNumber: string\n expiresAt: string\n daysUntilExpiry: number\n customerName?: string | null\n totalAmount?: string | null\n tenantId: string\n organizationId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(payload: QuoteExpiringPayload, ctx: ResolverContext) {\n try {\n const notificationService = resolveNotificationService(ctx)\n const typeDef = notificationTypes.find((type) => type.type === 'sales.quote.expiring')\n if (!typeDef) return\n\n const notificationInput = buildFeatureNotificationFromType(typeDef, {\n requiredFeature: 'sales.quotes.manage',\n bodyVariables: {\n quoteNumber: payload.quoteNumber,\n expiresAt: payload.expiresAt,\n daysUntilExpiry: String(payload.daysUntilExpiry),\n customerName: payload.customerName ?? '',\n },\n sourceEntityType: 'sales:quote',\n sourceEntityId: payload.quoteId,\n linkHref: `/backend/sales/quotes/${payload.quoteId}`,\n })\n\n await notificationService.createForFeature(notificationInput, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n })\n } catch (err) {\n console.error('[sales:quote-expiring-notification] Failed to create notification:', err)\n }\n}\n"],
5
- "mappings": "AACA,SAAS,kCAAkC;AAC3C,SAAS,wCAAwC;AACjD,SAAS,yBAAyB;AAE3B,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAiBA,eAAO,OAA8B,SAA+B,KAAsB;AACxF,MAAI;AACF,UAAM,sBAAsB,2BAA2B,GAAG;AAC1D,UAAM,UAAU,kBAAkB,KAAK,CAAC,SAAS,KAAK,SAAS,sBAAsB;AACrF,QAAI,CAAC,QAAS;AAEd,UAAM,oBAAoB,iCAAiC,SAAS;AAAA,MAClE,iBAAiB;AAAA,MACjB,eAAe;AAAA,QACb,aAAa,QAAQ;AAAA,QACrB,WAAW,QAAQ;AAAA,QACnB,iBAAiB,OAAO,QAAQ,eAAe;AAAA,QAC/C,cAAc,QAAQ,gBAAgB;AAAA,MACxC;AAAA,MACA,kBAAkB;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,UAAU,yBAAyB,QAAQ,OAAO;AAAA,IACpD,CAAC;AAED,UAAM,oBAAoB,iBAAiB,mBAAmB;AAAA,MAC5D,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ,kBAAkB;AAAA,IAC5C,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,sEAAsE,GAAG;AAAA,EACzF;AACF;",
6
- "names": []
7
- }
@@ -1,137 +0,0 @@
1
- "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import * as React from "react";
4
- import { ShoppingCart, ExternalLink, DollarSign, User, Calendar } from "lucide-react";
5
- import { useRouter } from "next/navigation";
6
- import { Button } from "@open-mercato/ui/primitives/button";
7
- import { cn } from "@open-mercato/shared/lib/utils";
8
- import { useT } from "@open-mercato/shared/lib/i18n/context";
9
- import { formatMoney } from "../../components/documents/lineItemUtils.js";
10
- import { useSalesDocumentTotals } from "./useSalesDocumentTotals.js";
11
- function formatTimeAgo(dateString, t) {
12
- const date = new Date(dateString);
13
- const now = /* @__PURE__ */ new Date();
14
- const diffMs = now.getTime() - date.getTime();
15
- const diffMins = Math.floor(diffMs / 6e4);
16
- const diffHours = Math.floor(diffMs / 36e5);
17
- const diffDays = Math.floor(diffMs / 864e5);
18
- if (diffMins < 1) return t("common.time.justNow", "just now");
19
- if (diffMins < 60) return t("common.time.minutesAgo", "{count}m ago").replace("{count}", String(diffMins));
20
- if (diffHours < 24) return t("common.time.hoursAgo", "{count}h ago").replace("{count}", String(diffHours));
21
- if (diffDays < 7) return t("common.time.daysAgo", "{count}d ago").replace("{count}", String(diffDays));
22
- return date.toLocaleDateString();
23
- }
24
- function normalizeTotal(value) {
25
- if (!value) return null;
26
- let trimmed = value.trim();
27
- if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
28
- trimmed = trimmed.slice(1, -1).trim();
29
- }
30
- return trimmed.length ? trimmed : null;
31
- }
32
- function SalesOrderCreatedRenderer({
33
- notification,
34
- onAction,
35
- onDismiss,
36
- actions = []
37
- }) {
38
- const t = useT();
39
- const router = useRouter();
40
- const [executing, setExecuting] = React.useState(false);
41
- const isUnread = notification.status === "unread";
42
- const orderNumber = notification.bodyVariables?.orderNumber ?? notification.titleVariables?.orderNumber;
43
- const fallbackTotal = normalizeTotal(notification.bodyVariables?.totalAmount ?? null) ?? normalizeTotal(notification.bodyVariables?.total ?? null);
44
- const { totals } = useSalesDocumentTotals("order", notification.sourceEntityId);
45
- const currentTotal = totals && typeof totals.grandTotalGrossAmount === "number" ? formatMoney(totals.grandTotalGrossAmount, totals.currencyCode) : fallbackTotal;
46
- const viewAction = actions.find((action) => action.id === "view") ?? actions[0] ?? null;
47
- const handleView = async () => {
48
- if (!viewAction) {
49
- if (notification.linkHref) router.push(notification.linkHref);
50
- return;
51
- }
52
- setExecuting(true);
53
- try {
54
- await onAction(viewAction.id);
55
- } finally {
56
- setExecuting(false);
57
- }
58
- };
59
- return /* @__PURE__ */ jsxs(
60
- "div",
61
- {
62
- className: cn(
63
- "group relative px-4 py-3 hover:bg-muted/50 cursor-pointer transition-colors border-l-4 border-l-blue-500",
64
- isUnread && "bg-blue-50/50 dark:bg-blue-950/20"
65
- ),
66
- onClick: handleView,
67
- children: [
68
- isUnread && /* @__PURE__ */ jsx("div", { className: "absolute left-1.5 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full bg-primary" }),
69
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
70
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 mt-0.5", children: /* @__PURE__ */ jsx("div", { className: "h-10 w-10 rounded-lg bg-blue-100 dark:bg-blue-900/40 flex items-center justify-center", children: /* @__PURE__ */ jsx(ShoppingCart, { className: "h-5 w-5 text-blue-600 dark:text-blue-400" }) }) }),
71
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
72
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
73
- /* @__PURE__ */ jsxs("div", { children: [
74
- /* @__PURE__ */ jsx("h4", { className: cn("text-sm font-medium", isUnread && "font-semibold"), children: notification.title }),
75
- orderNumber && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mt-0.5", children: /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono text-muted-foreground bg-muted px-1.5 py-0.5 rounded", children: [
76
- "#",
77
- orderNumber
78
- ] }) })
79
- ] }),
80
- /* @__PURE__ */ jsxs("span", { className: "flex-shrink-0 text-xs text-muted-foreground flex items-center gap-1", children: [
81
- /* @__PURE__ */ jsx(Calendar, { className: "h-3 w-3" }),
82
- formatTimeAgo(notification.createdAt, t)
83
- ] })
84
- ] }),
85
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-4 text-xs text-muted-foreground", children: [
86
- currentTotal && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
87
- /* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
88
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: currentTotal })
89
- ] }),
90
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
91
- /* @__PURE__ */ jsx(User, { className: "h-3 w-3" }),
92
- /* @__PURE__ */ jsx("span", { children: t("sales.notifications.renderer.assignedToYou", "Assigned to you") })
93
- ] })
94
- ] }),
95
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex gap-2", children: [
96
- /* @__PURE__ */ jsxs(
97
- Button,
98
- {
99
- variant: "default",
100
- size: "sm",
101
- onClick: (e) => {
102
- e.stopPropagation();
103
- handleView();
104
- },
105
- disabled: executing || !viewAction && !notification.linkHref,
106
- className: "gap-1",
107
- children: [
108
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" }),
109
- t("sales.notifications.renderer.viewOrder", "View Order")
110
- ]
111
- }
112
- ),
113
- /* @__PURE__ */ jsx(
114
- Button,
115
- {
116
- variant: "ghost",
117
- size: "sm",
118
- onClick: (e) => {
119
- e.stopPropagation();
120
- onDismiss();
121
- },
122
- children: t("notifications.actions.dismiss", "Dismiss")
123
- }
124
- )
125
- ] })
126
- ] })
127
- ] })
128
- ]
129
- }
130
- );
131
- }
132
- var SalesOrderCreatedRenderer_default = SalesOrderCreatedRenderer;
133
- export {
134
- SalesOrderCreatedRenderer,
135
- SalesOrderCreatedRenderer_default as default
136
- };
137
- //# sourceMappingURL=SalesOrderCreatedRenderer.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../../src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ShoppingCart, ExternalLink, DollarSign, User, Calendar } from 'lucide-react'\nimport { useRouter } from 'next/navigation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'\nimport { formatMoney } from '../../components/documents/lineItemUtils'\nimport { useSalesDocumentTotals } from './useSalesDocumentTotals'\n\nfunction formatTimeAgo(dateString: string, t: (key: string, fallback?: string) => string): string {\n const date = new Date(dateString)\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n const diffMins = Math.floor(diffMs / 60000)\n const diffHours = Math.floor(diffMs / 3600000)\n const diffDays = Math.floor(diffMs / 86400000)\n\n if (diffMins < 1) return t('common.time.justNow', 'just now')\n if (diffMins < 60) return t('common.time.minutesAgo', '{count}m ago').replace('{count}', String(diffMins))\n if (diffHours < 24) return t('common.time.hoursAgo', '{count}h ago').replace('{count}', String(diffHours))\n if (diffDays < 7) return t('common.time.daysAgo', '{count}d ago').replace('{count}', String(diffDays))\n return date.toLocaleDateString()\n}\n\nfunction normalizeTotal(value?: string | null): string | null {\n if (!value) return null\n let trimmed = value.trim()\n if (trimmed.startsWith('(') && trimmed.endsWith(')')) {\n trimmed = trimmed.slice(1, -1).trim()\n }\n return trimmed.length ? trimmed : null\n}\n\nexport function SalesOrderCreatedRenderer({\n notification,\n onAction,\n onDismiss,\n actions = [],\n}: NotificationRendererProps) {\n const t = useT()\n const router = useRouter()\n const [executing, setExecuting] = React.useState(false)\n const isUnread = notification.status === 'unread'\n const orderNumber = notification.bodyVariables?.orderNumber ?? notification.titleVariables?.orderNumber\n const fallbackTotal =\n normalizeTotal(notification.bodyVariables?.totalAmount ?? null) ??\n normalizeTotal(notification.bodyVariables?.total ?? null)\n const { totals } = useSalesDocumentTotals('order', notification.sourceEntityId)\n\n const currentTotal =\n totals && typeof totals.grandTotalGrossAmount === 'number'\n ? formatMoney(totals.grandTotalGrossAmount, totals.currencyCode)\n : fallbackTotal\n\n const viewAction = actions.find((action) => action.id === 'view') ?? actions[0] ?? null\n\n const handleView = async () => {\n if (!viewAction) {\n if (notification.linkHref) router.push(notification.linkHref)\n return\n }\n setExecuting(true)\n try {\n await onAction(viewAction.id)\n } finally {\n setExecuting(false)\n }\n }\n\n return (\n <div\n className={cn(\n 'group relative px-4 py-3 hover:bg-muted/50 cursor-pointer transition-colors border-l-4 border-l-blue-500',\n isUnread && 'bg-blue-50/50 dark:bg-blue-950/20'\n )}\n onClick={handleView}\n >\n {isUnread && (\n <div className=\"absolute left-1.5 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full bg-primary\" />\n )}\n\n <div className=\"flex gap-3\">\n <div className=\"flex-shrink-0 mt-0.5\">\n <div className=\"h-10 w-10 rounded-lg bg-blue-100 dark:bg-blue-900/40 flex items-center justify-center\">\n <ShoppingCart className=\"h-5 w-5 text-blue-600 dark:text-blue-400\" />\n </div>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <h4 className={cn('text-sm font-medium', isUnread && 'font-semibold')}>\n {notification.title}\n </h4>\n {orderNumber && (\n <div className=\"flex items-center gap-1 mt-0.5\">\n <span className=\"text-xs font-mono text-muted-foreground bg-muted px-1.5 py-0.5 rounded\">\n #{orderNumber}\n </span>\n </div>\n )}\n </div>\n <span className=\"flex-shrink-0 text-xs text-muted-foreground flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n {formatTimeAgo(notification.createdAt, t)}\n </span>\n </div>\n\n <div className=\"mt-2 flex items-center gap-4 text-xs text-muted-foreground\">\n {currentTotal && (\n <div className=\"flex items-center gap-1\">\n <DollarSign className=\"h-3 w-3\" />\n <span className=\"font-medium text-foreground\">{currentTotal}</span>\n </div>\n )}\n <div className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" />\n <span>{t('sales.notifications.renderer.assignedToYou', 'Assigned to you')}</span>\n </div>\n </div>\n\n <div className=\"mt-3 flex gap-2\">\n <Button\n variant=\"default\"\n size=\"sm\"\n onClick={(e) => {\n e.stopPropagation()\n handleView()\n }}\n disabled={executing || (!viewAction && !notification.linkHref)}\n className=\"gap-1\"\n >\n <ExternalLink className=\"h-3 w-3\" />\n {t('sales.notifications.renderer.viewOrder', 'View Order')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => {\n e.stopPropagation()\n onDismiss()\n }}\n >\n {t('notifications.actions.dismiss', 'Dismiss')}\n </Button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nexport default SalesOrderCreatedRenderer\n"],
5
- "mappings": "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",
6
- "names": []
7
- }