@graph8/sdk 0.362.0 → 0.373.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 (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -6,17 +6,26 @@
6
6
  * is launched (SequenceList does that).
7
7
  *
8
8
  * SDK operations, in order:
9
- * create: 1. listMailboxes GET /mailboxes (read; the sender)
9
+ * create: 1. listMailboxes GET /mailboxes (read; the email sender, with `source`)
10
10
  * 2. listLists GET /lists (read; the audience, becomes associated_list_id)
11
- * 3. createSequence POST /sequences (external tier: confirmed; steps + sender in ONE call)
12
- * edit: 1. getSequence / listSequenceSteps GET /sequences/{id}, /sequences/{id}/steps (read)
13
- * 2. addSequenceSteps POST /sequences/{id}/steps (write; appended steps)
14
- * 3. updateSequenceStep PATCH /sequences/{id}/steps/{step_id} (external: confirmed; replaces step_data)
15
- * 4. deleteSequenceStep DELETE /sequences/{id}/steps/{step_id} (destructive: confirmed)
11
+ * 3. listSequencerPhoneNumbers GET /sequencer/channels/phone_numbers (read; only with call steps)
12
+ * listLinkedinAccounts GET /sequencer/channels/linkedin_accounts (read; only with LinkedIn steps)
13
+ * 4. createSequence POST /sequences (external tier: confirmed; steps + senders in ONE call)
14
+ * edit: 1. getSequence / listSequenceSteps / listSequenceChannels GET /sequences/{id}, /steps, /channels (read)
15
+ * 2. addSequenceChannels POST /sequences/{id}/channels (write; a sender new steps need, if picked)
16
+ * 3. addSequenceSteps POST /sequences/{id}/steps (write; appended steps)
17
+ * 4. updateSequenceStep PATCH /sequences/{id}/steps/{step_id} (external: confirmed; replaces step_data)
18
+ * 5. deleteSequenceStep DELETE /sequences/{id}/steps/{step_id} (destructive: confirmed)
16
19
  *
17
- * Ids passed between steps: the chosen mailbox `id` + `email` become the sequence channel; the list `id` becomes
18
- * `associated_list_id`; createSequence returns the new sequence `id` (handed to `onSaved`). Changing a LIVE
19
- * sequence's copy changes what real people receive, which is why the update dialog says so.
20
+ * Every step type needs its own sender or POST /sequences/{id}/run refuses the launch with 400: email steps a
21
+ * mailbox, AI call steps a Voice AI number, call tasks a dialer number, LinkedIn steps a LinkedIn account. The
22
+ * builder asks for each one the steps need and will not create the draft without them. The audience list is
23
+ * required too: the API has no way to set it later, and a sequence without one cannot launch.
24
+ *
25
+ * Ids passed between steps: the chosen mailbox `id` + `email` (channel type from its `source`), phone `id` +
26
+ * `phone_number`, LinkedIn account `id` become the sequence channels; the list `id` becomes `associated_list_id`;
27
+ * createSequence returns the new sequence `id` (handed to `onSaved`). Changing a LIVE sequence's copy changes
28
+ * what real people receive, which is why the update dialog says so.
20
29
  */
21
30
 
22
31
  import { useRef, useState } from "react";
@@ -25,23 +34,29 @@ import { g8Delete, g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
25
34
  import { useG8Query } from "@/g8/hooks/use-g8-query";
26
35
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
27
36
  import { ApiError } from "@/g8/components/api-error";
37
+ import { ListSelect } from "@/g8/crm/components/list-select";
38
+ import { FirstNotice } from "@/g8/components/paging-notices";
28
39
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
29
40
  import type { ConfirmTier } from "@/g8/components/confirm-spend-dialog";
30
41
  import { StepEditor } from "./step-editor";
31
42
  import {
32
- channelTypeFor,
33
43
  fromApiStep,
34
44
  KIND_LABEL,
35
45
  KINDS,
36
46
  listOf,
47
+ mailboxChannelType,
48
+ mailboxUnsupportedReason,
37
49
  newIdempotencyKey,
38
50
  newStep,
39
51
  objectOf,
52
+ requiredSenders,
40
53
  statusGroup,
41
54
  stepIssues,
42
55
  toApiStep,
43
56
  } from "../lib/sequencer-utils";
44
57
  import type { ApiStep, DraftStep, StepKind } from "../lib/sequencer-utils";
58
+ import { pluralWord } from "@/g8/lib/plural";
59
+ import { useBrand } from "@/g8/hooks/use-brand";
45
60
 
46
61
  export interface SequenceBuilderProps {
47
62
  /** Edit this sequence's steps. Omit to build a new draft. */
@@ -57,12 +72,40 @@ interface MailboxOption {
57
72
  id: string;
58
73
  email: string | null;
59
74
  provider?: string | null;
75
+ source?: string | null;
76
+ }
77
+ interface PhoneOption {
78
+ id: number | string;
79
+ phone_number: string;
80
+ /** "ai" (Voice AI) or "dialer"; the API matches AI call steps to "ai" and call tasks to "dialer". */
81
+ inbound_type?: string | null;
82
+ }
83
+ interface LinkedinOption {
84
+ id: number | string;
85
+ name?: string | null;
86
+ display_name?: string | null;
87
+ public_identifier?: string | null;
88
+ account_id?: string | null;
89
+ email?: string | null;
90
+ }
91
+ interface SenderChannel {
92
+ channel_id: number;
93
+ channel_value: string;
94
+ channel_type: string;
60
95
  }
61
- interface ListOption {
62
- id: number;
63
- title: string;
64
- contact_count?: number | null;
96
+
97
+ /** Every bucket of a picker response (available, active, drafted), each id once. */
98
+ function bucketed<T extends { id: number | string }>(envelope: unknown, keys: string[]): T[] {
99
+ const data = objectOf(envelope);
100
+ const all = keys.flatMap((k) => (Array.isArray(data[k]) ? (data[k] as T[]) : []));
101
+ return all.filter((item, i) => all.findIndex((o) => String(o.id) === String(item.id)) === i);
65
102
  }
103
+ const PHONE_KEYS = ["phone_numbers", "active_phone_numbers", "drafted_phone_numbers"];
104
+ const LINKEDIN_KEYS = ["linkedin_accounts", "active_linkedin_accounts", "drafted_linkedin_accounts"];
105
+ const phoneFits = (p: PhoneOption, type: "ai" | "dialer") => !p.inbound_type || p.inbound_type === type;
106
+ const linkedinLabel = (a: LinkedinOption) => a.name || a.display_name || a.public_identifier || a.email || `Account ${a.id}`;
107
+ /** The mailboxes API has no paging: a full read is announced under the sender dropdown. */
108
+ const MAILBOX_LIMIT = 200;
66
109
  type Pending = { kind: "create" } | { kind: "update" | "delete"; step: DraftStep };
67
110
 
68
111
  function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string; description: string; label: string } {
@@ -70,7 +113,7 @@ function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string
70
113
  return {
71
114
  tier: "external",
72
115
  title: "Create this sequence as a draft",
73
- description: "Saves the steps and sender. Nobody is contacted until you launch it.",
116
+ description: "Saves the steps, the audience list and a sender for every step type. Nobody is contacted until you launch it.",
74
117
  label: "Create draft",
75
118
  };
76
119
  if (p.kind === "update")
@@ -91,9 +134,13 @@ function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string
91
134
  }
92
135
 
93
136
  export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved, className }: SequenceBuilderProps) {
137
+ const brand = useBrand();
94
138
  const editing = Boolean(sequenceId);
95
139
  const [name, setName] = useState("");
96
140
  const [mailboxId, setMailboxId] = useState("");
141
+ const [aiPhoneId, setAiPhoneId] = useState("");
142
+ const [dialerPhoneId, setDialerPhoneId] = useState("");
143
+ const [linkedinId, setLinkedinId] = useState("");
97
144
  const [listId, setListId] = useState(defaultListId ? String(defaultListId) : "");
98
145
  const [steps, setSteps] = useState<DraftStep[]>([]);
99
146
  const [loadedFor, setLoadedFor] = useState<string | null>(null);
@@ -106,9 +153,10 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
106
153
  if (keys.current[slot]?.sig !== sig) keys.current[slot] = { sig, value: newIdempotencyKey() };
107
154
  return keys.current[slot].value;
108
155
  };
156
+ // Senders already attached in edit mode by an earlier (failed) append: never attach them twice.
157
+ const attached = useRef<Set<string>>(new Set());
109
158
 
110
- const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: 200 }, { signal }), [], { enabled: !editing });
111
- const lists = useG8Query((signal) => g8Get("/lists", { limit: 100 }, { signal }), [], { enabled: !editing });
159
+ const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: MAILBOX_LIMIT }, { signal }), [], { enabled: !editing });
112
160
  const existing = useG8Query(
113
161
  async (signal) => {
114
162
  const [seq, list] = await Promise.all([
@@ -120,24 +168,71 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
120
168
  [sequenceId],
121
169
  { enabled: editing },
122
170
  );
171
+ // Which senders the sequence already has (edit mode). Unknown when this read fails: nothing is then asked for.
172
+ const current = useG8Query(
173
+ async (signal) => listOf<{ channel_type?: string | null }>(await g8Get(`/sequences/${sequenceId}/channels`, undefined, { signal })),
174
+ [sequenceId],
175
+ { enabled: editing },
176
+ );
123
177
  if (editing && existing.data && loadedFor !== sequenceId) {
124
178
  setLoadedFor(sequenceId ?? null);
125
179
  setSteps(existing.data.steps);
126
180
  }
127
181
  const live = statusGroup(existing.data?.status) === "live";
128
182
 
183
+ // The senders the steps being written need. In edit mode only new steps count, minus what is attached.
184
+ const fresh = steps.filter((s) => !s.id);
185
+ const has = new Set((current.data ?? []).map((c) => (c.channel_type ?? "").toUpperCase()));
186
+ const want = requiredSenders((editing ? fresh : steps).map((s) => s.kind));
187
+ const need = editing
188
+ ? {
189
+ // Mailboxes are attached with the sequence; edit mode has no mailbox picker (the launch check names the gap).
190
+ email: false,
191
+ aiPhone: want.aiPhone && Boolean(current.data) && !has.has("PHONE"),
192
+ dialerPhone: want.dialerPhone && Boolean(current.data) && !has.has("PHONE"),
193
+ linkedin: want.linkedin && Boolean(current.data) && !has.has("LINKEDIN") && !has.has("LINKEDIN_NETRION"),
194
+ }
195
+ : want;
196
+ const phones = useG8Query((signal) => g8Get("/sequencer/channels/phone_numbers", undefined, { signal }), [], {
197
+ enabled: need.aiPhone || need.dialerPhone,
198
+ });
199
+ const linkedin = useG8Query((signal) => g8Get("/sequencer/channels/linkedin_accounts", undefined, { signal }), [], {
200
+ enabled: need.linkedin,
201
+ });
202
+ const provider = String(objectOf(linkedin.data).provider ?? "HEYREACH").toUpperCase();
203
+ const linkedinStepType = provider === "NETRION" ? "NETRION" : "HEYREACH";
204
+ const phoneOptions = bucketed<PhoneOption>(phones.data, PHONE_KEYS);
205
+ const linkedinOptions = bucketed<LinkedinOption>(linkedin.data, LINKEDIN_KEYS);
206
+
207
+ /** The sender channels picked for what `need` asks, in the shape POST /sequences and /channels take. */
208
+ const senderChannels = (): SenderChannel[] => {
209
+ const out: SenderChannel[] = [];
210
+ const mailbox = listOf<MailboxOption>(mailboxes.data).find((m) => m.id === mailboxId);
211
+ const type = mailbox ? mailboxChannelType(mailbox) : null;
212
+ if (mailbox && type) out.push({ channel_id: Number(mailbox.id), channel_value: mailbox.email ?? "", channel_type: type });
213
+ for (const id of new Set([need.aiPhone ? aiPhoneId : "", need.dialerPhone ? dialerPhoneId : ""].filter(Boolean))) {
214
+ const phone = phoneOptions.find((p) => String(p.id) === id);
215
+ if (phone) out.push({ channel_id: Number(phone.id), channel_value: phone.phone_number, channel_type: "PHONE" });
216
+ }
217
+ const account = need.linkedin ? linkedinOptions.find((a) => String(a.id) === linkedinId) : undefined;
218
+ if (account)
219
+ out.push({
220
+ channel_id: Number(account.id),
221
+ channel_value: provider === "NETRION" ? (account.public_identifier ?? account.display_name ?? account.account_id ?? "") : linkedinLabel(account),
222
+ channel_type: provider === "NETRION" ? "LINKEDIN_NETRION" : "LINKEDIN",
223
+ });
224
+ return out;
225
+ };
226
+
129
227
  const run = useG8Mutation(async (p: Pending) => {
130
228
  if (p.kind === "create") {
131
- const mailbox = listOf<MailboxOption>(mailboxes.data).find((m) => m.id === mailboxId);
132
229
  const body = {
133
230
  name: name.trim(),
134
231
  user_email: ownerEmail,
135
232
  associated_list_id: listId ? Number(listId) : undefined,
136
233
  finish_on_reply: true,
137
- steps: steps.map((s, i) => toApiStep(s, i + 1)),
138
- channels: mailbox
139
- ? [{ channel_id: Number(mailbox.id), channel_value: mailbox.email ?? "", channel_type: channelTypeFor(mailbox.provider) }]
140
- : [],
234
+ steps: steps.map((s, i) => toApiStep(s, i + 1, linkedinStepType)),
235
+ channels: senderChannels(),
141
236
  };
142
237
  const res = await g8Post<{ id: string }>("/sequences", body, { idempotencyKey: keyFor("create", body) });
143
238
  setSaved(`Draft "${body.name}" created. Launch it from the sequence list.`);
@@ -157,9 +252,22 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
157
252
  }
158
253
  });
159
254
  const append = useG8Mutation(async () => {
255
+ // Attach the senders the new steps need first (each one once, even across retries), then the steps.
256
+ const channels = senderChannels().filter((c) => !attached.current.has(`${c.channel_type}:${c.channel_id}`));
257
+ if (channels.length > 0) {
258
+ await g8Post(`/sequences/${sequenceId}/channels`, { channels });
259
+ channels.forEach((c) => attached.current.add(`${c.channel_type}:${c.channel_id}`));
260
+ void current.refetch();
261
+ }
160
262
  const fresh = steps.filter((s) => !s.id);
161
263
  const offset = steps.length - fresh.length;
162
- const body = { steps: fresh.map((s, i) => toApiStep(s, offset + i + 1)) };
264
+ // New LinkedIn steps follow the sequence's own provider: an existing LinkedIn step's type, else the LinkedIn
265
+ // sender already attached, else the org's provider. A HEYREACH step next to a LINKEDIN_NETRION sender
266
+ // (or the reverse) could never launch.
267
+ const existingLinkedin = steps.find((s) => s.id && s.kind === "linkedin")?.step_type;
268
+ const sequenceLinkedin =
269
+ existingLinkedin ?? (has.has("LINKEDIN_NETRION") ? "NETRION" : has.has("LINKEDIN") ? "HEYREACH" : linkedinStepType);
270
+ const body = { steps: fresh.map((s, i) => toApiStep(s, offset + i + 1, sequenceLinkedin)) };
163
271
  await g8Post(`/sequences/${sequenceId}/steps`, body, { idempotencyKey: keyFor("append", body) });
164
272
  // The steps are saved: drop the unsaved copies at once so a second click cannot append them again,
165
273
  // then reload the real list (with ids) instead of trusting the response shape.
@@ -179,53 +287,99 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
179
287
  return next;
180
288
  });
181
289
  const invalid = steps.some((s) => stepIssues(s).length > 0);
182
- const canCreate = name.trim() && steps.length > 0 && !invalid && mailboxId;
183
- const fresh = steps.filter((s) => !s.id);
290
+ // What is still missing before the draft can be created (shown under the button, so it is never a mystery).
291
+ const missing = [
292
+ !name.trim() && "a name",
293
+ !listId && "an audience list",
294
+ steps.length === 0 && "a step",
295
+ need.email && !mailboxId && "a mailbox for the email steps",
296
+ need.aiPhone && !aiPhoneId && "a Voice AI number for the AI call steps",
297
+ need.dialerPhone && !dialerPhoneId && "a dialer number for the call tasks",
298
+ need.linkedin && !linkedinId && "a LinkedIn account for the LinkedIn steps",
299
+ ].filter(Boolean) as string[];
300
+ const canCreate = missing.length === 0 && !invalid;
184
301
 
185
302
  if (editing && existing.error) return <ApiError error={existing.error} onRetry={() => void existing.refetch()} className={className} />;
186
303
  if (editing && existing.loading && !existing.data) return <Skeleton className={`h-48 w-full ${className ?? ""}`} />;
187
304
  const copy = pending ? describe(pending, live) : null;
305
+ const select = "h-9 rounded-md border border-border bg-background px-2 text-sm";
188
306
 
189
307
  return (
190
308
  <div className={`space-y-4 ${className ?? ""}`}>
191
309
  {!editing ? (
192
310
  <div className="grid gap-2 sm:grid-cols-3">
193
311
  <Input aria-label="Sequence name" placeholder="Q4 outbound" value={name} onChange={(e) => setName(e.target.value)} />
194
- <select
195
- aria-label="Send from"
196
- className="h-9 rounded-md border border-gray-300 px-2 text-sm"
197
- value={mailboxId}
198
- onChange={(e) => setMailboxId(e.target.value)}
199
- >
200
- <option value="">Send from...</option>
201
- {listOf<MailboxOption>(mailboxes.data).map((m) => (
202
- <option key={m.id} value={m.id}>
203
- {m.email}
204
- </option>
205
- ))}
312
+ <select aria-label="Send from" className={select} value={mailboxId} onChange={(e) => setMailboxId(e.target.value)}>
313
+ <option value="">{steps.length > 0 && !want.email ? "Mailbox (no email steps)" : "Send from..."}</option>
314
+ {listOf<MailboxOption>(mailboxes.data).map((m) => {
315
+ const reason = mailboxUnsupportedReason(m);
316
+ return (
317
+ <option key={m.id} value={m.id} disabled={Boolean(reason)} title={reason ?? undefined}>
318
+ {m.email}
319
+ {reason ? ` (${reason})` : ""}
320
+ </option>
321
+ );
322
+ })}
206
323
  </select>
207
- <select
208
- aria-label="Audience list"
209
- className="h-9 rounded-md border border-gray-300 px-2 text-sm"
324
+ <ListSelect
325
+ label="Audience list"
326
+ emptyOption="Audience list (required)"
327
+ optionLabel={(l) => `${l.title}${l.contact_count != null ? ` (${l.contact_count})` : ""}`}
328
+ pageSize={100}
210
329
  value={listId}
211
- onChange={(e) => setListId(e.target.value)}
212
- >
213
- <option value="">Audience list (optional)</option>
214
- {listOf<ListOption>(lists.data).map((l) => (
215
- <option key={l.id} value={l.id}>
216
- {l.title}
217
- {l.contact_count != null ? ` (${l.contact_count})` : ""}
218
- </option>
219
- ))}
220
- </select>
330
+ valueLabel={defaultListId ? `List ${defaultListId}` : undefined}
331
+ selectClassName={select}
332
+ onChange={(l) => setListId(l ? String(l.id) : "")}
333
+ />
334
+ <FirstNotice className="sm:col-span-3" shown={listOf<MailboxOption>(mailboxes.data).length} limit={MAILBOX_LIMIT} noun="mailboxes" />
335
+ </div>
336
+ ) : null}
337
+ {need.aiPhone || need.dialerPhone || need.linkedin ? (
338
+ <div className="grid gap-2 sm:grid-cols-3">
339
+ {need.aiPhone ? (
340
+ <select aria-label="AI call number" className={select} value={aiPhoneId} onChange={(e) => setAiPhoneId(e.target.value)}>
341
+ <option value="">{phones.loading ? "Loading numbers..." : "Voice AI number for AI calls..."}</option>
342
+ {phoneOptions.filter((p) => phoneFits(p, "ai")).map((p) => (
343
+ <option key={p.id} value={String(p.id)}>
344
+ {p.phone_number}
345
+ </option>
346
+ ))}
347
+ </select>
348
+ ) : null}
349
+ {need.dialerPhone ? (
350
+ <select aria-label="Dialer number" className={select} value={dialerPhoneId} onChange={(e) => setDialerPhoneId(e.target.value)}>
351
+ <option value="">{phones.loading ? "Loading numbers..." : "Dialer number for call tasks..."}</option>
352
+ {phoneOptions.filter((p) => phoneFits(p, "dialer")).map((p) => (
353
+ <option key={p.id} value={String(p.id)}>
354
+ {p.phone_number}
355
+ </option>
356
+ ))}
357
+ </select>
358
+ ) : null}
359
+ {need.linkedin ? (
360
+ <select aria-label="LinkedIn account" className={select} value={linkedinId} onChange={(e) => setLinkedinId(e.target.value)}>
361
+ <option value="">{linkedin.loading ? "Loading accounts..." : "LinkedIn account to send from..."}</option>
362
+ {linkedinOptions.map((a) => (
363
+ <option key={a.id} value={String(a.id)}>
364
+ {linkedinLabel(a)}
365
+ </option>
366
+ ))}
367
+ </select>
368
+ ) : null}
221
369
  </div>
222
370
  ) : null}
223
- {mailboxes.error || lists.error ? (
371
+ {need.linkedin && linkedin.data && linkedinOptions.length === 0 ? (
372
+ <p className="text-sm text-muted-foreground">
373
+ {String(objectOf(linkedin.data).message ?? "No LinkedIn account is connected.")} Connect one in the {brand.linkLabel} app.
374
+ </p>
375
+ ) : null}
376
+ {mailboxes.error || phones.error || linkedin.error ? (
224
377
  <ApiError
225
- error={mailboxes.error ?? lists.error}
378
+ error={mailboxes.error ?? phones.error ?? linkedin.error}
226
379
  onRetry={() => {
227
380
  void mailboxes.refetch();
228
- void lists.refetch();
381
+ if (need.aiPhone || need.dialerPhone) void phones.refetch();
382
+ if (need.linkedin) void linkedin.refetch();
229
383
  }}
230
384
  />
231
385
  ) : null}
@@ -258,13 +412,13 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
258
412
  </div>
259
413
  </div>
260
414
  ))}
261
- {steps.length === 0 ? <p className="text-sm text-gray-500">No steps yet. Add the first touch below.</p> : null}
415
+ {steps.length === 0 ? <p className="text-sm text-muted-foreground">No steps yet. Add the first touch below.</p> : null}
262
416
  </div>
263
417
 
264
418
  <div className="flex flex-wrap items-center gap-2">
265
419
  {KINDS.map((k) => (
266
420
  <Button key={k} size="sm" variant="outline" onClick={() => add(k)}>
267
- Add {KIND_LABEL[k].toLowerCase()}
421
+ Add {KIND_LABEL[k].split(" ").map((w) => (/^[A-Z]{2,}$/.test(w) ? w : w.toLowerCase())).join(" ")}
268
422
  </Button>
269
423
  ))}
270
424
  </div>
@@ -274,17 +428,27 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
274
428
  disabled={fresh.length === 0 || fresh.some((s) => stepIssues(s).length > 0) || append.loading}
275
429
  onClick={() => void append.mutate(undefined)}
276
430
  >
277
- Add {fresh.length} new step{fresh.length === 1 ? "" : "s"}
431
+ Add {fresh.length} new {pluralWord(fresh.length, "step")}
278
432
  </Button>
279
433
  ) : (
280
- <Button disabled={!canCreate} onClick={() => setPending({ kind: "create" })}>
281
- Create draft sequence
282
- </Button>
434
+ <div className="space-y-1">
435
+ <Button disabled={!canCreate} onClick={() => setPending({ kind: "create" })}>
436
+ Create draft sequence
437
+ </Button>
438
+ {missing.length > 0 && (name.trim() || steps.length > 0) ? (
439
+ <p className="text-xs text-muted-foreground">Still needed: {missing.join(", ")}.</p>
440
+ ) : null}
441
+ </div>
283
442
  )}
443
+ {editing && (need.aiPhone || need.dialerPhone || need.linkedin) ? (
444
+ <p className="text-xs text-muted-foreground">
445
+ This sequence has no sender for the new steps yet. Pick one above, or it cannot launch.
446
+ </p>
447
+ ) : null}
284
448
  {run.error ? <ApiError error={run.error} onRetry={run.reset} /> : null}
285
449
  {append.error ? <ApiError error={append.error} onRetry={append.reset} /> : null}
286
450
  {saved ? (
287
- <p role="status" className="text-sm text-green-700">
451
+ <p role="status" className="text-sm text-positive-foreground">
288
452
  {saved}
289
453
  </p>
290
454
  ) : null}