@graph8/sdk 0.362.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -20,6 +20,8 @@ import { ApiError } from "@/g8/components/api-error";
20
20
  import { EmptyState } from "@/g8/components/empty-state";
21
21
  import { formatDate, rowsOf, strengthClass, totalOf } from "../lib/intent-utils";
22
22
  import type { AbmAccount } from "../lib/intent-utils";
23
+ import { rowActivationProps } from "@/g8/hooks/use-row-activation";
24
+ import { useBrand } from "@/g8/hooks/use-brand";
23
25
 
24
26
  export interface IntentFeedProps {
25
27
  /** Look-back window in days. Default 30. */
@@ -57,7 +59,7 @@ function UrlLookup() {
57
59
  { enabled: submitted != null },
58
60
  );
59
61
  return (
60
- <section aria-label="URL lookup" className="rounded border border-gray-200 p-3">
62
+ <section aria-label="URL lookup" className="rounded border border-border p-3">
61
63
  <form
62
64
  className="flex gap-2"
63
65
  onSubmit={(e) => {
@@ -74,13 +76,13 @@ function UrlLookup() {
74
76
  {result.error ? <ApiError error={result.error} onRetry={() => void result.refetch()} /> : null}
75
77
  {submitted && !result.loading && !result.error ? (
76
78
  (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>
79
+ <p className="mt-3 text-sm text-muted-foreground">No identified companies visited this URL in the last 30 days.</p>
78
80
  ) : (
79
81
  <ul className="mt-3 divide-y text-sm">
80
82
  {(result.data ?? []).map((c) => (
81
83
  <li key={`${c.company_id}-${c.domain}`} className="flex justify-between py-1.5">
82
84
  <span>{c.name ?? c.domain}</span>
83
- <span className="text-gray-500">
85
+ <span className="text-muted-foreground">
84
86
  {c.visit_count} visits, {c.contacts_seen} people
85
87
  </span>
86
88
  </li>
@@ -93,6 +95,7 @@ function UrlLookup() {
93
95
  }
94
96
 
95
97
  export function IntentFeed({ days = 30, keywordIds, pageSize = 25, refreshSeconds, onSelectAccount, className }: IntentFeedProps) {
98
+ const brand = useBrand();
96
99
  const [highOnly, setHighOnly] = useState(false);
97
100
  const keywordKey = (keywordIds ?? []).join(",");
98
101
  // The page belongs to one set of inputs: when days, keywords or page size change, start again at page 1.
@@ -129,8 +132,8 @@ export function IntentFeed({ days = 30, keywordIds, pageSize = 25, refreshSecond
129
132
  </div>
130
133
 
131
134
  {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.
135
+ <p role="alert" className="rounded border border-warning-foreground/25 bg-warning p-3 text-sm text-warning-foreground">
136
+ Account intent is not enabled for this workspace. Ask {brand.linkLabel} to switch on ABM intent.
134
137
  </p>
135
138
  ) : feed.error ? (
136
139
  <ApiError error={feed.error} onRetry={() => void feed.refetch()} />
@@ -160,20 +163,20 @@ export function IntentFeed({ days = 30, keywordIds, pageSize = 25, refreshSecond
160
163
  <TableRow
161
164
  key={String(a.global_company_id ?? a.company_id ?? a.company_domain)}
162
165
  className={onSelectAccount ? "cursor-pointer" : undefined}
163
- onClick={() => onSelectAccount?.(a)}
166
+ {...(onSelectAccount ? rowActivationProps(() => onSelectAccount(a)) : {})}
164
167
  >
165
168
  <TableCell>
166
169
  <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>
170
+ <div className="text-xs text-muted-foreground">{a.company_domain}</div>
168
171
  </TableCell>
169
172
  <TableCell>
170
173
  <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}
174
+ {a.is_high_intent ? <span className="ml-1 text-xs font-medium text-error-foreground">High intent</span> : null}
172
175
  </TableCell>
173
176
  <TableCell>
174
177
  {a.total_signal_count ?? 0}
175
178
  {typeof a.trend_pct === "number" ? (
176
- <span className="ml-1 text-xs text-gray-500">
179
+ <span className="ml-1 text-xs text-muted-foreground">
177
180
  ({a.trend_pct > 0 ? "+" : ""}
178
181
  {Math.round(a.trend_pct)}%)
179
182
  </span>
@@ -192,7 +195,7 @@ export function IntentFeed({ days = 30, keywordIds, pageSize = 25, refreshSecond
192
195
  </Table>
193
196
  )}
194
197
 
195
- <div className="flex items-center justify-between text-sm text-gray-600">
198
+ <div className="flex items-center justify-between text-sm text-muted-foreground">
196
199
  <span>{total != null ? `${total.toLocaleString()} accounts` : ""}</span>
197
200
  <div className="flex gap-2">
198
201
  <Button variant="outline" size="sm" disabled={page <= 1 || feed.loading} onClick={() => setPage((p) => p - 1)}>
@@ -28,12 +28,15 @@ import type { ConfirmTier } from "@/g8/components/confirm-spend-dialog";
28
28
  import { EmptyState } from "@/g8/components/empty-state";
29
29
  import { rowsOf } from "../lib/intent-utils";
30
30
  import type { IntentKeywordRow } from "../lib/intent-utils";
31
+ import { plural } from "@/g8/lib/plural";
31
32
 
32
33
  export interface IntentKeywordManagerProps {
33
34
  /** Ask before listing (the list is billable in the contract). Default true. */
34
35
  confirmListing?: boolean;
35
36
  pageSize?: number;
36
37
  onSelectKeyword?: (keyword: IntentKeywordRow) => void;
38
+ /** Keyword ids to hide (for example one that was just removed from its detail screen). */
39
+ hiddenIds?: readonly string[];
37
40
  className?: string;
38
41
  }
39
42
 
@@ -76,7 +79,7 @@ function describe(p: Pending): { tier: ConfirmTier; title: string; description:
76
79
  case "add":
77
80
  return {
78
81
  tier: "billable",
79
- title: `Track ${p.keywords.length} keyword${p.keywords.length === 1 ? "" : "s"}`,
82
+ title: `Track ${plural(p.keywords.length, "keyword")}`,
80
83
  description: p.keywords.join(", "),
81
84
  confirmLabel: "Track keywords",
82
85
  };
@@ -114,7 +117,7 @@ function describe(p: Pending): { tier: ConfirmTier; title: string; description:
114
117
  const companyName = (c: CompanyRow) =>
115
118
  c.name ?? c.company_name ?? c.COMPANY_NAME ?? c.domain ?? c.company_domain ?? c.COMPANY_DOMAIN ?? "Unknown";
116
119
 
117
- export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onSelectKeyword, className }: IntentKeywordManagerProps) {
120
+ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onSelectKeyword, hiddenIds, className }: IntentKeywordManagerProps) {
118
121
  const [rows, setRows] = useState<IntentKeywordRow[] | null>(null);
119
122
  const [pending, setPending] = useState<Pending | null>(null);
120
123
  const [keywordText, setKeywordText] = useState("");
@@ -172,6 +175,7 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
172
175
  // Load once on mount when listing needs no confirmation.
173
176
  }, [confirmListing]); // eslint-disable-line react-hooks/exhaustive-deps
174
177
 
178
+ const visibleRows = rows == null ? null : rows.filter((k) => !(hiddenIds ?? []).includes(k.id));
175
179
  const confirm = pending ? describe(pending) : null;
176
180
  const words = keywordText
177
181
  .split(/[\n,]/)
@@ -181,8 +185,8 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
181
185
  return (
182
186
  <div className={`space-y-4 ${className ?? ""}`}>
183
187
  <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>
188
+ <section className="space-y-2 rounded border border-border p-3">
189
+ <h3 className="text-sm font-medium">Track keywords</h3>
186
190
  <Textarea
187
191
  aria-label="Keywords"
188
192
  rows={3}
@@ -194,8 +198,8 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
194
198
  Track keywords
195
199
  </Button>
196
200
  </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>
201
+ <section className="space-y-2 rounded border border-border p-3">
202
+ <h3 className="text-sm font-medium">Track a competitor domain</h3>
199
203
  <Input
200
204
  aria-label="Competitor domain"
201
205
  placeholder="competitor.com"
@@ -224,7 +228,7 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
224
228
  </Button>
225
229
  </div>
226
230
  {domainResult ? (
227
- <p className="text-xs text-gray-600" role="status">
231
+ <p className="text-xs text-muted-foreground" role="status">
228
232
  {domainResult.dry_run ? "Preview: " : "Saved: "}
229
233
  {domainResult.pages_seeded ?? 0} pages, {domainResult.contacts_seeded ?? 0} people, {domainResult.companies_seeded ?? 0}{" "}
230
234
  companies. {domainResult.message}
@@ -245,7 +249,7 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
245
249
  action={<Button onClick={() => setPending({ kind: "list" })}>Load keywords</Button>}
246
250
  />
247
251
  )
248
- ) : rows.length === 0 ? (
252
+ ) : (visibleRows ?? []).length === 0 ? (
249
253
  <EmptyState
250
254
  title="No tracked keywords yet"
251
255
  description="Add a keyword or a competitor domain above. Accounts that visit the matching pages show up in the intent feed."
@@ -267,7 +271,7 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
267
271
  </TableRow>
268
272
  </TableHeader>
269
273
  <TableBody>
270
- {rows.map((k) => (
274
+ {(visibleRows ?? []).map((k) => (
271
275
  <TableRow key={k.id}>
272
276
  <TableCell>
273
277
  <button type="button" className="font-medium underline-offset-2 hover:underline" onClick={() => onSelectKeyword?.(k)}>
@@ -297,10 +301,10 @@ export function IntentKeywordManager({ confirmListing = true, pageSize = 50, onS
297
301
  )}
298
302
 
299
303
  {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>
304
+ <section className="rounded border border-border p-3" aria-label="Keyword companies">
305
+ <h3 className="text-sm font-medium">Companies interested in "{companies.keyword}"</h3>
302
306
  {companies.rows.length === 0 ? (
303
- <p className="mt-2 text-sm text-gray-500">No resolved companies yet.</p>
307
+ <p className="mt-2 text-sm text-muted-foreground">No resolved companies yet.</p>
304
308
  ) : (
305
309
  <ul className="mt-2 grid gap-1 text-sm md:grid-cols-2">
306
310
  {companies.rows.map((c, i) => (
@@ -0,0 +1,265 @@
1
+ "use client";
2
+
3
+ /**
4
+ * IntentScreen: the finished buyer-intent product page (Apollo / 6sense "intent"). Two tabs:
5
+ * Accounts accounts showing intent, strongest first (IntentFeed), a look-back window filter, a keyword scope chip,
6
+ * and the selected account's detail panel (signals, trend, top keywords, buying-committee coverage).
7
+ * Keywords track keywords and competitor domains (IntentKeywordManager). Each keyword opens its complete detail screen
8
+ * (KeywordDetail: fields, accounts showing intent for it, companies behind it, stop tracking; `?id=`).
9
+ * Every detail also opens as a full page with `?view=full`.
10
+ *
11
+ * User flows: see who is showing intent, filter to high intent, change the window, open an account (deep link
12
+ * `?id=<company id>`), read its buying committee, look up who visited a URL, track keywords, preview and track a
13
+ * competitor domain, list the companies behind a keyword, stop tracking (destructive, confirmed).
14
+ *
15
+ * SDK operations: none of its own. Composes IntentFeed (read), AccountIntentScore / useAccountIntent (read),
16
+ * IntentKeywordManager (billable + destructive, every call confirmed) and one read of listAbmCompanies for the stat strip.
17
+ * Prerequisite: ABM intent enabled for the workspace (a 403 is explained inline), and something to measure.
18
+ */
19
+ import { useState } from "react";
20
+ import type { ReactNode } from "react";
21
+ import { Button } from "@graph8/sdk/ui";
22
+ import { Radar, Tags, X } from "lucide-react";
23
+ import { g8Get } from "@/g8/lib/g8-client";
24
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
25
+ import { useUrlState } from "@/g8/hooks/use-url-state";
26
+ import { ScreenLayout } from "@/g8/components/screen/screen-layout";
27
+ import type { StatItem } from "@/g8/components/screen/stat-strip";
28
+ import { SplitView } from "@/g8/components/screen/split-view";
29
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
30
+ import { ApiError } from "@/g8/components/api-error";
31
+ import { EmptyState } from "@/g8/components/empty-state";
32
+ import { Toolbar } from "@/g8/components/screen/toolbar";
33
+ import { StatusPill } from "@/g8/components/screen/status-pill";
34
+ import { EntityAvatar } from "@/g8/components/screen/entity-avatar";
35
+ import { SectionCard } from "@/g8/components/screen/section-card";
36
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
37
+ import { BarList } from "@/g8/components/screen/bar-list";
38
+ import { useAccountIntent } from "../hooks/use-account-intent";
39
+ import { formatDate, rowsOf, totalOf } from "../lib/intent-utils";
40
+ import type { AbmAccount, IntentKeywordRow } from "../lib/intent-utils";
41
+ import { AccountIntentScore } from "./account-intent-score";
42
+ import { IntentFeed } from "./intent-feed";
43
+ import { IntentKeywordManager } from "./intent-keyword-manager";
44
+ import { KeywordDetail } from "./keyword-detail";
45
+
46
+ export interface IntentScreenProps {
47
+ /** Extra buttons for the toolbar (for example "Add to list" glue from another block). */
48
+ toolbarActions?: ReactNode;
49
+ /** Buttons shown in the account detail header. */
50
+ renderDetailActions?: (account: AbmAccount) => ReactNode;
51
+ /** Extra tabs in the account detail panel. */
52
+ extraDetailTabs?: { id: string; label: string; render: (account: AbmAccount) => ReactNode }[];
53
+ className?: string;
54
+ }
55
+
56
+ const WINDOWS = [7, 30, 90] as const;
57
+ type Tab = "accounts" | "keywords";
58
+
59
+ export function strengthTone(strength: string | null | undefined): "positive" | "warning" | "neutral" {
60
+ const s = (strength ?? "").toLowerCase();
61
+ if (s.includes("strong") || s === "high") return "positive";
62
+ if (s.includes("moderate") || s === "medium") return "warning";
63
+ return "neutral";
64
+ }
65
+
66
+ interface PanelProps {
67
+ onClose: () => void;
68
+ onExpand?: () => void;
69
+ onCollapse?: () => void;
70
+ }
71
+
72
+ function AccountDetail({ account, panel, props }: { account: AbmAccount; panel: PanelProps; props: IntentScreenProps }) {
73
+ const name = account.company_name ?? account.company_domain ?? "Unknown company";
74
+ const keywords = (account.top_keywords ?? []).map((k) => ({ label: k.keyword ?? "keyword", value: k.signal_count ?? 0 }));
75
+ const trend = typeof account.trend_pct === "number" ? `${account.trend_pct > 0 ? "+" : ""}${Math.round(account.trend_pct)}%` : "No trend yet";
76
+ const tabs = [
77
+ {
78
+ id: "signals",
79
+ label: "Signals",
80
+ content: (
81
+ <div className="space-y-4">
82
+ <KeyValueList
83
+ columns={2}
84
+ items={[
85
+ { label: "Domain", value: account.company_domain ?? "-" },
86
+ { label: "Industry", value: account.industry ?? "-" },
87
+ { label: "Employees", value: account.employee_count ?? "-" },
88
+ { label: "Signals", value: account.total_signal_count ?? 0 },
89
+ { label: "Trend", value: trend },
90
+ { label: "Last signal", value: formatDate(account.latest_signal_date) },
91
+ ]}
92
+ />
93
+ <SectionCard title="Top keywords" description="Where this account's interest comes from.">
94
+ {keywords.length ? <BarList items={keywords} /> : <p className="text-sm text-muted-foreground">No keyword breakdown for this account.</p>}
95
+ </SectionCard>
96
+ </div>
97
+ ),
98
+ },
99
+ {
100
+ id: "committee",
101
+ label: "Buying committee",
102
+ content:
103
+ account.company_id != null ? (
104
+ <AccountIntentScore companyIds={[account.company_id]} />
105
+ ) : (
106
+ <p className="text-sm text-muted-foreground">This account has no company id to score.</p>
107
+ ),
108
+ },
109
+ ...(props.extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(account) })),
110
+ ];
111
+ return (
112
+ <DetailPanel
113
+ title={name}
114
+ subtitle={account.company_domain ?? undefined}
115
+ avatar={<EntityAvatar name={name} imageUrl={account.logo_url ?? undefined} kind="company" size="lg" />}
116
+ badges={
117
+ <>
118
+ <StatusPill tone={strengthTone(account.account_strength)}>{account.account_strength ?? "unscored"}</StatusPill>
119
+ {account.is_high_intent ? <StatusPill tone="negative" dot>High intent</StatusPill> : null}
120
+ </>
121
+ }
122
+ actions={props.renderDetailActions?.(account)}
123
+ tabs={tabs}
124
+ onClose={panel.onClose}
125
+ onExpand={panel.onExpand}
126
+ onCollapse={panel.onCollapse}
127
+ />
128
+ );
129
+ }
130
+
131
+ function useStats(days: number): { items: StatItem[] | undefined; loading: boolean } {
132
+ const q = useG8Query((signal) => g8Get("/intent/abm/companies", { days, page: 1, limit: 100 }, { signal }), [days]);
133
+ if (q.error || !q.data) return { items: undefined, loading: q.loading };
134
+ // The ABM payload is open-shaped in the contract: only report what the rows actually carry.
135
+ const rows = rowsOf<AbmAccount>(q.data);
136
+ const total = totalOf(q.data) ?? rows.length;
137
+ const items: StatItem[] = [{ label: "Accounts with intent", value: total.toLocaleString(), hint: `last ${days} days` }];
138
+ if (rows.some((r) => typeof r.is_high_intent === "boolean")) {
139
+ const high = rows.filter((r) => r.is_high_intent).length;
140
+ items.push({ label: "High intent", value: high, tone: high ? "negative" : "neutral", hint: "in the first 100" });
141
+ }
142
+ if (rows.some((r) => typeof r.account_strength === "string")) {
143
+ const strong = rows.filter((r) => strengthTone(r.account_strength) === "positive").length;
144
+ items.push({ label: "Strong accounts", value: strong, tone: strong ? "positive" : "neutral", hint: "in the first 100" });
145
+ }
146
+ return { loading: q.loading, items };
147
+ }
148
+
149
+ export function IntentScreen(props: IntentScreenProps) {
150
+ const [tab, setTab] = useUrlState("tab", "accounts");
151
+ const [id, setId] = useUrlState("id", "");
152
+ const [view, setView] = useUrlState("view", "");
153
+ const [days, setDays] = useState<number>(30);
154
+ const [selected, setSelected] = useState<AbmAccount | null>(null);
155
+ const [keyword, setKeyword] = useState<IntentKeywordRow | null>(null);
156
+ // `?kw=<keyword id>` scopes the account feed to one tracked keyword (chip in the toolbar).
157
+ const [scopeId, setScopeId] = useUrlState("kw", "");
158
+ const [scopeLabel, setScopeLabel] = useState<string | null>(null);
159
+ const [removed, setRemoved] = useState<string[]>([]);
160
+ const stats = useStats(days);
161
+ const active: Tab = tab === "keywords" ? "keywords" : "accounts";
162
+ const expanded = view === "full" && Boolean(id);
163
+ // A non-numeric `?id=` opens nothing instead of asking for account NaN.
164
+ const idNumber = id && Number.isFinite(Number(id)) ? Number(id) : null;
165
+ const deepLinked = useAccountIntent(active === "accounts" && idNumber != null && !selected ? [idNumber] : [], days);
166
+ const account = selected ?? (active === "accounts" && idNumber != null && deepLinked.rollups[id] ? { ...deepLinked.rollups[id], company_id: idNumber } : null);
167
+ // A deep-linked account with no signal in this window (or a bad id) gets a not-found state instead of a silent no-op.
168
+ const accountMissing = active === "accounts" && Boolean(id) && !account && (idNumber == null || (!deepLinked.loading && !deepLinked.error));
169
+ const accountFailed = active === "accounts" && Boolean(id) && !account && idNumber != null && Boolean(deepLinked.error);
170
+
171
+ const close = () => {
172
+ setSelected(null);
173
+ setKeyword(null);
174
+ setId("");
175
+ setView("");
176
+ };
177
+ const panel: PanelProps = {
178
+ onClose: close,
179
+ onExpand: expanded ? undefined : () => setView("full"),
180
+ onCollapse: expanded ? () => setView("") : undefined,
181
+ };
182
+ const showAccounts = (k: IntentKeywordRow) => {
183
+ close();
184
+ setScopeId(k.id);
185
+ setScopeLabel(k.keyword);
186
+ setTab("accounts");
187
+ };
188
+ const openAccount = (a: AbmAccount) => {
189
+ setTab("accounts");
190
+ setKeyword(null);
191
+ setSelected(a);
192
+ setId(String(a.company_id ?? ""));
193
+ };
194
+
195
+ return (
196
+ <ScreenLayout
197
+ title="Intent"
198
+ description="Accounts researching what you sell, and the keywords and competitors you track to find them."
199
+ icon={<Radar className="size-5" />}
200
+ actions={
201
+ <Button onClick={() => { setTab("keywords"); close(); }}>
202
+ <Tags className="mr-1.5 size-4" /> Track keywords
203
+ </Button>
204
+ }
205
+ tabs={[
206
+ { id: "accounts", label: "Accounts" },
207
+ { id: "keywords", label: "Keywords" },
208
+ ]}
209
+ activeTab={active}
210
+ onTabChange={(next) => {
211
+ setTab(next);
212
+ close();
213
+ }}
214
+ stats={active === "accounts" ? stats.items : undefined}
215
+ className={props.className}
216
+ >
217
+ {active === "accounts" ? (
218
+ <div className="space-y-3">
219
+ <Toolbar actions={props.toolbarActions}>
220
+ {scopeId ? (
221
+ <Button size="sm" variant="outline" aria-label={`Remove keyword scope ${scopeLabel ?? scopeId}`} onClick={() => { setScopeId(null); setScopeLabel(null); }}>
222
+ Keyword: {scopeLabel ?? scopeId} <X className="ml-1 size-3.5" aria-hidden />
223
+ </Button>
224
+ ) : null}
225
+ <div className="inline-flex gap-1" role="group" aria-label="Look-back window">
226
+ {WINDOWS.map((w) => (
227
+ <Button key={w} size="sm" variant={days === w ? "default" : "outline"} aria-pressed={days === w} onClick={() => setDays(w)}>
228
+ {w} days
229
+ </Button>
230
+ ))}
231
+ </div>
232
+ </Toolbar>
233
+ <SplitView
234
+ detailOpen={Boolean(account) || accountMissing || accountFailed}
235
+ onCloseDetail={close}
236
+ expanded={expanded && Boolean(account)}
237
+ onExpandedChange={(next) => setView(next ? "full" : "")}
238
+ detailWidth="lg"
239
+ list={<IntentFeed key={scopeId || "all"} days={days} keywordIds={scopeId ? [scopeId] : undefined} onSelectAccount={openAccount} />}
240
+ detail={account ? (
241
+ <AccountDetail key={account.company_id ?? account.company_domain} account={account} panel={panel} props={props} />
242
+ ) : accountFailed ? (
243
+ <div className="space-y-3 p-4"><ApiError error={deepLinked.error} title="Could not load this account's intent" onRetry={() => void deepLinked.refetch()} /><Button size="sm" variant="outline" onClick={close}>Close</Button></div>
244
+ ) : accountMissing ? (
245
+ <div className="space-y-3 p-4"><EmptyState title="No intent for this account" description={`No buying-intent signal for this account in the last ${days} days. Try a longer window.`} /><Button size="sm" variant="outline" onClick={close}>Close</Button></div>
246
+ ) : null}
247
+ />
248
+ </div>
249
+ ) : (
250
+ <SplitView
251
+ detailOpen={Boolean(id)}
252
+ onCloseDetail={close}
253
+ expanded={expanded}
254
+ onExpandedChange={(next) => setView(next ? "full" : "")}
255
+ detailWidth="lg"
256
+ list={<IntentKeywordManager hiddenIds={removed} onSelectKeyword={(k) => { setKeyword(k); setId(k.id); }} />}
257
+ detail={id ? (
258
+ <KeywordDetail key={id} keywordId={id} keyword={keyword?.id === id ? keyword : null} onLoaded={setKeyword}
259
+ onSelectAccount={openAccount} onShowAccounts={showAccounts} onRemoved={(kid) => setRemoved((r) => [...r, kid])} {...panel} />
260
+ ) : null}
261
+ />
262
+ )}
263
+ </ScreenLayout>
264
+ );
265
+ }
@@ -0,0 +1,124 @@
1
+ "use client";
2
+
3
+ /**
4
+ * KeywordDetail: the complete screen for one tracked keyword or competitor domain. Shows every field the list returns, the
5
+ * accounts currently showing intent for it (the intent feed scoped to this keyword), the companies resolved behind it, and the
6
+ * action to stop tracking. Pass the row from IntentKeywordManager; with only an id (a deep link) it offers to load the row.
7
+ *
8
+ * SDK operations, in order:
9
+ * 1. listAbmCompanies GET /intent/abm/companies?keyword_ids (read; inside IntentFeed)
10
+ * 2. keywordsList POST /intent/keywords/list (billable tier, confirmed; only to restore a row from an id)
11
+ * 3. keywordCompanies POST /intent/keywords/{id}/companies (billable, confirmed)
12
+ * 4. keywordsDelete DELETE /intent/keywords/{id} (destructive, confirmed; 404 counts as done)
13
+ */
14
+ import { useState } from "react";
15
+ import { Button } from "@graph8/sdk/ui";
16
+ import { ApiError } from "@/g8/components/api-error";
17
+ import { EmptyState } from "@/g8/components/empty-state";
18
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
19
+ import { g8Delete, g8Post } from "@/g8/lib/g8-client";
20
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
21
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
22
+ import { SectionCard } from "@/g8/components/screen/section-card";
23
+ import { StatusPill } from "@/g8/components/screen/status-pill";
24
+ import { rowsOf } from "../lib/intent-utils";
25
+ import type { AbmAccount, IntentKeywordRow } from "../lib/intent-utils";
26
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
27
+ import { AskButton, useConfirmAction } from "@/g8/components/confirmed-action";
28
+ import { IntentFeed } from "./intent-feed";
29
+
30
+ export interface KeywordDetailProps {
31
+ keywordId: string;
32
+ /** The row from the keyword list. Without it the screen can restore it after a confirmed list call. */
33
+ keyword?: IntentKeywordRow | null;
34
+ /** Called with the restored row. */
35
+ onLoaded?: (keyword: IntentKeywordRow) => void;
36
+ onSelectAccount?: (account: AbmAccount) => void;
37
+ onClose?: () => void;
38
+ onExpand?: () => void;
39
+ onCollapse?: () => void;
40
+ /** Called after the keyword was removed. */
41
+ onRemoved?: (keywordId: string) => void;
42
+ /** Shows a "Show accounts" button that scopes the account feed to this keyword. */
43
+ onShowAccounts?: (keyword: IntentKeywordRow) => void;
44
+ }
45
+
46
+ interface CompanyRow {
47
+ name?: string | null;
48
+ company_name?: string | null;
49
+ COMPANY_NAME?: string | null;
50
+ domain?: string | null;
51
+ company_domain?: string | null;
52
+ COMPANY_DOMAIN?: string | null;
53
+ }
54
+ const companyName = (c: CompanyRow) => c.name ?? c.company_name ?? c.COMPANY_NAME ?? c.domain ?? c.company_domain ?? c.COMPANY_DOMAIN ?? "Unknown";
55
+
56
+ export function KeywordDetail({ keywordId, keyword, onLoaded, onSelectAccount, onClose, onExpand, onCollapse, onRemoved, onShowAccounts }: KeywordDetailProps) {
57
+ const { ask, dialog, failure } = useConfirmAction();
58
+ const [companies, setCompanies] = useState<CompanyRow[] | null>(null);
59
+ const restore = useG8Mutation(async () => {
60
+ const found = rowsOf<IntentKeywordRow>(await g8Post("/intent/keywords/list", { page: 1, limit: 200 })).find((k) => k.id === keywordId);
61
+ if (found) onLoaded?.(found);
62
+ return found ?? null;
63
+ });
64
+
65
+ if (!keyword) {
66
+ return (
67
+ <div className="space-y-3 p-4">
68
+ {restore.data === null ? (
69
+ <EmptyState title="Keyword not found" description="It is no longer among your tracked keywords. It may have been removed." />
70
+ ) : (
71
+ <EmptyState title="Keyword details are not loaded" description="Listing tracked keywords is a billable read, so it asks first." />
72
+ )}
73
+ <div className="flex justify-center">
74
+ <AskButton ask={ask} label="Load keyword" tier="billable" title="Load tracked keywords" confirmLabel="Load keywords" description="Listing tracked keywords is classified billable by the API."
75
+ run={async () => { await restore.mutateAsync(undefined); }} />
76
+ </div>
77
+ {/* A failed load surfaces once, through the confirm hook's `failure`. */}
78
+ {failure ? <ApiError error={failure} title="That did not work" /> : null}
79
+ <ConfirmSpendDialog {...dialog} />
80
+ </div>
81
+ );
82
+ }
83
+
84
+ const actions = (
85
+ <div className="flex flex-wrap gap-1">
86
+ {onShowAccounts ? <Button size="sm" variant="outline" onClick={() => onShowAccounts(keyword)}>Show accounts</Button> : null}
87
+ <AskButton ask={ask} label="Load companies" tier="billable" title={`Load companies for "${keyword.keyword}"`} confirmLabel="Load companies" description="Resolves the companies behind this keyword's traffic."
88
+ run={async () => setCompanies(rowsOf<CompanyRow>(await g8Post(`/intent/keywords/${keyword.id}/companies`, { limit: 50, page: 1 })))} />
89
+ <AskButton ask={ask} label="Stop tracking" variant="ghost" tier="destructive" title={`Stop tracking "${keyword.keyword}"`} confirmLabel="Stop tracking" tolerateStatus={[404]}
90
+ description="The keyword is removed. Companies already saved to lists stay." run={() => g8Delete(`/intent/keywords/${keyword.id}`)} onDone={() => { onRemoved?.(keyword.id); onClose?.(); }} />
91
+ </div>
92
+ );
93
+ const tabs = [
94
+ {
95
+ id: "overview",
96
+ label: "Overview",
97
+ content: (
98
+ <div className="space-y-4">
99
+ <KeyValueList columns={2} items={[
100
+ { label: "Keyword", value: keyword.keyword },
101
+ { label: "Type", value: keyword.signal_type ?? "-" },
102
+ { label: "Companies resolved", value: keyword.total_resolved_companies ?? 0 },
103
+ { label: "People resolved", value: keyword.total_resolved_contacts ?? 0 },
104
+ { label: "Pages tracked", value: keyword.total_urls ?? "-" },
105
+ { label: "Auto sync", value: keyword.auto_sync_enabled == null ? "-" : keyword.auto_sync_enabled ? "on" : "off" },
106
+ ]} />
107
+ <SectionCard title="Companies behind this keyword" description="Loading them is billable, so it asks first (Load companies above).">
108
+ {companies == null ? <p className="text-sm text-muted-foreground">Not loaded yet.</p> : companies.length === 0 ? <p className="text-sm text-muted-foreground">No resolved companies yet.</p> : (
109
+ <ul className="grid gap-1 text-sm md:grid-cols-2">{companies.map((c, i) => <li key={i}>{companyName(c)}</li>)}</ul>
110
+ )}
111
+ </SectionCard>
112
+ </div>
113
+ ),
114
+ },
115
+ { id: "accounts", label: "Accounts showing intent", content: <IntentFeed keywordIds={[keyword.id]} onSelectAccount={onSelectAccount} /> },
116
+ ];
117
+ return (
118
+ <>
119
+ <DetailPanel title={keyword.keyword} subtitle="Tracked keyword" badges={<StatusPill tone={keyword.is_active === false ? "neutral" : "positive"}>{keyword.is_active === false ? "inactive" : "tracking"}</StatusPill>} actions={actions} tabs={tabs} onClose={onClose} onExpand={onExpand} onCollapse={onCollapse} />
120
+ {failure ? <ApiError error={failure} title="That did not work" /> : null}
121
+ <ConfirmSpendDialog {...dialog} />
122
+ </>
123
+ );
124
+ }
@@ -88,8 +88,8 @@ export function formatDate(value: string | null | undefined): string {
88
88
  /** Tailwind classes for an account-strength label. Unknown labels stay neutral. */
89
89
  export function strengthClass(strength: string | null | undefined): string {
90
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";
91
+ if (s.includes("strong") || s === "high") return "bg-positive text-positive-foreground";
92
+ if (s.includes("moderate") || s === "medium") return "bg-warning text-warning-foreground";
93
+ if (s.includes("weak") || s === "low") return "bg-muted text-foreground";
94
+ return "bg-muted text-foreground";
95
95
  }
@@ -13,17 +13,18 @@
13
13
  "crm"
14
14
  ],
15
15
  "operations": [
16
- "list_lifecycle_stages_lifecycle_stages_get",
16
+ "get_account_intent_signals_accounts__account_id__intent_signals_get",
17
+ "get_brief_customer_success_companies__company_id__brief_get",
18
+ "get_company_companies__company_id__get",
17
19
  "get_lifecycle_stage_counts_lifecycle_stages_counts_get",
20
+ "get_revenue_summary_accounts_revenue_summary_get",
21
+ "list_lifecycle_contacts_lifecycle_contacts_get",
18
22
  "list_lifecycle_stage_records_lifecycle_stages__stage__records_get",
23
+ "list_lifecycle_stages_lifecycle_stages_get",
19
24
  "move_company_lifecycle_stage_companies__company_id__lifecycle_stage_post",
20
25
  "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
26
  "synthesize_brief_customer_success_companies__company_id__brief_synthesize_post",
26
- "get_account_intent_signals_accounts__account_id__intent_signals_get"
27
+ "unlock_lifecycle_contacts_lifecycle_contacts_unlock_post"
27
28
  ],
28
29
  "components": [
29
30
  {
@@ -103,8 +104,12 @@
103
104
  "name": "LifecycleScreen",
104
105
  "file": "components/lifecycle-screen.tsx",
105
106
  "purpose": "Finished lifecycle product: board, one tab per stage (contact queue, customers, churned) and a CRM record panel with Move stage.",
106
- "operations": [],
107
- "tiers": []
107
+ "operations": [
108
+ "get_company_companies__company_id__get"
109
+ ],
110
+ "tiers": [
111
+ "read"
112
+ ]
108
113
  },
109
114
  {
110
115
  "name": "AccountBriefTab",