@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
@@ -0,0 +1,261 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SequenceDetail: one sequence, complete. Every field, the audience list, and every action: launch, pause,
5
+ * resume, duplicate, archive, enroll contacts. Tabs: Details, Steps (each step a record), Contacts (each
6
+ * enrollment a record, with the contact), Results (totals and per-step progress).
7
+ *
8
+ * SDK operations, in order:
9
+ * 1. getSequence GET /sequences/{id} (read; all fields, associated_list_id)
10
+ * 2. previewSequence GET /sequences/{id}/preview (read; checked before a launch: each step's
11
+ * `rendered.will_send_as_shown` / `content_issues`, and a sender channel for every step type.
12
+ * A missing sender, a missing audience list or an error-severity issue would make /run
13
+ * answer 400, so the launch is refused here with the reasons instead of offered)
14
+ * 3. runSequence / pauseSequence / resumeSequence POST /sequences/{id}/run|pause|resume
15
+ * (external: confirmed with the recipient count)
16
+ * 4. duplicateSequence POST /sequences/{id}/duplicate (write; the copy starts as a draft)
17
+ * 5. archiveSequence POST /sequences/{id}/archive (destructive: confirmed)
18
+ * Enroll contacts: EnrollSheet (ListPicker + ContactTable, or the whole list) then EnrollDialog (external: confirmed).
19
+ * Steps, Contacts and Results tabs compose SequenceStepsPanel, SequenceEnrollments and SequenceStats.
20
+ */
21
+
22
+ import { useState } from "react";
23
+ import type { ReactNode } from "react";
24
+ import { Copy, UserPlus } from "lucide-react";
25
+ import { Button } from "@graph8/sdk/ui";
26
+ import { ApiError } from "@/g8/components/api-error";
27
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
28
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
29
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
30
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
31
+ import { SectionCard } from "@/g8/components/screen/section-card";
32
+ import { StatusPill } from "@/g8/components/screen/status-pill";
33
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
34
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
35
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
36
+ import { ContactTable } from "@/g8/crm/components/contact-table";
37
+ import { ListPicker } from "@/g8/lists/components/list-picker";
38
+ import { fieldItems, launchCheck, listOf, objectOf, statusGroup } from "../lib/sequencer-utils";
39
+ import type { SequenceRow } from "../lib/sequencer-utils";
40
+ import { EnrollDialog } from "./enroll-dialog";
41
+ import { SequenceEnrollments } from "./sequence-enrollments";
42
+ import { SequenceStats } from "./sequence-stats";
43
+ import { SequenceStepsPanel } from "./sequence-steps-panel";
44
+ import { pluralWord } from "@/g8/lib/plural";
45
+
46
+ /** Whole-list enrolment reads at most this many pages of 100 contacts (5,000). */
47
+ const MAX_ENROLL_PAGES = 50;
48
+ const TONE = { draft: "neutral", live: "positive", paused: "warning", ended: "neutral", busy: "info" } as const;
49
+
50
+ export function EnrollSheet({ sequence, onClose, onEnrolled }: { sequence: SequenceRow & { associated_list_id?: number | null }; onClose: () => void; onEnrolled: () => void }) {
51
+ const [listId, setListId] = useState<number | null>(sequence.associated_list_id ?? null);
52
+ const [ids, setIds] = useState<number[]>([]);
53
+ const [confirm, setConfirm] = useState(false);
54
+ const [gathering, setGathering] = useState(false);
55
+ const [gatherError, setGatherError] = useState<unknown>(null);
56
+ // True when "Enroll the whole list" stopped at the cap, so the confirm says only part is included.
57
+ const [truncated, setTruncated] = useState(false);
58
+ const gatherAll = async () => {
59
+ if (listId == null) return;
60
+ setGathering(true);
61
+ setGatherError(null);
62
+ try {
63
+ const all: number[] = [];
64
+ let more = false;
65
+ for (let page = 1; page <= MAX_ENROLL_PAGES; page++) {
66
+ const rows = listOf<{ id: number }>(await g8Get("/contacts", { list_id: listId, page, limit: 100 }));
67
+ all.push(...rows.map((r) => r.id));
68
+ if (rows.length < 100) break;
69
+ if (page === MAX_ENROLL_PAGES) {
70
+ // A full last page: ask whether anything follows, so the confirm can say the list was cut.
71
+ more = listOf<{ id: number }>(await g8Get("/contacts", { list_id: listId, page: page + 1, limit: 100 })).length > 0;
72
+ }
73
+ }
74
+ setTruncated(more);
75
+ setIds([...new Set(all)]);
76
+ setConfirm(true);
77
+ } catch (err) {
78
+ setGatherError(err);
79
+ } finally {
80
+ setGathering(false);
81
+ }
82
+ };
83
+ return (
84
+ <FormSheet open width="xl" onOpenChange={(o) => !o && onClose()} title={`Enroll contacts in ${sequence.name ?? sequence.id}`} description="Choose a list, tick the contacts, then confirm.">
85
+ <div className="space-y-4">
86
+ <ListPicker value={listId} allowCreate={false} onChange={(l) => { setListId(l.id); setIds([]); setTruncated(false); }} />
87
+ {listId != null ? (
88
+ <ContactTable key={listId} listId={listId} selectable allowDelete={false} pageSize={10} onSelectionChange={(sel) => { setIds(sel); setTruncated(false); }} />
89
+ ) : null}
90
+ <Button disabled={ids.length === 0 || listId == null} onClick={() => setConfirm(true)}>
91
+ <UserPlus className="mr-1 h-4 w-4" aria-hidden /> {ids.length === 0 ? "Enroll contacts" : `Enroll ${ids.length.toLocaleString()} ${pluralWord(ids.length, "contact")}`}
92
+ </Button>
93
+ {listId != null ? (
94
+ <Button variant="outline" loading={gathering} onClick={() => void gatherAll()}>
95
+ Enroll the whole list
96
+ </Button>
97
+ ) : null}
98
+ {gatherError ? <ApiError error={gatherError} title="Could not read the list" onRetry={() => void gatherAll()} /> : null}
99
+ </div>
100
+ <EnrollDialog
101
+ open={confirm}
102
+ sequenceId={sequence.id}
103
+ sequenceName={sequence.name ?? undefined}
104
+ contactIds={ids}
105
+ listId={listId ?? undefined}
106
+ note={truncated ? (
107
+ <p role="alert" className="text-sm">
108
+ This list has more than {(MAX_ENROLL_PAGES * 100).toLocaleString()} contacts. Only the first {(MAX_ENROLL_PAGES * 100).toLocaleString()} are enrolled. Enroll the rest from a smaller list.
109
+ </p>
110
+ ) : undefined}
111
+ onClose={() => setConfirm(false)}
112
+ onEnrolled={() => { setConfirm(false); onEnrolled(); }}
113
+ />
114
+ </FormSheet>
115
+ );
116
+ }
117
+
118
+
119
+ type Action = "launch" | "pause" | "resume" | "archive";
120
+ const COPY: Record<Action, { title: string; description: string; label: string }> = {
121
+ launch: { title: "Launch", description: "Contacts in the audience start receiving the steps in order.", label: "Launch sequence" },
122
+ pause: { title: "Pause", description: "Sending stops. Contacts keep their place.", label: "Pause sequence" },
123
+ resume: { title: "Resume", description: "Contacts continue from where they stopped.", label: "Resume sequence" },
124
+ archive: { title: "Archive", description: "The sequence is hidden and stops sending. The API cannot restore an archived sequence.", label: "Archive sequence" },
125
+ };
126
+
127
+ export interface SequenceDetailProps {
128
+ sequenceId: string;
129
+ /** Controlled tab ("details" | "steps" | "contacts" | "results" | an extra tab id) for deep links. */
130
+ tab?: string;
131
+ onTabChange?: (tab: string) => void;
132
+ stepId?: string;
133
+ onStepChange?: (stepId: string | null) => void;
134
+ contactId?: string;
135
+ onContactChange?: (contactId: string | null) => void;
136
+ onClose?: () => void;
137
+ /** Full-page mode: show an "Open full page" (onExpand) or "Back to split view" (onCollapse) button. */
138
+ onExpand?: () => void;
139
+ onCollapse?: () => void;
140
+ /** Called after any change (status, steps, enrollment, duplicate) so a list can refresh. */
141
+ onChanged?: () => void;
142
+ /** Change it to make the detail reload (a list action changed this sequence). */
143
+ refreshKey?: number;
144
+ /** Open another sequence (the duplicate). */
145
+ onOpenSequence?: (sequenceId: string) => void;
146
+ /** Open an enrolled person in your contacts screen. */
147
+ onOpenContact?: (contactId: number) => void;
148
+ renderActions?: (sequence: SequenceRow) => ReactNode;
149
+ extraTabs?: { id: string; label: string; render: (sequence: SequenceRow) => ReactNode }[];
150
+ className?: string;
151
+ }
152
+
153
+ export function SequenceDetail(props: SequenceDetailProps) {
154
+ const { sequenceId: id, onClose, onChanged, className } = props;
155
+ const [enrolling, setEnrolling] = useState(false);
156
+ const [pending, setPending] = useState<{ action: Action; warnings: string[] } | null>(null);
157
+ const [blocked, setBlocked] = useState<string[] | null>(null);
158
+ const [statsKey, setStatsKey] = useState(0);
159
+ const [version, setVersion] = useState(0);
160
+ const seq = useG8Query((signal) => g8Get(`/sequences/${id}`, undefined, { signal }), [id, version, props.refreshKey], { key: ["sequences", "detail", id] });
161
+ const row = { id, ...objectOf(seq.data) } as SequenceRow & { associated_list_id?: number | null };
162
+ const group = statusGroup(row.status);
163
+ const changed = () => { setVersion((v) => v + 1); onChanged?.(); };
164
+
165
+ const prepare = useG8Mutation(async (action: Action) => {
166
+ let warnings: string[] = [];
167
+ setBlocked(null);
168
+ if (action === "launch") {
169
+ const check = launchCheck(objectOf(await g8Get(`/sequences/${id}/preview`)));
170
+ if (seq.data && row.associated_list_id == null) check.blockers.unshift("It has no audience list. Create the sequence again with one.");
171
+ if (check.blockers.length > 0) {
172
+ setBlocked(check.blockers);
173
+ return;
174
+ }
175
+ warnings = check.warnings;
176
+ }
177
+ setPending({ action, warnings });
178
+ });
179
+ const run = useG8Mutation(async (action: Action) => {
180
+ await g8Post(`/sequences/${id}/${action === "launch" ? "run" : action}`, action === "archive" ? {} : undefined);
181
+ setPending(null);
182
+ changed();
183
+ }, { invalidates: [["sequences", "list"], ["sequences", "summary"]], onError: () => setPending(null) });
184
+ const duplicate = useG8Mutation(async () => {
185
+ const res = objectOf(await g8Post(`/sequences/${id}/duplicate`, {}));
186
+ changed();
187
+ if (res.id != null) props.onOpenSequence?.(String(res.id));
188
+ }, { invalidates: [["sequences", "list"], ["sequences", "summary"]] });
189
+
190
+ if (seq.error) return <ApiError error={seq.error} title="Could not load the sequence" onRetry={() => void seq.refetch()} className={className} />;
191
+ const p = pending;
192
+ return (
193
+ <DetailPanel
194
+ className={className}
195
+ title={row.name ?? `Sequence ${id}`}
196
+ subtitle={row.step_count != null ? `${row.step_count} steps` : undefined}
197
+ badges={row.status ? <StatusPill tone={TONE[group]} dot>{row.status}</StatusPill> : null}
198
+ actions={
199
+ <>
200
+ {group === "draft" ? <Button size="sm" loading={prepare.loading} onClick={() => void prepare.mutate("launch")}>Launch</Button> : null}
201
+ {group === "live" ? <Button size="sm" variant="outline" onClick={() => void prepare.mutate("pause")}>Pause</Button> : null}
202
+ {group === "paused" ? <Button size="sm" variant="outline" onClick={() => void prepare.mutate("resume")}>Resume</Button> : null}
203
+ <Button size="sm" variant="outline" onClick={() => setEnrolling(true)}><UserPlus className="mr-1 h-4 w-4" aria-hidden /> Enroll contacts</Button>
204
+ <Button size="sm" variant="ghost" aria-label="Duplicate sequence" loading={duplicate.loading} onClick={() => void duplicate.mutate(undefined)}><Copy className="h-4 w-4" aria-hidden /></Button>
205
+ {group !== "ended" ? <Button size="sm" variant="ghost" onClick={() => void prepare.mutate("archive")}>Archive sequence</Button> : null}
206
+ {props.renderActions?.(row)}
207
+ {enrolling ? <EnrollSheet sequence={row} onClose={() => setEnrolling(false)} onEnrolled={() => { setEnrolling(false); setStatsKey((k) => k + 1); changed(); }} /> : null}
208
+ {p ? (
209
+ <ConfirmSpendDialog
210
+ open
211
+ tier={p.action === "archive" ? "destructive" : "external"}
212
+ title={`${COPY[p.action].title} "${row.name ?? id}"`}
213
+ description={
214
+ <>
215
+ <p>{COPY[p.action].description}</p>
216
+ {p.warnings.length > 0 ? <ul className="mt-2 list-disc pl-5 text-warning-foreground">{p.warnings.map((w) => <li key={w}>{w}</li>)}</ul> : null}
217
+ </>
218
+ }
219
+ recipients={p.action === "archive" ? undefined : (row.contact_count ?? undefined)}
220
+ affected={p.action === "archive" ? 1 : undefined}
221
+ confirmLabel={COPY[p.action].label}
222
+ confirming={run.loading}
223
+ onCancel={() => setPending(null)}
224
+ onConfirm={() => void run.mutate(p.action)}
225
+ />
226
+ ) : null}
227
+ </>
228
+ }
229
+ onClose={onClose}
230
+ onExpand={props.onExpand}
231
+ onCollapse={props.onCollapse}
232
+ activeTab={props.tab}
233
+ onTabChange={props.onTabChange}
234
+ tabs={[
235
+ {
236
+ id: "details",
237
+ label: "Details",
238
+ content: (
239
+ <div className="space-y-4">
240
+ {[prepare.error, run.error, duplicate.error].map((e, i) => (e ? <ApiError key={i} error={e} /> : null))}
241
+ {blocked ? (
242
+ <div role="alert" className="rounded-md border border-border p-3 text-sm">
243
+ <p className="font-medium">This sequence cannot launch yet</p>
244
+ <ul className="mt-1 list-disc pl-5 text-muted-foreground">{blocked.map((r) => <li key={r}>{r}</li>)}</ul>
245
+ </div>
246
+ ) : null}
247
+ <KeyValueList columns={2} items={fieldItems(objectOf(seq.data))} />
248
+ <SectionCard title="Audience" description="The list this sequence sends to.">
249
+ <p className="text-sm">{row.associated_list_id != null ? `List ${row.associated_list_id}` : "No audience list yet."}</p>
250
+ </SectionCard>
251
+ </div>
252
+ ),
253
+ },
254
+ { id: "steps", label: "Steps", count: row.step_count ?? undefined, content: <SequenceStepsPanel sequenceId={id} stepId={props.stepId} onStepChange={props.onStepChange} onChanged={changed} /> },
255
+ { id: "contacts", label: "Contacts", count: row.contact_count ?? undefined, content: <SequenceEnrollments key={statsKey} sequenceId={id} contactId={props.contactId} onContactChange={props.onContactChange} onOpenContact={props.onOpenContact} /> },
256
+ { id: "results", label: "Results", content: <SequenceStats key={`${id}:${statsKey}`} sequenceId={id} refreshSeconds={group === "live" ? 60 : undefined} /> },
257
+ ...(props.extraTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(row) })),
258
+ ]}
259
+ />
260
+ );
261
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SequenceEnrollments: the contacts enrolled in one sequence, each one a record. A filterable list; open one
5
+ * (deep link via `contactId`) for its state in this sequence, every field, the contact itself and the actions.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. listSequenceContacts GET /sequences/{id}/contacts?state&page&limit (read)
9
+ * 2. pauseSequenceContact POST /sequences/{id}/contacts/{contact_id}/pause (write: direct)
10
+ * 3. resumeSequenceContact POST /sequences/{id}/contacts/{contact_id}/resume (external: confirmed)
11
+ * The embedded ContactDetail (crm block) owns the contact's own reads and edits.
12
+ */
13
+
14
+ import { useState } from "react";
15
+ import { Button, 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 { KeyValueList } from "@/g8/components/screen/key-value-list";
20
+ import { SectionCard } from "@/g8/components/screen/section-card";
21
+ import { StatusPill } from "@/g8/components/screen/status-pill";
22
+ import type { Tone } from "@/g8/components/screen/status-pill";
23
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
24
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
25
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
26
+ import { ContactDetail } from "@/g8/crm/components/contact-detail";
27
+ import { fieldItems, listOf } from "../lib/sequencer-utils";
28
+
29
+ export interface SequenceEnrollmentsProps {
30
+ sequenceId: string;
31
+ /** The open enrollment, by contact id. Controlled so a screen can deep-link it. */
32
+ contactId?: string;
33
+ onContactChange?: (contactId: string | null) => void;
34
+ /** Open the enrolled person in your contacts screen (adds an "Open in contacts" button). */
35
+ onOpenContact?: (contactId: number) => void;
36
+ pageSize?: number;
37
+ className?: string;
38
+ }
39
+
40
+ interface Enrollment { id?: string; contact_id?: number; state?: string; current_step_order?: number | null; name?: string; email?: string; [k: string]: unknown }
41
+
42
+ const STATES = ["", "waiting", "paused", "completed", "replied", "bounced", "skipped"];
43
+ const TONE: Record<string, Tone> = { waiting: "info", paused: "warning", completed: "positive", replied: "positive", bounced: "negative", skipped: "neutral" };
44
+ const nameOf = (e: Enrollment) => e.name || e.email || (e.contact_id != null ? `Contact ${e.contact_id}` : "Contact");
45
+
46
+ export function SequenceEnrollments({ sequenceId, contactId, onContactChange, onOpenContact, pageSize = 25, className }: SequenceEnrollmentsProps) {
47
+ const [state, setState] = useState("");
48
+ const [page, setPage] = useState(1);
49
+ const [resume, setResume] = useState<Enrollment | null>(null);
50
+ const list = useG8Query(
51
+ (signal) => g8Get(`/sequences/${sequenceId}/contacts`, { page, limit: pageSize, state: state || undefined }, { signal }),
52
+ [sequenceId, page, pageSize, state],
53
+ { key: ["sequences", "contacts", sequenceId, { page, pageSize, state }] },
54
+ );
55
+ const pause = useG8Mutation(async (e: Enrollment) => { await g8Post(`/sequences/${sequenceId}/contacts/${e.contact_id}/pause`); await list.refetch(); });
56
+ const resumeRun = useG8Mutation(async (e: Enrollment) => { await g8Post(`/sequences/${sequenceId}/contacts/${e.contact_id}/resume`); setResume(null); await list.refetch(); }, { onError: () => setResume(null) });
57
+
58
+ const rows = listOf<Enrollment>(list.data);
59
+ const open = rows.find((r) => String(r.contact_id) === contactId);
60
+ const actions = (e: Enrollment) =>
61
+ e.state === "paused" ? (
62
+ <Button size="sm" variant="outline" onClick={() => setResume(e)}>Resume contact</Button>
63
+ ) : e.state === "waiting" ? (
64
+ <Button size="sm" variant="outline" loading={pause.loading} onClick={() => void pause.mutate(e)}>Pause contact</Button>
65
+ ) : null;
66
+
67
+ return (
68
+ <div className={`space-y-4 ${className ?? ""}`}>
69
+ <div className="flex items-center gap-2">
70
+ <select aria-label="Enrollment state" className="h-9 rounded-md border bg-background px-2 text-sm" value={state} onChange={(e) => { setState(e.target.value); setPage(1); onContactChange?.(null); }}>
71
+ {STATES.map((s) => <option key={s} value={s}>{s || "All states"}</option>)}
72
+ </select>
73
+ </div>
74
+ {list.error ? <ApiError error={list.error} onRetry={() => void list.refetch()} /> : null}
75
+ {[pause.error, resumeRun.error].map((e, i) => (e ? <ApiError key={i} error={e} /> : null))}
76
+ {list.loading && !list.data ? <Skeleton className="h-32 w-full" /> : null}
77
+ {!list.loading && !list.error && rows.length === 0 ? <EmptyState title="No enrolled contacts" description={state ? "No contact is in this state." : "Enroll contacts to start the sequence."} /> : null}
78
+ {rows.length > 0 ? (
79
+ <ul aria-label="Enrolled contacts" className="divide-y rounded-md border">
80
+ {rows.map((e) => (
81
+ <li key={e.id ?? e.contact_id} className={`flex items-center gap-3 px-3 py-2 text-sm ${String(e.contact_id) === contactId ? "bg-muted" : ""}`}>
82
+ <button type="button" className="min-w-0 flex-1 truncate text-left font-medium hover:underline" aria-label={`Open ${nameOf(e)}`} onClick={() => onContactChange?.(String(e.contact_id))}>{nameOf(e)}</button>
83
+ {e.current_step_order != null ? <span className="text-xs text-muted-foreground">step {e.current_step_order}</span> : null}
84
+ <StatusPill tone={TONE[e.state ?? ""] ?? "neutral"} dot>{e.state ?? "unknown"}</StatusPill>
85
+ </li>
86
+ ))}
87
+ </ul>
88
+ ) : null}
89
+ <div className="flex justify-end gap-2">
90
+ <Button size="sm" variant="outline" disabled={page <= 1} onClick={() => setPage((p) => p - 1)}>Previous</Button>
91
+ <Button size="sm" variant="outline" disabled={rows.length < pageSize} onClick={() => setPage((p) => p + 1)}>Next</Button>
92
+ </div>
93
+ {open ? (
94
+ <SectionCard title={`${nameOf(open)} in this sequence`} actions={<>{actions(open)}{onOpenContact && open.contact_id != null ? <Button size="sm" variant="outline" onClick={() => onOpenContact(open.contact_id as number)}>Open in contacts</Button> : null}<Button size="sm" variant="ghost" onClick={() => onContactChange?.(null)}>Close</Button></>}>
95
+ <KeyValueList columns={2} items={fieldItems(open as Record<string, unknown>)} />
96
+ {open.contact_id != null ? (
97
+ <SectionCard title="Contact" description="The full contact record.">
98
+ <ContactDetail contactId={open.contact_id} />
99
+ </SectionCard>
100
+ ) : null}
101
+ </SectionCard>
102
+ ) : null}
103
+ <ConfirmSpendDialog
104
+ open={resume !== null}
105
+ tier="external"
106
+ title={`Resume ${resume ? nameOf(resume) : "this contact"}`}
107
+ description="They continue from where they stopped and may receive the next step."
108
+ recipients={1}
109
+ confirmLabel="Resume contact"
110
+ confirming={resumeRun.loading}
111
+ onCancel={() => setResume(null)}
112
+ onConfirm={() => resume && void resumeRun.mutate(resume)}
113
+ />
114
+ </div>
115
+ );
116
+ }
@@ -6,8 +6,10 @@
6
6
  *
7
7
  * SDK operations, in order:
8
8
  * 1. listSequences GET /sequences?status=&page=&limit= (read)
9
- * 2. previewSequence GET /sequences/{id}/preview (read; checked before a launch: does the copy
10
- * send as shown? `will_send_as_shown`, `content_issues`)
9
+ * 2. previewSequence GET /sequences/{id}/preview (read; checked before a launch: does each step's
10
+ * copy send as shown (`steps[].rendered.will_send_as_shown` / `content_issues`), and does every
11
+ * step type have a sender channel? A missing sender or an error-severity issue makes /run
12
+ * answer 400, so the launch is refused here with the reasons instead of offered)
11
13
  * 3. runSequence POST /sequences/{id}/run (external: launches a draft; confirmed with the
12
14
  * recipient count)
13
15
  * pauseSequence POST /sequences/{id}/pause (external: confirmed)
@@ -27,7 +29,7 @@ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
27
29
  import { ApiError } from "@/g8/components/api-error";
28
30
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
29
31
  import { EmptyState } from "@/g8/components/empty-state";
30
- import { listOf, objectOf, STATUS_CLASS, statusGroup } from "../lib/sequencer-utils";
32
+ import { launchCheck, listOf, objectOf, STATUS_CLASS, statusGroup } from "../lib/sequencer-utils";
31
33
  import type { SequenceRow } from "../lib/sequencer-utils";
32
34
 
33
35
  export interface SequenceListProps {
@@ -36,6 +38,8 @@ export interface SequenceListProps {
36
38
  pageSize?: number;
37
39
  onOpen?: (sequence: SequenceRow) => void;
38
40
  onCreate?: () => void;
41
+ /** Called after a launch, pause, resume, duplicate or archive succeeded (a screen refreshes its header). */
42
+ onChanged?: () => void;
39
43
  className?: string;
40
44
  }
41
45
 
@@ -58,20 +62,26 @@ const COPY: Record<Action, { title: string; description: string; label: string }
58
62
  },
59
63
  };
