@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
package/src/lib/plugin.js CHANGED
@@ -21,6 +21,7 @@ import { CrmGlanceCard } from "./components/crm-glance-card.js";
21
21
  import { BUNDLE_ID } from "./constants/bundle-common.js";
22
22
  import { withCrmOrgMount } from "./hooks/use-crm-org-mount.js";
23
23
  import { registerCrmRecordRoutes } from "./model/crm-record-routes.js";
24
+ import { CRM_SEARCH_SOURCES } from "./model/crm-search-sources.js";
24
25
  import { registerPluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
25
26
  import { CRM_RECORD_ATTRIBUTION_ZONE } from "./components/crm-attribution-zone.js";
26
27
  import { CRM_RECORD_BOOKING_ZONE } from "./components/crm-booking-zone.js";
@@ -109,6 +110,8 @@ const CrmTasksDueCard = lazy(()=>import("./components/crm-tasks-due-card.js"));
109
110
  * and the Firestore rules ask the same question where the data is
110
111
  * (`server/suite-gate.ts`).
111
112
  */ featureFlag: 'crm',
113
+ // What the console's search finds here, under the two gates above.
114
+ searchSources: CRM_SEARCH_SOURCES,
112
115
  /*
113
116
  * Dashboard cards (AGL-2599, AGL-2604): the site dashboard's `hostDashboard`
114
117
  * slot composes this extension's permission over each, so a card appears
@@ -201,6 +204,42 @@ const CrmTasksDueCard = lazy(()=>import("./components/crm-tasks-due-card.js"));
201
204
  },
202
205
  Component: CrmConsolePage
203
206
  }
207
+ ],
208
+ /*
209
+ * The ORGANIZATION's CRM hub (AGL-2630), at `/[orgSlug]/crm`: the same
210
+ * sections over every site at once, mounted with no site and the org's
211
+ * sites as `orgMount`. Its listeners carry no scope clause, so the
212
+ * shell's org route refuses a site collaborator on REACH before it asks
213
+ * the `data.manage` declared above; a permission alone, which a
214
+ * collaborator's role can carry, never opens it.
215
+ *
216
+ * It carries the SITE tab's id on purpose: `release_contacts` names
217
+ * `nav-tab-contacts`, so one flag holds both halves of the CRM, and the
218
+ * org half cannot ship while the site half is switched off. First among
219
+ * the organization's plugin tabs, beside Data, the org's other shared
220
+ * collection of records. The help opens the docs heading about this
221
+ * mount. Its old address, `/[orgSlug]/contacts`, is a console redirect
222
+ * (`consoleRedirects`), so it needs no `legacyHrefs` here.
223
+ */ orgNavItems: [
224
+ {
225
+ label: 'CRM',
226
+ href: '/crm',
227
+ sections: CRM_CONSOLE_SECTIONS,
228
+ navTabId: 'nav-tab-contacts',
229
+ tabOrder: -100,
230
+ icon: {
231
+ path: mdiCardAccountDetailsOutline.path
232
+ },
233
+ header: {
234
+ title: 'CRM',
235
+ icon: {
236
+ path: mdiCardAccountDetailsOutline.path
237
+ },
238
+ docsTopic: 'crm',
239
+ docsAnchor: '#at-the-organization-level'
240
+ },
241
+ Component: CrmConsolePage
242
+ }
204
243
  ]
205
244
  });
