@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,91 @@
1
+ "use client";
2
+
3
+ /**
4
+ * AccountIntentScore: a compact intent badge for one account (or a row of them) with the buying-committee
5
+ * coverage behind it. Drop it into a company table cell or a company detail header.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. rollupAbmCompanies POST /intent/abm/rollup (read)
9
+ *
10
+ * Tiers: read only; no confirmation. Pass graph8 global company ids (`company_id` of an ABM account).
11
+ * Companies with no signal in the window render "No signal". An org without ABM enabled gets a 403,
12
+ * shown inline instead of a score.
13
+ */
14
+
15
+ import { Skeleton } from "@graph8/sdk/ui";
16
+ import { useAccountIntent } from "../hooks/use-account-intent";
17
+ import { strengthClass } from "../lib/intent-utils";
18
+ import type { AbmAccount } from "../lib/intent-utils";
19
+
20
+ export interface AccountIntentScoreProps {
21
+ /** The company ids to score. One id renders one badge; several render a stacked list. */
22
+ companyIds: readonly number[];
23
+ /** Look-back window in days (default 30). */
24
+ days?: number;
25
+ /** Show only the badge, without the committee coverage chips. */
26
+ compact?: boolean;
27
+ className?: string;
28
+ }
29
+
30
+ function Coverage({ account }: { account: AbmAccount }) {
31
+ const entries = Object.entries(account.persona_coverage ?? {});
32
+ if (entries.length === 0) return null;
33
+ return (
34
+ <ul className="mt-1 flex flex-wrap gap-1" aria-label="Buying committee coverage">
35
+ {entries.map(([category, cov]) => (
36
+ <li
37
+ key={category}
38
+ title={`${cov.contact_count ?? 0} contacts, ${cov.signal_count ?? 0} signals`}
39
+ className="rounded border border-gray-200 px-1.5 py-0.5 text-xs text-gray-700"
40
+ >
41
+ {category}: {cov.barometer ?? "none"}
42
+ </li>
43
+ ))}
44
+ </ul>
45
+ );
46
+ }
47
+
48
+ function Badge({ account, compact }: { account: AbmAccount; compact?: boolean }) {
49
+ const label = account.account_strength ?? "unscored";
50
+ return (
51
+ <div data-testid={`intent-score-${account.company_id}`}>
52
+ <div className="flex items-center gap-2">
53
+ <span className={`rounded px-2 py-0.5 text-xs font-medium ${strengthClass(account.account_strength)}`}>{label}</span>
54
+ {account.is_high_intent ? (
55
+ <span className="rounded bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800">High intent</span>
56
+ ) : null}
57
+ <span className="text-xs text-gray-500">{account.total_signal_count ?? 0} signals</span>
58
+ </div>
59
+ {compact ? null : <Coverage account={account} />}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export function AccountIntentScore({ companyIds, days = 30, compact, className }: AccountIntentScoreProps) {
65
+ const { rollups, loading, error } = useAccountIntent(companyIds, days);
66
+
67
+ if (loading) return <Skeleton className={`h-5 w-32 ${className ?? ""}`} />;
68
+ if (error) {
69
+ const status = (error as { status?: number }).status;
70
+ const text = status === 403 ? "Intent scoring is not enabled for this workspace." : "Could not load intent.";
71
+ return (
72
+ <span role="alert" className={`text-xs text-red-700 ${className ?? ""}`}>
73
+ {text}
74
+ </span>
75
+ );
76
+ }
77
+ return (
78
+ <div className={`space-y-2 ${className ?? ""}`}>
79
+ {[...new Set(companyIds)].map((id) => {
80
+ const account = rollups[String(id)];
81
+ return account ? (
82
+ <Badge key={id} account={{ ...account, company_id: id }} compact={compact} />
83
+ ) : (
84
+ <span key={id} className="block text-xs text-gray-500">
85
+ No signal
86
+ </span>
87
+ );
88
+ })}
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,215 @@
1
+ "use client";
2
+
3
+ /**
4
+ * IntentFeed: accounts showing buying intent, strongest first, with a "who visited this URL" lookup.
5
+ * A feed, not a stream: the API has no push channel, so it reloads on demand and optionally on a timer.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. listAbmCompanies GET /intent/abm/companies (read; days, keyword_ids, page, limit)
9
+ * 2. urlCompanies POST /intent/url-companies (read; only when the person looks up a URL)
10
+ *
11
+ * Tiers: both read, so no confirmation dialog. The ABM feed needs the ABM feature on for the org; a 403 is
12
+ * explained inline. `onSelectAccount` hands the row to your app (open a company page, add to a list...).
13
+ */
14
+
15
+ import { useEffect, useState } from "react";
16
+ import { Button, Input, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
17
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
18
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
19
+ import { ApiError } from "@/g8/components/api-error";
20
+ import { EmptyState } from "@/g8/components/empty-state";
21
+ import { formatDate, rowsOf, strengthClass, totalOf } from "../lib/intent-utils";
22
+ import type { AbmAccount } from "../lib/intent-utils";
23
+
24
+ export interface IntentFeedProps {
25
+ /** Look-back window in days. Default 30. */
26
+ days?: number;
27
+ /** Restrict the feed to these tracked keywords (ids from IntentKeywordManager). */
28
+ keywordIds?: readonly string[];
29
+ pageSize?: number;
30
+ /** Reload every N seconds (0 or undefined: only when the person clicks Refresh). */
31
+ refreshSeconds?: number;
32
+ onSelectAccount?: (account: AbmAccount) => void;
33
+ className?: string;
34
+ }
35
+
36
+ interface UrlCompany {
37
+ company_id: number | null;
38
+ name: string | null;
39
+ domain: string | null;
40
+ industry: string | null;
41
+ visit_count: number;
42
+ contacts_seen: number;
43
+ last_seen: string | null;
44
+ }
45
+
46
+ function UrlLookup() {
47
+ const [url, setUrl] = useState("");
48
+ const [submitted, setSubmitted] = useState<string | null>(null);
49
+ const result = useG8Query(
50
+ async (signal) => {
51
+ const res = await g8Post<unknown>("/intent/url-companies", { url: submitted, days: 30, limit: 25 }, { signal });
52
+ // This route answers with a bare aggregate rather than the { data } envelope.
53
+ const body = res as unknown as { companies?: UrlCompany[]; data?: { companies?: UrlCompany[] } };
54
+ return body.companies ?? body.data?.companies ?? [];
55
+ },
56
+ [submitted],
57
+ { enabled: submitted != null },
58
+ );
59
+ return (
60
+ <section aria-label="URL lookup" className="rounded border border-gray-200 p-3">
61
+ <form
62
+ className="flex gap-2"
63
+ onSubmit={(e) => {
64
+ e.preventDefault();
65
+ if (url.trim()) setSubmitted(url.trim());
66
+ }}
67
+ >
68
+ <Input aria-label="Page URL" placeholder="https://competitor.com/pricing" value={url} onChange={(e) => setUrl(e.target.value)} />
69
+ <Button type="submit" variant="outline">
70
+ Who visited?
71
+ </Button>
72
+ </form>
73
+ {result.loading ? <Skeleton className="mt-3 h-8 w-full" /> : null}
74
+ {result.error ? <ApiError error={result.error} onRetry={() => void result.refetch()} /> : null}
75
+ {submitted && !result.loading && !result.error ? (
76
+ (result.data ?? []).length === 0 ? (
77
+ <p className="mt-3 text-sm text-gray-500">No identified companies visited this URL in the last 30 days.</p>
78
+ ) : (
79
+ <ul className="mt-3 divide-y text-sm">
80
+ {(result.data ?? []).map((c) => (
81
+ <li key={`${c.company_id}-${c.domain}`} className="flex justify-between py-1.5">
82
+ <span>{c.name ?? c.domain}</span>
83
+ <span className="text-gray-500">
84
+ {c.visit_count} visits, {c.contacts_seen} people
85
+ </span>
86
+ </li>
87
+ ))}
88
+ </ul>
89
+ )
90
+ ) : null}
91
+ </section>
92
+ );
93
+ }
94
+
95
+ export function IntentFeed({ days = 30, keywordIds, pageSize = 25, refreshSeconds, onSelectAccount, className }: IntentFeedProps) {
96
+ const [highOnly, setHighOnly] = useState(false);
97
+ const keywordKey = (keywordIds ?? []).join(",");
98
+ // The page belongs to one set of inputs: when days, keywords or page size change, start again at page 1.
99
+ const scope = `${days}|${pageSize}|${keywordKey}`;
100
+ const [pageState, setPageState] = useState({ scope, n: 1 });
101
+ const page = pageState.scope === scope ? pageState.n : 1;
102
+ const setPage = (update: (p: number) => number) => setPageState({ scope, n: update(page) });
103
+ const feed = useG8Query(
104
+ (signal) => g8Get("/intent/abm/companies", { days, page, limit: pageSize, keyword_ids: keywordKey || undefined }, { signal }),
105
+ [days, page, pageSize, keywordKey],
106
+ );
107
+
108
+ useEffect(() => {
109
+ if (!refreshSeconds) return;
110
+ const timer = setInterval(() => void feed.refetch(), refreshSeconds * 1000);
111
+ return () => clearInterval(timer);
112
+ }, [refreshSeconds, feed.refetch]);
113
+
114
+ const rawAccounts = rowsOf<AbmAccount>(feed.data);
115
+ const accounts = rawAccounts.filter((a) => !highOnly || a.is_high_intent);
116
+ const total = totalOf(feed.data);
117
+ const status = (feed.error as { status?: number } | undefined)?.status;
118
+
119
+ return (
120
+ <div className={`space-y-4 ${className ?? ""}`}>
121
+ <div className="flex items-center justify-between">
122
+ <label className="flex items-center gap-2 text-sm">
123
+ <input type="checkbox" checked={highOnly} onChange={(e) => setHighOnly(e.target.checked)} />
124
+ High intent only
125
+ </label>
126
+ <Button variant="outline" size="sm" onClick={() => void feed.refetch()} disabled={feed.loading}>
127
+ Refresh
128
+ </Button>
129
+ </div>
130
+
131
+ {feed.error && status === 403 ? (
132
+ <p role="alert" className="rounded border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900">
133
+ Account intent is not enabled for this workspace. Ask graph8 to switch on ABM intent.
134
+ </p>
135
+ ) : feed.error ? (
136
+ <ApiError error={feed.error} onRetry={() => void feed.refetch()} />
137
+ ) : feed.loading && !feed.data ? (
138
+ <div className="space-y-2">
139
+ <Skeleton className="h-9 w-full" />
140
+ <Skeleton className="h-9 w-full" />
141
+ </div>
142
+ ) : accounts.length === 0 ? (
143
+ <EmptyState
144
+ title="No accounts showing intent"
145
+ description="Track a keyword or a competitor domain first. Accounts appear here as their people visit the pages you track."
146
+ />
147
+ ) : (
148
+ <Table>
149
+ <TableHeader>
150
+ <TableRow>
151
+ <TableHead>Account</TableHead>
152
+ <TableHead>Strength</TableHead>
153
+ <TableHead>Signals</TableHead>
154
+ <TableHead>Top keywords</TableHead>
155
+ <TableHead>Last signal</TableHead>
156
+ </TableRow>
157
+ </TableHeader>
158
+ <TableBody>
159
+ {accounts.map((a) => (
160
+ <TableRow
161
+ key={String(a.global_company_id ?? a.company_id ?? a.company_domain)}
162
+ className={onSelectAccount ? "cursor-pointer" : undefined}
163
+ onClick={() => onSelectAccount?.(a)}
164
+ >
165
+ <TableCell>
166
+ <div className="font-medium">{a.company_name ?? a.company_domain ?? "Unknown company"}</div>
167
+ <div className="text-xs text-gray-500">{a.company_domain}</div>
168
+ </TableCell>
169
+ <TableCell>
170
+ <span className={`rounded px-2 py-0.5 text-xs ${strengthClass(a.account_strength)}`}>{a.account_strength ?? "-"}</span>
171
+ {a.is_high_intent ? <span className="ml-1 text-xs font-medium text-red-700">High intent</span> : null}
172
+ </TableCell>
173
+ <TableCell>
174
+ {a.total_signal_count ?? 0}
175
+ {typeof a.trend_pct === "number" ? (
176
+ <span className="ml-1 text-xs text-gray-500">
177
+ ({a.trend_pct > 0 ? "+" : ""}
178
+ {Math.round(a.trend_pct)}%)
179
+ </span>
180
+ ) : null}
181
+ </TableCell>
182
+ <TableCell className="text-sm">
183
+ {(a.top_keywords ?? [])
184
+ .slice(0, 3)
185
+ .map((k) => k.keyword)
186
+ .join(", ") || "-"}
187
+ </TableCell>
188
+ <TableCell>{formatDate(a.latest_signal_date)}</TableCell>
189
+ </TableRow>
190
+ ))}
191
+ </TableBody>
192
+ </Table>
193
+ )}
194
+
195
+ <div className="flex items-center justify-between text-sm text-gray-600">
196
+ <span>{total != null ? `${total.toLocaleString()} accounts` : ""}</span>
197
+ <div className="flex gap-2">
198
+ <Button variant="outline" size="sm" disabled={page <= 1 || feed.loading} onClick={() => setPage((p) => p - 1)}>
199
+ Previous
200
+ </Button>
201
+ <Button
202
+ variant="outline"
203
+ size="sm"
204
+ disabled={feed.loading || rawAccounts.length < pageSize}
205
+ onClick={() => setPage((p) => p + 1)}
206
+ >
207
+ Next
208
+ </Button>
209
+ </div>
210
+ </div>
211
+
212
+ <UrlLookup />
213
+ </div>
214
+ );
215
+ }
@@ -0,0 +1,331 @@
1
+ "use client";
2
+
3
+ /**
4
+ * IntentKeywordManager: the keywords and competitor domains whose web traffic you track, with the
5
+ * companies that showed interest in each.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. keywordsList POST /intent/keywords/list (billable tier)
9
+ * 2. keywordsAdd POST /intent/keywords/add (billable)
10
+ * 3. keywordsCreateFromDomain POST /intent/keywords/create-from-domain (billable; dry_run first)
11
+ * 4. keywordCompanies POST /intent/keywords/{keyword_id}/companies (billable)
12
+ * 5. keywordsDelete DELETE /intent/keywords/{keyword_id} (destructive)
13
+ *
14
+ * Tiers: every call here is billable or destructive in the contract, so EVERY one opens ConfirmSpendDialog
15
+ * first, including the listing (set `confirmListing={false}` once you have checked that your plan does not
16
+ * charge for it; then the list loads on mount). Domain tracking previews with `dry_run` before it saves.
17
+ * Ids passed between steps: the `id` of a listed keyword feeds steps 4 and 5; the `keyword_id` from step 3
18
+ * is the new row.
19
+ */
20
+
21
+ import { useEffect, useState } from "react";
22
+ import { Button, Input, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea } from "@graph8/sdk/ui";
23
+ import { g8Delete, g8Post } from "@/g8/lib/g8-client";
24
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
+ import { ApiError } from "@/g8/components/api-error";
26
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
27
+ import type { ConfirmTier } from "@/g8/components/confirm-spend-dialog";
28
+ import { EmptyState } from "@/g8/components/empty-state";
29
+ import { rowsOf } from "../lib/intent-utils";
30
+ import type { IntentKeywordRow } from "../lib/intent-utils";
31
+
32
+ export interface IntentKeywordManagerProps {
33
+ /** Ask before listing (the list is billable in the contract). Default true. */
34
+ confirmListing?: boolean;
35
+ pageSize?: number;
36
+ onSelectKeyword?: (keyword: IntentKeywordRow) => void;
37
+ className?: string;
38
+ }
39
+
40
+ type Pending =
41
+ | { kind: "list" }
42
+ | { kind: "add"; keywords: string[] }
43
+ | { kind: "preview"; domain: string }
44
+ | { kind: "track"; domain: string }
45
+ | { kind: "companies"; keyword: IntentKeywordRow }
46
+ | { kind: "delete"; keyword: IntentKeywordRow };
47
+
48
+ interface DomainResult {
49
+ keyword?: string;
50
+ keyword_id?: string | null;
51
+ dry_run?: boolean;
52
+ pages_seeded?: number;
53
+ contacts_seeded?: number;
54
+ companies_seeded?: number;
55
+ message?: string;
56
+ }
57
+
58
+ interface CompanyRow {
59
+ name?: string | null;
60
+ company_name?: string | null;
61
+ COMPANY_NAME?: string | null;
62
+ domain?: string | null;
63
+ company_domain?: string | null;
64
+ COMPANY_DOMAIN?: string | null;
65
+ }
66
+
67
+ function describe(p: Pending): { tier: ConfirmTier; title: string; description: string; confirmLabel: string } {
68
+ switch (p.kind) {
69
+ case "list":
70
+ return {
71
+ tier: "billable",
72
+ title: "Load tracked keywords",
73
+ description: "Listing tracked keywords is classified billable by the API.",
74
+ confirmLabel: "Load keywords",
75
+ };
76
+ case "add":
77
+ return {
78
+ tier: "billable",
79
+ title: `Track ${p.keywords.length} keyword${p.keywords.length === 1 ? "" : "s"}`,
80
+ description: p.keywords.join(", "),
81
+ confirmLabel: "Track keywords",
82
+ };
83
+ case "preview":
84
+ return {
85
+ tier: "billable",
86
+ title: `Preview tracking ${p.domain}`,
87
+ description: "Finds the pages and people on this domain without saving anything.",
88
+ confirmLabel: "Run preview",
89
+ };
90
+ case "track":
91
+ return {
92
+ tier: "billable",
93
+ title: `Track ${p.domain}`,
94
+ description: "Saves the domain as a tracked keyword and resolves the people who visit its pages.",
95
+ confirmLabel: "Track domain",
96
+ };
97
+ case "companies":
98
+ return {
99
+ tier: "billable",
100
+ title: `Load companies for "${p.keyword.keyword}"`,
101
+ description: "Resolves the companies behind this keyword's traffic.",
102
+ confirmLabel: "Load companies",
103
+ };
104
+ case "delete":
105
+ return {
106
+ tier: "destructive",
107
+ title: `Stop tracking "${p.keyword.keyword}"`,
108
+ description: "The keyword is removed. Companies already saved to lists stay.",
109
+ confirmLabel: "Stop tracking",
110
+ };
111
+ }
112
+ }
113
+
114
+ const companyName = (c: CompanyRow) =>
115
+ c.name ?? c.company_name ?? c.COMPANY_NAME ?? c.domain ?? c.company_domain ?? c.COMPANY_DOMAIN ?? "Unknown";
116
+
117
+ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onSelectKeyword, className }: IntentKeywordManagerProps) {
118
+ const [rows, setRows] = useState<IntentKeywordRow[] | null>(null);
119
+ const [pending, setPending] = useState<Pending | null>(null);
120
+ const [keywordText, setKeywordText] = useState("");
121
+ const [domain, setDomain] = useState("");
122
+ const [domainResult, setDomainResult] = useState<DomainResult | null>(null);
123
+ const [previewed, setPreviewed] = useState<string | null>(null);
124
+ const [companies, setCompanies] = useState<{ keyword: string; rows: CompanyRow[] } | null>(null);
125
+
126
+ const run = useG8Mutation(async (p: Pending) => {
127
+ switch (p.kind) {
128
+ case "list":
129
+ setRows(rowsOf<IntentKeywordRow>(await g8Post("/intent/keywords/list", { page: 1, limit: pageSize })));
130
+ break;
131
+ case "add":
132
+ {
133
+ const res = await g8Post<{ created?: Array<{ id: string; keyword: string }> }>("/intent/keywords/add", { keywords: p.keywords });
134
+ setKeywordText("");
135
+ // Add the new rows locally. Reloading would be a second billable call the person did not confirm.
136
+ const created = (res.data?.created ?? []).map((k): IntentKeywordRow => ({ id: k.id, keyword: k.keyword }));
137
+ setRows((prev) => (prev == null ? prev : [...created, ...prev]));
138
+ }
139
+ break;
140
+ case "preview":
141
+ case "track": {
142
+ const res = await g8Post<DomainResult>("/intent/keywords/create-from-domain", { domain: p.domain, dry_run: p.kind === "preview" });
143
+ setDomainResult(res.data);
144
+ setPreviewed(p.kind === "preview" ? p.domain : null);
145
+ if (p.kind === "track" && res.data?.keyword_id) {
146
+ const added: IntentKeywordRow = {
147
+ id: res.data.keyword_id,
148
+ keyword: res.data.keyword ?? p.domain,
149
+ total_resolved_companies: res.data.companies_seeded,
150
+ total_resolved_contacts: res.data.contacts_seeded,
151
+ };
152
+ setRows((prev) => [added, ...(prev ?? [])]);
153
+ }
154
+ break;
155
+ }
156
+ case "companies":
157
+ setCompanies({
158
+ keyword: p.keyword.keyword,
159
+ rows: rowsOf<CompanyRow>(await g8Post(`/intent/keywords/${p.keyword.id}/companies`, { limit: 50, page: 1 })),
160
+ });
161
+ break;
162
+ case "delete":
163
+ await g8Delete(`/intent/keywords/${p.keyword.id}`);
164
+ setRows((prev) => (prev ?? []).filter((k) => k.id !== p.keyword.id));
165
+ setCompanies((c) => (c?.keyword === p.keyword.keyword ? null : c));
166
+ break;
167
+ }
168
+ });
169
+
170
+ useEffect(() => {
171
+ if (!confirmListing) void run.mutate({ kind: "list" });
172
+ // Load once on mount when listing needs no confirmation.
173
+ }, [confirmListing]); // eslint-disable-line react-hooks/exhaustive-deps
174
+
175
+ const confirm = pending ? describe(pending) : null;
176
+ const words = keywordText
177
+ .split(/[\n,]/)
178
+ .map((w) => w.trim())
179
+ .filter(Boolean);
180
+
181
+ return (
182
+ <div className={`space-y-4 ${className ?? ""}`}>
183
+ <div className="grid gap-3 md:grid-cols-2">
184
+ <section className="space-y-2 rounded border border-gray-200 p-3">
185
+ <h3 className="text-sm font-semibold">Track keywords</h3>
186
+ <Textarea
187
+ aria-label="Keywords"
188
+ rows={3}
189
+ placeholder="one keyword per line, or comma separated"
190
+ value={keywordText}
191
+ onChange={(e) => setKeywordText(e.target.value)}
192
+ />
193
+ <Button size="sm" disabled={words.length === 0} onClick={() => setPending({ kind: "add", keywords: words })}>
194
+ Track keywords
195
+ </Button>
196
+ </section>
197
+ <section className="space-y-2 rounded border border-gray-200 p-3">
198
+ <h3 className="text-sm font-semibold">Track a competitor domain</h3>
199
+ <Input
200
+ aria-label="Competitor domain"
201
+ placeholder="competitor.com"
202
+ value={domain}
203
+ onChange={(e) => {
204
+ setDomain(e.target.value);
205
+ setDomainResult(null);
206
+ setPreviewed(null);
207
+ }}
208
+ />
209
+ <div className="flex gap-2">
210
+ <Button
211
+ size="sm"
212
+ variant="outline"
213
+ disabled={domain.trim().length < 3}
214
+ onClick={() => setPending({ kind: "preview", domain: domain.trim() })}
215
+ >
216
+ Preview
217
+ </Button>
218
+ <Button
219
+ size="sm"
220
+ disabled={domain.trim().length < 3 || !domainResult?.dry_run || previewed !== domain.trim()}
221
+ onClick={() => setPending({ kind: "track", domain: domain.trim() })}
222
+ >
223
+ Track domain
224
+ </Button>
225
+ </div>
226
+ {domainResult ? (
227
+ <p className="text-xs text-gray-600" role="status">
228
+ {domainResult.dry_run ? "Preview: " : "Saved: "}
229
+ {domainResult.pages_seeded ?? 0} pages, {domainResult.contacts_seeded ?? 0} people, {domainResult.companies_seeded ?? 0}{" "}
230
+ companies. {domainResult.message}
231
+ </p>
232
+ ) : null}
233
+ </section>
234
+ </div>
235
+
236
+ {run.error ? <ApiError error={run.error} onRetry={run.reset} /> : null}
237
+
238
+ {rows == null ? (
239
+ run.loading ? (
240
+ <Skeleton className="h-24 w-full" />
241
+ ) : (
242
+ <EmptyState
243
+ title="Tracked keywords are not loaded"
244
+ description="Load them to see which keywords and domains you track."
245
+ action={<Button onClick={() => setPending({ kind: "list" })}>Load keywords</Button>}
246
+ />
247
+ )
248
+ ) : rows.length === 0 ? (
249
+ <EmptyState
250
+ title="No tracked keywords yet"
251
+ description="Add a keyword or a competitor domain above. Accounts that visit the matching pages show up in the intent feed."
252
+ />
253
+ ) : (
254
+ <>
255
+ <div className="flex justify-end">
256
+ <Button size="sm" variant="outline" onClick={() => setPending({ kind: "list" })}>
257
+ Reload
258
+ </Button>
259
+ </div>
260
+ <Table>
261
+ <TableHeader>
262
+ <TableRow>
263
+ <TableHead>Keyword</TableHead>
264
+ <TableHead>Companies</TableHead>
265
+ <TableHead>People</TableHead>
266
+ <TableHead />
267
+ </TableRow>
268
+ </TableHeader>
269
+ <TableBody>
270
+ {rows.map((k) => (
271
+ <TableRow key={k.id}>
272
+ <TableCell>
273
+ <button type="button" className="font-medium underline-offset-2 hover:underline" onClick={() => onSelectKeyword?.(k)}>
274
+ {k.keyword}
275
+ </button>
276
+ </TableCell>
277
+ <TableCell>{k.total_resolved_companies ?? 0}</TableCell>
278
+ <TableCell>{k.total_resolved_contacts ?? 0}</TableCell>
279
+ <TableCell className="space-x-2 text-right">
280
+ <Button size="sm" variant="ghost" onClick={() => setPending({ kind: "companies", keyword: k })}>
281
+ Companies
282
+ </Button>
283
+ <Button
284
+ size="sm"
285
+ variant="ghost"
286
+ aria-label={`Stop tracking ${k.keyword}`}
287
+ onClick={() => setPending({ kind: "delete", keyword: k })}
288
+ >
289
+ Remove
290
+ </Button>
291
+ </TableCell>
292
+ </TableRow>
293
+ ))}
294
+ </TableBody>
295
+ </Table>
296
+ </>
297
+ )}
298
+
299
+ {companies ? (
300
+ <section className="rounded border border-gray-200 p-3" aria-label="Keyword companies">
301
+ <h3 className="text-sm font-semibold">Companies interested in "{companies.keyword}"</h3>
302
+ {companies.rows.length === 0 ? (
303
+ <p className="mt-2 text-sm text-gray-500">No resolved companies yet.</p>
304
+ ) : (
305
+ <ul className="mt-2 grid gap-1 text-sm md:grid-cols-2">
306
+ {companies.rows.map((c, i) => (
307
+ <li key={i}>{companyName(c)}</li>
308
+ ))}
309
+ </ul>
310
+ )}
311
+ </section>
312
+ ) : null}
313
+
314
+ {confirm && pending ? (
315
+ <ConfirmSpendDialog
316
+ open
317
+ tier={confirm.tier}
318
+ title={confirm.title}
319
+ description={confirm.description}
320
+ confirmLabel={confirm.confirmLabel}
321
+ confirming={run.loading}
322
+ onCancel={() => setPending(null)}
323
+ onConfirm={() => {
324
+ const action = pending;
325
+ void run.mutate(action).then(() => setPending(null));
326
+ }}
327
+ />
328
+ ) : null}
329
+ </div>
330
+ );
331
+ }
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useAccountIntent: the ABM intent rollup for a set of companies, ready to drop into a table column.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. rollupAbmCompanies POST /intent/abm/rollup (read; up to 500 company ids per call)
8
+ *
9
+ * Tiers: read only, so no confirmation is needed. The ids are graph8's global COMPANY_IDs (the
10
+ * `company_id` of an ABM account), not your CRM row ids. The org needs the ABM feature on (403 otherwise).
11
+ */
12
+
13
+ import { useMemo } from "react";
14
+ import { g8Post } from "@/g8/lib/g8-client";
15
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
16
+ import { rollupsOf } from "../lib/intent-utils";
17
+ import type { AbmAccount } from "../lib/intent-utils";
18
+
19
+ const MAX_IDS = 500;
20
+
21
+ export interface UseAccountIntentResult {
22
+ /** Rollup per company id (as a string key). Companies with no signal are simply absent. */
23
+ rollups: Record<string, AbmAccount>;
24
+ loading: boolean;
25
+ error: unknown;
26
+ refetch: () => Promise<Record<string, AbmAccount> | undefined>;
27
+ }
28
+
29
+ export function useAccountIntent(companyIds: readonly number[], days = 30): UseAccountIntentResult {
30
+ const ids = useMemo(() => [...new Set(companyIds)], [companyIds]);
31
+ const key = ids.join(",");
32
+ const query = useG8Query(
33
+ async (signal) => {
34
+ // The API takes at most 500 ids per call: send every chunk, so no id is silently left unscored.
35
+ const chunks: number[][] = [];
36
+ for (let i = 0; i < ids.length; i += MAX_IDS) chunks.push(ids.slice(i, i + MAX_IDS));
37
+ const parts = await Promise.all(
38
+ chunks.map(async (chunk) => rollupsOf(await g8Post("/intent/abm/rollup", { company_ids: chunk, days }, { signal }))),
39
+ );
40
+ return Object.assign({}, ...parts) as Record<string, AbmAccount>;
41
+ },
42
+ [key, days],
43
+ { enabled: ids.length > 0 },
44
+ );
45
+ return { rollups: query.data ?? {}, loading: query.loading, error: query.error, refetch: query.refetch };
46
+ }