@graph8/sdk 0.370.0 → 0.374.0

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 (646) hide show
  1. package/README.md +22 -3
  2. package/blocks/_foundation/README.md +61 -0
  3. package/blocks/_foundation/block.json +57 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  6. package/blocks/_foundation/components/empty-state.tsx +5 -2
  7. package/blocks/_foundation/components/list-select.tsx +112 -0
  8. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  9. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  10. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  11. package/blocks/_foundation/components/screen/index.ts +1 -0
  12. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  13. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  14. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  15. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  16. package/blocks/_foundation/lib/paging.ts +57 -0
  17. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  18. package/blocks/_foundation/server/auth.ts +815 -0
  19. package/blocks/agents/README.md +81 -4
  20. package/blocks/agents/block.json +311 -2
  21. package/blocks/agents/components/agent-activity.tsx +112 -0
  22. package/blocks/agents/components/agent-approvals.tsx +135 -0
  23. package/blocks/agents/components/agent-builder.tsx +17 -3
  24. package/blocks/agents/components/agent-conversations.tsx +143 -0
  25. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  26. package/blocks/agents/components/agent-operators.tsx +164 -0
  27. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  28. package/blocks/agents/components/agent-runs.tsx +3 -3
  29. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  30. package/blocks/agents/components/agent-test-history.tsx +121 -0
  31. package/blocks/agents/components/agents-screen.tsx +53 -11
  32. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  33. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  34. package/blocks/agents/components/from-template-button.tsx +65 -0
  35. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  36. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  37. package/blocks/agents/components/knowledge-form.tsx +69 -0
  38. package/blocks/agents/components/name-dialog.tsx +32 -0
  39. package/blocks/agents/components/receptionist-button.tsx +46 -0
  40. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  41. package/blocks/agents/components/skill-editor.tsx +147 -0
  42. package/blocks/agents/components/skill-library.tsx +111 -0
  43. package/blocks/agents/lib/agent-types.ts +7 -1
  44. package/blocks/agents/lib/facts.tsx +13 -0
  45. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  46. package/blocks/agents/lib/load-more.tsx +141 -0
  47. package/blocks/agents/lib/skill-utils.ts +91 -0
  48. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  50. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  51. package/blocks/analytics/components/report-builder.tsx +15 -2
  52. package/blocks/appointments/README.md +6 -0
  53. package/blocks/appointments/block.json +27 -0
  54. package/blocks/appointments/components/booking-page.tsx +2 -2
  55. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  56. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  57. package/blocks/appointments/components/meeting-list.tsx +29 -13
  58. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  59. package/blocks/campaigns/README.md +1 -1
  60. package/blocks/campaigns/block.json +34 -1
  61. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  62. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  63. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  64. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  65. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  66. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  67. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  68. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  69. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  70. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  71. package/blocks/coach/components/call-scorecard.tsx +7 -7
  72. package/blocks/company-intelligence/README.md +132 -0
  73. package/blocks/company-intelligence/block.json +318 -0
  74. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  75. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  76. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  77. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  78. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  79. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  80. package/blocks/company-intelligence/components/research-run.tsx +165 -0
  81. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  82. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  83. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  84. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  85. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  86. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  87. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  88. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  89. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  90. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  91. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  92. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  93. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  94. package/blocks/crm/README.md +25 -0
  95. package/blocks/crm/block.json +257 -9
  96. package/blocks/crm/components/bulk-update.tsx +119 -0
  97. package/blocks/crm/components/companies-screen.tsx +23 -9
  98. package/blocks/crm/components/company-detail.tsx +2 -2
  99. package/blocks/crm/components/contact-detail.tsx +4 -4
  100. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  101. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  102. package/blocks/crm/components/contact-lookup-link.tsx +85 -0
  103. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  104. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  105. package/blocks/crm/components/contacts-screen.tsx +77 -18
  106. package/blocks/crm/components/dedupe-review.tsx +6 -1
  107. package/blocks/crm/components/import-wizard.tsx +3 -7
  108. package/blocks/crm/components/list-select.tsx +20 -0
  109. package/blocks/crm/components/owner-assign.tsx +132 -0
  110. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  111. package/blocks/crm/components/record-form.tsx +1 -1
  112. package/blocks/crm/components/record-open-links.tsx +79 -0
  113. package/blocks/crm/components/tag-assign.tsx +112 -0
  114. package/blocks/crm/components/tag-manager.tsx +178 -0
  115. package/blocks/crm/components/tasks-screen.tsx +35 -15
  116. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  117. package/blocks/crm/components/verify-email-button.tsx +93 -0
  118. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  119. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  120. package/blocks/crm/hooks/use-page-query.ts +3 -1
  121. package/blocks/crm/lib/crm-types.ts +14 -0
  122. package/blocks/crm/lib/read-lists.ts +16 -0
  123. package/blocks/crm/lib/record-actions.ts +102 -0
  124. package/blocks/deals/README.md +25 -6
  125. package/blocks/deals/block.json +168 -2
  126. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  127. package/blocks/deals/components/create-deal-button.tsx +109 -0
  128. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  129. package/blocks/deals/components/deal-detail.tsx +7 -5
  130. package/blocks/deals/components/deal-filters.tsx +65 -0
  131. package/blocks/deals/components/deal-line-items.tsx +120 -0
  132. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  133. package/blocks/deals/components/deal-related.tsx +30 -9
  134. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  135. package/blocks/deals/components/deals-screen.tsx +57 -22
  136. package/blocks/deals/components/forecast-view.tsx +4 -4
  137. package/blocks/deals/components/pipeline-board.tsx +8 -6
  138. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  139. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  140. package/blocks/deals/components/stage-editor.tsx +1 -1
  141. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  142. package/blocks/deals/lib/deal-extras.ts +156 -0
  143. package/blocks/dialer/README.md +14 -5
  144. package/blocks/dialer/block.json +17 -2
  145. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  146. package/blocks/dialer/components/call-log-screen.tsx +10 -2
  147. package/blocks/dialer/components/call-log.tsx +16 -4
  148. package/blocks/dialer/components/call-queue.tsx +3 -3
  149. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  150. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  151. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  152. package/blocks/dialer/components/session-start-form.tsx +58 -11
  153. package/blocks/dialer/components/softphone.tsx +1 -1
  154. package/blocks/documents/README.md +86 -0
  155. package/blocks/documents/block.json +378 -0
  156. package/blocks/documents/components/account-bar.tsx +48 -0
  157. package/blocks/documents/components/correspondence-form.tsx +95 -0
  158. package/blocks/documents/components/correspondence-import.tsx +106 -0
  159. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  160. package/blocks/documents/components/documents-screen.tsx +76 -0
  161. package/blocks/documents/components/library-document-detail.tsx +175 -0
  162. package/blocks/documents/components/library-document-form.tsx +105 -0
  163. package/blocks/documents/components/library-folder-form.tsx +74 -0
  164. package/blocks/documents/components/library-panel.tsx +235 -0
  165. package/blocks/documents/components/library-upload.tsx +160 -0
  166. package/blocks/documents/components/question-row.tsx +130 -0
  167. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  168. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  169. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  170. package/blocks/documents/lib/documents-types.ts +252 -0
  171. package/blocks/documents/lib/download.ts +64 -0
  172. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  173. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  174. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  175. package/blocks/email-signatures/README.md +123 -0
  176. package/blocks/email-signatures/block.json +341 -0
  177. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  178. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  179. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  180. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  181. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  182. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  183. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  184. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  185. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  186. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  187. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  188. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  189. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  190. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  191. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  192. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  193. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  194. package/blocks/enrichment/README.md +38 -10
  195. package/blocks/enrichment/block.json +164 -12
  196. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  197. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  198. package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
  199. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  200. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  201. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  202. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  203. package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
  204. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  205. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  206. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  207. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  208. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  209. package/blocks/enrichment/lib/enrichment-types.ts +51 -0
  210. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  211. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  212. package/blocks/forms/README.md +10 -0
  213. package/blocks/forms/block.json +107 -0
  214. package/blocks/forms/components/form-activity.tsx +71 -0
  215. package/blocks/forms/components/form-backfill.tsx +108 -0
  216. package/blocks/forms/components/form-builder.tsx +1 -1
  217. package/blocks/forms/components/form-embed.tsx +1 -1
  218. package/blocks/forms/components/form-groups.tsx +151 -0
  219. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  220. package/blocks/forms/components/form-route-history.tsx +72 -0
  221. package/blocks/forms/components/form-saved-views.tsx +81 -0
  222. package/blocks/forms/components/forms-screen.tsx +57 -20
  223. package/blocks/forms/components/more-rows.tsx +30 -0
  224. package/blocks/forms/components/submissions-export.tsx +67 -0
  225. package/blocks/forms/components/submissions-table.tsx +15 -6
  226. package/blocks/forms/lib/forms-extra.ts +67 -0
  227. package/blocks/inbox/README.md +45 -4
  228. package/blocks/inbox/block.json +145 -8
  229. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  230. package/blocks/inbox/components/compose-message.tsx +193 -0
  231. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  232. package/blocks/inbox/components/inbox-search.tsx +106 -0
  233. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  234. package/blocks/inbox/components/tag-manager.tsx +136 -0
  235. package/blocks/inbox/components/thread-actions.tsx +133 -0
  236. package/blocks/inbox/components/thread-badge.tsx +2 -2
  237. package/blocks/inbox/components/thread-view.tsx +3 -1
  238. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  239. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  240. package/blocks/integrations/README.md +54 -1
  241. package/blocks/integrations/block.json +189 -15
  242. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  243. package/blocks/integrations/components/connection-health.tsx +59 -0
  244. package/blocks/integrations/components/crm-detail.tsx +20 -1
  245. package/blocks/integrations/components/crm-records.tsx +103 -0
  246. package/blocks/integrations/components/field-mappings.tsx +61 -0
  247. package/blocks/integrations/components/integration-list.tsx +4 -4
  248. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  249. package/blocks/integrations/components/migration-panel.tsx +133 -0
  250. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  251. package/blocks/integrations/components/sync-status.tsx +6 -6
  252. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  253. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  254. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  255. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  256. package/blocks/integrations/lib/crm-utils.ts +39 -0
  257. package/blocks/integrations/lib/facts.tsx +13 -0
  258. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  259. package/blocks/integrations/lib/load-more.tsx +141 -0
  260. package/blocks/intent/components/account-intent-score.tsx +5 -5
  261. package/blocks/intent/components/intent-feed.tsx +8 -8
  262. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  263. package/blocks/intent/lib/intent-utils.ts +4 -4
  264. package/blocks/lifecycle/components/account-related.tsx +8 -1
  265. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  266. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  267. package/blocks/lists/README.md +7 -1
  268. package/blocks/lists/block.json +31 -3
  269. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  270. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  271. package/blocks/lists/components/list-picker.tsx +13 -3
  272. package/blocks/lists/components/list-table.tsx +1 -1
  273. package/blocks/lists/components/lists-screen.tsx +4 -2
  274. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  275. package/blocks/lists/components/suppression-manager.tsx +27 -6
  276. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  277. package/blocks/mailboxes/block.json +4 -0
  278. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  279. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  280. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  281. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  282. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  283. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  284. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  285. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  286. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  287. package/blocks/newsletter/README.md +12 -0
  288. package/blocks/newsletter/block.json +117 -0
  289. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  290. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  291. package/blocks/newsletter/components/more-rows.tsx +30 -0
  292. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  293. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  294. package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
  295. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  296. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  297. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  298. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  299. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  300. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  301. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  302. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  303. package/blocks/nurture/components/nurture-form.tsx +5 -8
  304. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  305. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  306. package/blocks/objects/README.md +1 -1
  307. package/blocks/objects/block.json +13 -5
  308. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  309. package/blocks/objects/components/object-record-detail.tsx +8 -1
  310. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  311. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  312. package/blocks/pages/README.md +14 -0
  313. package/blocks/pages/block.json +123 -2
  314. package/blocks/pages/components/more-rows.tsx +30 -0
  315. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  316. package/blocks/pages/components/page-analytics.tsx +176 -0
  317. package/blocks/pages/components/page-clone.tsx +99 -0
  318. package/blocks/pages/components/page-domains.tsx +167 -0
  319. package/blocks/pages/components/page-editor.tsx +1 -1
  320. package/blocks/pages/components/page-generation-status.tsx +48 -0
  321. package/blocks/pages/components/page-tracking.tsx +137 -0
  322. package/blocks/pages/components/pages-screen.tsx +57 -16
  323. package/blocks/pages/components/seo-report.tsx +6 -1
  324. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  325. package/blocks/pages/lib/studio.ts +7 -0
  326. package/blocks/phone-numbers/README.md +74 -0
  327. package/blocks/phone-numbers/block.json +262 -0
  328. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  329. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  330. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  331. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  332. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  333. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  334. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  335. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  336. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  337. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  338. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  339. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  340. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  341. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  342. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  343. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  344. package/blocks/pickers/README.md +23 -0
  345. package/blocks/pickers/block.json +103 -0
  346. package/blocks/pickers/components/company-picker.tsx +90 -0
  347. package/blocks/pickers/components/contact-picker.tsx +131 -0
  348. package/blocks/pickers/components/member-picker.tsx +99 -0
  349. package/blocks/pickers/components/search-picker.tsx +98 -0
  350. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  351. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  352. package/blocks/pickers/lib/use-debounced.ts +13 -0
  353. package/blocks/programmatic-pages/README.md +82 -0
  354. package/blocks/programmatic-pages/block.json +292 -0
  355. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  356. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  357. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  358. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  359. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  360. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  361. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  362. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  363. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  364. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  365. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  366. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  367. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  368. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  369. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  370. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  371. package/blocks/quotes/README.md +38 -1
  372. package/blocks/quotes/block.json +170 -6
  373. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  374. package/blocks/quotes/components/manual-products.tsx +144 -0
  375. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  376. package/blocks/quotes/components/quote-builder.tsx +1 -1
  377. package/blocks/quotes/components/quote-documents.tsx +94 -0
  378. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  379. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  380. package/blocks/quotes/components/quote-list.tsx +28 -8
  381. package/blocks/quotes/components/quote-related.tsx +3 -2
  382. package/blocks/quotes/components/quote-settings.tsx +184 -0
  383. package/blocks/quotes/components/quote-templates.tsx +189 -0
  384. package/blocks/quotes/components/quote-viewer.tsx +23 -5
  385. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  386. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  387. package/blocks/quotes/lib/quote-admin.ts +264 -0
  388. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  389. package/blocks/radar/components/radar-overview.tsx +4 -4
  390. package/blocks/radar/components/radar-screen.tsx +2 -2
  391. package/blocks/sequencer/README.md +61 -0
  392. package/blocks/sequencer/block.json +200 -6
  393. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  394. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  395. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  396. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  397. package/blocks/sequencer/components/sequence-builder.tsx +32 -23
  398. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  399. package/blocks/sequencer/components/sequence-detail.tsx +10 -2
  400. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  401. package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
  402. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  403. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  404. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  405. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  406. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  407. package/blocks/sequencer/components/sequences-screen.tsx +39 -6
  408. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  409. package/blocks/sequencer/components/step-editor.tsx +21 -12
  410. package/blocks/sequencer/components/step-templates.tsx +224 -0
  411. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  412. package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
  413. package/blocks/signals/README.md +39 -0
  414. package/blocks/signals/block.json +287 -0
  415. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  416. package/blocks/signals/components/listener-form.tsx +184 -0
  417. package/blocks/signals/components/matched-posts.tsx +195 -0
  418. package/blocks/signals/components/post-engagements.tsx +114 -0
  419. package/blocks/signals/components/record-social-tab.tsx +49 -0
  420. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  421. package/blocks/signals/components/social-settings.tsx +128 -0
  422. package/blocks/signals/components/source-lists.tsx +83 -0
  423. package/blocks/signals/components/top-engagers.tsx +68 -0
  424. package/blocks/signals/components/x-integration.tsx +129 -0
  425. package/blocks/signals/lib/signals-types.ts +228 -0
  426. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  427. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  428. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  429. package/blocks/social-ads/README.md +8 -0
  430. package/blocks/social-ads/block.json +137 -4
  431. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  432. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  433. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  434. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  435. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  436. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  437. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  438. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  439. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  440. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  441. package/blocks/social-ads/components/post-composer.tsx +4 -2
  442. package/blocks/social-ads/components/post-detail.tsx +3 -0
  443. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  444. package/blocks/social-ads/components/social-performance.tsx +80 -0
  445. package/blocks/social-ads/components/social-screen.tsx +33 -6
  446. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  447. package/blocks/surveys/README.md +91 -0
  448. package/blocks/surveys/block.json +299 -0
  449. package/blocks/surveys/components/account-nps.tsx +69 -0
  450. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  451. package/blocks/surveys/components/org-summary.tsx +82 -0
  452. package/blocks/surveys/components/sending-domains.tsx +113 -0
  453. package/blocks/surveys/components/survey-form.tsx +126 -0
  454. package/blocks/surveys/components/survey-responses.tsx +145 -0
  455. package/blocks/surveys/components/survey-results.tsx +72 -0
  456. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  457. package/blocks/surveys/components/survey-send.tsx +162 -0
  458. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  459. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  460. package/blocks/surveys/lib/surveys-types.ts +228 -0
  461. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  462. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  463. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  464. package/blocks/visitors/components/snippet-install.tsx +18 -9
  465. package/blocks/visitors/components/traffic-chart.tsx +7 -6
  466. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  467. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  468. package/blocks/web-analytics/README.md +102 -0
  469. package/blocks/web-analytics/block.json +292 -0
  470. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  471. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  472. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  473. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  474. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  475. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  476. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  477. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  478. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  479. package/blocks/web-analytics/components/report-card.tsx +71 -0
  480. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  481. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  482. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  483. package/blocks/web-analytics/lib/page-path.ts +14 -0
  484. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  485. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  486. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  487. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  488. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  489. package/blocks/webchat/components/conversation-list.tsx +1 -1
  490. package/blocks/workbench/README.md +54 -2
  491. package/blocks/workbench/block.json +234 -5
  492. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  493. package/blocks/workbench/components/run-progress.tsx +1 -1
  494. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  495. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  496. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  497. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  498. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  499. package/blocks/workbench/components/workbench-grid.tsx +71 -8
  500. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  501. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  502. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  503. package/blocks/workbench/components/workbench-screen.tsx +65 -22
  504. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  505. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  506. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  507. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  508. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  509. package/blocks/workbench/lib/api-status.ts +8 -0
  510. package/blocks/workbench/lib/automation-types.ts +257 -0
  511. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  512. package/dist/cli.js +744 -394
  513. package/dist/index.browser.js +1 -1
  514. package/dist/index.browser.js.map +1 -1
  515. package/dist/index.browser.mjs +1 -1
  516. package/dist/index.browser.mjs.map +1 -1
  517. package/dist/index.d.mts +2 -2
  518. package/dist/index.d.ts +2 -2
  519. package/dist/index.js +1 -1
  520. package/dist/index.js.map +1 -1
  521. package/dist/index.mjs +1 -1
  522. package/dist/index.mjs.map +1 -1
  523. package/dist/react.browser.js +1 -1
  524. package/dist/react.browser.js.map +1 -1
  525. package/dist/react.browser.mjs +1 -1
  526. package/dist/react.browser.mjs.map +1 -1
  527. package/dist/react.d.mts +2 -2
  528. package/dist/react.d.ts +2 -2
  529. package/dist/react.js +1 -1
  530. package/dist/react.js.map +1 -1
  531. package/dist/react.mjs +1 -1
  532. package/dist/react.mjs.map +1 -1
  533. package/dist/types/blocks/auth.d.ts +45 -0
  534. package/dist/types/blocks/brand.d.ts +11 -2
  535. package/dist/types/blocks/create.d.ts +11 -4
  536. package/dist/types/blocks/doctor.d.ts +1 -0
  537. package/dist/types/blocks/flows.d.ts +7 -1
  538. package/dist/types/blocks/orgBrand.d.ts +34 -0
  539. package/dist/types/cli.d.ts +3 -0
  540. package/dist/types/version.d.ts +2 -2
  541. package/docs/api/desk.d.ts +2 -2
  542. package/docs/api/desk.md +3 -3
  543. package/docs/api/forms.md +1 -1
  544. package/package.json +1 -1
  545. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  546. package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
  547. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  548. package/stacks/6sense.json +37 -4
  549. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  550. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  551. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  552. package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
  553. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  554. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  555. package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
  556. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  557. package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
  558. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  559. package/stacks/apollo.json +68 -9
  560. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  561. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  562. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  563. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  564. package/stacks/calendly.json +8 -4
  565. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  566. package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
  567. package/stacks/clay/pages/lists.tsx.tmpl +24 -0
  568. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  569. package/stacks/clay.json +16 -3
  570. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  571. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  572. package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
  573. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  574. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  575. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  576. package/stacks/gong.json +23 -6
  577. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  578. package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
  579. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  580. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  581. package/stacks/hootsuite.json +19 -4
  582. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  583. package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
  584. package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
  585. package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
  586. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  587. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  588. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  589. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  590. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  591. package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
  592. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  593. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  594. package/stacks/hubspot.json +66 -7
  595. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  596. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  597. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  598. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  599. package/stacks/intercom.json +18 -4
  600. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  601. package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
  602. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  603. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  604. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  605. package/stacks/mailchimp.json +22 -6
  606. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  607. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  608. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  609. package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
  610. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  611. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  612. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  613. package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
  614. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  615. package/stacks/outreach.json +38 -9
  616. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  617. package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
  618. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  619. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  620. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  621. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  622. package/stacks/pipedrive.json +14 -2
  623. package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
  624. package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
  625. package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
  626. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  627. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  628. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  629. package/stacks/salesforce.json +17 -3
  630. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  631. package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
  632. package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
  633. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  634. package/stacks/zoominfo.json +36 -3
  635. package/styles/styles.css +1 -1
  636. package/templates/auth/auth-route.ts.tmpl +16 -0
  637. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  638. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  639. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  640. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  641. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  642. package/templates/auth/middleware.ts.tmpl +14 -0
  643. package/templates/next-app/README.md.tmpl +5 -0
  644. package/templates/next-app/app/globals.css.tmpl +349 -64
  645. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  646. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
