@graph8/sdk 0.308.1 → 0.357.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 (654) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +1440 -21
  390. package/dist/index.d.ts +1440 -21
  391. package/dist/index.js +42 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +42 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +1340 -18
  400. package/dist/react.d.ts +1340 -18
  401. package/dist/react.js +41 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +41 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +6 -3
  611. package/docs/api/appObjects.d.ts +1 -1
  612. package/docs/api/appObjects.md +1 -1
  613. package/docs/api/by-path.md +29 -0
  614. package/docs/api/objects.d.ts +72 -5
  615. package/docs/api/objects.md +84 -7
  616. package/docs/api/processLists.d.ts +247 -0
  617. package/docs/api/processLists.md +409 -0
  618. package/docs/api/reports.d.ts +476 -0
  619. package/docs/api/reports.md +539 -0
  620. package/docs/api/scheduleBuy.d.ts +12 -8
  621. package/docs/api/scheduleBuy.md +13 -10
  622. package/docs/api/seo.d.ts +406 -3
  623. package/docs/api/seo.md +505 -1
  624. package/docs/api/workspaceBackup.d.ts +324 -0
  625. package/docs/api/workspaceBackup.md +427 -0
  626. package/package.json +174 -5
  627. package/stacks/6sense.json +46 -0
  628. package/stacks/README.md +20 -0
  629. package/stacks/apollo.json +128 -0
  630. package/stacks/clay.json +59 -0
  631. package/stacks/gong.json +72 -0
  632. package/stacks/hubspot.json +162 -0
  633. package/stacks/intercom.json +53 -0
  634. package/stacks/mailchimp.json +71 -0
  635. package/stacks/outreach.json +86 -0
  636. package/stacks/salesforce.json +94 -0
  637. package/stacks/stack.schema.json +32 -0
  638. package/stacks/starter.json +22 -0
  639. package/styles/styles.css +2 -0
  640. package/styles/theme.css +181 -0
  641. package/styles/tokens.css +284 -0
  642. package/templates/next-app/README.md.tmpl +35 -0
  643. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  644. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  645. package/templates/next-app/app/globals.css.tmpl +8 -0
  646. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  647. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  648. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  649. package/templates/next-app/dot-gitignore.tmpl +7 -0
  650. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  651. package/templates/next-app/next.config.mjs.tmpl +4 -0
  652. package/templates/next-app/package.json.tmpl +25 -0
  653. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  654. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,95 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Small helpers shared by the intent components: pull rows out of the API