60
64
 
61
- export function SequenceList({ status, pageSize = 25, onOpen, onCreate, className }: SequenceListProps) {
65
+ export function SequenceList({ status, pageSize = 25, onOpen, onCreate, onChanged, className }: SequenceListProps) {
62
66
  const [page, setPage] = useState(1);
63
67
  const [pending, setPending] = useState<Pending | null>(null);
64
68
  const [preparing, setPreparing] = useState<string | null>(null);
65
- const list = useG8Query((signal) => g8Get("/sequences", { page, limit: pageSize, status }, { signal }), [page, pageSize, status]);
69
+ const [blocked, setBlocked] = useState<{ sequence: SequenceRow; reasons: string[] } | null>(null);
70
+ const list = useG8Query((signal) => g8Get("/sequences", { page, limit: pageSize, status }, { signal }), [page, pageSize, status], { key: ["sequences", "list", { page, pageSize, status }] });
66
71
 
67
72
  const prepare = useG8Mutation(async (v: { action: Action; sequence: SequenceRow }) => {
68
73
  let warnings: string[] = [];
69
74
  setPreparing(v.sequence.id);
75
+ setBlocked(null);
70
76
  try {
71
77
  if (v.action === "launch") {
72
- const preview = objectOf(await g8Get(`/sequences/${v.sequence.id}/preview`));
73
- const issues = Array.isArray(preview.content_issues) ? (preview.content_issues as Array<{ message?: string }>) : [];
74
- warnings = preview.will_send_as_shown === false ? issues.map((i) => i.message ?? "Copy will not send as shown") : [];
78
+ const check = launchCheck(objectOf(await g8Get(`/sequences/${v.sequence.id}/preview`)));
79
+ if (v.sequence.associated_list_id === null) check.blockers.unshift("It has no audience list. Create the sequence again with one.");
80
+ if (check.blockers.length > 0) {
81
+ setBlocked({ sequence: v.sequence, reasons: check.blockers });
82
+ return;
83
+ }
84
+ warnings = check.warnings;
75
85
  }
76
86
  setPending({ ...v, warnings });
77
87
  } finally {
@@ -81,10 +91,12 @@ export function SequenceList({ status, pageSize = 25, onOpen, onCreate, classNam
81
91
  const run = useG8Mutation(async (p: Pending) => {
82
92
  await g8Post(`/sequences/${p.sequence.id}/${PATH[p.action]}`, p.action === "archive" ? {} : undefined);
83
93
  await list.refetch();
94
+ onChanged?.();
84
95
  });
85
96
  const duplicate = useG8Mutation(async (s: SequenceRow) => {
86
97
  await g8Post(`/sequences/${s.id}/duplicate`, {});
87
98
  await list.refetch();
99
+ onChanged?.();
88
100
  });
89
101
 
90
102
  const rows = listOf<SequenceRow>(list.data);
@@ -105,6 +117,19 @@ export function SequenceList({ status, pageSize = 25, onOpen, onCreate, classNam
105
117
  {[prepare.error, run.error, duplicate.error].filter(Boolean).map((e, i) => (
106
118
  <ApiError key={i} error={e} />
107
119
  ))}
120
+ {blocked ? (
121
+ <div role="alert" className="rounded-md border border-border p-3 text-sm">
122
+ <p className="font-medium">&quot;{blocked.sequence.name ?? blocked.sequence.id}&quot; cannot launch yet</p>
123
+ <ul className="mt-1 list-disc pl-5 text-muted-foreground">
124
+ {blocked.reasons.map((r) => (
125
+ <li key={r}>{r}</li>
126
+ ))}
127
+ </ul>
128
+ <Button size="sm" variant="ghost" className="mt-1" onClick={() => setBlocked(null)}>
129
+ Dismiss
130
+ </Button>
131
+ </div>
132
+ ) : null}
108
133
  <Table>
109
134
  <TableHeader>
110
135
  <TableRow>
@@ -179,7 +204,7 @@ export function SequenceList({ status, pageSize = 25, onOpen, onCreate, classNam
179
204
  <>
180
205
  <p>{COPY[pending.action].description}</p>
181
206
  {pending.warnings.length > 0 ? (
182
- <ul className="mt-2 list-disc pl-5 text-amber-800">
207
+ <ul className="mt-2 list-disc pl-5 text-warning-foreground">
183
208
  {pending.warnings.map((w) => (
184
209
  <li key={w}>{w}</li>
185
210
  ))}
@@ -50,9 +50,9 @@ const STATES = ["", "waiting", "paused", "completed", "replied", "bounced", "ski
50
50
 
51
51
  function Tile({ label, value }: { label: string; value: string | number }) {
52
52
  return (
53
- <div className="rounded border border-gray-200 p-3">
54
- <div className="text-xs capitalize text-gray-500">{label}</div>
55
- <div className="text-xl font-semibold">{value}</div>
53
+ <div className="rounded border border-border p-3">
54
+ <div className="text-xs capitalize text-muted-foreground">{label}</div>
55
+ <div className="text-xl font-medium">{value}</div>
56
56
  </div>
57
57
  );
58
58
  }
@@ -67,11 +67,12 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
67
67
  setPage(1);
68
68
  setResume(null);
69
69
  }, [sequenceId]);
70
- const stats = useG8Query((signal) => g8Get(`/sequences/${sequenceId}/stats`, undefined, { signal }), [sequenceId]);
71
- const analytics = useG8Query((signal) => g8Get(`/sequences/${sequenceId}/analytics`, undefined, { signal }), [sequenceId]);
70
+ const stats = useG8Query((signal) => g8Get(`/sequences/${sequenceId}/stats`, undefined, { signal }), [sequenceId], { key: ["sequences", "stats", sequenceId] });
71
+ const analytics = useG8Query((signal) => g8Get(`/sequences/${sequenceId}/analytics`, undefined, { signal }), [sequenceId], { key: ["sequences", "analytics", sequenceId] });
72
72
  const contacts = useG8Query(
73
73
  (signal) => g8Get(`/sequences/${sequenceId}/contacts`, { page, limit: pageSize, state: state || undefined }, { signal }),
74
74
  [sequenceId, page, pageSize, state],
75
+ { key: ["sequences", "contacts", sequenceId, { page, pageSize, state }] },
75
76
  );
76
77
 
77
78
  useEffect(() => {
@@ -144,7 +145,7 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
144
145
  <TableCell>{st.step_order}</TableCell>
145
146
  <TableCell>{st.step_type}</TableCell>
146
147
  <TableCell>{st.total_contacts ?? 0}</TableCell>
147
- <TableCell className="text-xs text-gray-600">
148
+ <TableCell className="text-xs text-muted-foreground">
148
149
  {Object.entries(st.counts ?? {})
149
150
  .filter(([, n]) => n > 0)
150
151
  .map(([k, n]) => `${k}: ${n}`)
@@ -155,11 +156,11 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
155
156
  </TableBody>
156
157
  </Table>
157
158
  ) : (
158
- <p className="text-sm text-gray-500">No step activity yet.</p>
159
+ <p className="text-sm text-muted-foreground">No step activity yet.</p>
159
160
  )}
160
161
 
161
162
  {skips.length > 0 ? (
162
- <p className="text-sm text-gray-600" data-testid="skip-reasons">
163
+ <p className="text-sm text-muted-foreground" data-testid="skip-reasons">
163
164
  Skipped contacts, counted once each: {skips.map(([k, n]) => `${k.replace(/^skipped_/, "").replace(/_/g, " ")} ${n}`).join(", ")}
164
165
  </p>
165
166
  ) : null}
@@ -170,7 +171,7 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
170
171
  Contacts in
171
172
  <select
172
173
  aria-label="Contact state"
173
- className="ml-2 h-8 rounded border border-gray-300 px-1"
174
+ className="ml-2 h-8 rounded border border-input px-1"
174
175
  value={state}
175
176
  onChange={(e) => {
176
177
  setState(e.target.value);
@@ -200,7 +201,7 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
200
201
  <TableBody>
201
202
  {rows.length === 0 ? (
202
203
  <TableRow>
203
- <TableCell colSpan={4} className="text-sm text-gray-500">
204
+ <TableCell colSpan={4} className="text-sm text-muted-foreground">
204
205
  {contacts.loading ? "Loading..." : "No contacts in this state."}
205
206
  </TableCell>
206
207
  </TableRow>