@@ -24,6 +24,21 @@ Defaults are unchanged from this release: both are on.
24
24
 
25
25
  Every detail opens as a full page with `&view=full` (the Open full page button).
26
26
 
27
+ **Management (new, on by default; `showManagement={false}` hides the three buttons).** The toolbar gains:
28
+
29
+ - **New message** (`ComposeMessage`): write a NEW email or text. The sender is never defaulted: you choose a mailbox
30
+ (from `GET /inbox/mailboxes/paginated`; ones you may not send as are listed disabled with the reason) or an SMS number,
31
+ and the confirm dialog names it ("from hamza@acme.com to maya@northwind.io, 2 recipients"). Sending is **external**.
32
+ - **Search all** (`InboxSearch`): server-side search of every email thread (across all your mailboxes) or LinkedIn
33
+ conversation, beyond the page the list loaded; pick a result to open it. SMS has no search route in the API. A LinkedIn result opens by the row's conversation id (`conversation_id`, else `id`); that this equals the unified inbox's LinkedIn thread id has not been checked against a live HeyReach payload, so if a result opens as "Conversation not found", the id differs.
34
+ - **Manage**: the tag library (`TagManager`: create, rename or recolour, delete; built-in tags follow the API's policy) and
35
+ `BlockedSenders` (block per mailbox, unblock). The tag list it loads also feeds the Tag filter and the tag picker.
36
+ - In an open conversation, **Manage conversation** (`ThreadActions`): mark read (and unread on LinkedIn), remove a tag,
37
+ unassign a teammate, block the sender.
38
+
39
+ A conversation this key cannot see (an org key sees shared threads only) or that was removed answers a uniform 404; the
40
+ conversation view says "Conversation not found" instead of showing a generic failure.
41
+
27
42
  ## The flow, in order
28
43
 
29
44
  1. **List** `GET /inbox?channel=&status=&page=&page_size=` (`InboxList`). Each row has `id` and `channel`.
@@ -52,7 +67,8 @@ IDs passed between steps: `thread.id` + `thread.channel` (list -> view -> draft/
52
67
 
53
68
  - Connect a mailbox in the graph8 app (Google / Microsoft OAuth exists only there; see the `mailboxes` block).
54
69
  Until then the inbox has no email threads. `where: "app"`.
55
- - Give the proxy's API key `inbox:read`, `inbox:write` and `inbox:run` (and `campaigns:read` for the SMS sender picker).
70
+ - Give the proxy's API key `inbox:read`, `inbox:write` and `inbox:run` (and `campaigns:read` for the SMS sender picker,
71
+ `inbox:delete` to delete a tag, remove a tag or teammate from a conversation or unblock a sender, `workflows:read` to list tags).
56
72
  - Credits for AI drafts (402 when none; there is no balance read, so the confirm dialog shows no estimate).
57
73
 
58
74
  ## Defaults (the agent does not need to ask)
@@ -62,9 +78,13 @@ IDs passed between steps: `thread.id` + `thread.channel` (list -> view -> draft/
62
78
  - A reply leaves from the thread's own mailbox, number or LinkedIn account and goes to the thread's contact.
63
79
  - No assignee filter: the API cannot filter by an arbitrary assignee email (an org key owns no threads), so
64
80
  filter `thread.assignees` client-side if you need it.
65
- - Ask the human only about **tags**: there is no list-tags operation. Pass `availableTags` from your own
66
- state (collect them from `thread.tags` of loaded rows, or keep a list); tags created in the panel are
67
- offered for the session.
81
+ - Tags: `TagManager` lists them (`GET /workflows/inbox-tags`, scope `workflows:read`), so no input is needed. Pass
82
+ `availableTags` only to seed the pickers before the Manage sheet is opened; tags on loaded threads are always offered.
83
+ - A NEW message is never sent without an explicit sender; the API refuses a compose without `from_mailbox` or
84
+ `from_number`, and this block does not guess one.
85
+ - Not offered, because the API has no such operation: snooze, archive, mark an email or SMS unread. Not offered by
86
+ choice (see the PR): composing a NEW LinkedIn message (`/inbox/linkedin/compose` only continues an existing
87
+ conversation, which `ReplyComposer` already does), starring chats, internal comments, saving compose drafts.
68
88
 
69
89
  ## Gotchas
70
90
 
@@ -88,9 +108,30 @@ IDs passed between steps: `thread.id` + `thread.channel` (list -> view -> draft/
88
108
  | `aiInbox.createInboxTag` | POST /inbox/tags | write | TagAndAssign |
89
109
  | `inbox.tagReply` | POST /inbox/{reply_id}/tag | external | TagAndAssign |
90
110
  | `inbox.assignReply` | POST /inbox/{reply_id}/assign | external | TagAndAssign |
111
+ | `aiInbox.listInboxMailboxesPaginated` | GET /inbox/mailboxes/paginated | read | ComposeMessage, InboxSearch, BlockedSenders |
112
+ | `aiInbox.composeInboxEmail` | POST /inbox/emails/compose | external | ComposeMessage |
113
+ | `aiInbox.composeInboxSms` | POST /inbox/sms/compose | external | ComposeMessage |
114
+ | `inbox.searchInboxEmails` | POST /inbox/emails/search | read | InboxSearch |
115
+ | `aiInbox.searchInboxLinkedinConversations` | POST /inbox/linkedin/conversations/search | read | InboxSearch |
116
+ | `workflows.listInboxTags` | GET /workflows/inbox-tags | read | TagManager |
117
+ | `aiInbox.updateInboxTag` | PUT /inbox/tags/{tag_id} | write | TagManager (changed fields only) |
118
+ | `aiInbox.deleteInboxTag` | DELETE /inbox/tags/{tag_id} | destructive | TagManager |
119
+ | `aiInbox.markInboxEmailRead` | POST /inbox/emails/{email_id}/mark-read | write | ThreadActions |
120
+ | `aiInbox.markInboxSmsRead` | POST /inbox/sms/{thread_id}/mark-read | write | ThreadActions |
121
+ | `aiInbox.markInboxLinkedinConversationRead` / `Unread` | POST /inbox/linkedin/accounts/{account_id}/conversations/{id}/read, /unread | write | ThreadActions |
122
+ | `aiInbox.removeChannelTag` | DELETE /inbox/channels/{channel_id}/tags | destructive | ThreadActions |
123
+ | `aiInbox.unassignChannel` | DELETE /inbox/channels/{channel_id}/assign | destructive | ThreadActions |
124
+ | `aiInbox.blockInboxSender` | POST /inbox/blocked-senders | write | ThreadActions, BlockedSenders |
125
+ | `aiInbox.listInboxBlockedSenders` | GET /inbox/blocked-senders | read | BlockedSenders |
126
+ | `aiInbox.unblockInboxSender` | DELETE /inbox/blocked-senders/{blocked_sender_id} | destructive | BlockedSenders |
91
127
 
92
128
  ## Recipes (server side)
93
129
 
94
130
  - `01-triage-and-reply.ts`: list, read, AI draft (billable), send (external).
95
131
  - `02-tag-and-assign.ts`: create a tag, apply it, assign a teammate.
96
132
  - `03-hot-contacts-digest.ts`: read-only digest of engaging contacts and unowned threads.
133
+
134
+ ## Cross-block glue
135
+
136
+
137
+ - `RecordConversationsTab` (`components/record-conversations-tab.tsx`): the email conversations with a contact. The inbox has no filter by contact id, so it matches the contact's email through `searchInboxEmails` (`search_emails`) across the mailboxes from `listInboxMailboxes`, 10 per page with "Load more"; opening a row reads `getReply`, and the API's uniform 404 for a hidden thread shows "not available to you" (no retry). `extraDetailTabs` entry for ContactsScreen.
@@ -2,13 +2,17 @@
2
2
  "$schema": "../block.schema.json",
3
3
  "id": "inbox",
4
4
  "title": "AI inbox",
5
- "summary": "Unified reply inbox for email, SMS and LinkedIn: thread list, conversation view, AI-drafted replies, tagging and assignment.",
5
+ "summary": "Unified reply inbox for email, SMS and LinkedIn: threads, AI-drafted replies, new messages, search, read state, tag library, tagging and assignment.",
6
6
  "features": [
7
7
  "one queue across email, SMS and LinkedIn with channel and status filters",
8
8
  "conversation view with unsent drafts flagged",
9
9
  "AI draft (confirmed, billable) and send (confirmed, external) in one composer",
10
10
  "tag and assign teammates",
11
- "drop-in two-pane InboxScreen"
11
+ "drop-in two-pane InboxScreen",
12
+ "compose a new email or text from an explicit sender (confirmed)",
13
+ "server-side search across email and LinkedIn",
14
+ "tag library (create, rename, delete) and blocked senders",
15
+ "mark read or unread, remove a tag, unassign, block a sender"
12
16
  ],
13
17
  "dependsOn": [
14
18
  "crm",
@@ -16,15 +20,33 @@
16
20
  ],
17
21
  "operations": [
18
22
  "assign_reply_inbox__reply_id__assign_post",
23
+ "block_inbox_sender_inbox_blocked_senders_post",
24
+ "compose_inbox_email_inbox_emails_compose_post",
25
+ "compose_inbox_sms_inbox_sms_compose_post",
19
26
  "create_inbox_tag_inbox_tags_post",
27
+ "delete_inbox_tag_inbox_tags__tag_id__delete",
20
28
  "generate_inbox_draft_inbox_drafts_generate_post",
21
29
  "get_reply_inbox__reply_id__get",
30
+ "list_inbox_blocked_senders_inbox_blocked_senders_get",
22
31
  "list_inbox_inbox_get",
23
32
  "list_inbox_linkedin_accounts_inbox_linkedin_accounts_get",
33
+ "list_inbox_mailboxes_inbox_mailboxes_get",
34
+ "list_inbox_mailboxes_paginated_inbox_mailboxes_paginated_get",
35
+ "list_inbox_tags_workflows_inbox_tags_get",
24
36
  "list_inbox_users_inbox_users_get",
25
37
  "list_sms_phone_numbers_sms_phone_numbers_get",
38
+ "mark_inbox_email_read_inbox_emails__email_id__mark_read_post",
39
+ "mark_inbox_linkedin_conversation_read_inbox_linkedin_accounts__account_id__conversations__conversation_id__read_post",
40
+ "mark_inbox_linkedin_conversation_unread_inbox_linkedin_accounts__account_id__conversations__conversation_id__unread_post",
41
+ "mark_inbox_sms_read_inbox_sms__thread_id__mark_read_post",
42
+ "remove_channel_tag_inbox_channels__channel_id__tags_delete",
43
+ "search_inbox_emails_inbox_emails_search_post",
44
+ "search_inbox_linkedin_conversations_inbox_linkedin_conversations_search_post",
26
45
  "send_reply_inbox__reply_id__send_post",
27
- "tag_reply_inbox__reply_id__tag_post"
46
+ "tag_reply_inbox__reply_id__tag_post",
47
+ "unassign_channel_inbox_channels__channel_id__assign_delete",
48
+ "unblock_inbox_sender_inbox_blocked_senders__blocked_sender_id__delete",
49
+ "update_inbox_tag_inbox_tags__tag_id__put"
28
50
  ],
29
51
  "components": [
30
52
  {
@@ -81,10 +103,88 @@
81
103
  "external"
82
104
  ]
83
105
  },
106
+ {
107
+ "name": "ComposeMessage",
108
+ "file": "components/compose-message.tsx",
109
+ "purpose": "Write a NEW email or text (not a reply): choose a sender you may send as (never defaulted), recipients, subject and body; sending reaches a real person and is confirmed with the sender and recipient count.",
110
+ "operations": [
111
+ "compose_inbox_email_inbox_emails_compose_post",
112
+ "compose_inbox_sms_inbox_sms_compose_post",
113
+ "list_inbox_mailboxes_paginated_inbox_mailboxes_paginated_get",
114
+ "list_sms_phone_numbers_sms_phone_numbers_get"
115
+ ],
116
+ "tiers": [
117
+ "read",
118
+ "external"
119
+ ]
120
+ },
121
+ {
122
+ "name": "InboxSearch",
123
+ "file": "components/inbox-search.tsx",
124
+ "purpose": "Search every email thread or LinkedIn conversation on the server (not just the loaded page) and open a result.",
125
+ "operations": [
126
+ "list_inbox_mailboxes_paginated_inbox_mailboxes_paginated_get",
127
+ "search_inbox_emails_inbox_emails_search_post",
128
+ "search_inbox_linkedin_conversations_inbox_linkedin_conversations_search_post"
129
+ ],
130
+ "tiers": [
131
+ "read"
132
+ ]
133
+ },
134
+ {
135
+ "name": "TagManager",
136
+ "file": "components/tag-manager.tsx",
137
+ "purpose": "The tag library: list, create, rename or recolour (only changed fields are sent) and delete (confirmed) inbox tags; built-in tags follow the API's edit and delete policy.",
138
+ "operations": [
139
+ "create_inbox_tag_inbox_tags_post",
140
+ "delete_inbox_tag_inbox_tags__tag_id__delete",
141
+ "list_inbox_tags_workflows_inbox_tags_get",
142
+ "update_inbox_tag_inbox_tags__tag_id__put"
143
+ ],
144
+ "tiers": [
145
+ "read",
146
+ "write",
147
+ "destructive"
148
+ ]
149
+ },
150
+ {
151
+ "name": "ThreadActions",
152
+ "file": "components/thread-actions.tsx",
153
+ "purpose": "Per-conversation housekeeping: mark read (and unread on LinkedIn), remove a tag or unassign a teammate (confirmed), block the sender.",
154
+ "operations": [
155
+ "block_inbox_sender_inbox_blocked_senders_post",
156
+ "mark_inbox_email_read_inbox_emails__email_id__mark_read_post",
157
+ "mark_inbox_linkedin_conversation_read_inbox_linkedin_accounts__account_id__conversations__conversation_id__read_post",
158
+ "mark_inbox_linkedin_conversation_unread_inbox_linkedin_accounts__account_id__conversations__conversation_id__unread_post",
159
+ "mark_inbox_sms_read_inbox_sms__thread_id__mark_read_post",
160
+ "remove_channel_tag_inbox_channels__channel_id__tags_delete",
161
+ "unassign_channel_inbox_channels__channel_id__assign_delete"
162
+ ],
163
+ "tiers": [
164
+ "write",
165
+ "destructive"
166
+ ]
167
+ },
168
+ {
169
+ "name": "BlockedSenders",
170
+ "file": "components/blocked-senders.tsx",
171
+ "purpose": "Blocked senders per mailbox: list, block one, unblock one (confirmed).",
172
+ "operations": [
173
+ "block_inbox_sender_inbox_blocked_senders_post",
174
+ "list_inbox_blocked_senders_inbox_blocked_senders_get",
175
+ "list_inbox_mailboxes_paginated_inbox_mailboxes_paginated_get",
176
+ "unblock_inbox_sender_inbox_blocked_senders__blocked_sender_id__delete"
177
+ ],
178
+ "tiers": [
179
+ "read",
180
+ "write",
181
+ "destructive"
182
+ ]
183
+ },
84
184
  {
85
185
  "name": "InboxScreen",
86
186
  "file": "components/inbox-screen.tsx",
87
- "purpose": "Finished two-pane inbox composing the list, thread view, composer and tag/assign panel.",
187
+ "purpose": "Finished two-pane inbox composing the list, thread view, composer and tag/assign panel, plus New message, Search all and Manage (tags, blocked senders) and per-conversation housekeeping.",
88
188
  "operations": [],
89
189
  "tiers": []
90
190
  },
@@ -94,6 +194,19 @@
94
194
  "purpose": "Small status pill shared by the thread view and tag panel.",
95
195
  "operations": [],
96
196
  "tiers": []
197
+ },
198
+ {
199
+ "name": "RecordConversationsTab",
200
+ "file": "components/record-conversations-tab.tsx",
201
+ "purpose": "The email conversations with one contact (matched by address), with Load more and a calm answer for a thread that 404s; an extraDetailTabs entry for ContactsScreen.",
202
+ "operations": [
203
+ "list_inbox_mailboxes_inbox_mailboxes_get",
204
+ "search_inbox_emails_inbox_emails_search_post",
205
+ "get_reply_inbox__reply_id__get"
206
+ ],
207
+ "tiers": [
208
+ "read"
209
+ ]
97
210
  }
98
211
  ],
99
212
  "hooks": [
@@ -112,6 +225,14 @@
112
225
  "operations": [
113
226
  "get_reply_inbox__reply_id__get"
114
227
  ]
228
+ },
229
+ {
230
+ "name": "useInboxMailboxes",
231
+ "file": "hooks/use-inbox-mailboxes.ts",
232
+ "purpose": "Every mailbox the caller can see and whether they may send as it, read page by page; reports when the page cap truncated the list.",
233
+ "operations": [
234
+ "list_inbox_mailboxes_paginated_inbox_mailboxes_paginated_get"
235
+ ]
115
236
  }
116
237
  ],