5
+ * envelope (the intent endpoints return `{data: [...]}`, `{data: {items}}`, `{data: {rows}}` or a bare
6
+ * `{rows}` depending on the route), and the row types the screens read.
7
+ * Every field is optional: intent payloads are open-shaped and grow with the corpus.
8
+ */
9
+
10
+ export interface IntentKeywordRow {
11
+ id: string;
12
+ keyword: string;
13
+ total_resolved_contacts?: number;
14
+ total_resolved_companies?: number;
15
+ total_urls?: number;
16
+ auto_sync_enabled?: boolean;
17
+ signal_type?: string;
18
+ is_active?: boolean;
19
+ }
20
+
21
+ export interface PersonaCoverage {
22
+ category?: string;
23
+ barometer?: string;
24
+ signal_count?: number;
25
+ contact_count?: number;
26
+ }
27
+
28
+ export interface KeywordSignal {
29
+ keyword_id?: string;
30
+ keyword?: string;
31
+ signal_count?: number;
32
+ }
33
+
34
+ /** One scored account from `GET /intent/abm/companies` and `POST /intent/abm/rollup`. */
35
+ export interface AbmAccount {
36
+ company_id?: number | null;
37
+ global_company_id?: string | null;
38
+ company_name?: string | null;
39
+ company_domain?: string | null;
40
+ industry?: string | null;
41
+ employee_count?: number | string | null;
42
+ logo_url?: string | null;
43
+ account_strength?: string | null;
44
+ is_high_intent?: boolean;
45
+ persona_coverage?: Record<string, PersonaCoverage>;
46
+ top_keywords?: KeywordSignal[];
47
+ trend_pct?: number | null;
48
+ total_signal_count?: number;
49
+ latest_signal_date?: string | null;
50
+ }
51
+
52
+ /** Rows of a list response, whichever way the route wraps them. */
53
+ export function rowsOf<T>(envelope: unknown): T[] {
54
+ const env = envelope as Record<string, unknown> | null | undefined;
55
+ const data = (env?.data ?? env) as unknown;
56
+ if (Array.isArray(data)) return data as T[];
57
+ const obj = data as Record<string, unknown> | null | undefined;
58
+ for (const key of ["items", "rows", "keywords", "companies", "data"]) {
59
+ if (Array.isArray(obj?.[key])) return obj![key] as T[];
60
+ }
61
+ return [];
62
+ }
63
+
64
+ /** The total the API reports for a list, when it reports one. */
65
+ export function totalOf(envelope: unknown): number | undefined {
66
+ const env = envelope as Record<string, unknown> | null | undefined;
67
+ const data = env?.data as Record<string, unknown> | undefined;
68
+ const pagination = env?.pagination as { total?: number } | undefined;
69
+ const candidates = [data?.total, pagination?.total, env?.total];
70
+ return candidates.find((v): v is number => typeof v === "number");
71
+ }
72
+
73
+ /** `{ "<company id>": rollup }` out of the rollup response (`data.rollups`). */
74
+ export function rollupsOf(envelope: unknown): Record<string, AbmAccount> {
75
+ const data = (envelope as { data?: { rollups?: Record<string, AbmAccount> } } | null)?.data;
76
+ return data?.rollups ?? {};
77
+ }
78
+
79
+ export function formatDate(value: string | null | undefined): string {
80
+ if (!value) return "-";
81
+ // A date-only value is a calendar day, not an instant: read and show it in UTC so no zone shifts it.
82
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value);
83
+ const date = new Date(dateOnly ? `${value}T00:00:00Z` : value);
84
+ if (Number.isNaN(date.getTime())) return value;
85
+ return date.toLocaleDateString(undefined, dateOnly ? { timeZone: "UTC" } : undefined);
86
+ }
87
+
88
+ /** Tailwind classes for an account-strength label. Unknown labels stay neutral. */
89
+ export function strengthClass(strength: string | null | undefined): string {
90
+ const s = (strength ?? "").toLowerCase();
91
+ if (s.includes("strong") || s === "high") return "bg-green-100 text-green-800";
92
+ if (s.includes("moderate") || s === "medium") return "bg-amber-100 text-amber-800";
93
+ if (s.includes("weak") || s === "low") return "bg-gray-100 text-gray-700";
94
+ return "bg-gray-100 text-gray-700";
95
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @title Rank accounts by buying intent
3
+ * @when Building a "who to call today" list. Reads only (no spend). The ABM feed returns one scored row per
4
+ * company; `company_id` is the global id the rollup endpoint takes. Needs the ABM feature on for the org.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ interface AbmRow {
11
+ company_id?: number | null;
12
+ company_name?: string | null;
13
+ company_domain?: string | null;
14
+ is_high_intent?: boolean;
15
+ total_signal_count?: number;
16
+ }
17
+
18
+ export async function topIntentAccounts(days = 30, limit = 50) {
19
+ // The feed returns at most 500 rows a page, and the rollup takes at most 500 ids.
20
+ limit = Math.min(Math.max(1, limit), 500);
21
+ const feed = await g8.api.signals.listAbmCompanies({ query: { days, limit } });
22
+ const payload = feed.data as { items?: AbmRow[] } | undefined;
23
+ const rows = payload?.items ?? [];
24
+ const ids = rows.map((r) => r.company_id).filter((id): id is number => typeof id === "number");
25
+
26
+ if (ids.length === 0) return [];
27
+
28
+ // Fresh rollup for exactly these companies (up to 500 per call; an empty list is rejected, hence the guard above).
29
+ const rollup = await g8.api.signals.rollupAbmCompanies({ body: { company_ids: ids, days } });
30
+ const rollups = (rollup.data as { rollups?: Record<string, AbmRow> } | undefined)?.rollups ?? {};
31
+
32
+ return rows
33
+ .map((r) => ({ ...r, ...(r.company_id != null ? rollups[String(r.company_id)] : {}) }))
34
+ .filter((r) => r.is_high_intent)
35
+ .sort((a, b) => (b.total_signal_count ?? 0) - (a.total_signal_count ?? 0));
36
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @title Track a competitor's website and read who visits it
3
+ * @when Starting competitor intent. Preview first (nothing saved), show the counts to a human, then save.
4
+ * Both calls are tier `billable`: confirm before the second one. The saved keyword's `keyword_id` feeds the
5
+ * companies lookup.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ export async function trackCompetitor(domain: string, humanApproved: boolean) {
12
+ const preview = await g8.api.intent.keywordsCreateFromDomain({ body: { domain, dry_run: true } });
13
+ console.log("Would seed", preview.data?.pages_seeded, "pages and", preview.data?.contacts_seeded, "people");
14
+ if (!humanApproved) return { saved: false as const, preview: preview.data };
15
+
16
+ const saved = await g8.api.intent.keywordsCreateFromDomain({ body: { domain, dry_run: false } });
17
+ const keywordId = saved.data?.keyword_id;
18
+ if (!keywordId) throw new Error(`Nothing was saved: ${saved.data?.message ?? "no keyword_id returned"}`);
19
+
20
+ // This route's response is open-shaped (no typed schema), so narrow it yourself.
21
+ const companies = (await g8.api.intent.keywordCompanies({ path: { keyword_id: keywordId }, body: { limit: 50 } })) as { data?: unknown };
22
+ return { saved: true as const, keywordId, companies: companies.data };
23
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @title List the companies whose people visited one URL
3
+ * @when Checking interest in a single page (a competitor pricing page, your own landing page). Read tier,
4
+ * free. The route answers with a bare aggregate rather than a `{ data }` envelope, so read `companies` off
5
+ * the result directly.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ export async function whoVisited(url: string, days = 30) {
12
+ const res = await g8.intent.urlCompanies(url, { days, limit: 50 });
13
+ return {
14
+ total: res.total_companies,
15
+ b2b: res.b2b_count,
16
+ companies: res.companies.map((c) => ({ name: c.name, domain: c.domain, visits: c.visit_count, people: c.contacts_seen })),
17
+ };
18
+ }
@@ -0,0 +1,79 @@
1
+ # lifecycle: revenue lifecycle
2
+
3
+ Suspects, Leads, Trial, Customers and Churned as editable React screens. Install with `npx @graph8/sdk add lifecycle`.
4
+ These five are ONE parametrised view keyed by stage, not five features: the stages come from `GET /lifecycle-stages`
5
+ (custom stages work too).
6
+
7
+ | Component | What it is |
8
+ |---|---|
9
+ | `LifecycleBoard` | One column per stage with counts, the companies in it, and Move. |
10
+ | `StageMoveDialog` | Move a company (dry run first) or a contact to another stage. Used by the others. |
11
+ | `LeadQueue` | Contacts in one stage (Leads by default): search, masked details, billable unlock, move. |
12
+ | `CustomerTable` | Customers with MRR, headline counts, and an AI account brief. |
13
+ | `ChurnList` | Churned companies, last MRR, on-demand activity check, win-back to Leads. |
14
+ | `useLifecycleStages` (hook) | Stages and their counts. |
15
+
16
+ ## Flow, in order (and the ids passed between steps)
17
+
18
+ 1. `GET /lifecycle-stages` -> each stage has `id` (entry id) and `slug` (`suspects`, `leads`, `trials`, `customers`, `churned`).
19
+ 2. `GET /lifecycle-stages/counts` -> `{ <stage id>: count }`. **Keyed by entry id, not slug**; `lib/lifecycle.ts countFor` joins them.
20
+ 3. `GET /lifecycle-stages/{slug}/records?page&limit` -> companies: `company_id` (= `mashup_companies.id`, the id `GET /companies/{id}` takes), name, domain, `mrr_cents`, `subscription_status`.
21
+ 4. Move: `POST /companies/{company_id}/lifecycle-stage` with `{ target_slug, dry_run: true }` first, then without `dry_run`. Contacts: `POST /contacts/{id}/lifecycle-stage` (no dry run).
22
+ The API refuses Customers without an active subscription and Churned for a company that never was a customer; the dry run shows that before the click.
23
+ 5. Contacts of a stage: `GET /lifecycle-contacts?lifecycle_id=<stage entry id>` -> `contacts[]`. Locked emails are `"***"`.
24
+ `POST /lifecycle-contacts/unlock { lifecycle_id, contact_ids }` **charges credits per newly unlocked contact** (billable, confirmed).
25
+ 6. Customer brief: `GET /customer-success/companies/{company_id}/brief` (free); `POST .../brief/synthesize` runs a model (billable, confirmed).
26
+
27
+ Money is cents. Contact-level stage membership has no counts endpoint, so the contact queue shows `total` from the list call.
28
+
29
+ ## Mounting
30
+
31
+ ```tsx
32
+ import { LifecycleBoard } from "@/components/g8/lifecycle/components/lifecycle-board";
33
+ export default function Board() { return <LifecycleBoard onOpenCompany={(id) => router.push(`/companies/${id}`)} />; }
34
+ ```
35
+
36
+ Link `onOpenCompany` / `onOpenContact` to the crm block's CompanyDetail / ContactDetail routes.
37
+
38
+ ## Prerequisites
39
+
40
+ - `foundation` installed (and `crm` for the detail routes); proxy `allowedPrefixes` include `lifecycle-stages`, `lifecycle-contacts`, `companies`, `contacts`, `accounts`, `customer-success`.
41
+ - `where: "api"`: lifecycle stages exist (the five ship seeded).
42
+ - `where: "app"`: a billing source (Stripe) connected if Customers and MRR should fill in.
43
+
44
+ ## Defaults the agent should use without asking
45
+
46
+ - Win-back moves a company to **Leads**. 25 rows per column or page. Always dry-run company moves.
47
+
48
+ ## Ask the person first
49
+
50
+ - **Unlocking contacts** (credits) and **rewriting a customer brief** (credits).
51
+
52
+ ## Known limits
53
+
54
+ - `leads.*` operations (`generateLeadContent`, `removeLeads`) take lead ids from list membership, not company ids from stage records, so they are not wired into these screens.
55
+ - Stage configuration (renaming or adding stages) is not in the API; it is edited in the graph8 app.
56
+ - `CustomerTable` shows MRR for the current page only: there is no total-MRR field on the records route.
57
+
58
+ ## Operations
59
+
60
+ | SDK method (`g8.api.*`) | Route | Tier | Scope |
61
+ |---|---|---|---|
62
+ | `listLifecycleStages` | GET `/api/v1/lifecycle-stages` | read | `deals:read` |
63
+ | `getLifecycleStageCounts` | GET `/api/v1/lifecycle-stages/counts` | read | `deals:read` |
64
+ | `listLifecycleStageRecords` | GET `/api/v1/lifecycle-stages/{stage}/records` | read | `deals:read` |
65
+ | `moveCompanyLifecycleStage` | POST `/api/v1/companies/{company_id}/lifecycle-stage` | write | `deals:write` |
66
+ | `moveContactLifecycleStage` | POST `/api/v1/contacts/{contact_id}/lifecycle-stage` | write | `deals:write` |
67
+ | `listLifecycleContacts` | GET `/api/v1/lifecycle-contacts` | read | `deals:read` |
68
+ | `unlockLifecycleContacts` | POST `/api/v1/lifecycle-contacts/unlock` | billable | `deals:run` |
69
+ | `getRevenueSummary` | GET `/api/v1/accounts/revenue-summary` | read | `companies:read` |
70
+ | `getBrief` | GET `/api/v1/customer-success/companies/{company_id}/brief` | read | `companies:read` |
71
+ | `synthesizeBrief` | POST `/api/v1/customer-success/companies/{company_id}/brief/synthesize` | billable | `knowledge:run` |
72
+ | `getAccountIntentSignals` | GET `/api/v1/accounts/{account_id}/intent-signals` | read | `companies:read` |
73
+
74
+ ## Recipes
75
+
76
+ - `recipes/read-lifecycle-board.ts`: Read the lifecycle board: stages, counts and who is in each
77
+ - `recipes/move-company-stage.ts`: Move a company to another lifecycle stage, checking first
78
+ - `recipes/unlock-stage-contacts.ts`: List a stage's contacts and unlock their details
79
+ - `recipes/customer-brief.ts`: Read a customer's brief, and rewrite it when it is stale
@@ -0,0 +1,198 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "lifecycle",
4
+ "title": "Revenue lifecycle",
5
+ "summary": "Suspects, leads, trial, customers and churned as a stage board, a contact queue, a customer book and a win-back list.",
6
+ "features": [
7
+ "stage board with counts and a checked stage move",
8
+ "contact queue with masked details and billable unlock",
9
+ "customer table with MRR and AI account briefs",
10
+ "churn list with win-back"
11
+ ],
12
+ "dependsOn": [
13
+ "crm"
14
+ ],
15
+ "operations": [
16
+ "list_lifecycle_stages_lifecycle_stages_get",
17
+ "get_lifecycle_stage_counts_lifecycle_stages_counts_get",
18
+ "list_lifecycle_stage_records_lifecycle_stages__stage__records_get",
19
+ "move_company_lifecycle_stage_companies__company_id__lifecycle_stage_post",
20
+ "move_contact_lifecycle_stage_contacts__contact_id__lifecycle_stage_post",
21
+ "list_lifecycle_contacts_lifecycle_contacts_get",
22
+ "unlock_lifecycle_contacts_lifecycle_contacts_unlock_post",
23
+ "get_revenue_summary_accounts_revenue_summary_get",
24
+ "get_brief_customer_success_companies__company_id__brief_get",
25
+ "synthesize_brief_customer_success_companies__company_id__brief_synthesize_post",
26
+ "get_account_intent_signals_accounts__account_id__intent_signals_get"
27
+ ],
28
+ "components": [
29
+ {
30
+ "name": "LifecycleBoard",
31
+ "file": "components/lifecycle-board.tsx",
32
+ "purpose": "One column per lifecycle stage with counts, the companies in each, and a Move action on every card.",
33
+ "operations": [
34
+ "list_lifecycle_stages_lifecycle_stages_get",
35
+ "get_lifecycle_stage_counts_lifecycle_stages_counts_get",
36
+ "list_lifecycle_stage_records_lifecycle_stages__stage__records_get",
37
+ "move_company_lifecycle_stage_companies__company_id__lifecycle_stage_post"
38
+ ],
39
+ "tiers": [
40
+ "read",
41
+ "write"
42
+ ]
43
+ },
44
+ {
45
+ "name": "StageMoveDialog",
46
+ "file": "components/stage-move-dialog.tsx",
47
+ "purpose": "Move a company (dry run first) or a contact to another lifecycle stage.",
48
+ "operations": [
49
+ "move_company_lifecycle_stage_companies__company_id__lifecycle_stage_post",
50
+ "move_contact_lifecycle_stage_contacts__contact_id__lifecycle_stage_post"
51
+ ],
52
+ "tiers": [
53
+ "write"
54
+ ]
55
+ },
56
+ {
57
+ "name": "LeadQueue",
58
+ "file": "components/lead-queue.tsx",
59
+ "purpose": "Contacts in one stage as a work queue: search, masked details, billable unlock behind a confirm, and move.",
60
+ "operations": [
61
+ "list_lifecycle_stages_lifecycle_stages_get",
62
+ "list_lifecycle_contacts_lifecycle_contacts_get",
63
+ "unlock_lifecycle_contacts_lifecycle_contacts_unlock_post",
64
+ "move_contact_lifecycle_stage_contacts__contact_id__lifecycle_stage_post"
65
+ ],
66
+ "tiers": [
67
+ "read",
68
+ "write",
69
+ "billable"
70
+ ]
71
+ },
72
+ {
73
+ "name": "CustomerTable",
74
+ "file": "components/customer-table.tsx",
75
+ "purpose": "Customers with MRR, headline counts, and an account brief that can be rewritten with AI behind a confirm.",
76
+ "operations": [
77
+ "get_revenue_summary_accounts_revenue_summary_get",
78
+ "list_lifecycle_stage_records_lifecycle_stages__stage__records_get",
79
+ "get_brief_customer_success_companies__company_id__brief_get",
80
+ "synthesize_brief_customer_success_companies__company_id__brief_synthesize_post"
81
+ ],
82
+ "tiers": [
83
+ "read",
84
+ "billable"
85
+ ]
86
+ },
87
+ {
88
+ "name": "ChurnList",
89
+ "file": "components/churn-list.tsx",
90
+ "purpose": "Churned companies with last MRR, an on-demand activity check, and a win-back move to Leads.",
91
+ "operations": [
92
+ "list_lifecycle_stages_lifecycle_stages_get",
93
+ "list_lifecycle_stage_records_lifecycle_stages__stage__records_get",
94
+ "get_account_intent_signals_accounts__account_id__intent_signals_get",
95
+ "move_company_lifecycle_stage_companies__company_id__lifecycle_stage_post"
96
+ ],
97
+ "tiers": [
98
+ "read",
99
+ "write"
100
+ ]
101
+ }
102
+ ],
103
+ "hooks": [
104
+ {
105
+ "name": "useLifecycleStages",
106
+ "file": "hooks/use-lifecycle-stages.ts",
107
+ "purpose": "The configured lifecycle stages and their company counts (counts are keyed by stage id).",
108
+ "operations": [
109
+ "list_lifecycle_stages_lifecycle_stages_get",
110
+ "get_lifecycle_stage_counts_lifecycle_stages_counts_get"
111
+ ]
112
+ },
113
+ {
114
+ "name": "useDebounced",
115
+ "file": "hooks/use-debounced.ts",
116
+ "purpose": "Debounce a fast-changing value (search boxes)."
117
+ }
118
+ ],
119
+ "lib": [
120
+ {
121
+ "file": "lib/lifecycle.ts",
122
+ "purpose": "Types and helpers: stage and record shapes, counts by stage id, money in cents, masked-value check."
123
+ }
124
+ ],
125
+ "recipes": [
126
+ {
127
+ "file": "recipes/read-lifecycle-board.ts",
128
+ "title": "Read the lifecycle board: stages, counts and who is in each",
129
+ "when": "A stage overview or report is needed; free, counts are keyed by stage id."
130
+ },
131
+ {
132
+ "file": "recipes/move-company-stage.ts",
133
+ "title": "Move a company to another lifecycle stage, checking first",
134
+ "when": "A company changes stage; dry run first because some moves are refused."
135
+ },
136
+ {
137
+ "file": "recipes/unlock-stage-contacts.ts",
138
+ "title": "List a stage's locked contacts, then unlock the ones the person chose",
139
+ "when": "Real emails and phones are needed; billable per newly unlocked contact."
140
+ },
141
+ {
142
+ "file": "recipes/customer-brief.ts",
143
+ "title": "Read a customer's brief, and rewrite it when it is stale",
144
+ "when": "Before a renewal or check-in; rewriting runs a model and spends credits."
145
+ }
146
+ ],
147
+ "prerequisites": [
148
+ {
149
+ "id": "stages-configured",
150
+ "description": "The workspace has its lifecycle stages configured (Suspects, Leads, Trial, Customers, Churned ship by default).",
151
+ "where": "api",
152
+ "verify": "GET /lifecycle-stages returns the five seeded stages."
153
+ },
154
+ {
155
+ "id": "billing-connected",
156
+ "description": "Customers and MRR come from a connected billing source (Stripe); without one the Customers stage stays empty and moves to Customers are refused.",
157
+ "where": "app",
158
+ "verify": "GET /lifecycle-stages/customers/records returns companies with subscription_status."
159
+ }
160
+ ],
161
+ "scopes": [
162
+ "companies:read",
163
+ "deals:read",
164
+ "deals:run",
165
+ "deals:write",
166
+ "knowledge:run"
167
+ ],
168
+ "defaults": [
169
+ {
170
+ "decision": "Which stage a win-back moves a company to",
171
+ "default": "Leads",
172
+ "askHuman": false
173
+ },
174
+ {
175
+ "decision": "Unlock contact details",
176
+ "default": "Never unlock automatically; list first, show how many are locked, unlock only what the person selected",
177
+ "askHuman": true
178
+ },
179
+ {
180
+ "decision": "Rewrite a customer brief with AI",
181
+ "default": "Only when missing or older than the person's threshold; the UI always confirms",
182
+ "askHuman": true
183
+ },
184
+ {
185
+ "decision": "Rows per column or page",
186
+ "default": "25",
187
+ "askHuman": false
188
+ },
189
+ {
190
+ "decision": "Where a company or contact click goes",
191
+ "default": "The crm block's CompanyDetail / ContactDetail routes",
192
+ "askHuman": false
193
+ }
194
+ ],
195
+ "peerDependencies": {
196
+ "react": ">=18"
197
+ }
198
+ }
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ChurnList: companies in the Churned stage, what they were worth, and a win-back action.
5
+ *
6
+ * SDK operations, in the order the screen uses them:
7
+ * 1. listLifecycleStages GET /lifecycle-stages (read)
8
+ * 2. listLifecycleStageRecords GET /lifecycle-stages/churned/records?page&limit (read)
9
+ * 3. getAccountIntentSignals GET /accounts/{account_id}/intent-signals?limit (read; on click: is the account active again?)
10
+ * 4. moveCompanyLifecycleStage POST /companies/{company_id}/lifecycle-stage (write; win back = move to Leads)
11
+ *
12
+ * "Win back" is only a stage change: it puts the company back in front of the team. It sends nothing. Starting a
13
+ * win-back sequence is a separate step with the sequencer block (enrolling is `external` tier there).
14
+ * The intent lookup uses the company id as the account id, which is how the revenue routes key accounts.
15
+ */
16
+
17
+ import { useState } from "react";
18
+ import { Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
19
+ import { g8Get } from "@/g8/lib/g8-client";
20
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
21
+ import { ApiError } from "@/g8/components/api-error";
22
+ import { EmptyState } from "@/g8/components/empty-state";
23
+ import { useLifecycleStages } from "../hooks/use-lifecycle-stages";
24
+ import { asRec, body, findStage, money, text } from "../lib/lifecycle";
25
+ import type { CompanyRecord } from "../lib/lifecycle";
26
+ import { StageMoveDialog } from "./stage-move-dialog";
27
+
28
+ export interface ChurnListProps {
29
+ stageSlug?: string;
30
+ /** Slug a win-back moves a company to. Defaults to "leads". */
31
+ winBackSlug?: string;
32
+ onOpenCompany?: (companyId: number) => void;
33
+ pageSize?: number;
34
+ className?: string;
35
+ }
36
+
37
+ /** Loaded on click: one call per row up front would be 25 calls for a page the person may only skim. */
38
+ function Intent({ companyId }: { companyId: number }) {
39
+ const [asked, setAsked] = useState(false);
40
+ const q = useG8Query((signal) => g8Get(`/accounts/${companyId}/intent-signals`, { limit: 20 }, { signal }), [companyId], { enabled: asked });
41
+ if (!asked) return <Button size="sm" variant="ghost" aria-label={`Check activity for ${companyId}`} onClick={() => setAsked(true)}>Check</Button>;
42
+ if (q.loading) return <Skeleton className="h-4 w-16" />;
43
+ if (q.error) return <span className="text-muted-foreground text-xs">n/a</span>;
44
+ const payload = body(q.data);
45
+ const signals = Array.isArray(payload) ? payload : Array.isArray(asRec(payload).signals) ? (asRec(payload).signals as unknown[]) : [];
46
+ return <span className="text-xs">{signals.length ? `${signals.length} recent signals` : "Quiet"}</span>;
47
+ }
48
+
49
+ export function ChurnList({ stageSlug = "churned", winBackSlug = "leads", onOpenCompany, pageSize = 25, className }: ChurnListProps) {
50
+ const { stages } = useLifecycleStages();
51
+ const [page, setPage] = useState(1);
52
+ const [winning, setWinning] = useState<CompanyRecord | null>(null);
53
+ const list = useG8Query((signal) => g8Get(`/lifecycle-stages/${encodeURIComponent(stageSlug)}/records`, { page, limit: pageSize }, { signal }), [stageSlug, page, pageSize]);
54
+ const rows = (Array.isArray(body(list.data)) ? body(list.data) : []) as CompanyRecord[];
55
+ const total = (asRec(asRec(list.data).pagination).total as number | undefined) ?? rows.length;
56
+ const lost = rows.reduce((sum, r) => sum + (r.mrr_cents ?? 0), 0);
57
+ const target = findStage(stages, winBackSlug);
58
+
59
+ return (
60
+ <div className={className}>
61
+ {list.error ? <ApiError error={list.error} onRetry={() => void list.refetch()} /> : null}
62
+ {list.loading && !list.data ? <div className="space-y-2" aria-busy="true">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-10 w-full" />)}</div> : !list.error && rows.length === 0 ? (
63
+ <EmptyState title="No churned accounts" description="Companies that cancel move here, with what they were paying." />
64
+ ) : (
65
+ <Table>
66
+ <TableHeader><TableRow><TableHead>Company</TableHead><TableHead className="text-right">Last MRR</TableHead><TableHead>Activity</TableHead><TableHead className="text-right">Actions</TableHead></TableRow></TableHeader>
67
+ <TableBody>
68
+ {rows.map((r) => (
69
+ <TableRow key={r.company_id}>
70
+ <TableCell><button type="button" className="font-medium hover:underline" onClick={() => onOpenCompany?.(r.company_id)}>{r.name || r.domain || `Company ${r.company_id}`}</button><div className="text-muted-foreground text-xs">{text(r.industry)}</div></TableCell>
71
+ <TableCell className="text-right">{money(r.mrr_cents)}</TableCell>
72
+ <TableCell><Intent companyId={r.company_id} /></TableCell>
73
+ <TableCell className="text-right"><Button size="sm" variant="outline" disabled={!target} onClick={() => setWinning(r)}>Win back</Button></TableCell>
74
+ </TableRow>
75
+ ))}
76
+ </TableBody>
77
+ </Table>
78
+ )}
79
+ <div className="mt-3 flex items-center justify-between text-sm">
80
+ <span className="text-muted-foreground">{total.toLocaleString()} churned · {money(lost)} MRR lost on this page</span>
81
+ <div className="flex gap-2">
82
+ <Button size="sm" variant="outline" disabled={page === 1} onClick={() => setPage(page - 1)}>Previous</Button>
83
+ <Button size="sm" variant="outline" disabled={page * pageSize >= total} onClick={() => setPage(page + 1)}>Next</Button>
84
+ </div>
85
+ </div>
86
+ {winning && target ? (
87
+ <StageMoveDialog kind="company" recordId={winning.company_id} recordName={winning.name ?? `company ${winning.company_id}`} currentSlug={stageSlug} stages={stages}
88
+ defaultTargetSlug={target.slug} onClose={() => setWinning(null)} onMoved={() => { setWinning(null); void list.refetch(); }} />
89
+ ) : null}
90
+ </div>
91
+ );
92
+ }