206
245
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/plugin.ts"],"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\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiCardAccountDetailsOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { CRM_CONSOLE_SECTIONS } from './components/crm-console-sections'\nimport { CrmGlanceCard } from './components/crm-glance-card'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { withCrmOrgMount } from './hooks/use-crm-org-mount'\nimport { registerCrmRecordRoutes } from './model/crm-record-routes'\nimport { registerPluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { CRM_RECORD_ATTRIBUTION_ZONE } from './components/crm-attribution-zone'\nimport { CRM_RECORD_BOOKING_ZONE } from './components/crm-booking-zone'\n\n/** Code-split: the CRM hub only loads when opened. */\nconst CrmConsolePage = lazy(\n () => import('./components/crm-console-page'),\n)\n/** The dashboard glance, split the same way: it loads where the slot mounts it. */\n/** Where a form's fields save on a contact, drawn on the form's own page. */\nconst FormContactFieldsCard = lazy(\n () => import('./components/form-contact-fields-card'),\n)\n\nconst CrmTasksDueCard = lazy(\n () => import('./components/crm-tasks-due-card'),\n)\n\n/**\n * CRM feature plugin (AGL-395, the hub since AGL-2595). Console-only — its\n * records live in Firestore and have no canvas element, so there is no UI\n * bundle. The console half declares the CRM nav + hub through the\n * ConsoleExtension registry (release_crm gate via the nav tab); the\n * contacts section reads the `contactsPerHost` quota off the shell-passed\n * `org`.\n *\n * The plugin id is `crm` (AGL-2595). It was `contacts` while the surface was\n * one list; the id is persisted in every org's `enabledPlugins` and every\n * host's `disabledPlugins`, so the runtime read the old value as this one\n * until the stored documents had been rewritten, and the alias was retired\n * once that backfill reported nothing left (AGL-2614). The `/contacts`\n * address is a URL, not a stored id, and the nav item keeps redirecting it.\n */\nexport function registerCrmConsole(): void {\n registerCrmRecordRoutes()\n registerPluginZone(\n {\n zone: CRM_RECORD_BOOKING_ZONE,\n label: 'Book a meeting from a CRM record',\n surface: 'console',\n // One control in a record header's row of actions, and one beside the\n // composer's fields: the page lays them out, so the zone adds nothing.\n layout: 'bare',\n description:\n 'In a record’s header and beside the one-to-one composer. A widget here offers the site’s bookable services and the link a visitor books one at; it is handed the site, the record and, from the composer, a way to insert the chosen link. It writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: CRM_RECORD_ATTRIBUTION_ZONE,\n label: 'Where a CRM record came from',\n surface: 'console',\n description:\n 'On a contact’s page and in a lead’s history. A widget here says which campaign or link brought the person; it is handed the site, what the record is and its id, and it writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'CRM',\n /*\n * WHO may open the CRM, declared so the shell enforces it.\n *\n * `navTabId` below is a release flag, not authorization: it says whether\n * the surface has shipped, and `FeatureGate` reads it as\n * `released || isStaff`. Nothing above this page decided who among a\n * workspace's members may read it, and the page is a full editor over\n * org-shared people data — names, addresses, order history, notes,\n * consent, and a CSV export of all of it.\n *\n * `data.manage` rather than a key of this plugin's own, because it is\n * the catalog key whose subject is exactly this data. The Firestore\n * rules place contacts in the org-shared data block with datasets and\n * gate writes on `canWriteOrgData()` — owner, admin, editor — which is\n * the population `data.manage` defaults to, so the console gate and the\n * rules agree on the built-in roles by construction. A custom role or a\n * per-member override then narrows the console further, and the rules\n * stay the boundary for the data itself.\n *\n * Minting a `contacts.*` key instead would advertise a control that does\n * not exist. Console contact writes are client-direct against the rules,\n * so a new key would have no server enforcement point to sit on, and a\n * permission a customer can untick that changes nothing is worse than\n * its absence.\n *\n * A SITE COLLABORATOR holding it reaches this page deliberately. They\n * are a real org member document, so this gate is the first thing that\n * has ever asked about them here — and the answer is yes, scoped: the\n * listener filters on `visibleTo`, the rules prove the same predicate\n * per document, and every field the page shows is read through the\n * viewing group's own facet. Refusing them outright would delete a\n * shipped capability rather than close a hole.\n */\n permission: 'data.manage',\n /*\n * WHAT the workspace must have bought (AGL-2851). The CRM is included\n * from Starter and a Free workspace has none of it, so the flag is\n * declared once, here, and the shell refuses every surface this\n * registers on a plan without it: the hub body at both mounts, every\n * section and record beneath it, drawn locked beside the upgrade notice,\n * and the dashboard cards. The nav tab stays, as every plan-locked tab\n * does, because it is the way to that notice. The CRM's routes, `/v1`\n * and the Firestore rules ask the same question where the data is\n * (`server/suite-gate.ts`).\n */\n featureFlag: 'crm',\n /*\n * Dashboard cards (AGL-2599, AGL-2604): the site dashboard's `hostDashboard`\n * slot composes this extension's permission over each, so a card appears\n * only where the CRM is enabled and only for a reader who may open it —\n * registered here rather than imported by the dashboard so the shell's\n * enablement and entitlement gate decides whether a site that never turned\n * the CRM on sees them, the rule every card on that row follows. The tasks\n * card is a glance at the reader's own overdue and due-today work and\n * renders nothing on a workspace that has never made a task; the glance\n * card is four server-counted figures, each a link into the hub.\n *\n * Both name `features.crm`, as the extension does (AGL-2611, AGL-2851):\n * the slot composes the two, so a plan without the CRM gets neither card\n * — absent, not upsold, the treatment every gated card gets.\n *\n * The same two cards again on the organization's `orgDashboard` slot\n * (AGL-2636) — the org's sites page — under the same flag, with the same\n * ids: the id names the card, and it is one card totaling the whole\n * organization there instead of one site. The shell hands that slot the\n * org mount as a prop, and `withCrmOrgMount` is what puts the mount's\n * provider around each card, since the sites page cannot import one.\n */\n widgets: [\n // Which contact field each of a form's fields saves to, on the form's\n // own page. The forms plugin hosts the zone and writes the form\n // document; what a contact's fields ARE is this plugin's, so the card\n // is too.\n {\n slot: 'formContactFields',\n widgetId: 'crm-form-contact-fields',\n title: 'Saves to contact fields',\n // A contact's custom fields are part of the suite, so a workspace\n // without it has none to map a form's field onto.\n featureFlag: 'crm',\n Component: FormContactFieldsCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: CrmTasksDueCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: CrmGlanceCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmTasksDueCard),\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmGlanceCard),\n },\n ],\n navItems: [\n {\n label: 'CRM',\n href: '/crm',\n // The address the surface had while it was one list. A link kept\n // from then — a bookmark, a docs page, an email — still opens the\n // hub rather than the shell's \"not available\" notice.\n legacyHrefs: ['/contacts'],\n // Sections as ROUTES (AGL-2595): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read and a\n // bare `/crm` lands on the first. Every section inherits this\n // item's `release_crm` gate.\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n },\n Component: CrmConsolePage,\n },\n ],\n })\n}\n"],"names":["Aglyn","mdiCardAccountDetailsOutline","lazy","CRM_CONSOLE_SECTIONS","CrmGlanceCard","BUNDLE_ID","withCrmOrgMount","registerCrmRecordRoutes","registerPluginZone","CRM_RECORD_ATTRIBUTION_ZONE","CRM_RECORD_BOOKING_ZONE","CrmConsolePage","FormContactFieldsCard","CrmTasksDueCard","registerCrmConsole","zone","label","surface","layout","description","pluginId","registerConsoleExtension","displayName","permission","featureFlag","widgets","slot","widgetId","title","Component","CONSOLE_WIDGET_SLOTS","hostDashboard","orgDashboard","navItems","href","legacyHrefs","sections","navTabId","icon","path","header","docsTopic"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,4BAA4B,QAAQ,yBAAwB;AACrE,SAASC,IAAI,QAAQ,QAAO;AAC5B,SAASC,oBAAoB,QAAQ,uCAAmC;AACxE,SAASC,aAAa,QAAQ,kCAA8B;AAC5D,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,eAAe,QAAQ,+BAA2B;AAC3D,SAASC,uBAAuB,QAAQ,+BAA2B;AACnE,SAASC,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,2BAA2B,QAAQ,uCAAmC;AAC/E,SAASC,uBAAuB,QAAQ,mCAA+B;AAEvE,oDAAoD,GACpD,MAAMC,iBAAiBT,KACrB,IAAM,MAAM,CAAC;AAEf,iFAAiF,GACjF,2EAA2E,GAC3E,MAAMU,wBAAwBV,KAC5B,IAAM,MAAM,CAAC;AAGf,MAAMW,kBAAkBX,KACtB,IAAM,MAAM,CAAC;AAGf;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASY;IACdP;IACAC,mBACE;QACEO,MAAML;QACNM,OAAO;QACPC,SAAS;QACT,sEAAsE;QACtE,uEAAuE;QACvEC,QAAQ;QACRC,aACE;IACJ,GACA;QAAEC,UAAUf;IAAU;IAExBG,mBACE;QACEO,MAAMN;QACNO,OAAO;QACPC,SAAS;QACTE,aACE;IACJ,GACA;QAAEC,UAAUf;IAAU;IAExBL,MAAMqB,wBAAwB,CAAC;QAC7BD,UAAUf;QACViB,aAAa;QACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgCC,GACDC,YAAY;QACZ;;;;;;;;;;KAUC,GACDC,aAAa;QACb;;;;;;;;;;;;;;;;;;;;;KAqBC,GACDC,SAAS;YACP,sEAAsE;YACtE,gEAAgE;YAChE,sEAAsE;YACtE,UAAU;YACV;gBACEC,MAAM;gBACNC,UAAU;gBACVC,OAAO;gBACP,kEAAkE;gBAClE,kDAAkD;gBAClDJ,aAAa;gBACbK,WAAWjB;YACb;YACA;gBACEc,MAAM1B,MAAM8B,oBAAoB,CAACC,aAAa;gBAC9CJ,UAAU;gBACVC,OAAO;gBACPJ,aAAa;gBACbK,WAAWhB;YACb;YACA;gBACEa,MAAM1B,MAAM8B,oBAAoB,CAACC,aAAa;gBAC9CJ,UAAU;gBACVC,OAAO;gBACPJ,aAAa;gBACbK,WAAWzB;YACb;YACA;gBACEsB,MAAM1B,MAAM8B,oBAAoB,CAACE,YAAY;gBAC7CL,UAAU;gBACVC,OAAO;gBACPJ,aAAa;gBACbK,WAAWvB,gBAAgBO;YAC7B;YACA;gBACEa,MAAM1B,MAAM8B,oBAAoB,CAACE,YAAY;gBAC7CL,UAAU;gBACVC,OAAO;gBACPJ,aAAa;gBACbK,WAAWvB,gBAAgBF;YAC7B;SACD;QACD6B,UAAU;YACR;gBACEjB,OAAO;gBACPkB,MAAM;gBACN,iEAAiE;gBACjE,kEAAkE;gBAClE,sDAAsD;gBACtDC,aAAa;oBAAC;iBAAY;gBAC1B,8DAA8D;gBAC9D,kEAAkE;gBAClE,8DAA8D;gBAC9D,6BAA6B;gBAC7BC,UAAUjC;gBACVkC,UAAU;gBACVC,MAAM;oBAAEC,MAAMtC,6BAA6BsC,IAAI;gBAAC;gBAChDC,QAAQ;oBACNZ,OAAO;oBACPU,MAAM;wBAAEC,MAAMtC,6BAA6BsC,IAAI;oBAAC;oBAChDE,WAAW;gBACb;gBACAZ,WAAWlB;YACb;SACD;IACH;AACF"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/plugin.ts"],"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\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiCardAccountDetailsOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { CRM_CONSOLE_SECTIONS } from './components/crm-console-sections'\nimport { CrmGlanceCard } from './components/crm-glance-card'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { withCrmOrgMount } from './hooks/use-crm-org-mount'\nimport { registerCrmRecordRoutes } from './model/crm-record-routes'\nimport { CRM_SEARCH_SOURCES } from './model/crm-search-sources'\nimport { registerPluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { CRM_RECORD_ATTRIBUTION_ZONE } from './components/crm-attribution-zone'\nimport { CRM_RECORD_BOOKING_ZONE } from './components/crm-booking-zone'\n\n/** Code-split: the CRM hub only loads when opened. */\nconst CrmConsolePage = lazy(\n () => import('./components/crm-console-page'),\n)\n/** The dashboard glance, split the same way: it loads where the slot mounts it. */\n/** Where a form's fields save on a contact, drawn on the form's own page. */\nconst FormContactFieldsCard = lazy(\n () => import('./components/form-contact-fields-card'),\n)\n\nconst CrmTasksDueCard = lazy(\n () => import('./components/crm-tasks-due-card'),\n)\n\n/**\n * CRM feature plugin (AGL-395, the hub since AGL-2595). Console-only — its\n * records live in Firestore and have no canvas element, so there is no UI\n * bundle. The console half declares the CRM nav + hub through the\n * ConsoleExtension registry (release_crm gate via the nav tab); the\n * contacts section reads the `contactsPerHost` quota off the shell-passed\n * `org`.\n *\n * The plugin id is `crm` (AGL-2595). It was `contacts` while the surface was\n * one list; the id is persisted in every org's `enabledPlugins` and every\n * host's `disabledPlugins`, so the runtime read the old value as this one\n * until the stored documents had been rewritten, and the alias was retired\n * once that backfill reported nothing left (AGL-2614). The `/contacts`\n * address is a URL, not a stored id, and the nav item keeps redirecting it.\n */\nexport function registerCrmConsole(): void {\n registerCrmRecordRoutes()\n registerPluginZone(\n {\n zone: CRM_RECORD_BOOKING_ZONE,\n label: 'Book a meeting from a CRM record',\n surface: 'console',\n // One control in a record header's row of actions, and one beside the\n // composer's fields: the page lays them out, so the zone adds nothing.\n layout: 'bare',\n description:\n 'In a record’s header and beside the one-to-one composer. A widget here offers the site’s bookable services and the link a visitor books one at; it is handed the site, the record and, from the composer, a way to insert the chosen link. It writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: CRM_RECORD_ATTRIBUTION_ZONE,\n label: 'Where a CRM record came from',\n surface: 'console',\n description:\n 'On a contact’s page and in a lead’s history. A widget here says which campaign or link brought the person; it is handed the site, what the record is and its id, and it writes nothing.',\n },\n { pluginId: BUNDLE_ID },\n )\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'CRM',\n /*\n * WHO may open the CRM, declared so the shell enforces it.\n *\n * `navTabId` below is a release flag, not authorization: it says whether\n * the surface has shipped, and `FeatureGate` reads it as\n * `released || isStaff`. Nothing above this page decided who among a\n * workspace's members may read it, and the page is a full editor over\n * org-shared people data — names, addresses, order history, notes,\n * consent, and a CSV export of all of it.\n *\n * `data.manage` rather than a key of this plugin's own, because it is\n * the catalog key whose subject is exactly this data. The Firestore\n * rules place contacts in the org-shared data block with datasets and\n * gate writes on `canWriteOrgData()` — owner, admin, editor — which is\n * the population `data.manage` defaults to, so the console gate and the\n * rules agree on the built-in roles by construction. A custom role or a\n * per-member override then narrows the console further, and the rules\n * stay the boundary for the data itself.\n *\n * Minting a `contacts.*` key instead would advertise a control that does\n * not exist. Console contact writes are client-direct against the rules,\n * so a new key would have no server enforcement point to sit on, and a\n * permission a customer can untick that changes nothing is worse than\n * its absence.\n *\n * A SITE COLLABORATOR holding it reaches this page deliberately. They\n * are a real org member document, so this gate is the first thing that\n * has ever asked about them here — and the answer is yes, scoped: the\n * listener filters on `visibleTo`, the rules prove the same predicate\n * per document, and every field the page shows is read through the\n * viewing group's own facet. Refusing them outright would delete a\n * shipped capability rather than close a hole.\n */\n permission: 'data.manage',\n /*\n * WHAT the workspace must have bought (AGL-2851). The CRM is included\n * from Starter and a Free workspace has none of it, so the flag is\n * declared once, here, and the shell refuses every surface this\n * registers on a plan without it: the hub body at both mounts, every\n * section and record beneath it, drawn locked beside the upgrade notice,\n * and the dashboard cards. The nav tab stays, as every plan-locked tab\n * does, because it is the way to that notice. The CRM's routes, `/v1`\n * and the Firestore rules ask the same question where the data is\n * (`server/suite-gate.ts`).\n */\n featureFlag: 'crm',\n // What the console's search finds here, under the two gates above.\n searchSources: CRM_SEARCH_SOURCES,\n /*\n * Dashboard cards (AGL-2599, AGL-2604): the site dashboard's `hostDashboard`\n * slot composes this extension's permission over each, so a card appears\n * only where the CRM is enabled and only for a reader who may open it —\n * registered here rather than imported by the dashboard so the shell's\n * enablement and entitlement gate decides whether a site that never turned\n * the CRM on sees them, the rule every card on that row follows. The tasks\n * card is a glance at the reader's own overdue and due-today work and\n * renders nothing on a workspace that has never made a task; the glance\n * card is four server-counted figures, each a link into the hub.\n *\n * Both name `features.crm`, as the extension does (AGL-2611, AGL-2851):\n * the slot composes the two, so a plan without the CRM gets neither card\n * — absent, not upsold, the treatment every gated card gets.\n *\n * The same two cards again on the organization's `orgDashboard` slot\n * (AGL-2636) — the org's sites page — under the same flag, with the same\n * ids: the id names the card, and it is one card totaling the whole\n * organization there instead of one site. The shell hands that slot the\n * org mount as a prop, and `withCrmOrgMount` is what puts the mount's\n * provider around each card, since the sites page cannot import one.\n */\n widgets: [\n // Which contact field each of a form's fields saves to, on the form's\n // own page. The forms plugin hosts the zone and writes the form\n // document; what a contact's fields ARE is this plugin's, so the card\n // is too.\n {\n slot: 'formContactFields',\n widgetId: 'crm-form-contact-fields',\n title: 'Saves to contact fields',\n // A contact's custom fields are part of the suite, so a workspace\n // without it has none to map a form's field onto.\n featureFlag: 'crm',\n Component: FormContactFieldsCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: CrmTasksDueCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: CrmGlanceCard,\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-tasks-due',\n title: 'Tasks due',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmTasksDueCard),\n },\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.orgDashboard,\n widgetId: 'crm-glance',\n title: 'CRM at a glance',\n featureFlag: 'crm',\n Component: withCrmOrgMount(CrmGlanceCard),\n },\n ],\n navItems: [\n {\n label: 'CRM',\n href: '/crm',\n // The address the surface had while it was one list. A link kept\n // from then — a bookmark, a docs page, an email — still opens the\n // hub rather than the shell's \"not available\" notice.\n legacyHrefs: ['/contacts'],\n // Sections as ROUTES (AGL-2595): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read and a\n // bare `/crm` lands on the first. Every section inherits this\n // item's `release_crm` gate.\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n },\n Component: CrmConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's CRM hub (AGL-2630), at `/[orgSlug]/crm`: the same\n * sections over every site at once, mounted with no site and the org's\n * sites as `orgMount`. Its listeners carry no scope clause, so the\n * shell's org route refuses a site collaborator on REACH before it asks\n * the `data.manage` declared above; a permission alone, which a\n * collaborator's role can carry, never opens it.\n *\n * It carries the SITE tab's id on purpose: `release_contacts` names\n * `nav-tab-contacts`, so one flag holds both halves of the CRM, and the\n * org half cannot ship while the site half is switched off. First among\n * the organization's plugin tabs, beside Data, the org's other shared\n * collection of records. The help opens the docs heading about this\n * mount. Its old address, `/[orgSlug]/contacts`, is a console redirect\n * (`consoleRedirects`), so it needs no `legacyHrefs` here.\n */\n orgNavItems: [\n {\n label: 'CRM',\n href: '/crm',\n sections: CRM_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-contacts',\n tabOrder: -100,\n icon: { path: mdiCardAccountDetailsOutline.path },\n header: {\n title: 'CRM',\n icon: { path: mdiCardAccountDetailsOutline.path },\n docsTopic: 'crm',\n docsAnchor: '#at-the-organization-level',\n },\n Component: CrmConsolePage,\n },\n ],\n })\n}\n"],"names":["Aglyn","mdiCardAccountDetailsOutline","lazy","CRM_CONSOLE_SECTIONS","CrmGlanceCard","BUNDLE_ID","withCrmOrgMount","registerCrmRecordRoutes","CRM_SEARCH_SOURCES","registerPluginZone","CRM_RECORD_ATTRIBUTION_ZONE","CRM_RECORD_BOOKING_ZONE","CrmConsolePage","FormContactFieldsCard","CrmTasksDueCard","registerCrmConsole","zone","label","surface","layout","description","pluginId","registerConsoleExtension","displayName","permission","featureFlag","searchSources","widgets","slot","widgetId","title","Component","CONSOLE_WIDGET_SLOTS","hostDashboard","orgDashboard","navItems","href","legacyHrefs","sections","navTabId","icon","path","header","docsTopic","orgNavItems","tabOrder","docsAnchor"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,4BAA4B,QAAQ,yBAAwB;AACrE,SAASC,IAAI,QAAQ,QAAO;AAC5B,SAASC,oBAAoB,QAAQ,uCAAmC;AACxE,SAASC,aAAa,QAAQ,kCAA8B;AAC5D,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,eAAe,QAAQ,+BAA2B;AAC3D,SAASC,uBAAuB,QAAQ,+BAA2B;AACnE,SAASC,kBAAkB,QAAQ,gCAA4B;AAC/D,SAASC,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,2BAA2B,QAAQ,uCAAmC;AAC/E,SAASC,uBAAuB,QAAQ,mCAA+B;AAEvE,oDAAoD,GACpD,MAAMC,iBAAiBV,KACrB,IAAM,MAAM,CAAC;AAEf,iFAAiF,GACjF,2EAA2E,GAC3E,MAAMW,wBAAwBX,KAC5B,IAAM,MAAM,CAAC;AAGf,MAAMY,kBAAkBZ,KACtB,IAAM,MAAM,CAAC;AAGf;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASa;IACdR;IACAE,mBACE;QACEO,MAAML;QACNM,OAAO;QACPC,SAAS;QACT,sEAAsE;QACtE,uEAAuE;QACvEC,QAAQ;QACRC,aACE;IACJ,GACA;QAAEC,UAAUhB;IAAU;IAExBI,mBACE;QACEO,MAAMN;QACNO,OAAO;QACPC,SAAS;QACTE,aACE;IACJ,GACA;QAAEC,UAAUhB;IAAU;IAExBL,MAAMsB,wBAAwB,CAAC;QAC7BD,UAAUhB;QACVkB,aAAa;QACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgCC,GACDC,YAAY;QACZ;;;;;;;;;;KAUC,GACDC,aAAa;QACb,mEAAmE;QACnEC,eAAelB;QACf;;;;;;;;;;;;;;;;;;;;;KAqBC,GACDmB,SAAS;YACP,sEAAsE;YACtE,gEAAgE;YAChE,sEAAsE;YACtE,UAAU;YACV;gBACEC,MAAM;gBACNC,UAAU;gBACVC,OAAO;gBACP,kEAAkE;gBAClE,kDAAkD;gBAClDL,aAAa;gBACbM,WAAWlB;YACb;YACA;gBACEe,MAAM5B,MAAMgC,oBAAoB,CAACC,aAAa;gBAC9CJ,UAAU;gBACVC,OAAO;gBACPL,aAAa;gBACbM,WAAWjB;YACb;YACA;gBACEc,MAAM5B,MAAMgC,oBAAoB,CAACC,aAAa;gBAC9CJ,UAAU;gBACVC,OAAO;gBACPL,aAAa;gBACbM,WAAW3B;YACb;YACA;gBACEwB,MAAM5B,MAAMgC,oBAAoB,CAACE,YAAY;gBAC7CL,UAAU;gBACVC,OAAO;gBACPL,aAAa;gBACbM,WAAWzB,gBAAgBQ;YAC7B;YACA;gBACEc,MAAM5B,MAAMgC,oBAAoB,CAACE,YAAY;gBAC7CL,UAAU;gBACVC,OAAO;gBACPL,aAAa;gBACbM,WAAWzB,gBAAgBF;YAC7B;SACD;QACD+B,UAAU;YACR;gBACElB,OAAO;gBACPmB,MAAM;gBACN,iEAAiE;gBACjE,kEAAkE;gBAClE,sDAAsD;gBACtDC,aAAa;oBAAC;iBAAY;gBAC1B,8DAA8D;gBAC9D,kEAAkE;gBAClE,8DAA8D;gBAC9D,6BAA6B;gBAC7BC,UAAUnC;gBACVoC,UAAU;gBACVC,MAAM;oBAAEC,MAAMxC,6BAA6BwC,IAAI;gBAAC;gBAChDC,QAAQ;oBACNZ,OAAO;oBACPU,MAAM;wBAAEC,MAAMxC,6BAA6BwC,IAAI;oBAAC;oBAChDE,WAAW;gBACb;gBACAZ,WAAWnB;YACb;SACD;QACD;;;;;;;;;;;;;;;KAeC,GACDgC,aAAa;YACX;gBACE3B,OAAO;gBACPmB,MAAM;gBACNE,UAAUnC;gBACVoC,UAAU;gBACVM,UAAU,CAAC;gBACXL,MAAM;oBAAEC,MAAMxC,6BAA6BwC,IAAI;gBAAC;gBAChDC,QAAQ;oBACNZ,OAAO;oBACPU,MAAM;wBAAEC,MAAMxC,6BAA6BwC,IAAI;oBAAC;oBAChDE,WAAW;oBACXG,YAAY;gBACd;gBACAf,WAAWnB;YACb;SACD;IACH;AACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type ApiV1Context } from '@aglyn/tenant-data-admin/server/api-v1-kit';
18
+ /** The key an integration names the record to fold in by. */
19
+ export declare const MERGE_SOURCE_FIELD = "sourceContactId";
20
+ export declare function mergeContactRoute(request: Request, ctx: ApiV1Context, survivorRef: FirebaseFirestore.DocumentReference, view: (snapshot: FirebaseFirestore.DocumentSnapshot) => unknown): Promise<Response>;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * `POST /v1/contacts/{contactId}/merge` (AGL-2625) — fold another contact
18
+ * into this one.
19
+ *
20
+ * The contact in the path SURVIVES: it keeps its address as the identity,
21
+ * and the record named by `sourceContactId` is folded into it and deleted.
22
+ * `mergeContacts` (`../merge-contacts.ts`) is the whole of the work — the same
23
+ * function behind the console's `crm/contacts-merge` — so an integration
24
+ * and a person at the record page cannot merge two different ways.
25
+ *
26
+ * ## Idempotency
27
+ *
28
+ * Takes an `Idempotency-Key` with the delete's exact semantics, and for the
29
+ * delete's reason: a merge destroys a record, and a retry after a lost
30
+ * response must be able to tell "already merged" from "wrong id". Without a
31
+ * key, a second call finds no source contact and answers `404`, which is
32
+ * true and is the confusion the key removes.
33
+ *
34
+ * ## What it answers
35
+ *
36
+ * The surviving contact, read back the way `GET` reads it — the
37
+ * organization-wide profile, or one site's through `?consentSiteId=` —
38
+ * with its `alternateEmails` now carrying the merged address.
39
+ */ import { ApiErrors, apiJson } from "@aglyn/tenant-data-admin";
40
+ import { apiKeyActorLabel } from "@aglyn/aglyn/app-utils/activity-presenter";
41
+ import { claimWrite, readJsonBody } from "@aglyn/tenant-data-admin/server/api-v1-kit";
42
+ import { mergeContacts } from "../merge-contacts.js";
43
+ /** The key an integration names the record to fold in by. */ export const MERGE_SOURCE_FIELD = 'sourceContactId';
44
+ export async function mergeContactRoute(request, ctx, survivorRef, view) {
45
+ const body = await readJsonBody(request);
46
+ const raw = body[MERGE_SOURCE_FIELD];
47
+ const sourceId = typeof raw === 'string' ? raw.trim() : '';
48
+ const fields = {};
49
+ if (raw === undefined) fields[MERGE_SOURCE_FIELD] = 'Required — the contact to merge into this one';
50
+ else if (!sourceId) fields[MERGE_SOURCE_FIELD] = 'Must be a contact id';
51
+ else if (sourceId === survivorRef.id) {
52
+ fields[MERGE_SOURCE_FIELD] = 'Must be a different contact from the one in the path';
53
+ }
54
+ for (const key of Object.keys(body)){
55
+ if (key !== MERGE_SOURCE_FIELD) fields[key] = 'Not accepted on a merge';
56
+ }
57
+ if (Object.keys(fields).length) {
58
+ return ApiErrors.badRequest({
59
+ message: 'Merge failed validation',
60
+ code: 'validation_failed',
61
+ fields,
62
+ headers: ctx.headers
63
+ });
64
+ }
65
+ const claimed = await claimWrite(ctx, '*', request.headers.get('Idempotency-Key'), 'contact-merges');
66
+ if ('replay' in claimed) return claimed.replay;
67
+ const { claim } = claimed;
68
+ try {
69
+ const result = await mergeContacts({
70
+ firestore: ctx.firestore,
71
+ orgRef: ctx.firestore.collection('orgs').doc(ctx.orgId),
72
+ survivorId: survivorRef.id,
73
+ mergedId: sourceId,
74
+ // A key has no uid; `'api'` is the attribution every API-created CRM
75
+ // record carries in `createdByUid`, and the note it leaves names the
76
+ // key when the organization named it.
77
+ actor: {
78
+ uid: 'api',
79
+ email: null,
80
+ apiKeyName: ctx.keyName
81
+ },
82
+ hostId: null,
83
+ actorName: ctx.keyName ? apiKeyActorLabel(ctx.keyName) : 'API'
84
+ });
85
+ if (result.ok === false) {
86
+ await claim.release();
87
+ if (result.reason === 'survivor-missing') {
88
+ return ApiErrors.notFound({
89
+ message: 'No such contact',
90
+ headers: ctx.headers
91
+ });
92
+ }
93
+ if (result.reason === 'merged-missing') {
94
+ return ApiErrors.notFound({
95
+ message: 'No such contact to merge',
96
+ code: 'source_not_found',
97
+ headers: ctx.headers
98
+ });
99
+ }
100
+ return ApiErrors.badRequest({
101
+ message: 'Merge failed validation',
102
+ code: 'validation_failed',
103
+ fields: {
104
+ [MERGE_SOURCE_FIELD]: 'Must be a different contact from the one in the path'
105
+ },
106
+ headers: ctx.headers
107
+ });
108
+ }
109
+ const answer = view(await survivorRef.get());
110
+ await claim.record(200, answer);
111
+ return apiJson(answer, {
112
+ headers: ctx.headers
113
+ });
114
+ } catch (error) {
115
+ await claim.release();
116
+ throw error;
117
+ }
118
+ }
119
+
120
+ //# sourceMappingURL=contacts-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/server/api-v1/contacts-merge.ts"],"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\n/**\n * `POST /v1/contacts/{contactId}/merge` (AGL-2625) — fold another contact\n * into this one.\n *\n * The contact in the path SURVIVES: it keeps its address as the identity,\n * and the record named by `sourceContactId` is folded into it and deleted.\n * `mergeContacts` (`../merge-contacts.ts`) is the whole of the work — the same\n * function behind the console's `crm/contacts-merge` — so an integration\n * and a person at the record page cannot merge two different ways.\n *\n * ## Idempotency\n *\n * Takes an `Idempotency-Key` with the delete's exact semantics, and for the\n * delete's reason: a merge destroys a record, and a retry after a lost\n * response must be able to tell \"already merged\" from \"wrong id\". Without a\n * key, a second call finds no source contact and answers `404`, which is\n * true and is the confusion the key removes.\n *\n * ## What it answers\n *\n * The surviving contact, read back the way `GET` reads it — the\n * organization-wide profile, or one site's through `?consentSiteId=` —\n * with its `alternateEmails` now carrying the merged address.\n */\n\nimport { ApiErrors, apiJson } from '@aglyn/tenant-data-admin'\nimport { apiKeyActorLabel } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport {\n type ApiV1Context,\n claimWrite,\n readJsonBody,\n} from '@aglyn/tenant-data-admin/server/api-v1-kit'\nimport { mergeContacts } from '../merge-contacts'\n\n/** The key an integration names the record to fold in by. */\nexport const MERGE_SOURCE_FIELD = 'sourceContactId'\n\nexport async function mergeContactRoute(\n request: Request,\n ctx: ApiV1Context,\n survivorRef: FirebaseFirestore.DocumentReference,\n view: (snapshot: FirebaseFirestore.DocumentSnapshot) => unknown,\n): Promise<Response> {\n const body = await readJsonBody(request)\n const raw = body[MERGE_SOURCE_FIELD]\n const sourceId = typeof raw === 'string' ? raw.trim() : ''\n const fields: Record<string, string> = {}\n if (raw === undefined) fields[MERGE_SOURCE_FIELD] = 'Required — the contact to merge into this one'\n else if (!sourceId) fields[MERGE_SOURCE_FIELD] = 'Must be a contact id'\n else if (sourceId === survivorRef.id) {\n fields[MERGE_SOURCE_FIELD] = 'Must be a different contact from the one in the path'\n }\n for (const key of Object.keys(body)) {\n if (key !== MERGE_SOURCE_FIELD) fields[key] = 'Not accepted on a merge'\n }\n if (Object.keys(fields).length) {\n return ApiErrors.badRequest({\n message: 'Merge failed validation',\n code: 'validation_failed',\n fields,\n headers: ctx.headers,\n })\n }\n\n const claimed = await claimWrite(\n ctx,\n '*',\n request.headers.get('Idempotency-Key'),\n 'contact-merges',\n )\n if ('replay' in claimed) return claimed.replay\n const { claim } = claimed\n\n try {\n const result = await mergeContacts({\n firestore: ctx.firestore,\n orgRef: ctx.firestore.collection('orgs').doc(ctx.orgId),\n survivorId: survivorRef.id,\n mergedId: sourceId,\n // A key has no uid; `'api'` is the attribution every API-created CRM\n // record carries in `createdByUid`, and the note it leaves names the\n // key when the organization named it.\n actor: { uid: 'api', email: null, apiKeyName: ctx.keyName },\n hostId: null,\n actorName: ctx.keyName ? apiKeyActorLabel(ctx.keyName) : 'API',\n })\n if (result.ok === false) {\n await claim.release()\n if (result.reason === 'survivor-missing') {\n return ApiErrors.notFound({ message: 'No such contact', headers: ctx.headers })\n }\n if (result.reason === 'merged-missing') {\n return ApiErrors.notFound({\n message: 'No such contact to merge',\n code: 'source_not_found',\n headers: ctx.headers,\n })\n }\n return ApiErrors.badRequest({\n message: 'Merge failed validation',\n code: 'validation_failed',\n fields: { [MERGE_SOURCE_FIELD]: 'Must be a different contact from the one in the path' },\n headers: ctx.headers,\n })\n }\n const answer = view(await survivorRef.get())\n await claim.record(200, answer)\n return apiJson(answer, { headers: ctx.headers })\n } catch (error) {\n await claim.release()\n throw error\n }\n}\n"],"names":["ApiErrors","apiJson","apiKeyActorLabel","claimWrite","readJsonBody","mergeContacts","MERGE_SOURCE_FIELD","mergeContactRoute","request","ctx","survivorRef","view","body","raw","sourceId","trim","fields","undefined","id","key","Object","keys","length","badRequest","message","code","headers","claimed","get","replay","claim","result","firestore","orgRef","collection","doc","orgId","survivorId","mergedId","actor","uid","email","apiKeyName","keyName","hostId","actorName","ok","release","reason","notFound","answer","record","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GAED,SAASA,SAAS,EAAEC,OAAO,QAAQ,2BAA0B;AAC7D,SAASC,gBAAgB,QAAQ,4CAA2C;AAC5E,SAEEC,UAAU,EACVC,YAAY,QACP,6CAA4C;AACnD,SAASC,aAAa,QAAQ,uBAAmB;AAEjD,2DAA2D,GAC3D,OAAO,MAAMC,qBAAqB,kBAAiB;AAEnD,OAAO,eAAeC,kBACpBC,OAAgB,EAChBC,GAAiB,EACjBC,WAAgD,EAChDC,IAA+D;IAE/D,MAAMC,OAAO,MAAMR,aAAaI;IAChC,MAAMK,MAAMD,IAAI,CAACN,mBAAmB;IACpC,MAAMQ,WAAW,OAAOD,QAAQ,WAAWA,IAAIE,IAAI,KAAK;IACxD,MAAMC,SAAiC,CAAC;IACxC,IAAIH,QAAQI,WAAWD,MAAM,CAACV,mBAAmB,GAAG;SAC/C,IAAI,CAACQ,UAAUE,MAAM,CAACV,mBAAmB,GAAG;SAC5C,IAAIQ,aAAaJ,YAAYQ,EAAE,EAAE;QACpCF,MAAM,CAACV,mBAAmB,GAAG;IAC/B;IACA,KAAK,MAAMa,OAAOC,OAAOC,IAAI,CAACT,MAAO;QACnC,IAAIO,QAAQb,oBAAoBU,MAAM,CAACG,IAAI,GAAG;IAChD;IACA,IAAIC,OAAOC,IAAI,CAACL,QAAQM,MAAM,EAAE;QAC9B,OAAOtB,UAAUuB,UAAU,CAAC;YAC1BC,SAAS;YACTC,MAAM;YACNT;YACAU,SAASjB,IAAIiB,OAAO;QACtB;IACF;IAEA,MAAMC,UAAU,MAAMxB,WACpBM,KACA,KACAD,QAAQkB,OAAO,CAACE,GAAG,CAAC,oBACpB;IAEF,IAAI,YAAYD,SAAS,OAAOA,QAAQE,MAAM;IAC9C,MAAM,EAAEC,KAAK,EAAE,GAAGH;IAElB,IAAI;QACF,MAAMI,SAAS,MAAM1B,cAAc;YACjC2B,WAAWvB,IAAIuB,SAAS;YACxBC,QAAQxB,IAAIuB,SAAS,CAACE,UAAU,CAAC,QAAQC,GAAG,CAAC1B,IAAI2B,KAAK;YACtDC,YAAY3B,YAAYQ,EAAE;YAC1BoB,UAAUxB;YACV,qEAAqE;YACrE,qEAAqE;YACrE,sCAAsC;YACtCyB,OAAO;gBAAEC,KAAK;gBAAOC,OAAO;gBAAMC,YAAYjC,IAAIkC,OAAO;YAAC;YAC1DC,QAAQ;YACRC,WAAWpC,IAAIkC,OAAO,GAAGzC,iBAAiBO,IAAIkC,OAAO,IAAI;QAC3D;QACA,IAAIZ,OAAOe,EAAE,KAAK,OAAO;YACvB,MAAMhB,MAAMiB,OAAO;YACnB,IAAIhB,OAAOiB,MAAM,KAAK,oBAAoB;gBACxC,OAAOhD,UAAUiD,QAAQ,CAAC;oBAAEzB,SAAS;oBAAmBE,SAASjB,IAAIiB,OAAO;gBAAC;YAC/E;YACA,IAAIK,OAAOiB,MAAM,KAAK,kBAAkB;gBACtC,OAAOhD,UAAUiD,QAAQ,CAAC;oBACxBzB,SAAS;oBACTC,MAAM;oBACNC,SAASjB,IAAIiB,OAAO;gBACtB;YACF;YACA,OAAO1B,UAAUuB,UAAU,CAAC;gBAC1BC,SAAS;gBACTC,MAAM;gBACNT,QAAQ;oBAAE,CAACV,mBAAmB,EAAE;gBAAuD;gBACvFoB,SAASjB,IAAIiB,OAAO;YACtB;QACF;QACA,MAAMwB,SAASvC,KAAK,MAAMD,YAAYkB,GAAG;QACzC,MAAME,MAAMqB,MAAM,CAAC,KAAKD;QACxB,OAAOjD,QAAQiD,QAAQ;YAAExB,SAASjB,IAAIiB,OAAO;QAAC;IAChD,EAAE,OAAO0B,OAAO;QACd,MAAMtB,MAAMiB,OAAO;QACnB,MAAMK;IACR;AACF"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type ApiV1Context } from '@aglyn/tenant-data-admin/server/api-v1-kit';
18
+ export declare function handleContacts(request: Request, ctx: ApiV1Context, segments: string[], url: URL): Promise<Response>;