@aglyn/plugins-crm 1.0.0-beta.218 → 1.0.0-beta.220

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 (346) hide show
  1. package/README.md +2 -0
  2. package/package.json +14 -14
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js.map +1 -1
  5. package/src/lib/components/companies-bulk-bar.js +12 -4
  6. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  7. package/src/lib/components/company-detail-page.js +11 -0
  8. package/src/lib/components/company-detail-page.js.map +1 -1
  9. package/src/lib/components/company-edit-drawer.js +12 -2
  10. package/src/lib/components/company-edit-drawer.js.map +1 -1
  11. package/src/lib/components/company-import-drawer.d.ts +1 -1
  12. package/src/lib/components/company-picker.js +10 -3
  13. package/src/lib/components/company-picker.js.map +1 -1
  14. package/src/lib/components/contact-associations-card.js +4 -3
  15. package/src/lib/components/contact-associations-card.js.map +1 -1
  16. package/src/lib/components/contact-detail-page.d.ts +4 -4
  17. package/src/lib/components/contact-detail-page.js +50 -53
  18. package/src/lib/components/contact-detail-page.js.map +1 -1
  19. package/src/lib/components/contact-list-columns.d.ts +3 -0
  20. package/src/lib/components/contact-list-columns.js +21 -5
  21. package/src/lib/components/contact-list-columns.js.map +1 -1
  22. package/src/lib/components/contact-merge-dialog.js +2 -1
  23. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  24. package/src/lib/components/contact-timeline-card.js +4 -3
  25. package/src/lib/components/contact-timeline-card.js.map +1 -1
  26. package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
  27. package/src/lib/components/contacts-bulk-bar.js +24 -51
  28. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  29. package/src/lib/components/contacts-section.js +9 -3
  30. package/src/lib/components/contacts-section.js.map +1 -1
  31. package/src/lib/components/crm-booking-zone.d.ts +8 -2
  32. package/src/lib/components/crm-booking-zone.js.map +1 -1
  33. package/src/lib/components/crm-export-all-button.d.ts +1 -1
  34. package/src/lib/components/crm-export-all-button.js.map +1 -1
  35. package/src/lib/components/crm-glance-card.js +2 -1
  36. package/src/lib/components/crm-glance-card.js.map +1 -1
  37. package/src/lib/components/crm-send-email-dialog.js +9 -6
  38. package/src/lib/components/crm-send-email-dialog.js.map +1 -1
  39. package/src/lib/components/csv-import-drawer.js +2 -2
  40. package/src/lib/components/csv-import-drawer.js.map +1 -1
  41. package/src/lib/components/deal-detail-page.js +18 -2
  42. package/src/lib/components/deal-detail-page.js.map +1 -1
  43. package/src/lib/components/deal-edit-drawer.js +10 -0
  44. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  45. package/src/lib/components/deal-products-card.js +27 -16
  46. package/src/lib/components/deal-products-card.js.map +1 -1
  47. package/src/lib/components/deals-bulk-bar.js +13 -5
  48. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  49. package/src/lib/components/lead-campaigns-card.d.ts +4 -4
  50. package/src/lib/components/lead-campaigns-card.js +15 -8
  51. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  52. package/src/lib/components/lead-detail-page.js +25 -5
  53. package/src/lib/components/lead-detail-page.js.map +1 -1
  54. package/src/lib/components/lead-properties-card.js +10 -0
  55. package/src/lib/components/lead-properties-card.js.map +1 -1
  56. package/src/lib/components/lead-status-chip.js +3 -1
  57. package/src/lib/components/lead-status-chip.js.map +1 -1
  58. package/src/lib/components/lead-surfaces-note.js +4 -5
  59. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  60. package/src/lib/components/lead-unqualify-dialog.js +6 -0
  61. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  62. package/src/lib/components/leads-bulk-bar.js +34 -11
  63. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  64. package/src/lib/components/leads-section.js +41 -8
  65. package/src/lib/components/leads-section.js.map +1 -1
  66. package/src/lib/components/new-lead-drawer.js +5 -4
  67. package/src/lib/components/new-lead-drawer.js.map +1 -1
  68. package/src/lib/components/org-lead-surfaces-note.js +6 -2
  69. package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
  70. package/src/lib/components/recipes-card.js +9 -4
  71. package/src/lib/components/recipes-card.js.map +1 -1
  72. package/src/lib/components/record-sharing-card.d.ts +57 -0
  73. package/src/lib/components/record-sharing-card.js +244 -0
  74. package/src/lib/components/record-sharing-card.js.map +1 -0
  75. package/src/lib/components/reports/activity-card.js +4 -3
  76. package/src/lib/components/reports/activity-card.js.map +1 -1
  77. package/src/lib/components/reports/closed-deals-card.js +4 -3
  78. package/src/lib/components/reports/closed-deals-card.js.map +1 -1
  79. package/src/lib/components/reports/contacts-mix-card.js +3 -2
  80. package/src/lib/components/reports/contacts-mix-card.js.map +1 -1
  81. package/src/lib/components/reports/contacts-trend-card.js +2 -1
  82. package/src/lib/components/reports/contacts-trend-card.js.map +1 -1
  83. package/src/lib/components/reports/forecast-card.js +3 -2
  84. package/src/lib/components/reports/forecast-card.js.map +1 -1
  85. package/src/lib/components/reports/lead-funnel-card.js +8 -6
  86. package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
  87. package/src/lib/components/reports/pipeline-card.js +4 -3
  88. package/src/lib/components/reports/pipeline-card.js.map +1 -1
  89. package/src/lib/components/reports/report-format.d.ts +1 -1
  90. package/src/lib/components/reports/report-format.js.map +1 -1
  91. package/src/lib/components/reports/report-scope.d.ts +1 -1
  92. package/src/lib/components/reports/report-scope.js.map +1 -1
  93. package/src/lib/components/reports/source-conversion-card.js +2 -1
  94. package/src/lib/components/reports/source-conversion-card.js.map +1 -1
  95. package/src/lib/components/reports/tasks-card.js +2 -1
  96. package/src/lib/components/reports/tasks-card.js.map +1 -1
  97. package/src/lib/components/reports/won-lost-by-owner-card.js +3 -2
  98. package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -1
  99. package/src/lib/components/reports-section.js +4 -4
  100. package/src/lib/components/reports-section.js.map +1 -1
  101. package/src/lib/components/settings-section.js +7 -0
  102. package/src/lib/components/settings-section.js.map +1 -1
  103. package/src/lib/components/share-records-dialog.d.ts +28 -0
  104. package/src/lib/components/share-records-dialog.js +184 -0
  105. package/src/lib/components/share-records-dialog.js.map +1 -0
  106. package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
  107. package/src/lib/components/sharing-rule-drawer.js +374 -0
  108. package/src/lib/components/sharing-rule-drawer.js.map +1 -0
  109. package/src/lib/components/sharing-rules-card.d.ts +24 -0
  110. package/src/lib/components/sharing-rules-card.js +287 -0
  111. package/src/lib/components/sharing-rules-card.js.map +1 -0
  112. package/src/lib/components/use-crm-api.d.ts +1 -1
  113. package/src/lib/components/use-crm-api.js.map +1 -1
  114. package/src/lib/components/use-site-route-context.d.ts +25 -0
  115. package/src/lib/components/use-site-route-context.js +58 -0
  116. package/src/lib/components/use-site-route-context.js.map +1 -0
  117. package/src/lib/constants/api-routes.d.ts +31 -0
  118. package/src/lib/constants/api-routes.js +27 -1
  119. package/src/lib/constants/api-routes.js.map +1 -1
  120. package/src/lib/constants/bundle-common.d.ts +2 -0
  121. package/src/lib/constants/bundle-common.js +1 -0
  122. package/src/lib/constants/bundle-common.js.map +1 -1
  123. package/src/lib/declarations.console-server.d.ts +41 -0
  124. package/src/lib/declarations.console-server.js +115 -0
  125. package/src/lib/declarations.console-server.js.map +1 -0
  126. package/src/lib/declarations.server.d.ts +12 -0
  127. package/src/lib/declarations.server.js +84 -1
  128. package/src/lib/declarations.server.js.map +1 -1
  129. package/src/lib/hooks/use-contact-remove.d.ts +35 -0
  130. package/src/lib/hooks/use-contact-remove.js +77 -0
  131. package/src/lib/hooks/use-contact-remove.js.map +1 -0
  132. package/src/lib/hooks/use-crm-campaigns.d.ts +2 -2
  133. package/src/lib/hooks/use-crm-campaigns.js +3 -3
  134. package/src/lib/hooks/use-crm-campaigns.js.map +1 -1
  135. package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
  136. package/src/lib/hooks/use-crm-sharing.js +109 -0
  137. package/src/lib/hooks/use-crm-sharing.js.map +1 -0
  138. package/src/lib/model/campaign-filing-activity.d.ts +1 -1
  139. package/src/lib/model/campaign-filing-activity.js +1 -1
  140. package/src/lib/model/campaign-filing-activity.js.map +1 -1
  141. package/src/lib/model/companies-csv.d.ts +1 -1
  142. package/src/lib/model/companies-csv.js +2 -2
  143. package/src/lib/model/companies-csv.js.map +1 -1
  144. package/src/lib/model/contact-holder.d.ts +68 -0
  145. package/src/lib/model/contact-holder.js +129 -0
  146. package/src/lib/model/contact-holder.js.map +1 -0
  147. package/src/lib/model/contact-interaction-links.d.ts +46 -0
  148. package/src/lib/model/contact-interaction-links.js +65 -0
  149. package/src/lib/model/contact-interaction-links.js.map +1 -0
  150. package/src/lib/model/contact-merge.d.ts +136 -0
  151. package/src/lib/model/contact-merge.js +412 -0
  152. package/src/lib/model/contact-merge.js.map +1 -0
  153. package/src/lib/model/contact-record.d.ts +7 -1
  154. package/src/lib/model/contact-record.js +7 -6
  155. package/src/lib/model/contact-record.js.map +1 -1
  156. package/src/lib/model/contact-remove.d.ts +48 -0
  157. package/src/lib/model/contact-remove.js +22 -0
  158. package/src/lib/model/contact-remove.js.map +1 -0
  159. package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
  160. package/src/lib/model/contacts-bulk-writes.js +2 -82
  161. package/src/lib/model/contacts-bulk-writes.js.map +1 -1
  162. package/src/lib/model/contacts-csv.d.ts +2 -2
  163. package/src/lib/model/contacts-csv.js +2 -2
  164. package/src/lib/model/contacts-csv.js.map +1 -1
  165. package/src/lib/model/crm-csv.d.ts +239 -0
  166. package/src/lib/model/crm-csv.js +467 -0
  167. package/src/lib/model/crm-csv.js.map +1 -0
  168. package/src/lib/model/crm-digest.d.ts +227 -0
  169. package/src/lib/model/crm-digest.js +336 -0
  170. package/src/lib/model/crm-digest.js.map +1 -0
  171. package/src/lib/model/crm-import.d.ts +7 -1
  172. package/src/lib/model/crm-import.js +2 -1
  173. package/src/lib/model/crm-import.js.map +1 -1
  174. package/src/lib/model/crm-inbound.d.ts +162 -0
  175. package/src/lib/model/crm-inbound.js +208 -0
  176. package/src/lib/model/crm-inbound.js.map +1 -0
  177. package/src/lib/model/crm-lead-import.d.ts +1 -1
  178. package/src/lib/model/crm-lead-import.js +4 -4
  179. package/src/lib/model/crm-lead-import.js.map +1 -1
  180. package/src/lib/model/crm-record-routes.d.ts +5 -0
  181. package/src/lib/model/crm-record-routes.js +7 -0
  182. package/src/lib/model/crm-record-routes.js.map +1 -1
  183. package/src/lib/model/crm-reports.d.ts +467 -0
  184. package/src/lib/model/crm-reports.js +667 -0
  185. package/src/lib/model/crm-reports.js.map +1 -0
  186. package/src/lib/model/crm-routes.d.ts +6 -5
  187. package/src/lib/model/crm-routes.js +7 -7
  188. package/src/lib/model/crm-routes.js.map +1 -1
  189. package/src/lib/model/crm-search-sources.d.ts +15 -0
  190. package/src/lib/model/crm-search-sources.js +162 -0
  191. package/src/lib/model/crm-search-sources.js.map +1 -0
  192. package/src/lib/model/crm-sharing-api.d.ts +32 -0
  193. package/src/lib/model/crm-sharing-api.js +59 -0
  194. package/src/lib/model/crm-sharing-api.js.map +1 -0
  195. package/src/lib/model/crm-sharing.d.ts +333 -0
  196. package/src/lib/model/crm-sharing.js +603 -0
  197. package/src/lib/model/crm-sharing.js.map +1 -0
  198. package/src/lib/model/deals-csv.d.ts +1 -1
  199. package/src/lib/model/deals-csv.js +4 -5
  200. package/src/lib/model/deals-csv.js.map +1 -1
  201. package/src/lib/model/lead-filters.d.ts +5 -4
  202. package/src/lib/model/lead-filters.js +7 -5
  203. package/src/lib/model/lead-filters.js.map +1 -1
  204. package/src/lib/model/leads-csv.d.ts +1 -1
  205. package/src/lib/model/leads-csv.js +1 -1
  206. package/src/lib/model/leads-csv.js.map +1 -1
  207. package/src/lib/model/tasks-csv.d.ts +1 -1
  208. package/src/lib/model/tasks-csv.js +1 -1
  209. package/src/lib/model/tasks-csv.js.map +1 -1
  210. package/src/lib/plan-entitlements.d.ts +75 -0
  211. package/src/lib/plan-entitlements.js +56 -0
  212. package/src/lib/plan-entitlements.js.map +1 -0
  213. package/src/lib/plugin.js +39 -0
  214. package/src/lib/plugin.js.map +1 -1
  215. package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
  216. package/src/lib/server/api-v1/contacts-merge.js +120 -0
  217. package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
  218. package/src/lib/server/api-v1/contacts.d.ts +18 -0
  219. package/src/lib/server/api-v1/contacts.js +1076 -0
  220. package/src/lib/server/api-v1/contacts.js.map +1 -0
  221. package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
  222. package/src/lib/server/api-v1/crm-activities.js +269 -0
  223. package/src/lib/server/api-v1/crm-activities.js.map +1 -0
  224. package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
  225. package/src/lib/server/api-v1/crm-companies.js +391 -0
  226. package/src/lib/server/api-v1/crm-companies.js.map +1 -0
  227. package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
  228. package/src/lib/server/api-v1/crm-deals.js +631 -0
  229. package/src/lib/server/api-v1/crm-deals.js.map +1 -0
  230. package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
  231. package/src/lib/server/api-v1/crm-email-templates.js +336 -0
  232. package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
  233. package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
  234. package/src/lib/server/api-v1/crm-leads.js +833 -0
  235. package/src/lib/server/api-v1/crm-leads.js.map +1 -0
  236. package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
  237. package/src/lib/server/api-v1/crm-pipelines.js +146 -0
  238. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
  239. package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
  240. package/src/lib/server/api-v1/crm-shared.js +436 -0
  241. package/src/lib/server/api-v1/crm-shared.js.map +1 -0
  242. package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
  243. package/src/lib/server/api-v1/crm-tasks.js +398 -0
  244. package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
  245. package/src/lib/server/api-v1/openapi.d.ts +29 -0
  246. package/src/lib/server/api-v1/openapi.js +985 -0
  247. package/src/lib/server/api-v1/openapi.js.map +1 -0
  248. package/src/lib/server/api-v1/usage.d.ts +26 -0
  249. package/src/lib/server/api-v1/usage.js +73 -0
  250. package/src/lib/server/api-v1/usage.js.map +1 -0
  251. package/src/lib/server/campaign-filing-activity.d.ts +1 -1
  252. package/src/lib/server/campaign-filing-activity.js +7 -11
  253. package/src/lib/server/campaign-filing-activity.js.map +1 -1
  254. package/src/lib/server/capture-contact.js +15 -4
  255. package/src/lib/server/capture-contact.js.map +1 -1
  256. package/src/lib/server/consent-group-participant.js +1 -1
  257. package/src/lib/server/consent-group-participant.js.map +1 -1
  258. package/src/lib/server/contact-email-engagement.d.ts +33 -0
  259. package/src/lib/server/contact-email-engagement.js +146 -0
  260. package/src/lib/server/contact-email-engagement.js.map +1 -0
  261. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  262. package/src/lib/server/contact-lifecycle-floor.js +91 -0
  263. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  264. package/src/lib/server/contact-remove.d.ts +75 -0
  265. package/src/lib/server/contact-remove.js +244 -0
  266. package/src/lib/server/contact-remove.js.map +1 -0
  267. package/src/lib/server/contact-update.js +4 -3
  268. package/src/lib/server/contact-update.js.map +1 -1
  269. package/src/lib/server/contacts-import.d.ts +47 -11
  270. package/src/lib/server/contacts-import.js +171 -71
  271. package/src/lib/server/contacts-import.js.map +1 -1
  272. package/src/lib/server/contacts-merge.d.ts +1 -1
  273. package/src/lib/server/contacts-merge.js +3 -2
  274. package/src/lib/server/contacts-merge.js.map +1 -1
  275. package/src/lib/server/container-detach.d.ts +31 -0
  276. package/src/lib/server/container-detach.js +115 -0
  277. package/src/lib/server/container-detach.js.map +1 -0
  278. package/src/lib/server/convert-host-lead.d.ts +189 -0
  279. package/src/lib/server/convert-host-lead.js +518 -0
  280. package/src/lib/server/convert-host-lead.js.map +1 -0
  281. package/src/lib/server/crm-inbound-email.d.ts +162 -0
  282. package/src/lib/server/crm-inbound-email.js +259 -0
  283. package/src/lib/server/crm-inbound-email.js.map +1 -0
  284. package/src/lib/server/crm-records-meter.d.ts +50 -0
  285. package/src/lib/server/crm-records-meter.js +126 -0
  286. package/src/lib/server/crm-records-meter.js.map +1 -0
  287. package/src/lib/server/crm-sharing.d.ts +108 -0
  288. package/src/lib/server/crm-sharing.js +790 -0
  289. package/src/lib/server/crm-sharing.js.map +1 -0
  290. package/src/lib/server/daily-digest-route.d.ts +101 -0
  291. package/src/lib/server/daily-digest-route.js +578 -0
  292. package/src/lib/server/daily-digest-route.js.map +1 -0
  293. package/src/lib/server/export-route.d.ts +60 -0
  294. package/src/lib/server/export-route.js +447 -0
  295. package/src/lib/server/export-route.js.map +1 -0
  296. package/src/lib/server/inbound-address.js +5 -3
  297. package/src/lib/server/inbound-address.js.map +1 -1
  298. package/src/lib/server/inbound-route.d.ts +74 -0
  299. package/src/lib/server/inbound-route.js +260 -0
  300. package/src/lib/server/inbound-route.js.map +1 -0
  301. package/src/lib/server/lead-campaign-carry.js +3 -3
  302. package/src/lib/server/lead-campaign-carry.js.map +1 -1
  303. package/src/lib/server/lead-convert.d.ts +1 -1
  304. package/src/lib/server/lead-convert.js +2 -2
  305. package/src/lib/server/lead-convert.js.map +1 -1
  306. package/src/lib/server/lead-create.d.ts +1 -1
  307. package/src/lib/server/lead-create.js +10 -15
  308. package/src/lib/server/lead-create.js.map +1 -1
  309. package/src/lib/server/lead-nurturing.d.ts +23 -0
  310. package/src/lib/server/lead-nurturing.js +82 -0
  311. package/src/lib/server/lead-nurturing.js.map +1 -0
  312. package/src/lib/server/leads-import.js +7 -7
  313. package/src/lib/server/leads-import.js.map +1 -1
  314. package/src/lib/server/merge-contacts.d.ts +55 -0
  315. package/src/lib/server/merge-contacts.js +274 -0
  316. package/src/lib/server/merge-contacts.js.map +1 -0
  317. package/src/lib/server/pipeline-record-index.d.ts +36 -0
  318. package/src/lib/server/pipeline-record-index.js +87 -0
  319. package/src/lib/server/pipeline-record-index.js.map +1 -0
  320. package/src/lib/server/record-email-state.js +23 -0
  321. package/src/lib/server/record-email-state.js.map +1 -1
  322. package/src/lib/server/record-facts.js +2 -1
  323. package/src/lib/server/record-facts.js.map +1 -1
  324. package/src/lib/server/record-timeline.d.ts +12 -0
  325. package/src/lib/server/record-timeline.js +127 -15
  326. package/src/lib/server/record-timeline.js.map +1 -1
  327. package/src/lib/server/task-reminders-route.d.ts +82 -0
  328. package/src/lib/server/task-reminders-route.js +499 -0
  329. package/src/lib/server/task-reminders-route.js.map +1 -0
  330. package/src/lib/server/task-routes.d.ts +2 -0
  331. package/src/lib/server/task-routes.js +22 -3
  332. package/src/lib/server/task-routes.js.map +1 -1
  333. package/src/lib/server-deal-stage.js +2 -1
  334. package/src/lib/server-deal-stage.js.map +1 -1
  335. package/src/lib/server.d.ts +5 -2
  336. package/src/lib/server.js +47 -2
  337. package/src/lib/server.js.map +1 -1
  338. package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
  339. package/src/lib/testing/stand-in-site-record-routes.js +55 -0
  340. package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
  341. package/src/lib/usage-axes.d.ts +35 -0
  342. package/src/lib/usage-axes.js +75 -0
  343. package/src/lib/usage-axes.js.map +1 -0
  344. package/src/lib/components/use-emails-hub-path.d.ts +0 -24
  345. package/src/lib/components/use-emails-hub-path.js +0 -59
  346. package/src/lib/components/use-emails-hub-path.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-glance-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { money } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport {\n getAggregateFromServer,\n getCountFromServer,\n query,\n sum,\n Timestamp,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useOrgDataScope,\n useOrgPlan,\n} from '@aglyn/tenant-feature-instance'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { crmRoutes } from '../model/crm-routes'\nimport { openLeadsForScope } from './reports/lead-counts'\nimport { scopedCollection, visibleToClause } from './reports/report-scope'\nimport { ReportStatTile } from './reports/report-stat-tile'\nimport { useAggregateRead } from './reports/use-aggregate-read'\n\nconst WEEK_MS = 7 * 24 * 60 * 60 * 1000\n\ninterface GlanceFigures {\n contacts: number\n newThisWeek: number\n pipelineCents: number\n tasksDue: number\n /** Open leads — new or being worked — on this site, or across the org's sites. */\n leadsToWork: number\n}\n\nexport interface CrmGlanceCardProps {\n /**\n * The site the card is on the dashboard of, or `null` beneath the org\n * hub's mount (AGL-2634), where the figures total every site: the\n * org-wide reads carry no scope clause, and the lead figure fans out\n * over the mount's site list.\n */\n hostId: string | null\n /** The org hub's own path, for the links when there is no site to derive them from. */\n basePath?: string\n}\n\n/**\n * CRM at a glance (AGL-2604): a `hostDashboard` widget with five numbers —\n * contacts, new this week, open pipeline value, tasks due today or overdue,\n * and leads still to work (AGL-2624) — each a link into the section that\n * explains it.\n *\n * Server aggregates and nothing else: a dashboard card mounts on every\n * visit to the site's front page, so it is the one surface on which a\n * bounded read of a thousand rows would be a cost paid for nothing. Each\n * number is a count or a sum the server takes over the same `visibleTo`\n * predicate the hub's sections query with, so the card never shows a figure\n * the section behind the link would not.\n *\n * The lead figure is two counts and a subtraction — every lead on the site,\n * less the closed ones — because a lead nobody has touched carries no status\n * field and Firestore cannot select on a field's absence; see\n * `openLeadsFromCounts`. Leads are host-scoped by path rather than by\n * `visibleTo`, so those two counts run on `hosts/{hostId}/leads` under the\n * site's own rules, the way the Leads section reads it.\n *\n * The widget is handed a host doc id and nothing else, so the org — needed\n * for the consent group whose tokens scope the reads — comes from\n * `useOrgPlan`, and the link base from `useConsoleHostRoute`, the way the\n * commerce glance card builds its store link. Nothing is read until the org\n * has settled: a first round of aggregates against the solo group, replaced\n * by a second against the real one, would be two reads for one answer.\n *\n * ## The organization level (AGL-2634)\n *\n * Mounted with no site beneath the org hub's mount, the card totals the\n * organization: the reads carry no scope clause — the rules admit an\n * org-wide member to every row — and the lead figure is the same two\n * counts per site, over every site the mount lists, summed. Nothing is\n * read until that list has settled, for the reason nothing is read under\n * a site until the org has. The org's dashboard has no widget slot yet, so\n * the mount is a capability rather than a page; what it needs from the\n * shell is a slot and the hub's `basePath`.\n */\nexport function CrmGlanceCard(props: CrmGlanceCardProps) {\n const { hostId, basePath } = props\n const mount = useCrmOrgMount()\n const consoleRoute = useConsoleHostRoute(hostId)\n const { org, ready: planReady } = useOrgPlan(hostId ?? undefined)\n const { scope } = useOrgDataScope(hostId ? { hostId } : { orgId: mount?.orgId })\n const firestore = useFirestore()\n // Under a site, the site's group's tokens once the org has settled; at\n // the org level `null` — no clause — once the site list has.\n const tokens = useMemo(() => {\n if (!hostId) return null\n if (!planReady) return undefined\n const group = Aglyn.consentGroupForHost(org ?? null, hostId)\n return [\n Aglyn.ORG_SCOPE_TOKEN,\n ...group.hostIds.map((id) => Aglyn.hostScopeToken(id)),\n ].slice(0, Aglyn.MAX_SCOPE_HOSTS)\n }, [org, planReady, hostId])\n const leadSites = useMemo(\n () =>\n hostId\n ? [hostId]\n : mount?.hostsReady\n ? mount.hosts.map((host) => host.id)\n : null,\n [hostId, mount],\n )\n const leadSitesKey = leadSites?.join('\\n') ?? ''\n // Anchored once per mount so the queries are stable across renders.\n const nowMs = useMemo(() => Date.now(), [])\n\n const figures = useAggregateRead<GlanceFigures>(() => {\n if (!scope || tokens === undefined || !leadSites) return null\n const contacts = scopedCollection(firestore, scope, 'contacts')\n const deals = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.deals)\n const tasks = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.tasks)\n const day = Aglyn.localDayBounds(nowMs)\n const countOf = (target: ReturnType<typeof query>) =>\n getCountFromServer(target).then((snapshot) => snapshot.data().count)\n return Promise.all([\n countOf(query(contacts, ...visibleToClause(tokens))),\n countOf(\n query(\n contacts,\n ...visibleToClause(tokens),\n where('createdAt', '>=', Timestamp.fromMillis(nowMs - WEEK_MS)),\n ),\n ),\n getAggregateFromServer(\n query(deals, ...visibleToClause(tokens), where('status', '==', 'open')),\n { amountCents: sum('amountCents') },\n ).then((snapshot) => Number(snapshot.data().amountCents ?? 0)),\n // Due today OR overdue: everything open that is due before the day ends.\n countOf(\n query(\n tasks,\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n where('dueAtMs', '<', day.end),\n ),\n ),\n // One scoped count over the org collection (AGL-3275) — see the\n // function's note on why summing per site would double-count.\n openLeadsForScope(firestore, scope[1], tokens),\n ]).then(([contacts, newThisWeek, pipelineCents, tasksDue, leadsToWork]) => ({\n contacts,\n newThisWeek,\n pipelineCents,\n tasksDue,\n leadsToWork,\n }))\n }, [firestore, scope, tokens, nowMs, leadSitesKey])\n\n const routes = useMemo(\n () =>\n hostId\n ? consoleRoute.base\n ? crmRoutes(`${consoleRoute.base}/crm`)\n : null\n : basePath\n ? crmRoutes(basePath)\n : null,\n [hostId, consoleRoute.base, basePath],\n )\n const hubHref = hostId\n ? consoleRoute.base\n ? `${consoleRoute.base}/crm`\n : undefined\n : basePath\n const value = figures.value\n\n return (\n <CardDisplay\n header={'CRM at a glance'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#crm-at-a-glance',\n excerpt:\n 'Contacts, new contacts this week, the value of every open deal, ' +\n 'the tasks due today or overdue, and the leads still to work — ' +\n 'each counted on the server and each a link into the CRM.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={hubHref}\n size=\"small\"\n color=\"primary\"\n >\n {'Open CRM'}\n </Button>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Contacts'}\n value={value ? value.contacts.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'New this week'}\n value={value ? value.newThisWeek.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'Open pipeline'}\n value={value ? money(value.pipelineCents, 'usd') : null}\n href={routes?.section('deals')}\n />\n <ReportStatTile\n label={'Tasks due'}\n value={value ? value.tasksDue.toLocaleString() : null}\n note={'today or overdue'}\n color={value?.tasksDue ? 'warning.main' : undefined}\n href={routes?.section('tasks')}\n />\n <ReportStatTile\n label={'Leads to work'}\n value={value ? value.leadsToWork.toLocaleString() : null}\n note={hostId ? 'open on this site' : 'open across every site'}\n href={routes?.section('leads')}\n />\n </Stack>\n {figures.status === 'error' ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The CRM figures could not be read. Open the CRM for the full picture.'}\n </Typography>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nCrmGlanceCard.displayName = 'CrmGlanceCard'\n\nexport default CrmGlanceCard\n"],"names":["Aglyn","money","AppLink","CardDisplay","Button","Stack","Typography","getAggregateFromServer","getCountFromServer","query","sum","Timestamp","where","useMemo","useConsoleHostRoute","useFirestore","useOrgDataScope","useOrgPlan","useCrmOrgMount","crmRoutes","openLeadsForScope","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","WEEK_MS","CrmGlanceCard","props","hostId","basePath","mount","consoleRoute","org","ready","planReady","undefined","scope","orgId","firestore","tokens","group","consentGroupForHost","ORG_SCOPE_TOKEN","hostIds","map","id","hostScopeToken","slice","MAX_SCOPE_HOSTS","leadSites","hostsReady","hosts","host","leadSitesKey","join","nowMs","Date","now","figures","contacts","deals","CRM_COLLECTIONS","tasks","day","localDayBounds","countOf","target","then","snapshot","data","count","Promise","all","fromMillis","amountCents","Number","end","newThisWeek","pipelineCents","tasksDue","leadsToWork","routes","base","hubHref","value","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","component","componentVariant","nativeButton","href","size","color","spacing","direction","sx","flexWrap","label","toLocaleString","section","note","status","variant","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-glance-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { money } from '@aglyn/shared-ui-jsx/components/measured-figures.component'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport {\n getAggregateFromServer,\n getCountFromServer,\n query,\n sum,\n Timestamp,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useConsoleHostRoute,\n useFirestore,\n useOrgDataScope,\n useOrgPlan,\n} from '@aglyn/tenant-feature-instance'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { crmRoutes } from '../model/crm-routes'\nimport { openLeadsForScope } from './reports/lead-counts'\nimport { scopedCollection, visibleToClause } from './reports/report-scope'\nimport { ReportStatTile } from './reports/report-stat-tile'\nimport { useAggregateRead } from './reports/use-aggregate-read'\nimport { localDayBounds } from '../model/crm-reports'\n\nconst WEEK_MS = 7 * 24 * 60 * 60 * 1000\n\ninterface GlanceFigures {\n contacts: number\n newThisWeek: number\n pipelineCents: number\n tasksDue: number\n /** Open leads — new or being worked — on this site, or across the org's sites. */\n leadsToWork: number\n}\n\nexport interface CrmGlanceCardProps {\n /**\n * The site the card is on the dashboard of, or `null` beneath the org\n * hub's mount (AGL-2634), where the figures total every site: the\n * org-wide reads carry no scope clause, and the lead figure fans out\n * over the mount's site list.\n */\n hostId: string | null\n /** The org hub's own path, for the links when there is no site to derive them from. */\n basePath?: string\n}\n\n/**\n * CRM at a glance (AGL-2604): a `hostDashboard` widget with five numbers —\n * contacts, new this week, open pipeline value, tasks due today or overdue,\n * and leads still to work (AGL-2624) — each a link into the section that\n * explains it.\n *\n * Server aggregates and nothing else: a dashboard card mounts on every\n * visit to the site's front page, so it is the one surface on which a\n * bounded read of a thousand rows would be a cost paid for nothing. Each\n * number is a count or a sum the server takes over the same `visibleTo`\n * predicate the hub's sections query with, so the card never shows a figure\n * the section behind the link would not.\n *\n * The lead figure is two counts and a subtraction — every lead on the site,\n * less the closed ones — because a lead nobody has touched carries no status\n * field and Firestore cannot select on a field's absence; see\n * `openLeadsFromCounts`. Leads are host-scoped by path rather than by\n * `visibleTo`, so those two counts run on `hosts/{hostId}/leads` under the\n * site's own rules, the way the Leads section reads it.\n *\n * The widget is handed a host doc id and nothing else, so the org — needed\n * for the consent group whose tokens scope the reads — comes from\n * `useOrgPlan`, and the link base from `useConsoleHostRoute`, the way the\n * commerce glance card builds its store link. Nothing is read until the org\n * has settled: a first round of aggregates against the solo group, replaced\n * by a second against the real one, would be two reads for one answer.\n *\n * ## The organization level (AGL-2634)\n *\n * Mounted with no site beneath the org hub's mount, the card totals the\n * organization: the reads carry no scope clause — the rules admit an\n * org-wide member to every row — and the lead figure is the same two\n * counts per site, over every site the mount lists, summed. Nothing is\n * read until that list has settled, for the reason nothing is read under\n * a site until the org has. The org's dashboard has no widget slot yet, so\n * the mount is a capability rather than a page; what it needs from the\n * shell is a slot and the hub's `basePath`.\n */\nexport function CrmGlanceCard(props: CrmGlanceCardProps) {\n const { hostId, basePath } = props\n const mount = useCrmOrgMount()\n const consoleRoute = useConsoleHostRoute(hostId)\n const { org, ready: planReady } = useOrgPlan(hostId ?? undefined)\n const { scope } = useOrgDataScope(hostId ? { hostId } : { orgId: mount?.orgId })\n const firestore = useFirestore()\n // Under a site, the site's group's tokens once the org has settled; at\n // the org level `null` — no clause — once the site list has.\n const tokens = useMemo(() => {\n if (!hostId) return null\n if (!planReady) return undefined\n const group = Aglyn.consentGroupForHost(org ?? null, hostId)\n return [\n Aglyn.ORG_SCOPE_TOKEN,\n ...group.hostIds.map((id) => Aglyn.hostScopeToken(id)),\n ].slice(0, Aglyn.MAX_SCOPE_HOSTS)\n }, [org, planReady, hostId])\n const leadSites = useMemo(\n () =>\n hostId\n ? [hostId]\n : mount?.hostsReady\n ? mount.hosts.map((host) => host.id)\n : null,\n [hostId, mount],\n )\n const leadSitesKey = leadSites?.join('\\n') ?? ''\n // Anchored once per mount so the queries are stable across renders.\n const nowMs = useMemo(() => Date.now(), [])\n\n const figures = useAggregateRead<GlanceFigures>(() => {\n if (!scope || tokens === undefined || !leadSites) return null\n const contacts = scopedCollection(firestore, scope, 'contacts')\n const deals = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.deals)\n const tasks = scopedCollection(firestore, scope, Aglyn.CRM_COLLECTIONS.tasks)\n const day = localDayBounds(nowMs)\n const countOf = (target: ReturnType<typeof query>) =>\n getCountFromServer(target).then((snapshot) => snapshot.data().count)\n return Promise.all([\n countOf(query(contacts, ...visibleToClause(tokens))),\n countOf(\n query(\n contacts,\n ...visibleToClause(tokens),\n where('createdAt', '>=', Timestamp.fromMillis(nowMs - WEEK_MS)),\n ),\n ),\n getAggregateFromServer(\n query(deals, ...visibleToClause(tokens), where('status', '==', 'open')),\n { amountCents: sum('amountCents') },\n ).then((snapshot) => Number(snapshot.data().amountCents ?? 0)),\n // Due today OR overdue: everything open that is due before the day ends.\n countOf(\n query(\n tasks,\n ...visibleToClause(tokens),\n where('status', '==', 'open'),\n where('dueAtMs', '<', day.end),\n ),\n ),\n // One scoped count over the org collection (AGL-3275) — see the\n // function's note on why summing per site would double-count.\n openLeadsForScope(firestore, scope[1], tokens),\n ]).then(([contacts, newThisWeek, pipelineCents, tasksDue, leadsToWork]) => ({\n contacts,\n newThisWeek,\n pipelineCents,\n tasksDue,\n leadsToWork,\n }))\n }, [firestore, scope, tokens, nowMs, leadSitesKey])\n\n const routes = useMemo(\n () =>\n hostId\n ? consoleRoute.base\n ? crmRoutes(`${consoleRoute.base}/crm`)\n : null\n : basePath\n ? crmRoutes(basePath)\n : null,\n [hostId, consoleRoute.base, basePath],\n )\n const hubHref = hostId\n ? consoleRoute.base\n ? `${consoleRoute.base}/crm`\n : undefined\n : basePath\n const value = figures.value\n\n return (\n <CardDisplay\n header={'CRM at a glance'}\n help={Aglyn.pluginDocsHelp('crmReports', {\n anchor: '#crm-at-a-glance',\n excerpt:\n 'Contacts, new contacts this week, the value of every open deal, ' +\n 'the tasks due today or overdue, and the leads still to work — ' +\n 'each counted on the server and each a link into the CRM.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={hubHref}\n size=\"small\"\n color=\"primary\"\n >\n {'Open CRM'}\n </Button>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap' }}>\n <ReportStatTile\n label={'Contacts'}\n value={value ? value.contacts.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'New this week'}\n value={value ? value.newThisWeek.toLocaleString() : null}\n href={routes?.section('contacts')}\n />\n <ReportStatTile\n label={'Open pipeline'}\n value={value ? money(value.pipelineCents, 'usd') : null}\n href={routes?.section('deals')}\n />\n <ReportStatTile\n label={'Tasks due'}\n value={value ? value.tasksDue.toLocaleString() : null}\n note={'today or overdue'}\n color={value?.tasksDue ? 'warning.main' : undefined}\n href={routes?.section('tasks')}\n />\n <ReportStatTile\n label={'Leads to work'}\n value={value ? value.leadsToWork.toLocaleString() : null}\n note={hostId ? 'open on this site' : 'open across every site'}\n href={routes?.section('leads')}\n />\n </Stack>\n {figures.status === 'error' ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The CRM figures could not be read. Open the CRM for the full picture.'}\n </Typography>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nCrmGlanceCard.displayName = 'CrmGlanceCard'\n\nexport default CrmGlanceCard\n"],"names":["Aglyn","money","AppLink","CardDisplay","Button","Stack","Typography","getAggregateFromServer","getCountFromServer","query","sum","Timestamp","where","useMemo","useConsoleHostRoute","useFirestore","useOrgDataScope","useOrgPlan","useCrmOrgMount","crmRoutes","openLeadsForScope","scopedCollection","visibleToClause","ReportStatTile","useAggregateRead","localDayBounds","WEEK_MS","CrmGlanceCard","props","hostId","basePath","mount","consoleRoute","org","ready","planReady","undefined","scope","orgId","firestore","tokens","group","consentGroupForHost","ORG_SCOPE_TOKEN","hostIds","map","id","hostScopeToken","slice","MAX_SCOPE_HOSTS","leadSites","hostsReady","hosts","host","leadSitesKey","join","nowMs","Date","now","figures","contacts","deals","CRM_COLLECTIONS","tasks","day","countOf","target","then","snapshot","data","count","Promise","all","fromMillis","amountCents","Number","end","newThisWeek","pipelineCents","tasksDue","leadsToWork","routes","base","hubHref","value","header","help","pluginDocsHelp","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","component","componentVariant","nativeButton","href","size","color","spacing","direction","sx","flexWrap","label","toLocaleString","section","note","status","variant","displayName"],"mappings":"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"}
@@ -18,6 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { CRM_EMAIL_BODY_MAX, CRM_EMAIL_SUBJECT_MAX, normalizeContactEmail, consentGroupForHost, CRM_COLLECTIONS, createResourceUid, crmMergeUnresolvedMessage, hasCrmMergeFields, resolveCrmMergeFields } from "@aglyn/aglyn";
20
20
  import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
21
+ import { pluginRecordListHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
21
22
  import { useSendingApi } from "@aglyn/tenant-feature-instance/hooks/use-sending-identity-api";
22
23
  import { AppLink, useConfirmationContext } from "@aglyn/shared-ui-jsx";
23
24
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
@@ -33,7 +34,7 @@ import { CrmSitePicker } from "./crm-site-picker.js";
33
34
  import { CrmEmailCheckAlert, useCrmEmailCheck } from "./crm-email-check.js";
34
35
  import { useCrmApi } from "./use-crm-api.js";
35
36
  import { useCrmInboundAddress } from "./use-crm-inbound-address.js";
36
- import { useEmailsHubPath } from "./use-emails-hub-path.js";
37
+ import { useSiteRouteContext } from "./use-site-route-context.js";
37
38
  import { useCrmEmailTemplates, useCrmEmailTemplateScope } from "../hooks/use-crm-email-templates.js";
38
39
  import { CrmInsertMenu, CrmSaveAsTemplateDialog, CrmTemplatePicker } from "./crm-email-template-tools.js";
39
40
  const NO_MERGE_RECORDS = {
@@ -89,17 +90,19 @@ const NO_MERGE_RECORDS = {
89
90
  * level cannot do without one.
90
91
  */ const sendHostId = (_ref1 = hostId != null ? hostId : mount == null ? void 0 : mount.createHostId) != null ? _ref1 : null;
91
92
  const crmApi = useCrmApi(sendHostId);
92
- const emailsHub = useEmailsHubPath(sendHostId);
93
+ const sendSite = useSiteRouteContext(sendHostId);
93
94
  // The capture address (AGL-2657), for the reply this send will get: the
94
95
  // console logs what IT sends, so the address is shown to be forwarded
95
96
  // to or copied from a mailbox, never put on this message.
96
97
  const capture = useCrmInboundAddress(sendHostId, {
97
98
  enabled: open
98
99
  });
99
- // The Sending section of the Emails console — the page that fixes a
100
- // missing identity. `null` on a surface that cannot name the site's hub,
101
- // so the refusal prints the section's name instead of a link to nowhere.
102
- const sendingPath = emailsHub ? `${emailsHub}/sending` : null;
100
+ // Where the site's sending identities are kept — the page that fixes a
101
+ // missing one, at the address the plugin that keeps them publishes as
102
+ // `sendingIdentity` (AGL-3080). `null` on a surface that cannot name the
103
+ // site, or where nothing publishes the kind, so the refusal prints the
104
+ // section's name instead of a link to nowhere.
105
+ const sendingPath = sendSite ? pluginRecordListHref('sendingIdentity', sendSite) : null;
103
106
  const [identity, setIdentity] = useState({
104
107
  status: 'loading'
105
108
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-send-email-dialog.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n CRM_EMAIL_BODY_MAX,\n CRM_EMAIL_SUBJECT_MAX,\n normalizeContactEmail,\n consentGroupForHost,\n CRM_COLLECTIONS,\n createResourceUid,\n type CrmEmailTemplateRow,\n type CrmMergeContext,\n type CrmMergeFieldGroup,\n crmMergeUnresolvedMessage,\n hasCrmMergeFields,\n resolveCrmMergeFields,\n} from '@aglyn/aglyn'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport type { ConsoleRecordEmailDraft } from '@aglyn/aglyn/plugin-manager/record-zone-props'\nimport { useSendingApi } from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\nimport { AppLink, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useOrgDataScope,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n InputAdornment,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getDoc, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { CrmRecordBookingZone } from './crm-booking-zone'\nimport { insertLinkAtCaret } from './insert-link-at-caret'\nimport {\n CAPTURE_ADDRESS_HELPER,\n CopyCaptureAddressButton,\n} from './copy-capture-address-button'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CrmEmailCheckAlert, useCrmEmailCheck } from './crm-email-check'\nimport { useCrmApi } from './use-crm-api'\nimport { useCrmInboundAddress } from './use-crm-inbound-address'\nimport { useEmailsHubPath } from './use-emails-hub-path'\nimport {\n useCrmEmailTemplates,\n useCrmEmailTemplateScope,\n} from '../hooks/use-crm-email-templates'\nimport {\n CrmInsertMenu,\n CrmSaveAsTemplateDialog,\n CrmTemplatePicker,\n} from './crm-email-template-tools'\n\n/**\n * The documents a merge-field preview reads (AGL-2658), fetched once per\n * open and only once the draft names a field: a letter with no fields\n * costs no reads. `site` is the sending site's name; `loaded` says the\n * fetch has answered, so the helper text can tell \"no value\" from \"not\n * yet read\".\n */\ninterface MergeRecords {\n contact: Record<string, unknown> | null\n deal: Record<string, unknown> | null\n lead: Record<string, unknown> | null\n site: string | null\n loaded: boolean\n}\n\nconst NO_MERGE_RECORDS: MergeRecords = {\n contact: null,\n deal: null,\n lead: null,\n site: null,\n loaded: false,\n}\n\n/**\n * What the sending-identity route said about this site, as the dialog\n * needs it: an address to print, or the reason there is none — or, at the\n * organization level, that no site has been picked to ask about.\n */\ntype IdentityState =\n | { status: 'loading' }\n | { status: 'site' }\n | { status: 'ready'; from: string }\n | { status: 'refused'; message: string; canManage: boolean }\n | { status: 'error'; message: string }\n\nexport interface CrmSendEmailDialogProps {\n open: boolean\n onClose: () => void\n /**\n * The site the message leaves from — passed in, never assumed from the\n * URL. At the organization level the record's own capturing site, or\n * `null` for a record no site has captured (AGL-2634): the dialog then\n * offers the org's sites to send from, defaulting to the reader's pick.\n */\n hostId: string | null\n /** The org the shell passed; lets a deal's contact be read without a lookup. */\n org?: Partial<AglynOrgBilling> | null\n contactId?: string\n leadId?: string\n dealId?: string\n /**\n * The recipient as the record shows it. A deal's page holds no address,\n * so a caller with only a `contactId` may leave this out and the dialog\n * reads it off the contact when it opens.\n */\n email?: string | null\n /** What the dialog says it is writing to, beside the address. */\n name?: string | null\n /** Told once the route has accepted the message. */\n onSent?: (result: { activityId: string }) => void\n}\n\n/**\n * One email to one person, from their record (AGL-2615).\n *\n * ## What the reader sees, and what they cannot change\n *\n * To is the record's address and is read-only: the route resolves the\n * recipient off the record again, so an editable field here would be a\n * promise the server does not keep. From is the site's sending identity as\n * the sending-identity route reports it — the same answer the campaign\n * composer prints — and when that route refuses, the dialog says why and\n * points at **Emails › Sending** rather than offering a Send button that\n * 409s. Reply-to is the signed-in user's own address, because a reply to a\n * relationship email belongs in the inbox of the person who wrote it, not\n * the site's shared mailbox. Subject and Body are the whole of what a\n * person writes; the body is plain paragraphs, and the email design system\n * is deliberately not here — this is a letter, not a campaign.\n *\n * ## A refusal stays on screen\n *\n * The route's refusals — the daily cap, a suppressed address, a stated\n * refusal, the ceiling — are answers the rep should read, so they render\n * inside the dialog with the draft intact rather than as a toast over a\n * closed one. Only a send the provider accepted closes it.\n *\n * Mounted on open by its button, so the identity request and the contact\n * read happen when somebody asks and not on every record page paint.\n */\nexport function CrmSendEmailDialog(props: CrmSendEmailDialogProps) {\n const { open, onClose, hostId, org, contactId, leadId, dealId, onSent } = props\n const firestore = useFirestore()\n const mount = useCrmOrgMount()\n const { orgId } = useOrgDataScope({\n hostId: hostId ?? undefined,\n orgId: org?.$id ?? mount?.orgId,\n })\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const sendingApi = useSendingApi()\n /*\n * The site the message LEAVES FROM: the record's own, or at the\n * organization level — for a record no site captured — the site the\n * reader picked (AGL-2634). The identity, the suppression list and the\n * person's stated refusal are each that site's, which is why the org\n * level cannot do without one.\n */\n const sendHostId = hostId ?? mount?.createHostId ?? null\n const crmApi = useCrmApi(sendHostId)\n const emailsHub = useEmailsHubPath(sendHostId)\n // The capture address (AGL-2657), for the reply this send will get: the\n // console logs what IT sends, so the address is shown to be forwarded\n // to or copied from a mailbox, never put on this message.\n const capture = useCrmInboundAddress(sendHostId, { enabled: open })\n // The Sending section of the Emails console — the page that fixes a\n // missing identity. `null` on a surface that cannot name the site's hub,\n // so the refusal prints the section's name instead of a link to nowhere.\n const sendingPath = emailsHub ? `${emailsHub}/sending` : null\n\n const [identity, setIdentity] = useState<IdentityState>({ status: 'loading' })\n const [recipient, setRecipient] = useState<string | null>(\n normalizeContactEmail(props.email) ?? null,\n )\n const [subject, setSubject] = useState('')\n const [body, setBody] = useState('')\n const [sending, setSending] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n /*\n * TEMPLATES, SNIPPETS AND MERGE FIELDS (AGL-2658). The listener is the\n * dialog's own scope — the mounted site's tokens, or none at the org\n * level — and what a saved template is stamped with follows the same\n * rule. The message field's element is held so a snippet lands at the\n * caret rather than at the end.\n */\n const { confirm } = useConfirmationContext()\n const templateScope = useCrmEmailTemplateScope({ hostId, org })\n const { templates } = useCrmEmailTemplates({\n scope: templateScope.scope,\n visibleTo: templateScope.visibleTo,\n uid: user?.uid,\n })\n const pickable = useMemo(() => templates.filter((row) => row.kind === 'template'), [templates])\n const snippets = useMemo(() => templates.filter((row) => row.kind === 'snippet'), [templates])\n const [appliedTemplate, setAppliedTemplate] = useState<CrmEmailTemplateRow | null>(null)\n const [saveOpen, setSaveOpen] = useState(false)\n const [records, setRecords] = useState<MergeRecords>(NO_MERGE_RECORDS)\n const messageRef = useRef<HTMLTextAreaElement | null>(null)\n const draftHasFields = hasCrmMergeFields(`${subject}\\n${body}`)\n // The groups the Insert menu offers: what the record can answer.\n const fieldGroups = useMemo<CrmMergeFieldGroup[]>(\n () => [\n ...(contactId || dealId ? (['Contact'] as const) : []),\n ...(leadId && !contactId ? (['Lead'] as const) : []),\n ...(dealId ? (['Deal'] as const) : []),\n 'You',\n 'Site',\n ],\n [contactId, dealId, leadId],\n )\n\n /*\n * The identity, asked for on open. The route is the send path's own\n * resolver, so what it prints here is what the message will leave as; a\n * dialog that derived \"verified\" from the domain list would be a second\n * opinion that drifts from the send.\n */\n useEffect(() => {\n if (!open) return\n let cancelled = false\n setError(null)\n // The draft survives a change of sending site — the site is not the\n // letter — and is cleared only when the dialog opens.\n if (!sendHostId) {\n setIdentity({ status: 'site' })\n return undefined\n }\n setIdentity({ status: 'loading' })\n void (async () => {\n const { response, payload } = await sendingApi({\n path: 'sending-identity',\n method: 'GET',\n query: { hostId: sendHostId },\n })\n if (cancelled) return\n if (!response.ok) {\n setIdentity({\n status: 'error',\n message: String(payload['error'] ?? 'The sending identity could not be read.'),\n })\n return\n }\n const refusal = payload['refusal'] as { message?: string } | null\n if (refusal) {\n setIdentity({\n status: 'refused',\n message: String(refusal.message ?? 'This site has no verified sending identity.'),\n canManage: payload['canManage'] === true,\n })\n return\n }\n const senders = Array.isArray(payload['senders'])\n ? (payload['senders'] as { isDefault?: boolean; from?: string | null }[])\n : []\n const from =\n senders.find((sender) => sender.isDefault && sender.from)?.from ??\n senders.find((sender) => sender.from)?.from ??\n null\n setIdentity(\n from\n ? { status: 'ready', from }\n : {\n status: 'error',\n message: String(payload['identity'] ?? 'No sending address is in effect.'),\n },\n )\n })()\n return () => {\n cancelled = true\n }\n }, [open, sendHostId, sendingApi])\n useEffect(() => {\n if (!open) return\n setSubject('')\n setBody('')\n setAppliedTemplate(null)\n setSaveOpen(false)\n setRecords(NO_MERGE_RECORDS)\n }, [open])\n\n /*\n * The documents a preview reads, fetched the first time the draft names\n * a merge field and kept for the rest of the open: the contact, the\n * deal, the lead, and the sending site's name — off the org mount when\n * it knows the site, off the host document otherwise. The route reads\n * the same documents again for the send; these are what the rep sees.\n */\n useEffect(() => {\n if (!open || !draftHasFields || records.loaded) return\n let cancelled = false\n const read = async (path: string[] | null): Promise<Record<string, unknown> | null> => {\n if (!path) return null\n const snapshot = await getDoc(doc(firestore, path[0], ...path.slice(1)))\n return snapshot.exists() ? ((snapshot.data() ?? null) as Record<string, unknown> | null) : null\n }\n const mountedSite = sendHostId\n ? (mount?.hosts ?? []).find((host) => host.id === sendHostId)\n : undefined\n void Promise.all([\n read(contactId && orgId ? ['orgs', orgId, 'contacts', contactId] : null),\n read(dealId && orgId ? ['orgs', orgId, CRM_COLLECTIONS.deals, dealId] : null),\n // One org row (AGL-3275); the site still decides WHICH lead is offered,\n // but not where it lives.\n read(leadId && orgId ? ['orgs', orgId, 'leads', leadId] : null),\n mountedSite || !sendHostId ? null : read(['hosts', sendHostId]),\n ])\n .then(([contact, deal, lead, host]) => {\n if (cancelled) return\n // A deal's contact, when the page named only the deal.\n const dealContactId = String(deal?.['contactId'] ?? '').trim()\n const withContact =\n !contact && dealContactId && orgId\n ? read(['orgs', orgId, 'contacts', dealContactId])\n : Promise.resolve(contact)\n return withContact.then((resolvedContact) => {\n if (cancelled) return\n setRecords({\n contact: resolvedContact,\n deal,\n lead,\n site: mountedSite?.name ?? (host ? String(host['displayName'] ?? '') : null),\n loaded: true,\n })\n })\n })\n .catch((cause) => {\n console.error(cause)\n if (!cancelled) setRecords({ ...NO_MERGE_RECORDS, loaded: true })\n })\n return () => {\n cancelled = true\n }\n }, [\n open,\n draftHasFields,\n records.loaded,\n firestore,\n orgId,\n contactId,\n dealId,\n leadId,\n sendHostId,\n mount?.hosts,\n ])\n\n const mergeContext = useMemo<CrmMergeContext>(\n () => ({\n contact: records.contact,\n contactGroupId: sendHostId ? consentGroupForHost(org ?? null, sendHostId).groupId : null,\n deal: records.deal,\n lead: records.lead,\n sender: { name: user?.displayName ?? '', email: user?.email ?? '' },\n site: { name: records.site ?? '' },\n }),\n [records, sendHostId, org, user?.displayName, user?.email],\n )\n const preview = useMemo(\n () =>\n draftHasFields && records.loaded\n ? {\n subject: resolveCrmMergeFields(subject, mergeContext),\n body: resolveCrmMergeFields(body, mergeContext),\n }\n : null,\n [draftHasFields, records.loaded, subject, body, mergeContext],\n )\n const unresolved = useMemo(\n () =>\n preview\n ? [...new Set([...preview.subject.unresolved, ...preview.body.unresolved])]\n : [],\n [preview],\n )\n\n /** A template into both fields — asking first when a message is already written. */\n const applyTemplate = useCallback(\n async (template: CrmEmailTemplateRow | null) => {\n if (!template) {\n setAppliedTemplate(null)\n return\n }\n const written = body.trim().length > 0 && body !== appliedTemplate?.body\n if (written) {\n const accepted = await confirm({\n title: 'Replace the message?',\n description: `\"${template.name}\" replaces what you have written in the subject and the message.`,\n confirmationText: 'Replace',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n if (template.subject) setSubject(template.subject)\n setBody(template.body)\n setAppliedTemplate(template)\n },\n [body, appliedTemplate, confirm],\n )\n\n /*\n * The `recordEmail` zone (AGL-2917), hosted through the shell's renderer: a\n * widget there proposes a subject and a message, which land in the fields\n * above like a template does, asking first when a message is already\n * written. Send is still the only write, and the zone reads the record the\n * message is written from: the deal over its contact, a lead on its own site,\n * and at the organization level a contact or a deal the org reads.\n */\n const EmailDraftSlot = useConsoleWidgetSlot()\n const proposeDraft = useCallback(\n async (draft: ConsoleRecordEmailDraft) => {\n const next = { subject: draft.subject.slice(0, CRM_EMAIL_SUBJECT_MAX), body: draft.body.slice(0, CRM_EMAIL_BODY_MAX) }\n if (body.trim() && body !== next.body) {\n const accepted = await confirm({\n title: 'Replace the message?',\n description: 'The draft replaces what you have written in the subject and the message.',\n confirmationText: 'Replace',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n setSubject(next.subject)\n setBody(next.body)\n setAppliedTemplate(null)\n },\n [body, confirm],\n )\n const draftRecord = dealId\n ? { kind: 'deal', id: dealId }\n : leadId\n ? { kind: 'lead', id: leadId }\n : contactId\n ? { kind: 'contact', id: contactId }\n : null\n\n /** A snippet or a field where the caret is, the caret moved past it. */\n const insertAtCaret = useCallback(\n (text: string) => {\n const element = messageRef.current\n const start = element?.selectionStart ?? body.length\n const end = element?.selectionEnd ?? body.length\n const next = `${body.slice(0, start)}${text}${body.slice(end)}`.slice(0, CRM_EMAIL_BODY_MAX)\n setBody(next)\n const caret = Math.min(start + text.length, next.length)\n window.setTimeout(() => {\n element?.focus()\n element?.setSelectionRange(caret, caret)\n }, 0)\n },\n [body],\n )\n\n /**\n * The draft kept as a template, stamped as this surface's scope stamps\n * every CRM record — the site's tokens under a site, the org token from\n * the organization's hub — and, when personal, owned by the writer.\n */\n const saveAsTemplate = useCallback(\n async (draft: { name: string; visibility: 'shared' | 'personal' }) => {\n const scope = templateScope.scope\n const uid = user?.uid\n if (!scope || !uid || templateScope.createTokens.length === 0) {\n throw new Error('a template needs an organization and a signed-in writer')\n }\n const now = Date.now()\n await setDoc(\n doc(collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.emailTemplates), createResourceUid()),\n {\n name: draft.name,\n subject: subject.trim(),\n body: body.trim(),\n kind: 'template',\n visibility: draft.visibility,\n ...(draft.visibility === 'personal' ? { ownerUid: uid } : {}),\n createdByUid: uid,\n createdAtMs: now,\n updatedAtMs: now,\n hostId: templateScope.createHostId,\n visibleTo: [...templateScope.createTokens],\n createdAt: new Date(now),\n updatedAt: new Date(now),\n },\n )\n enqueueSnackbar(`Template \"${draft.name}\" saved`, { variant: 'success', persist: false })\n setSaveOpen(false)\n },\n [templateScope, user?.uid, firestore, subject, body, enqueueSnackbar],\n )\n\n /*\n * The address, when the caller had none: one read of the contact, on\n * open. The route reads it again for itself; this one is what the To\n * field prints, and a field that printed nothing until the send answered\n * would ask the rep to write to somebody unnamed.\n */\n useEffect(() => {\n if (!open) return\n const given = normalizeContactEmail(props.email)\n if (given) {\n setRecipient(given)\n return\n }\n if (!contactId || !orgId) return\n let cancelled = false\n void getDoc(doc(firestore, 'orgs', orgId, 'contacts', contactId))\n .then((snapshot) => {\n if (cancelled) return\n setRecipient(normalizeContactEmail(snapshot.get('email')) ?? null)\n })\n .catch((cause) => {\n console.error(cause)\n if (!cancelled) setRecipient(null)\n })\n return () => {\n cancelled = true\n }\n }, [open, props.email, contactId, orgId, firestore])\n\n /*\n * What the platform knows about the address before Send (AGL-3328): a\n * domain with no mail server, or a mail gateway that refused this site's\n * sending domain twice this month. Read off the server's cache — the\n * browser does no DNS — and said as a warning; the route decides.\n */\n const deliverability = useCrmEmailCheck(sendHostId, recipient, { enabled: open })\n\n const replyTo = normalizeContactEmail(user?.email) ?? ''\n const canSend =\n identity.status === 'ready' &&\n Boolean(recipient) &&\n subject.trim().length > 0 &&\n body.trim().length > 0 &&\n !sending\n\n const handleSend = useCallback(async () => {\n if (!canSend) return\n setSending(true)\n setError(null)\n try {\n const { response, payload } = await crmApi('email-send', {\n ...(contactId ? { contactId } : {}),\n ...(leadId ? { leadId } : {}),\n ...(dealId ? { dealId } : {}),\n subject: subject.trim(),\n body: body.trim(),\n })\n if (!response.ok) {\n setError(String(payload['error'] ?? 'The email could not be sent.'))\n return\n }\n enqueueSnackbar('Email sent', { variant: 'success', persist: false })\n onSent?.({ activityId: String(payload['activityId'] ?? '') })\n onClose()\n } catch (cause) {\n console.error(cause)\n setError('The email could not be sent.')\n } finally {\n setSending(false)\n }\n }, [canSend, crmApi, contactId, leadId, dealId, subject, body, enqueueSnackbar, onSent, onClose])\n\n const toLabel = props.name && recipient ? `${props.name} <${recipient}>` : (recipient ?? '')\n\n /*\n * The booking door (AGL-2660): a link to one of the sending site's\n * services, dropped where the caret is. The link carries the most\n * specific record this message is about — the deal over its contact, so\n * the booking lands on the deal's timeline as the email does.\n */\n const bookingRef = dealId\n ? { kind: 'deal' as const, id: dealId }\n : leadId\n ? { kind: 'lead' as const, id: leadId }\n : contactId\n ? { kind: 'contact' as const, id: contactId }\n : null\n const handleInsertLink = useCallback((link: string) => {\n const input = messageRef.current\n const current = input?.value ?? ''\n const start = input?.selectionStart ?? current.length\n const end = input?.selectionEnd ?? start\n const next = insertLinkAtCaret(current, link, start, end)\n setBody(next.text)\n // Once the new value has painted, typing continues after the link.\n window.setTimeout(() => {\n input?.focus()\n input?.setSelectionRange(next.caret, next.caret)\n }, 0)\n }, [])\n\n return (\n <Dialog open={open} onClose={sending ? undefined : onClose} maxWidth=\"sm\" fullWidth>\n <DialogTitle>{'Send email'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n {!hostId ? (\n <CrmSitePicker\n hostId={hostId}\n label=\"Send from\"\n helperText=\"The site whose sending address the email leaves on.\"\n disabled={sending}\n />\n ) : null}\n {identity.status === 'site' ? (\n <Alert severity=\"info\" sx={{ mt: 1 }}>\n {'Pick the site the email leaves from.'}\n </Alert>\n ) : identity.status === 'refused' ? (\n <Alert severity=\"warning\" sx={{ mt: 1 }}>\n <Typography variant=\"body2\">{identity.message}</Typography>\n {sendingPath ? (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n <AppLink href={sendingPath}>\n {identity.canManage ? 'Set up sending' : 'See sending'}\n </AppLink>\n </Typography>\n ) : (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {'An admin sets this up under Emails › Sending.'}\n </Typography>\n )}\n </Alert>\n ) : identity.status === 'error' ? (\n <Alert severity=\"error\" sx={{ mt: 1 }}>\n {identity.message}\n </Alert>\n ) : null}\n {open && !recipient && (identity.status === 'ready' || identity.status === 'loading') ? (\n <Alert severity=\"warning\" sx={{ mt: 1 }}>\n {'This record has no email address.'}\n </Alert>\n ) : null}\n <TextField\n size=\"small\"\n label=\"To\"\n value={toLabel}\n slotProps={{ input: { readOnly: true }, inputLabel: { shrink: true } }}\n sx={{ mt: identity.status === 'ready' || identity.status === 'loading' ? 1 : 0 }}\n />\n <CrmEmailCheckAlert check={deliverability} />\n <TextField\n size=\"small\"\n label=\"From\"\n value={\n identity.status === 'ready'\n ? identity.from\n : identity.status === 'loading'\n ? 'Resolving the site’s sending address…'\n : identity.status === 'site'\n ? 'Pick a site to send from'\n : ''\n }\n slotProps={{ input: { readOnly: true }, inputLabel: { shrink: true } }}\n />\n <TextField\n size=\"small\"\n label=\"Reply-to\"\n value={replyTo}\n helperText=\"Replies come to you, not to the site's mailbox.\"\n slotProps={{ input: { readOnly: true }, inputLabel: { shrink: true } }}\n />\n {capture.status === 'ready' ? (\n <TextField\n size=\"small\"\n label=\"Log replies\"\n value={capture.address}\n helperText={CAPTURE_ADDRESS_HELPER}\n slotProps={{\n input: {\n readOnly: true,\n endAdornment: (\n <InputAdornment position=\"end\">\n <CopyCaptureAddressButton address={capture.address} />\n </InputAdornment>\n ),\n },\n inputLabel: { shrink: true },\n }}\n />\n ) : null}\n {pickable.length ? (\n <CrmTemplatePicker\n templates={pickable}\n value={appliedTemplate}\n onChange={(template) => void applyTemplate(template)}\n disabled={sending}\n />\n ) : null}\n <TextField\n size=\"small\"\n label=\"Subject\"\n value={subject}\n onChange={(event) => setSubject(event.target.value)}\n autoFocus\n slotProps={{ htmlInput: { maxLength: CRM_EMAIL_SUBJECT_MAX } }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n <CrmInsertMenu\n snippets={snippets}\n groups={fieldGroups}\n disabled={sending}\n onInsert={insertAtCaret}\n />\n <Button\n size=\"small\"\n disabled={sending || !templateScope.scope || (!subject.trim() && !body.trim())}\n onClick={() => setSaveOpen(true)}\n >\n {'Save as template…'}\n </Button>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Message\"\n value={body}\n onChange={(event) => setBody(event.target.value)}\n multiline\n minRows={6}\n inputRef={messageRef}\n helperText={\n unresolved.length\n ? crmMergeUnresolvedMessage(unresolved)\n : draftHasFields\n ? 'Plain text. Merge fields are filled in from this record when the email is sent.'\n : 'Plain text. A blank line starts a new paragraph.'\n }\n slotProps={{\n htmlInput: { maxLength: CRM_EMAIL_BODY_MAX },\n formHelperText: { sx: unresolved.length ? { color: 'warning.main' } : undefined },\n }}\n />\n {EmailDraftSlot && draftRecord ? (\n <EmailDraftSlot\n slot=\"recordEmail\"\n // A lead is read on its own site; a contact or a deal at the level\n // the composer was opened at.\n hostId={draftRecord.kind === 'lead' || !mount ? hostId : null}\n orgId={orgId ?? undefined}\n record={{ ...draftRecord, name: String(props.name ?? '') }}\n subject={subject}\n body={body}\n proposeDraft={(draft: ConsoleRecordEmailDraft) => void proposeDraft(draft)}\n />\n ) : null}\n {preview ? (\n <Stack spacing={0.5} data-testid=\"crm-email-preview\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Preview — as it will be sent'}\n </Typography>\n <Typography variant=\"subtitle2\">{preview.subject.text}</Typography>\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {preview.body.text}\n </Typography>\n </Stack>\n ) : null}\n {bookingRef ? (\n <CrmRecordBookingZone\n variant=\"chip\"\n hostId={sendHostId}\n org={org}\n kind={bookingRef.kind}\n recordId={bookingRef.id}\n onInsert={handleInsertLink}\n />\n ) : null}\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </DialogContent>\n <CrmSaveAsTemplateDialog\n open={saveOpen}\n onClose={() => setSaveOpen(false)}\n onSave={saveAsTemplate}\n />\n <DialogActions>\n <Button onClick={onClose} disabled={sending}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" color=\"primary\" disabled={!canSend} onClick={handleSend}>\n {sending ? 'Sending…' : 'Send'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nCrmSendEmailDialog.displayName = 'CrmSendEmailDialog'\n\nexport default CrmSendEmailDialog\n"],"names":["CRM_EMAIL_BODY_MAX","CRM_EMAIL_SUBJECT_MAX","normalizeContactEmail","consentGroupForHost","CRM_COLLECTIONS","createResourceUid","crmMergeUnresolvedMessage","hasCrmMergeFields","resolveCrmMergeFields","useConsoleWidgetSlot","useSendingApi","AppLink","useConfirmationContext","useSnackbar","useFirestore","useOrgDataScope","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","InputAdornment","Stack","TextField","Typography","collection","doc","getDoc","setDoc","useCallback","useEffect","useMemo","useRef","useState","useCrmOrgMount","CrmRecordBookingZone","insertLinkAtCaret","CAPTURE_ADDRESS_HELPER","CopyCaptureAddressButton","CrmSitePicker","CrmEmailCheckAlert","useCrmEmailCheck","useCrmApi","useCrmInboundAddress","useEmailsHubPath","useCrmEmailTemplates","useCrmEmailTemplateScope","CrmInsertMenu","CrmSaveAsTemplateDialog","CrmTemplatePicker","NO_MERGE_RECORDS","contact","deal","lead","site","loaded","CrmSendEmailDialog","props","hostId","open","onClose","org","contactId","leadId","dealId","onSent","firestore","mount","orgId","undefined","$id","data","user","enqueueSnackbar","sendingApi","sendHostId","createHostId","crmApi","emailsHub","capture","enabled","sendingPath","identity","setIdentity","status","recipient","setRecipient","email","subject","setSubject","body","setBody","sending","setSending","error","setError","confirm","templateScope","templates","scope","visibleTo","uid","pickable","filter","row","kind","snippets","appliedTemplate","setAppliedTemplate","saveOpen","setSaveOpen","records","setRecords","messageRef","draftHasFields","fieldGroups","cancelled","senders","payload","response","path","method","query","ok","message","String","refusal","canManage","Array","isArray","from","find","sender","isDefault","read","snapshot","slice","exists","mountedSite","hosts","host","id","Promise","all","deals","then","dealContactId","trim","withContact","resolve","resolvedContact","name","catch","cause","console","mergeContext","contactGroupId","groupId","displayName","preview","unresolved","Set","applyTemplate","template","written","length","accepted","title","description","confirmationText","EmailDraftSlot","proposeDraft","draft","next","draftRecord","insertAtCaret","text","element","current","start","selectionStart","end","selectionEnd","caret","Math","min","window","setTimeout","focus","setSelectionRange","saveAsTemplate","createTokens","Error","now","Date","emailTemplates","visibility","ownerUid","createdByUid","createdAtMs","updatedAtMs","createdAt","updatedAt","variant","persist","given","get","deliverability","replyTo","canSend","Boolean","handleSend","activityId","toLabel","bookingRef","handleInsertLink","link","input","value","maxWidth","fullWidth","sx","display","flexDirection","gap","label","helperText","disabled","severity","mt","href","size","slotProps","readOnly","inputLabel","shrink","check","address","endAdornment","position","onChange","event","target","autoFocus","htmlInput","maxLength","direction","spacing","justifyContent","groups","onInsert","onClick","multiline","minRows","inputRef","formHelperText","color","slot","record","data-testid","whiteSpace","recordId","onSave"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/crm-send-email-dialog.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n CRM_EMAIL_BODY_MAX,\n CRM_EMAIL_SUBJECT_MAX,\n normalizeContactEmail,\n consentGroupForHost,\n CRM_COLLECTIONS,\n createResourceUid,\n type CrmEmailTemplateRow,\n type CrmMergeContext,\n type CrmMergeFieldGroup,\n crmMergeUnresolvedMessage,\n hasCrmMergeFields,\n resolveCrmMergeFields,\n} from '@aglyn/aglyn'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { pluginRecordListHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport type { ConsoleRecordEmailDraft } from '@aglyn/aglyn/plugin-manager/record-zone-props'\nimport { useSendingApi } from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\nimport { AppLink, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useOrgDataScope,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n InputAdornment,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getDoc, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { CrmRecordBookingZone } from './crm-booking-zone'\nimport { insertLinkAtCaret } from './insert-link-at-caret'\nimport {\n CAPTURE_ADDRESS_HELPER,\n CopyCaptureAddressButton,\n} from './copy-capture-address-button'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CrmEmailCheckAlert, useCrmEmailCheck } from './crm-email-check'\nimport { useCrmApi } from './use-crm-api'\nimport { useCrmInboundAddress } from './use-crm-inbound-address'\nimport { useSiteRouteContext } from './use-site-route-context'\nimport {\n useCrmEmailTemplates,\n useCrmEmailTemplateScope,\n} from '../hooks/use-crm-email-templates'\nimport {\n CrmInsertMenu,\n CrmSaveAsTemplateDialog,\n CrmTemplatePicker,\n} from './crm-email-template-tools'\n\n/**\n * The documents a merge-field preview reads (AGL-2658), fetched once per\n * open and only once the draft names a field: a letter with no fields\n * costs no reads. `site` is the sending site's name; `loaded` says the\n * fetch has answered, so the helper text can tell \"no value\" from \"not\n * yet read\".\n */\ninterface MergeRecords {\n contact: Record<string, unknown> | null\n deal: Record<string, unknown> | null\n lead: Record<string, unknown> | null\n site: string | null\n loaded: boolean\n}\n\nconst NO_MERGE_RECORDS: MergeRecords = {\n contact: null,\n deal: null,\n lead: null,\n site: null,\n loaded: false,\n}\n\n/**\n * What the sending-identity route said about this site, as the dialog\n * needs it: an address to print, or the reason there is none — or, at the\n * organization level, that no site has been picked to ask about.\n */\ntype IdentityState =\n | { status: 'loading' }\n | { status: 'site' }\n | { status: 'ready'; from: string }\n | { status: 'refused'; message: string; canManage: boolean }\n | { status: 'error'; message: string }\n\nexport interface CrmSendEmailDialogProps {\n open: boolean\n onClose: () => void\n /**\n * The site the message leaves from — passed in, never assumed from the\n * URL. At the organization level the record's own capturing site, or\n * `null` for a record no site has captured (AGL-2634): the dialog then\n * offers the org's sites to send from, defaulting to the reader's pick.\n */\n hostId: string | null\n /** The org the shell passed; lets a deal's contact be read without a lookup. */\n org?: Partial<AglynOrgBilling> | null\n contactId?: string\n leadId?: string\n dealId?: string\n /**\n * The recipient as the record shows it. A deal's page holds no address,\n * so a caller with only a `contactId` may leave this out and the dialog\n * reads it off the contact when it opens.\n */\n email?: string | null\n /** What the dialog says it is writing to, beside the address. */\n name?: string | null\n /** Told once the route has accepted the message. */\n onSent?: (result: { activityId: string }) => void\n}\n\n/**\n * One email to one person, from their record (AGL-2615).\n *\n * ## What the reader sees, and what they cannot change\n *\n * To is the record's address and is read-only: the route resolves the\n * recipient off the record again, so an editable field here would be a\n * promise the server does not keep. From is the site's sending identity as\n * the sending-identity route reports it — the same answer the campaign\n * composer prints — and when that route refuses, the dialog says why and\n * points at **Emails › Sending** rather than offering a Send button that\n * 409s. Reply-to is the signed-in user's own address, because a reply to a\n * relationship email belongs in the inbox of the person who wrote it, not\n * the site's shared mailbox. Subject and Body are the whole of what a\n * person writes; the body is plain paragraphs, and the email design system\n * is deliberately not here — this is a letter, not a campaign.\n *\n * ## A refusal stays on screen\n *\n * The route's refusals — the daily cap, a suppressed address, a stated\n * refusal, the ceiling — are answers the rep should read, so they render\n * inside the dialog with the draft intact rather than as a toast over a\n * closed one. Only a send the provider accepted closes it.\n *\n * Mounted on open by its button, so the identity request and the contact\n * read happen when somebody asks and not on every record page paint.\n */\nexport function CrmSendEmailDialog(props: CrmSendEmailDialogProps) {\n const { open, onClose, hostId, org, contactId, leadId, dealId, onSent } = props\n const firestore = useFirestore()\n const mount = useCrmOrgMount()\n const { orgId } = useOrgDataScope({\n hostId: hostId ?? undefined,\n orgId: org?.$id ?? mount?.orgId,\n })\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const sendingApi = useSendingApi()\n /*\n * The site the message LEAVES FROM: the record's own, or at the\n * organization level — for a record no site captured — the site the\n * reader picked (AGL-2634). The identity, the suppression list and the\n * person's stated refusal are each that site's, which is why the org\n * level cannot do without one.\n */\n const sendHostId = hostId ?? mount?.createHostId ?? null\n const crmApi = useCrmApi(sendHostId)\n const sendSite = useSiteRouteContext(sendHostId)\n // The capture address (AGL-2657), for the reply this send will get: the\n // console logs what IT sends, so the address is shown to be forwarded\n // to or copied from a mailbox, never put on this message.\n const capture = useCrmInboundAddress(sendHostId, { enabled: open })\n // Where the site's sending identities are kept — the page that fixes a\n // missing one, at the address the plugin that keeps them publishes as\n // `sendingIdentity` (AGL-3080). `null` on a surface that cannot name the\n // site, or where nothing publishes the kind, so the refusal prints the\n // section's name instead of a link to nowhere.\n const sendingPath = sendSite ? pluginRecordListHref('sendingIdentity', sendSite) : null\n\n const [identity, setIdentity] = useState<IdentityState>({ status: 'loading' })\n const [recipient, setRecipient] = useState<string | null>(\n normalizeContactEmail(props.email) ?? null,\n )\n const [subject, setSubject] = useState('')\n const [body, setBody] = useState('')\n const [sending, setSending] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n /*\n * TEMPLATES, SNIPPETS AND MERGE FIELDS (AGL-2658). The listener is the\n * dialog's own scope — the mounted site's tokens, or none at the org\n * level — and what a saved template is stamped with follows the same\n * rule. The message field's element is held so a snippet lands at the\n * caret rather than at the end.\n */\n const { confirm } = useConfirmationContext()\n const templateScope = useCrmEmailTemplateScope({ hostId, org })\n const { templates } = useCrmEmailTemplates({\n scope: templateScope.scope,\n visibleTo: templateScope.visibleTo,\n uid: user?.uid,\n })\n const pickable = useMemo(() => templates.filter((row) => row.kind === 'template'), [templates])\n const snippets = useMemo(() => templates.filter((row) => row.kind === 'snippet'), [templates])\n const [appliedTemplate, setAppliedTemplate] = useState<CrmEmailTemplateRow | null>(null)\n const [saveOpen, setSaveOpen] = useState(false)\n const [records, setRecords] = useState<MergeRecords>(NO_MERGE_RECORDS)\n const messageRef = useRef<HTMLTextAreaElement | null>(null)\n const draftHasFields = hasCrmMergeFields(`${subject}\\n${body}`)\n // The groups the Insert menu offers: what the record can answer.\n const fieldGroups = useMemo<CrmMergeFieldGroup[]>(\n () => [\n ...(contactId || dealId ? (['Contact'] as const) : []),\n ...(leadId && !contactId ? (['Lead'] as const) : []),\n ...(dealId ? (['Deal'] as const) : []),\n 'You',\n 'Site',\n ],\n [contactId, dealId, leadId],\n )\n\n /*\n * The identity, asked for on open. The route is the send path's own\n * resolver, so what it prints here is what the message will leave as; a\n * dialog that derived \"verified\" from the domain list would be a second\n * opinion that drifts from the send.\n */\n useEffect(() => {\n if (!open) return\n let cancelled = false\n setError(null)\n // The draft survives a change of sending site — the site is not the\n // letter — and is cleared only when the dialog opens.\n if (!sendHostId) {\n setIdentity({ status: 'site' })\n return undefined\n }\n setIdentity({ status: 'loading' })\n void (async () => {\n const { response, payload } = await sendingApi({\n path: 'sending-identity',\n method: 'GET',\n query: { hostId: sendHostId },\n })\n if (cancelled) return\n if (!response.ok) {\n setIdentity({\n status: 'error',\n message: String(payload['error'] ?? 'The sending identity could not be read.'),\n })\n return\n }\n const refusal = payload['refusal'] as { message?: string } | null\n if (refusal) {\n setIdentity({\n status: 'refused',\n message: String(refusal.message ?? 'This site has no verified sending identity.'),\n canManage: payload['canManage'] === true,\n })\n return\n }\n const senders = Array.isArray(payload['senders'])\n ? (payload['senders'] as { isDefault?: boolean; from?: string | null }[])\n : []\n const from =\n senders.find((sender) => sender.isDefault && sender.from)?.from ??\n senders.find((sender) => sender.from)?.from ??\n null\n setIdentity(\n from\n ? { status: 'ready', from }\n : {\n status: 'error',\n message: String(payload['identity'] ?? 'No sending address is in effect.'),\n },\n )\n })()\n return () => {\n cancelled = true\n }\n }, [open, sendHostId, sendingApi])\n useEffect(() => {\n if (!open) return\n setSubject('')\n setBody('')\n setAppliedTemplate(null)\n setSaveOpen(false)\n setRecords(NO_MERGE_RECORDS)\n }, [open])\n\n /*\n * The documents a preview reads, fetched the first time the draft names\n * a merge field and kept for the rest of the open: the contact, the\n * deal, the lead, and the sending site's name — off the org mount when\n * it knows the site, off the host document otherwise. The route reads\n * the same documents again for the send; these are what the rep sees.\n */\n useEffect(() => {\n if (!open || !draftHasFields || records.loaded) return\n let cancelled = false\n const read = async (path: string[] | null): Promise<Record<string, unknown> | null> => {\n if (!path) return null\n const snapshot = await getDoc(doc(firestore, path[0], ...path.slice(1)))\n return snapshot.exists() ? ((snapshot.data() ?? null) as Record<string, unknown> | null) : null\n }\n const mountedSite = sendHostId\n ? (mount?.hosts ?? []).find((host) => host.id === sendHostId)\n : undefined\n void Promise.all([\n read(contactId && orgId ? ['orgs', orgId, 'contacts', contactId] : null),\n read(dealId && orgId ? ['orgs', orgId, CRM_COLLECTIONS.deals, dealId] : null),\n // One org row (AGL-3275); the site still decides WHICH lead is offered,\n // but not where it lives.\n read(leadId && orgId ? ['orgs', orgId, 'leads', leadId] : null),\n mountedSite || !sendHostId ? null : read(['hosts', sendHostId]),\n ])\n .then(([contact, deal, lead, host]) => {\n if (cancelled) return\n // A deal's contact, when the page named only the deal.\n const dealContactId = String(deal?.['contactId'] ?? '').trim()\n const withContact =\n !contact && dealContactId && orgId\n ? read(['orgs', orgId, 'contacts', dealContactId])\n : Promise.resolve(contact)\n return withContact.then((resolvedContact) => {\n if (cancelled) return\n setRecords({\n contact: resolvedContact,\n deal,\n lead,\n site: mountedSite?.name ?? (host ? String(host['displayName'] ?? '') : null),\n loaded: true,\n })\n })\n })\n .catch((cause) => {\n console.error(cause)\n if (!cancelled) setRecords({ ...NO_MERGE_RECORDS, loaded: true })\n })\n return () => {\n cancelled = true\n }\n }, [\n open,\n draftHasFields,\n records.loaded,\n firestore,\n orgId,\n contactId,\n dealId,\n leadId,\n sendHostId,\n mount?.hosts,\n ])\n\n const mergeContext = useMemo<CrmMergeContext>(\n () => ({\n contact: records.contact,\n contactGroupId: sendHostId ? consentGroupForHost(org ?? null, sendHostId).groupId : null,\n deal: records.deal,\n lead: records.lead,\n sender: { name: user?.displayName ?? '', email: user?.email ?? '' },\n site: { name: records.site ?? '' },\n }),\n [records, sendHostId, org, user?.displayName, user?.email],\n )\n const preview = useMemo(\n () =>\n draftHasFields && records.loaded\n ? {\n subject: resolveCrmMergeFields(subject, mergeContext),\n body: resolveCrmMergeFields(body, mergeContext),\n }\n : null,\n [draftHasFields, records.loaded, subject, body, mergeContext],\n )\n const unresolved = useMemo(\n () =>\n preview\n ? [...new Set([...preview.subject.unresolved, ...preview.body.unresolved])]\n : [],\n [preview],\n )\n\n /** A template into both fields — asking first when a message is already written. */\n const applyTemplate = useCallback(\n async (template: CrmEmailTemplateRow | null) => {\n if (!template) {\n setAppliedTemplate(null)\n return\n }\n const written = body.trim().length > 0 && body !== appliedTemplate?.body\n if (written) {\n const accepted = await confirm({\n title: 'Replace the message?',\n description: `\"${template.name}\" replaces what you have written in the subject and the message.`,\n confirmationText: 'Replace',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n if (template.subject) setSubject(template.subject)\n setBody(template.body)\n setAppliedTemplate(template)\n },\n [body, appliedTemplate, confirm],\n )\n\n /*\n * The `recordEmail` zone (AGL-2917), hosted through the shell's renderer: a\n * widget there proposes a subject and a message, which land in the fields\n * above like a template does, asking first when a message is already\n * written. Send is still the only write, and the zone reads the record the\n * message is written from: the deal over its contact, a lead on its own site,\n * and at the organization level a contact or a deal the org reads.\n */\n const EmailDraftSlot = useConsoleWidgetSlot()\n const proposeDraft = useCallback(\n async (draft: ConsoleRecordEmailDraft) => {\n const next = { subject: draft.subject.slice(0, CRM_EMAIL_SUBJECT_MAX), body: draft.body.slice(0, CRM_EMAIL_BODY_MAX) }\n if (body.trim() && body !== next.body) {\n const accepted = await confirm({\n title: 'Replace the message?',\n description: 'The draft replaces what you have written in the subject and the message.',\n confirmationText: 'Replace',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n setSubject(next.subject)\n setBody(next.body)\n setAppliedTemplate(null)\n },\n [body, confirm],\n )\n const draftRecord = dealId\n ? { kind: 'deal', id: dealId }\n : leadId\n ? { kind: 'lead', id: leadId }\n : contactId\n ? { kind: 'contact', id: contactId }\n : null\n\n /** A snippet or a field where the caret is, the caret moved past it. */\n const insertAtCaret = useCallback(\n (text: string) => {\n const element = messageRef.current\n const start = element?.selectionStart ?? body.length\n const end = element?.selectionEnd ?? body.length\n const next = `${body.slice(0, start)}${text}${body.slice(end)}`.slice(0, CRM_EMAIL_BODY_MAX)\n setBody(next)\n const caret = Math.min(start + text.length, next.length)\n window.setTimeout(() => {\n element?.focus()\n element?.setSelectionRange(caret, caret)\n }, 0)\n },\n [body],\n )\n\n /**\n * The draft kept as a template, stamped as this surface's scope stamps\n * every CRM record — the site's tokens under a site, the org token from\n * the organization's hub — and, when personal, owned by the writer.\n */\n const saveAsTemplate = useCallback(\n async (draft: { name: string; visibility: 'shared' | 'personal' }) => {\n const scope = templateScope.scope\n const uid = user?.uid\n if (!scope || !uid || templateScope.createTokens.length === 0) {\n throw new Error('a template needs an organization and a signed-in writer')\n }\n const now = Date.now()\n await setDoc(\n doc(collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.emailTemplates), createResourceUid()),\n {\n name: draft.name,\n subject: subject.trim(),\n body: body.trim(),\n kind: 'template',\n visibility: draft.visibility,\n ...(draft.visibility === 'personal' ? { ownerUid: uid } : {}),\n createdByUid: uid,\n createdAtMs: now,\n updatedAtMs: now,\n hostId: templateScope.createHostId,\n visibleTo: [...templateScope.createTokens],\n createdAt: new Date(now),\n updatedAt: new Date(now),\n },\n )\n enqueueSnackbar(`Template \"${draft.name}\" saved`, { variant: 'success', persist: false })\n setSaveOpen(false)\n },\n [templateScope, user?.uid, firestore, subject, body, enqueueSnackbar],\n )\n\n /*\n * The address, when the caller had none: one read of the contact, on\n * open. The route reads it again for itself; this one is what the To\n * field prints, and a field that printed nothing until the send answered\n * would ask the rep to write to somebody unnamed.\n */\n useEffect(() => {\n if (!open) return\n const given = normalizeContactEmail(props.email)\n if (given) {\n setRecipient(given)\n return\n }\n if (!contactId || !orgId) return\n let cancelled = false\n void getDoc(doc(firestore, 'orgs', orgId, 'contacts', contactId))\n .then((snapshot) => {\n if (cancelled) return\n setRecipient(normalizeContactEmail(snapshot.get('email')) ?? null)\n })\n .catch((cause) => {\n console.error(cause)\n if (!cancelled) setRecipient(null)\n })\n return () => {\n cancelled = true\n }\n }, [open, props.email, contactId, orgId, firestore])\n\n /*\n * What the platform knows about the address before Send (AGL-3328): a\n * domain with no mail server, or a mail gateway that refused this site's\n * sending domain twice this month. Read off the server's cache — the\n * browser does no DNS — and said as a warning; the route decides.\n */\n const deliverability = useCrmEmailCheck(sendHostId, recipient, { enabled: open })\n\n const replyTo = normalizeContactEmail(user?.email) ?? ''\n const canSend =\n identity.status === 'ready' &&\n Boolean(recipient) &&\n subject.trim().length > 0 &&\n body.trim().length > 0 &&\n !sending\n\n const handleSend = useCallback(async () => {\n if (!canSend) return\n setSending(true)\n setError(null)\n try {\n const { response, payload } = await crmApi('email-send', {\n ...(contactId ? { contactId } : {}),\n ...(leadId ? { leadId } : {}),\n ...(dealId ? { dealId } : {}),\n subject: subject.trim(),\n body: body.trim(),\n })\n if (!response.ok) {\n setError(String(payload['error'] ?? 'The email could not be sent.'))\n return\n }\n enqueueSnackbar('Email sent', { variant: 'success', persist: false })\n onSent?.({ activityId: String(payload['activityId'] ?? '') })\n onClose()\n } catch (cause) {\n console.error(cause)\n setError('The email could not be sent.')\n } finally {\n setSending(false)\n }\n }, [canSend, crmApi, contactId, leadId, dealId, subject, body, enqueueSnackbar, onSent, onClose])\n\n const toLabel = props.name && recipient ? `${props.name} <${recipient}>` : (recipient ?? '')\n\n /*\n * The booking door (AGL-2660): a link to one of the sending site's\n * services, dropped where the caret is. The link carries the most\n * specific record this message is about — the deal over its contact, so\n * the booking lands on the deal's timeline as the email does.\n */\n const bookingRef = dealId\n ? { kind: 'deal' as const, id: dealId }\n : leadId\n ? { kind: 'lead' as const, id: leadId }\n : contactId\n ? { kind: 'contact' as const, id: contactId }\n : null\n const handleInsertLink = useCallback((link: string) => {\n const input = messageRef.current\n const current = input?.value ?? ''\n const start = input?.selectionStart ?? current.length\n const end = input?.selectionEnd ?? start\n const next = insertLinkAtCaret(current, link, start, end)\n setBody(next.text)\n // Once the new value has painted, typing continues after the link.\n window.setTimeout(() => {\n input?.focus()\n input?.setSelectionRange(next.caret, next.caret)\n }, 0)\n }, [])\n\n return (\n <Dialog open={open} onClose={sending ? undefined : onClose} maxWidth=\"sm\" fullWidth>\n <DialogTitle>{'Send email'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n {!hostId ? (\n <CrmSitePicker\n hostId={hostId}\n label=\"Send from\"\n helperText=\"The site whose sending address the email leaves on.\"\n disabled={sending}\n />\n ) : null}\n {identity.status === 'site' ? (\n <Alert severity=\"info\" sx={{ mt: 1 }}>\n {'Pick the site the email leaves from.'}\n </Alert>\n ) : identity.status === 'refused' ? (\n <Alert severity=\"warning\" sx={{ mt: 1 }}>\n <Typography variant=\"body2\">{identity.message}</Typography>\n {sendingPath ? (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n <AppLink href={sendingPath}>\n {identity.canManage ? 'Set up sending' : 'See sending'}\n </AppLink>\n </Typography>\n ) : (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {'An admin sets this up under Emails › Sending.'}\n </Typography>\n )}\n </Alert>\n ) : identity.status === 'error' ? (\n <Alert severity=\"error\" sx={{ mt: 1 }}>\n {identity.message}\n </Alert>\n ) : null}\n {open && !recipient && (identity.status === 'ready' || identity.status === 'loading') ? (\n <Alert severity=\"warning\" sx={{ mt: 1 }}>\n {'This record has no email address.'}\n </Alert>\n ) : null}\n <TextField\n size=\"small\"\n label=\"To\"\n value={toLabel}\n slotProps={{ input: { readOnly: true }, inputLabel: { shrink: true } }}\n sx={{ mt: identity.status === 'ready' || identity.status === 'loading' ? 1 : 0 }}\n />\n <CrmEmailCheckAlert check={deliverability} />\n <TextField\n size=\"small\"\n label=\"From\"\n value={\n identity.status === 'ready'\n ? identity.from\n : identity.status === 'loading'\n ? 'Resolving the site’s sending address…'\n : identity.status === 'site'\n ? 'Pick a site to send from'\n : ''\n }\n slotProps={{ input: { readOnly: true }, inputLabel: { shrink: true } }}\n />\n <TextField\n size=\"small\"\n label=\"Reply-to\"\n value={replyTo}\n helperText=\"Replies come to you, not to the site's mailbox.\"\n slotProps={{ input: { readOnly: true }, inputLabel: { shrink: true } }}\n />\n {capture.status === 'ready' ? (\n <TextField\n size=\"small\"\n label=\"Log replies\"\n value={capture.address}\n helperText={CAPTURE_ADDRESS_HELPER}\n slotProps={{\n input: {\n readOnly: true,\n endAdornment: (\n <InputAdornment position=\"end\">\n <CopyCaptureAddressButton address={capture.address} />\n </InputAdornment>\n ),\n },\n inputLabel: { shrink: true },\n }}\n />\n ) : null}\n {pickable.length ? (\n <CrmTemplatePicker\n templates={pickable}\n value={appliedTemplate}\n onChange={(template) => void applyTemplate(template)}\n disabled={sending}\n />\n ) : null}\n <TextField\n size=\"small\"\n label=\"Subject\"\n value={subject}\n onChange={(event) => setSubject(event.target.value)}\n autoFocus\n slotProps={{ htmlInput: { maxLength: CRM_EMAIL_SUBJECT_MAX } }}\n />\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n <CrmInsertMenu\n snippets={snippets}\n groups={fieldGroups}\n disabled={sending}\n onInsert={insertAtCaret}\n />\n <Button\n size=\"small\"\n disabled={sending || !templateScope.scope || (!subject.trim() && !body.trim())}\n onClick={() => setSaveOpen(true)}\n >\n {'Save as template…'}\n </Button>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Message\"\n value={body}\n onChange={(event) => setBody(event.target.value)}\n multiline\n minRows={6}\n inputRef={messageRef}\n helperText={\n unresolved.length\n ? crmMergeUnresolvedMessage(unresolved)\n : draftHasFields\n ? 'Plain text. Merge fields are filled in from this record when the email is sent.'\n : 'Plain text. A blank line starts a new paragraph.'\n }\n slotProps={{\n htmlInput: { maxLength: CRM_EMAIL_BODY_MAX },\n formHelperText: { sx: unresolved.length ? { color: 'warning.main' } : undefined },\n }}\n />\n {EmailDraftSlot && draftRecord ? (\n <EmailDraftSlot\n slot=\"recordEmail\"\n // A lead is read on its own site; a contact or a deal at the level\n // the composer was opened at.\n hostId={draftRecord.kind === 'lead' || !mount ? hostId : null}\n orgId={orgId ?? undefined}\n record={{ ...draftRecord, name: String(props.name ?? '') }}\n subject={subject}\n body={body}\n proposeDraft={(draft: ConsoleRecordEmailDraft) => void proposeDraft(draft)}\n />\n ) : null}\n {preview ? (\n <Stack spacing={0.5} data-testid=\"crm-email-preview\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Preview — as it will be sent'}\n </Typography>\n <Typography variant=\"subtitle2\">{preview.subject.text}</Typography>\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {preview.body.text}\n </Typography>\n </Stack>\n ) : null}\n {bookingRef ? (\n <CrmRecordBookingZone\n variant=\"chip\"\n hostId={sendHostId}\n org={org}\n kind={bookingRef.kind}\n recordId={bookingRef.id}\n onInsert={handleInsertLink}\n />\n ) : null}\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </DialogContent>\n <CrmSaveAsTemplateDialog\n open={saveOpen}\n onClose={() => setSaveOpen(false)}\n onSave={saveAsTemplate}\n />\n <DialogActions>\n <Button onClick={onClose} disabled={sending}>\n {'Cancel'}\n </Button>\n <Button variant=\"contained\" color=\"primary\" disabled={!canSend} onClick={handleSend}>\n {sending ? 'Sending…' : 'Send'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nCrmSendEmailDialog.displayName = 'CrmSendEmailDialog'\n\nexport default CrmSendEmailDialog\n"],"names":["CRM_EMAIL_BODY_MAX","CRM_EMAIL_SUBJECT_MAX","normalizeContactEmail","consentGroupForHost","CRM_COLLECTIONS","createResourceUid","crmMergeUnresolvedMessage","hasCrmMergeFields","resolveCrmMergeFields","useConsoleWidgetSlot","pluginRecordListHref","useSendingApi","AppLink","useConfirmationContext","useSnackbar","useFirestore","useOrgDataScope","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","InputAdornment","Stack","TextField","Typography","collection","doc","getDoc","setDoc","useCallback","useEffect","useMemo","useRef","useState","useCrmOrgMount","CrmRecordBookingZone","insertLinkAtCaret","CAPTURE_ADDRESS_HELPER","CopyCaptureAddressButton","CrmSitePicker","CrmEmailCheckAlert","useCrmEmailCheck","useCrmApi","useCrmInboundAddress","useSiteRouteContext","useCrmEmailTemplates","useCrmEmailTemplateScope","CrmInsertMenu","CrmSaveAsTemplateDialog","CrmTemplatePicker","NO_MERGE_RECORDS","contact","deal","lead","site","loaded","CrmSendEmailDialog","props","hostId","open","onClose","org","contactId","leadId","dealId","onSent","firestore","mount","orgId","undefined","$id","data","user","enqueueSnackbar","sendingApi","sendHostId","createHostId","crmApi","sendSite","capture","enabled","sendingPath","identity","setIdentity","status","recipient","setRecipient","email","subject","setSubject","body","setBody","sending","setSending","error","setError","confirm","templateScope","templates","scope","visibleTo","uid","pickable","filter","row","kind","snippets","appliedTemplate","setAppliedTemplate","saveOpen","setSaveOpen","records","setRecords","messageRef","draftHasFields","fieldGroups","cancelled","senders","payload","response","path","method","query","ok","message","String","refusal","canManage","Array","isArray","from","find","sender","isDefault","read","snapshot","slice","exists","mountedSite","hosts","host","id","Promise","all","deals","then","dealContactId","trim","withContact","resolve","resolvedContact","name","catch","cause","console","mergeContext","contactGroupId","groupId","displayName","preview","unresolved","Set","applyTemplate","template","written","length","accepted","title","description","confirmationText","EmailDraftSlot","proposeDraft","draft","next","draftRecord","insertAtCaret","text","element","current","start","selectionStart","end","selectionEnd","caret","Math","min","window","setTimeout","focus","setSelectionRange","saveAsTemplate","createTokens","Error","now","Date","emailTemplates","visibility","ownerUid","createdByUid","createdAtMs","updatedAtMs","createdAt","updatedAt","variant","persist","given","get","deliverability","replyTo","canSend","Boolean","handleSend","activityId","toLabel","bookingRef","handleInsertLink","link","input","value","maxWidth","fullWidth","sx","display","flexDirection","gap","label","helperText","disabled","severity","mt","href","size","slotProps","readOnly","inputLabel","shrink","check","address","endAdornment","position","onChange","event","target","autoFocus","htmlInput","maxLength","direction","spacing","justifyContent","groups","onInsert","onClick","multiline","minRows","inputRef","formHelperText","color","slot","record","data-testid","whiteSpace","recordId","onSave"],"mappings":"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"}
@@ -49,7 +49,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
49
49
  * is idempotent by construction (the door dedupes on the address, or the
50
50
  * domain and the name), so running the same file again finishes what a
51
51
  * closed drawer left, reporting the earlier rows as merged.
52
- */ import { customImportTarget, customImportTargetKey, importNoMailServerSentence, LIST_IMPORT_MAX_CHARACTERS, parseCsv } from "@aglyn/aglyn";
52
+ */ import { customImportTarget, customImportTargetKey, importNoMailServerSentence, CSV_UPLOAD_MAX_CHARACTERS, parseCsv } from "@aglyn/aglyn";
53
53
  import { ICON_VARIANT_CLOSE } from "@aglyn/shared-data-enums";
54
54
  import { Container, HelpTip, MdiIcon, SrOnly } from "@aglyn/shared-ui-jsx";
55
55
  import { NavigationDrawerComponent } from "@aglyn/shared-ui-jsx/components/navigation-drawer.component";
@@ -125,7 +125,7 @@ export function CsvImportDrawer(props) {
125
125
  const handleFile = useCallback(async (file)=>{
126
126
  if (!file) return;
127
127
  const contents = await file.text().catch(()=>'');
128
- if (contents.length > LIST_IMPORT_MAX_CHARACTERS) {
128
+ if (contents.length > CSV_UPLOAD_MAX_CHARACTERS) {
129
129
  return void enqueueSnackbar('That file is too large to read in one go. Split it and import the ' + 'pieces.', {
130
130
  variant: 'warning',
131
131
  allowDuplicate: true