117
238
  "lib": [
@@ -122,6 +243,10 @@
122
243
  {
123
244
  "file": "lib/idempotency.ts",
124
245
  "purpose": "useIdempotencyKey: one Idempotency-Key per distinct payload, rotated when the body changes so an edited retry never hits a 409."
246
+ },
247
+ {
248
+ "file": "lib/inbox-manage.ts",
249
+ "purpose": "Compose validation and HTML conversion, mailbox, tag, blocked-sender and search-result readers, per-channel route helpers and the hidden-thread 404 check."
125
250
  }
126
251
  ],
127
252
  "recipes": [
@@ -163,9 +288,11 @@
163
288
  ],
164
289
  "scopes": [
165
290
  "campaigns:read",
291
+ "inbox:delete",
166
292
  "inbox:read",
167
293
  "inbox:run",
168
- "inbox:write"
294
+ "inbox:write",
295
+ "workflows:read"
169
296
  ],
170
297
  "defaults": [
171
298
  {
@@ -184,9 +311,9 @@
184
311
  "askHuman": false
185
312
  },
186
313
  {
187
- "decision": "Where to get the list of tags the picker offers (no list-tags operation exists)",
188
- "default": "Pass availableTags from your own state; tags created in the panel are added for the session",
189
- "askHuman": true
314
+ "decision": "Where the tag picker gets its tags",
315
+ "default": "GET /workflows/inbox-tags through TagManager (the Manage sheet); pass availableTags to seed it before the sheet is opened. Tags on the loaded threads are always included",
316
+ "askHuman": false
190
317
  },
191
318
  {
192
319
  "decision": "Filter by assignee",
@@ -197,6 +324,16 @@
197
324
  "decision": "Which mailbox, number or LinkedIn account a reply is sent from",
198
325
  "default": "The thread's own (mailbox / from_number / linkedin_account_id on the thread); an SMS or LinkedIn thread without one shows a sender picker",
199
326
  "askHuman": false
327
+ },
328
+ {
329
+ "decision": "Which sender a NEW message goes from",
330
+ "default": "Never defaulted: the person chooses a mailbox (only ones they may send as are selectable) or an SMS number, and the confirm dialog names it. The API refuses a compose without a sender",
331
+ "askHuman": false
332
+ },
333
+ {
334
+ "decision": "Snooze and archive",
335
+ "default": "Not offered: the API has no snooze or archive operation for inbox threads. Use tags and assignment to triage",
336
+ "askHuman": false
200
337
  }
201
338
  ],
202
339
  "peerDependencies": {
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ /**
4
+ * BlockedSenders: the senders whose mail is kept out of a mailbox's inbox. See them, block one, unblock one.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. GET /inbox/blocked-senders (aiInbox.listInboxBlockedSenders) READ
8
+ * 2. GET /inbox/mailboxes/paginated (aiInbox.listInboxMailboxesPaginated) READ - which mailbox the block applies to
9
+ * 3. POST /inbox/blocked-senders (aiInbox.blockInboxSender) write
10
+ * 4. DELETE /inbox/blocked-senders/{id} (aiInbox.unblockInboxSender) DESTRUCTIVE -> ConfirmSpendDialog first
11
+ * A block is per mailbox, so the form makes you choose the mailbox; nothing is guessed.
12
+ */
13
+ import { useState } from "react";
14
+ import { Ban, Trash2 } from "lucide-react";
15
+ import { Button, Input, Skeleton } from "@graph8/sdk/ui";
16
+ import { ApiError } from "@/g8/components/api-error";
17
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
18
+ import { EmptyState } from "@/g8/components/empty-state";
19
+ import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
20
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
21
+ import { FirstNotice } from "@/g8/components/paging-notices";
22
+ import { useAllPages } from "@/g8/hooks/use-all-pages";
23
+ import { hasNextOf } from "@/g8/lib/paging";
24
+ import { useInboxMailboxes } from "../hooks/use-inbox-mailboxes";
25
+ import { blockedSenders, isEmail } from "../lib/inbox-manage";
26
+ import type { BlockedSender } from "../lib/inbox-manage";
27
+
28
+ export interface BlockedSendersProps {
29
+ className?: string;
30
+ }
31
+
32
+ export function BlockedSenders({ className }: BlockedSendersProps) {
33
+ const [mailbox, setMailbox] = useState("");
34
+ const [email, setEmail] = useState("");
35
+ const [reason, setReason] = useState("");
36
+ const [removing, setRemoving] = useState<BlockedSender | null>(null);
37
+ const blocked = useAllPages<BlockedSender>(
38
+ async (page, signal) => {
39
+ const env = await g8Get("/inbox/blocked-senders", { page, page_size: 100 }, { signal });
40
+ const rows = blockedSenders(env);
41
+ return { rows, hasNext: hasNextOf(env, 100, rows.length) };
42
+ },
43
+ [],
44
+ { pageSize: 100 },
45
+ );
46
+ const list = { data: blocked.loaded ? blocked.items : undefined, loading: blocked.loading, error: blocked.error, refetch: blocked.refetch };
47
+ const boxes = useInboxMailboxes();
48
+ const block = useG8Mutation(async () => {
49
+ await g8Post("/inbox/blocked-senders", { mailbox, sender_email: email.trim(), ...(reason.trim() ? { reason: reason.trim() } : {}) });
50
+ setEmail("");
51
+ setReason("");
52
+ blocked.refetch();
53
+ }, { invalidates: ["inbox"] });
54
+ const unblock = useG8Mutation(async (s: BlockedSender) => {
55
+ await g8Delete(`/inbox/blocked-senders/${encodeURIComponent(s.id)}`);
56
+ setRemoving(null);
57
+ blocked.refetch();
58
+ }, { invalidates: ["inbox"], onError: () => setRemoving(null) });
59
+
60
+ const valid = Boolean(mailbox) && isEmail(email);
61
+ return (
62
+ <div className={`space-y-3 ${className ?? ""}`}>
63
+ <h3 className="font-medium">Blocked senders</h3>
64
+ <form className="space-y-2 rounded-md border border-border p-3" onSubmit={(e) => { e.preventDefault(); if (valid) void block.mutate(undefined); }}>
65
+ <select aria-label="Mailbox" className="h-9 w-full rounded-md border border-border bg-background px-2 text-sm" value={mailbox} disabled={boxes.loading} onChange={(e) => setMailbox(e.target.value)}>
66
+ <option value="">{boxes.loading ? "Loading mailboxes..." : "Choose the mailbox to block them in"}</option>
67
+ {(boxes.data ?? []).map((m) => <option key={m.address} value={m.address}>{m.label}</option>)}
68
+ </select>
69
+ {boxes.error ? <ApiError error={boxes.error} title="Could not load your mailboxes" onRetry={() => void boxes.refetch()} /> : null}
70
+ <Input aria-label="Sender email" placeholder="sender@company.com" value={email} onChange={(e) => setEmail(e.target.value)} />
71
+ <Input aria-label="Reason" placeholder="Reason (optional)" value={reason} onChange={(e) => setReason(e.target.value)} />
72
+ {block.error ? <ApiError error={block.error} title="Could not block the sender" /> : null}
73
+ <div className="flex justify-end"><Button type="submit" size="sm" disabled={!valid || block.loading}><Ban className="mr-1 size-4" aria-hidden />Block sender</Button></div>
74
+ </form>
75
+ {list.error ? <ApiError error={list.error} title="Could not load blocked senders" onRetry={() => void list.refetch()} /> : null}
76
+ {list.loading && !list.data ? <Skeleton className="h-10 w-full" /> : null}
77
+ {list.data && list.data.length === 0 ? <EmptyState compact title="Nobody is blocked" description="Mail from blocked senders never reaches the inbox." /> : null}
78
+ {list.data && list.data.length > 0 ? (
79
+ <ul aria-label="Blocked senders" className="divide-y rounded-md border border-border text-sm">
80
+ {list.data.map((s) => (
81
+ <li key={s.id} className="flex items-center gap-2 p-2">
82
+ <span className="min-w-0 flex-1"><span className="font-medium">{s.email}</span><span className="block truncate text-xs text-muted-foreground">{s.mailbox}{s.reason ? ` - ${s.reason}` : ""}</span></span>
83
+ <Button size="sm" variant="ghost" aria-label={`Unblock ${s.email}`} onClick={() => setRemoving(s)}><Trash2 className="size-4" aria-hidden /></Button>
84
+ </li>
85
+ ))}
86
+ </ul>
87
+ ) : null}
88
+ <FirstNotice shown={blocked.items.length} limit={0} force={blocked.truncated} noun="blocked senders" />
89
+ {unblock.error ? <ApiError error={unblock.error} title="Could not unblock the sender" /> : null}
90
+ <ConfirmSpendDialog
91
+ open={removing != null}
92
+ tier="destructive"
93
+ title={`Unblock ${removing?.email ?? ""}?`}
94
+ description="Their mail starts reaching the inbox again. Mail that was held back while they were blocked is not recovered."
95
+ confirmLabel="Unblock"
96
+ affected={1}
97
+ confirming={unblock.loading}
98
+ onConfirm={() => removing && void unblock.mutate(removing)}
99
+ onCancel={() => setRemoving(null)}
100
+ />
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,193 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ComposeMessage: write a NEW message (not a reply) to someone, by email or by text, from a sender you choose.
5
+ *
6
+ * SDK operations, in order:
7
+ * Email: 1. GET /inbox/mailboxes/paginated (aiInbox.listInboxMailboxesPaginated) READ - the mailboxes you may send as
8
+ * (each row says `can_send`, and why not when it cannot)
9
+ * 2. POST /inbox/emails/compose (aiInbox.composeInboxEmail) EXTERNAL -> ConfirmSpendDialog first
10
+ * SMS: 1. GET /sms/phone-numbers (channels.listSmsPhoneNumbers) READ - the numbers you own
11
+ * 2. POST /inbox/sms/compose (aiInbox.composeInboxSms) EXTERNAL -> ConfirmSpendDialog first
12
+ *
13
+ * Sending reaches a real person and cannot be recalled. The sender is NEVER defaulted: the API refuses a compose
14
+ * without `from_mailbox` / `from_number` (and, for a reply, `from_address`), so this form makes the person choose one
15
+ * and shows it in the confirm dialog ("from hamza@acme.com to maya@northwind.io"). A mailbox the caller may not send
16
+ * as is listed but disabled, with the reason. A fixed Idempotency-Key per distinct payload makes a double click or a
17
+ * retry after a timeout safe; it rotates after a success.
18
+ *
19
+ * LinkedIn is not offered: `POST /inbox/linkedin/compose` only continues an EXISTING conversation (it needs the
20
+ * conversation id), which is what ReplyComposer already does from the thread.
21
+ */
22
+ import { useState } from "react";
23
+ import { Button, Input, Textarea } from "@graph8/sdk/ui";
24
+ import { ApiError } from "@/g8/components/api-error";
25
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
26
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
27
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
28
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
29
+ import { useBrand } from "@/g8/hooks/use-brand";
30
+ import { FirstNotice } from "@/g8/components/paging-notices";
31
+ import { useInboxMailboxes } from "../hooks/use-inbox-mailboxes";
32
+ import { useIdempotencyKey } from "../lib/idempotency";
33
+ import { senderOptions } from "../lib/inbox-types";
34
+ import {
35
+ emailComposeProblems,
36
+ normalizePhone,
37
+ smsComposeProblems,
38
+ splitAddresses,
39
+ textToHtml,
40
+ } from "../lib/inbox-manage";
41
+
42
+ export type ComposeChannel = "email" | "sms";
43
+
44
+ export interface ComposeMessageProps {
45
+ defaultChannel?: ComposeChannel;
46
+ /** Prefill the recipient (an email address, or a phone number for SMS). */
47
+ defaultTo?: string;
48
+ defaultSubject?: string;
49
+ /** Called after the message was accepted by the API. */
50
+ onSent?: (channel: ComposeChannel) => void;
51
+ onCancel?: () => void;
52
+ className?: string;
53
+ }
54
+
55
+ const SELECT = "h-9 w-full rounded-md border border-border bg-background px-2 text-sm";
56
+
57
+ export function ComposeMessage({ defaultChannel = "email", defaultTo = "", defaultSubject = "", onSent, onCancel, className }: ComposeMessageProps) {
58
+ const brand = useBrand();
59
+ const [channel, setChannel] = useState<ComposeChannel>(defaultChannel);
60
+ const [from, setFrom] = useState("");
61
+ const [to, setTo] = useState(defaultTo);
62
+ const [cc, setCc] = useState("");
63
+ const [subject, setSubject] = useState(defaultSubject);
64
+ const [body, setBody] = useState("");
65
+ const [signature, setSignature] = useState(true);
66
+ const [confirming, setConfirming] = useState(false);
67
+ const [sent, setSent] = useState<string | null>(null);
68
+ const idempotency = useIdempotencyKey(JSON.stringify([channel, from, to, cc, subject, body, signature]));
69
+
70
+ const mailboxes = useInboxMailboxes({ enabled: channel === "email" });
71
+ const numbers = useG8Query(
72
+ async (signal) => senderOptions("sms", (await g8Get("/sms/phone-numbers", undefined, { signal })).data),
73
+ [],
74
+ { enabled: channel === "sms" },
75
+ );
76
+
77
+ const send = useG8Mutation(async () => {
78
+ // Belt and braces: the form already blocks this, but a send without an explicit sender must never leave.
79
+ if (!from.trim()) throw new Error("Choose a sender before sending.");
80
+ const chosen = channel === "email" ? (mailboxes.data ?? []).find((m) => m.address === from) : undefined;
81
+ if (chosen && !chosen.canSend) throw new Error(`You are not allowed to send as ${chosen.address} (${chosen.reason ?? "not authorised"}).`);
82
+ const key = { idempotencyKey: idempotency.get() };
83
+ if (channel === "email") {
84
+ return g8Post("/inbox/emails/compose", {
85
+ from_mailbox: from,
86
+ to: splitAddresses(to),
87
+ cc: splitAddresses(cc),
88
+ subject: subject.trim(),
89
+ content: textToHtml(body),
90
+ include_signature: signature,
91
+ save_as_draft: false,
92
+ }, key);
93
+ }
94
+ return g8Post("/inbox/sms/compose", { from_number: from, to_number: normalizePhone(to), body: body.trim(), save_as_draft: false }, key);
95
+ }, { invalidates: ["inbox"] });
96
+
97
+ const problems = channel === "email" ? emailComposeProblems({ from, to, cc, subject, body }) : smsComposeProblems({ from, to, body });
98
+ const recipients = channel === "email" ? splitAddresses(to) : to.trim() ? [to.trim()] : [];
99
+ const switchTo = (next: ComposeChannel) => {
100
+ if (next === channel) return;
101
+ setChannel(next);
102
+ setFrom(""); // a sender of one channel is never a sender of the other
103
+ setTo("");
104
+ setCc("");
105
+ setSent(null);
106
+ send.reset();
107
+ };
108
+
109
+ const confirm = async () => {
110
+ try {
111
+ await send.mutateAsync(undefined);
112
+ idempotency.rotate();
113
+ setSent(channel === "email" ? "Email sent." : "Text sent.");
114
+ setBody("");
115
+ setSubject("");
116
+ setConfirming(false);
117
+ onSent?.(channel);
118
+ } catch {
119
+ setConfirming(false); // the error renders below; the form keeps what was typed so the person can retry
120
+ }
121
+ };
122
+
123
+ const sendersLoading = channel === "email" ? mailboxes.loading : numbers.loading;
124
+ const senderError = channel === "email" ? mailboxes.error : numbers.error;
125
+ const senderChoices =
126
+ channel === "email"
127
+ ? (mailboxes.data ?? []).map((m) => ({ value: m.address, label: m.canSend ? m.label : `${m.label} (${m.reason})`, disabled: !m.canSend }))
128
+ : (numbers.data ?? []).map((n) => ({ value: n.value, label: n.label, disabled: false }));
129
+ const noSenders = !sendersLoading && !senderError && senderChoices.length === 0;
130
+
131
+ return (
132
+ <div className={`space-y-3 ${className ?? ""}`}>
133
+ <div role="group" aria-label="Channel" className="flex gap-1">
134
+ <Button size="sm" variant={channel === "email" ? "default" : "outline"} onClick={() => switchTo("email")}>Email</Button>
135
+ <Button size="sm" variant={channel === "sms" ? "default" : "outline"} onClick={() => switchTo("sms")}>Text message</Button>
136
+ </div>
137
+
138
+ <label className="block space-y-1 text-sm">
139
+ <span className="font-medium">{channel === "email" ? "From" : "Send from"}</span>
140
+ <select aria-label={channel === "email" ? "From mailbox" : "From number"} className={SELECT} value={from} disabled={sendersLoading} onChange={(e) => setFrom(e.target.value)}>
141
+ <option value="">{sendersLoading ? "Loading senders..." : channel === "email" ? "Choose the mailbox to send from" : "Choose the number to text from"}</option>
142
+ {senderChoices.map((o) => <option key={o.value} value={o.value} disabled={o.disabled}>{o.label}</option>)}
143
+ </select>
144
+ </label>
145
+ {senderError ? <ApiError error={senderError} title="Could not load your senders" onRetry={() => void (channel === "email" ? mailboxes : numbers).refetch()} /> : null}
146
+ {channel === "email" ? <FirstNotice shown={mailboxes.items.length} limit={0} force={mailboxes.truncated} noun="mailboxes" /> : null}
147
+ {noSenders ? <p className="text-sm text-muted-foreground">{channel === "email" ? `No mailbox is connected yet. Connect one in the ${brand.linkLabel} app first.` : "No SMS number on this account yet."}</p> : null}
148
+
149
+ <label className="block space-y-1 text-sm">
150
+ <span className="font-medium">To</span>
151
+ <Input aria-label="To" placeholder={channel === "email" ? "name@company.com, another@company.com" : "+14155550100"} value={to} onChange={(e) => setTo(e.target.value)} />
152
+ </label>
153
+ {channel === "email" ? (
154
+ <>
155
+ <Input aria-label="Cc" placeholder="Cc (optional)" value={cc} onChange={(e) => setCc(e.target.value)} />
156
+ <Input aria-label="Subject" placeholder="Subject" value={subject} onChange={(e) => setSubject(e.target.value)} />
157
+ </>
158
+ ) : null}
159
+ <Textarea aria-label="Message" rows={8} placeholder="Write your message" value={body} onChange={(e) => setBody(e.target.value)} />
160
+ {channel === "email" ? (
161
+ <label className="flex items-center gap-2 text-sm">
162
+ <input type="checkbox" checked={signature} onChange={(e) => setSignature(e.target.checked)} />
163
+ Add my email signature
164
+ </label>
165
+ ) : null}
166
+
167
+ {problems.length > 0 && (to || body || from) ? (
168
+ <ul aria-label="Still needed" className="list-disc pl-5 text-xs text-muted-foreground">{problems.map((p) => <li key={p}>{p}</li>)}</ul>
169
+ ) : null}
170
+ {send.error ? <ApiError error={send.error} title="Could not send the message" onRetry={() => setConfirming(true)} /> : null}
171
+ {sent ? <p role="status" className="text-sm text-positive-foreground">{sent}</p> : null}
172
+
173
+ <div className="flex justify-end gap-2">
174
+ {onCancel ? <Button variant="outline" onClick={onCancel}>Cancel</Button> : null}
175
+ <Button disabled={problems.length > 0 || send.loading} onClick={() => { setSent(null); setConfirming(true); }}>
176
+ {channel === "email" ? "Send email" : "Send text"}
177
+ </Button>
178
+ </div>
179
+
180
+ <ConfirmSpendDialog
181
+ open={confirming}
182
+ tier="external"
183
+ title={`Send this ${channel === "email" ? "email" : "text message"}?`}
184
+ description={`It goes to ${recipients.join(", ") || "the recipient"} from ${from || "no sender chosen"}. It reaches a real person and cannot be recalled.`}
185
+ recipients={recipients.length}
186
+ confirmLabel={channel === "email" ? "Send email" : "Send text"}
187
+ confirming={send.loading}
188
+ onConfirm={() => void confirm()}
189
+ onCancel={() => setConfirming(false)}
190
+ />
191
+ </div>
192
+ );
193
+ }