@graph8/sdk 0.370.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 (466) hide show
  1. package/README.md +19 -0
  2. package/blocks/_foundation/README.md +14 -0
  3. package/blocks/_foundation/block.json +44 -0
  4. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  5. package/blocks/_foundation/components/empty-state.tsx +5 -2
  6. package/blocks/_foundation/components/list-select.tsx +112 -0
  7. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  8. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  9. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  10. package/blocks/_foundation/components/screen/index.ts +1 -0
  11. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  12. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  13. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  14. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  15. package/blocks/_foundation/lib/paging.ts +57 -0
  16. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  17. package/blocks/agents/README.md +81 -4
  18. package/blocks/agents/block.json +311 -2
  19. package/blocks/agents/components/agent-activity.tsx +112 -0
  20. package/blocks/agents/components/agent-approvals.tsx +135 -0
  21. package/blocks/agents/components/agent-builder.tsx +17 -3
  22. package/blocks/agents/components/agent-conversations.tsx +143 -0
  23. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  24. package/blocks/agents/components/agent-operators.tsx +164 -0
  25. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  26. package/blocks/agents/components/agent-runs.tsx +3 -3
  27. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  28. package/blocks/agents/components/agent-test-history.tsx +121 -0
  29. package/blocks/agents/components/agents-screen.tsx +53 -11
  30. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  31. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  32. package/blocks/agents/components/from-template-button.tsx +65 -0
  33. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  34. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  35. package/blocks/agents/components/knowledge-form.tsx +69 -0
  36. package/blocks/agents/components/name-dialog.tsx +32 -0
  37. package/blocks/agents/components/receptionist-button.tsx +46 -0
  38. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  39. package/blocks/agents/components/skill-editor.tsx +147 -0
  40. package/blocks/agents/components/skill-library.tsx +111 -0
  41. package/blocks/agents/lib/agent-types.ts +7 -1
  42. package/blocks/agents/lib/facts.tsx +13 -0
  43. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  44. package/blocks/agents/lib/load-more.tsx +141 -0
  45. package/blocks/agents/lib/skill-utils.ts +91 -0
  46. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  47. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  48. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  49. package/blocks/analytics/components/report-builder.tsx +15 -2
  50. package/blocks/appointments/README.md +6 -0
  51. package/blocks/appointments/block.json +27 -0
  52. package/blocks/appointments/components/booking-page.tsx +2 -2
  53. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  54. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  55. package/blocks/appointments/components/meeting-list.tsx +29 -13
  56. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  57. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  58. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  59. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  60. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  61. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  62. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  63. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  64. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  65. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  66. package/blocks/coach/components/call-scorecard.tsx +7 -7
  67. package/blocks/company-intelligence/README.md +132 -0
  68. package/blocks/company-intelligence/block.json +318 -0
  69. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  70. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  71. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  72. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  73. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  74. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  75. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  76. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  77. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  78. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  79. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  80. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  81. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  82. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  83. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  84. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  85. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  86. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  87. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  88. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  89. package/blocks/crm/README.md +6 -0
  90. package/blocks/crm/block.json +53 -4
  91. package/blocks/crm/components/companies-screen.tsx +23 -9
  92. package/blocks/crm/components/company-detail.tsx +2 -2
  93. package/blocks/crm/components/contact-detail.tsx +4 -4
  94. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  95. package/blocks/crm/components/contacts-screen.tsx +31 -13
  96. package/blocks/crm/components/dedupe-review.tsx +6 -1
  97. package/blocks/crm/components/import-wizard.tsx +3 -7
  98. package/blocks/crm/components/list-select.tsx +20 -0
  99. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  100. package/blocks/crm/components/record-form.tsx +1 -1
  101. package/blocks/crm/components/tasks-screen.tsx +35 -15
  102. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  103. package/blocks/crm/hooks/use-page-query.ts +3 -1
  104. package/blocks/crm/lib/crm-types.ts +14 -0
  105. package/blocks/crm/lib/read-lists.ts +16 -0
  106. package/blocks/deals/README.md +24 -6
  107. package/blocks/deals/block.json +151 -2
  108. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  109. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  110. package/blocks/deals/components/deal-detail.tsx +2 -2
  111. package/blocks/deals/components/deal-filters.tsx +65 -0
  112. package/blocks/deals/components/deal-line-items.tsx +120 -0
  113. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  114. package/blocks/deals/components/deal-related.tsx +30 -9
  115. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  116. package/blocks/deals/components/deals-screen.tsx +57 -22
  117. package/blocks/deals/components/forecast-view.tsx +4 -4
  118. package/blocks/deals/components/pipeline-board.tsx +8 -6
  119. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  120. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  121. package/blocks/deals/components/stage-editor.tsx +1 -1
  122. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  123. package/blocks/deals/lib/deal-extras.ts +156 -0
  124. package/blocks/dialer/README.md +14 -5
  125. package/blocks/dialer/block.json +17 -2
  126. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  127. package/blocks/dialer/components/call-log-screen.tsx +4 -1
  128. package/blocks/dialer/components/call-log.tsx +16 -4
  129. package/blocks/dialer/components/call-queue.tsx +3 -3
  130. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  131. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  132. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  133. package/blocks/dialer/components/session-start-form.tsx +58 -11
  134. package/blocks/dialer/components/softphone.tsx +1 -1
  135. package/blocks/enrichment/README.md +16 -6
  136. package/blocks/enrichment/block.json +19 -7
  137. package/blocks/enrichment/components/bulk-enrich-panel.tsx +76 -27
  138. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  139. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  140. package/blocks/enrichment/components/enrichment-screen.tsx +70 -17
  141. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  142. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  143. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  144. package/blocks/forms/README.md +8 -0
  145. package/blocks/forms/block.json +107 -0
  146. package/blocks/forms/components/form-activity.tsx +71 -0
  147. package/blocks/forms/components/form-backfill.tsx +108 -0
  148. package/blocks/forms/components/form-builder.tsx +1 -1
  149. package/blocks/forms/components/form-embed.tsx +1 -1
  150. package/blocks/forms/components/form-groups.tsx +151 -0
  151. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  152. package/blocks/forms/components/form-route-history.tsx +72 -0
  153. package/blocks/forms/components/form-saved-views.tsx +81 -0
  154. package/blocks/forms/components/forms-screen.tsx +51 -17
  155. package/blocks/forms/components/more-rows.tsx +30 -0
  156. package/blocks/forms/components/submissions-export.tsx +67 -0
  157. package/blocks/forms/components/submissions-table.tsx +15 -6
  158. package/blocks/forms/lib/forms-extra.ts +67 -0
  159. package/blocks/inbox/README.md +5 -0
  160. package/blocks/inbox/block.json +15 -0
  161. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  162. package/blocks/inbox/components/thread-badge.tsx +2 -2
  163. package/blocks/inbox/components/thread-view.tsx +1 -1
  164. package/blocks/integrations/README.md +54 -1
  165. package/blocks/integrations/block.json +189 -15
  166. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  167. package/blocks/integrations/components/connection-health.tsx +59 -0
  168. package/blocks/integrations/components/crm-detail.tsx +20 -1
  169. package/blocks/integrations/components/crm-records.tsx +103 -0
  170. package/blocks/integrations/components/field-mappings.tsx +61 -0
  171. package/blocks/integrations/components/integration-list.tsx +4 -4
  172. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  173. package/blocks/integrations/components/migration-panel.tsx +133 -0
  174. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  175. package/blocks/integrations/components/sync-status.tsx +6 -6
  176. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  177. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  178. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  179. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  180. package/blocks/integrations/lib/crm-utils.ts +39 -0
  181. package/blocks/integrations/lib/facts.tsx +13 -0
  182. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  183. package/blocks/integrations/lib/load-more.tsx +141 -0
  184. package/blocks/intent/components/account-intent-score.tsx +5 -5
  185. package/blocks/intent/components/intent-feed.tsx +8 -8
  186. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  187. package/blocks/intent/lib/intent-utils.ts +4 -4
  188. package/blocks/lifecycle/components/account-related.tsx +8 -1
  189. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  190. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  191. package/blocks/lists/README.md +7 -1
  192. package/blocks/lists/block.json +31 -3
  193. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  194. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  195. package/blocks/lists/components/list-picker.tsx +13 -3
  196. package/blocks/lists/components/list-table.tsx +1 -1
  197. package/blocks/lists/components/lists-screen.tsx +4 -2
  198. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  199. package/blocks/lists/components/suppression-manager.tsx +27 -6
  200. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  201. package/blocks/mailboxes/block.json +4 -0
  202. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  203. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  204. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  205. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  206. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  207. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  208. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  209. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  210. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  211. package/blocks/newsletter/README.md +12 -0
  212. package/blocks/newsletter/block.json +117 -0
  213. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  214. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  215. package/blocks/newsletter/components/more-rows.tsx +30 -0
  216. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  217. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  218. package/blocks/newsletter/components/newsletter-screen.tsx +36 -13
  219. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  220. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  221. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  222. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  223. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  224. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  225. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  226. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  227. package/blocks/nurture/components/nurture-form.tsx +5 -8
  228. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  229. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  230. package/blocks/objects/README.md +1 -1
  231. package/blocks/objects/block.json +13 -5
  232. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  233. package/blocks/objects/components/object-record-detail.tsx +8 -1
  234. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  235. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  236. package/blocks/pages/README.md +14 -0
  237. package/blocks/pages/block.json +113 -1
  238. package/blocks/pages/components/more-rows.tsx +30 -0
  239. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  240. package/blocks/pages/components/page-analytics.tsx +176 -0
  241. package/blocks/pages/components/page-clone.tsx +99 -0
  242. package/blocks/pages/components/page-domains.tsx +167 -0
  243. package/blocks/pages/components/page-editor.tsx +1 -1
  244. package/blocks/pages/components/page-generation-status.tsx +48 -0
  245. package/blocks/pages/components/page-tracking.tsx +137 -0
  246. package/blocks/pages/components/pages-screen.tsx +57 -16
  247. package/blocks/pages/components/seo-report.tsx +6 -1
  248. package/blocks/pages/lib/studio.ts +7 -0
  249. package/blocks/phone-numbers/README.md +74 -0
  250. package/blocks/phone-numbers/block.json +262 -0
  251. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  252. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  253. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  254. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  255. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  256. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  257. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  258. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  259. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  260. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  261. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  262. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  263. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  264. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  265. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  266. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  267. package/blocks/pickers/README.md +23 -0
  268. package/blocks/pickers/block.json +103 -0
  269. package/blocks/pickers/components/company-picker.tsx +90 -0
  270. package/blocks/pickers/components/contact-picker.tsx +131 -0
  271. package/blocks/pickers/components/member-picker.tsx +99 -0
  272. package/blocks/pickers/components/search-picker.tsx +98 -0
  273. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  274. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  275. package/blocks/pickers/lib/use-debounced.ts +13 -0
  276. package/blocks/quotes/README.md +13 -0
  277. package/blocks/quotes/block.json +48 -1
  278. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  279. package/blocks/quotes/components/quote-builder.tsx +1 -1
  280. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  281. package/blocks/quotes/components/quote-list.tsx +5 -3
  282. package/blocks/quotes/components/quote-related.tsx +3 -2
  283. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  284. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  285. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  286. package/blocks/radar/components/radar-overview.tsx +4 -4
  287. package/blocks/radar/components/radar-screen.tsx +2 -2
  288. package/blocks/sequencer/README.md +6 -0
  289. package/blocks/sequencer/block.json +28 -1
  290. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  291. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  292. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  293. package/blocks/sequencer/components/sequence-builder.tsx +19 -20
  294. package/blocks/sequencer/components/sequence-detail.tsx +4 -2
  295. package/blocks/sequencer/components/sequence-enrollments.tsx +4 -2
  296. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  297. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  298. package/blocks/sequencer/components/sequences-screen.tsx +3 -0
  299. package/blocks/sequencer/components/step-editor.tsx +11 -11
  300. package/blocks/sequencer/lib/sequencer-utils.ts +5 -5
  301. package/blocks/social-ads/README.md +8 -0
  302. package/blocks/social-ads/block.json +137 -4
  303. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  304. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  305. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  306. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  307. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  308. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  309. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  310. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  311. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  312. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  313. package/blocks/social-ads/components/post-composer.tsx +4 -2
  314. package/blocks/social-ads/components/post-detail.tsx +3 -0
  315. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  316. package/blocks/social-ads/components/social-performance.tsx +80 -0
  317. package/blocks/social-ads/components/social-screen.tsx +33 -6
  318. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  319. package/blocks/surveys/README.md +91 -0
  320. package/blocks/surveys/block.json +299 -0
  321. package/blocks/surveys/components/account-nps.tsx +69 -0
  322. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  323. package/blocks/surveys/components/org-summary.tsx +82 -0
  324. package/blocks/surveys/components/sending-domains.tsx +113 -0
  325. package/blocks/surveys/components/survey-form.tsx +126 -0
  326. package/blocks/surveys/components/survey-responses.tsx +145 -0
  327. package/blocks/surveys/components/survey-results.tsx +72 -0
  328. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  329. package/blocks/surveys/components/survey-send.tsx +162 -0
  330. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  331. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  332. package/blocks/surveys/lib/surveys-types.ts +228 -0
  333. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  334. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  335. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  336. package/blocks/visitors/components/snippet-install.tsx +18 -9
  337. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  338. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  339. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  340. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  341. package/blocks/webchat/components/conversation-list.tsx +1 -1
  342. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  343. package/blocks/workbench/components/run-progress.tsx +1 -1
  344. package/blocks/workbench/components/workbench-grid.tsx +1 -1
  345. package/blocks/workbench/components/workbench-screen.tsx +15 -5
  346. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  347. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  348. package/dist/cli.js +536 -392
  349. package/dist/index.browser.js +1 -1
  350. package/dist/index.browser.js.map +1 -1
  351. package/dist/index.browser.mjs +1 -1
  352. package/dist/index.browser.mjs.map +1 -1
  353. package/dist/index.d.mts +2 -2
  354. package/dist/index.d.ts +2 -2
  355. package/dist/index.js +1 -1
  356. package/dist/index.js.map +1 -1
  357. package/dist/index.mjs +1 -1
  358. package/dist/index.mjs.map +1 -1
  359. package/dist/react.browser.js +1 -1
  360. package/dist/react.browser.js.map +1 -1
  361. package/dist/react.browser.mjs +1 -1
  362. package/dist/react.browser.mjs.map +1 -1
  363. package/dist/react.d.mts +2 -2
  364. package/dist/react.d.ts +2 -2
  365. package/dist/react.js +1 -1
  366. package/dist/react.js.map +1 -1
  367. package/dist/react.mjs +1 -1
  368. package/dist/react.mjs.map +1 -1
  369. package/dist/types/blocks/brand.d.ts +11 -2
  370. package/dist/types/blocks/create.d.ts +7 -3
  371. package/dist/types/blocks/doctor.d.ts +1 -0
  372. package/dist/types/blocks/flows.d.ts +7 -1
  373. package/dist/types/blocks/orgBrand.d.ts +34 -0
  374. package/dist/types/cli.d.ts +3 -0
  375. package/dist/types/version.d.ts +2 -2
  376. package/docs/api/desk.d.ts +2 -2
  377. package/docs/api/desk.md +3 -3
  378. package/package.json +1 -1
  379. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  380. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  381. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  382. package/stacks/6sense.json +17 -4
  383. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  384. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  385. package/stacks/apollo/pages/contacts.tsx.tmpl +29 -95
  386. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  387. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  388. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  389. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  390. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  391. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  392. package/stacks/apollo.json +36 -8
  393. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  394. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  395. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  396. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  397. package/stacks/calendly.json +8 -4
  398. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  399. package/stacks/clay/pages/contacts.tsx.tmpl +12 -0
  400. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  401. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  402. package/stacks/clay.json +16 -3
  403. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  404. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  405. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  406. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  407. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  408. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  409. package/stacks/gong.json +23 -6
  410. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  411. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  412. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  413. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  414. package/stacks/hootsuite.json +9 -4
  415. package/stacks/hubspot/pages/companies.tsx.tmpl +14 -35
  416. package/stacks/hubspot/pages/contacts.tsx.tmpl +29 -95
  417. package/stacks/hubspot/pages/deals.tsx.tmpl +10 -1
  418. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  419. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  420. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  421. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  422. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  423. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  424. package/stacks/hubspot.json +21 -5
  425. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  426. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  427. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  428. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  429. package/stacks/intercom.json +8 -4
  430. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  431. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  432. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  433. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  434. package/stacks/mailchimp.json +20 -5
  435. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  436. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  437. package/stacks/outreach/pages/contacts.tsx.tmpl +27 -66
  438. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  439. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  440. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  441. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  442. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  443. package/stacks/outreach.json +25 -7
  444. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  445. package/stacks/pipedrive/pages/contacts.tsx.tmpl +15 -35
  446. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  447. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  448. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  449. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  450. package/stacks/pipedrive.json +4 -2
  451. package/stacks/salesforce/pages/companies.tsx.tmpl +14 -35
  452. package/stacks/salesforce/pages/contacts.tsx.tmpl +20 -35
  453. package/stacks/salesforce/pages/deals.tsx.tmpl +10 -1
  454. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  455. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  456. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  457. package/stacks/salesforce.json +7 -3
  458. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  459. package/stacks/zoominfo/pages/contacts.tsx.tmpl +12 -0
  460. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  461. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  462. package/stacks/zoominfo.json +16 -3
  463. package/styles/styles.css +1 -1
  464. package/templates/next-app/app/globals.css.tmpl +349 -64
  465. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  466. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
@@ -0,0 +1,228 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Types, vocabularies and pure helpers for the surveys block.
5
+ *
6
+ * A graph8 survey is ONE question with a numeric answer (plus an optional comment): NPS (0 to 10) or CSAT (1 to 5).
7
+ * The vocabularies below are copied from the API's request patterns (`^(nps|csat)$`, `^(email|bridge|in_app)$`,
8
+ * `^(none|link|html)$`, `^(email|bridge)$`, `^(scheduled|list_entry|stage_entry)$`), not from memory: the API refuses
9
+ * anything else. Calls nothing.
10
+ */
11
+
12
+ export type SurveyType = "nps" | "csat";
13
+ export type DeliveryChannel = "email" | "bridge" | "in_app";
14
+ export type CommentMode = "none" | "link" | "html";
15
+ export type SendChannel = "email" | "bridge";
16
+ export type TriggerKind = "scheduled" | "list_entry" | "stage_entry";
17
+ export type Bucket = "day" | "week" | "month";
18
+
19
+ export const SURVEY_TYPES: { value: SurveyType; label: string; hint: string; min: number; max: number }[] = [
20
+ { value: "nps", label: "NPS", hint: "How likely are you to recommend us? Scored 0 to 10.", min: 0, max: 10 },
21
+ { value: "csat", label: "CSAT", hint: "How satisfied were you? Scored 1 to 5.", min: 1, max: 5 },
22
+ ];
23
+ export const DELIVERY_CHANNELS: { value: DeliveryChannel; label: string }[] = [
24
+ { value: "email", label: "Email" },
25
+ { value: "bridge", label: "Chat message (messaging bridge)" },
26
+ { value: "in_app", label: "In-app widget" },
27
+ ];
28
+ export const COMMENT_MODES: { value: CommentMode; label: string }[] = [
29
+ { value: "none", label: "Score only" },
30
+ { value: "link", label: "Score, then a comment form" },
31
+ { value: "html", label: "Comment box inside the email" },
32
+ ];
33
+ export const SEND_CHANNELS: { value: SendChannel; label: string }[] = [
34
+ { value: "email", label: "Email" },
35
+ { value: "bridge", label: "Chat message" },
36
+ ];
37
+ export const TRIGGER_KINDS: { value: TriggerKind; label: string; hint: string }[] = [
38
+ { value: "scheduled", label: "On a schedule", hint: "Re-sends to the list every N days." },
39
+ { value: "list_entry", label: "When someone joins a list", hint: "Sends to each person who enters the watched list." },
40
+ { value: "stage_entry", label: "When a record reaches a stage", hint: "Sends when a record moves into the stage you name." },
41
+ ];
42
+ export const BUCKETS: { value: Bucket; label: string }[] = [
43
+ { value: "day", label: "Daily" },
44
+ { value: "week", label: "Weekly" },
45
+ { value: "month", label: "Monthly" },
46
+ ];
47
+ export const CADENCE_DAYS = [30, 60, 90] as const;
48
+
49
+ /** One survey definition (SurveyResponseDTO). */
50
+ export interface Survey {
51
+ id: string;
52
+ name: string;
53
+ type: string;
54
+ question_text: string;
55
+ scale_min: number;
56
+ scale_max: number;
57
+ delivery_channel: string;
58
+ is_active: boolean;
59
+ comment_mode?: string | null;
60
+ thank_you_copy?: string | null;
61
+ template_id?: number | null;
62
+ score_field_column_id?: string | null;
63
+ created_at?: string | null;
64
+ }
65
+
66
+ export interface TrendBar { period: string; total: number; promoters: number; passives: number; detractors: number; nps_score?: number | null; csat_avg?: number | null }
67
+ export interface Summary {
68
+ survey_id: string;
69
+ type: string;
70
+ bucket: string;
71
+ delivery_channel: string;
72
+ total_respondents: number;
73
+ nps_score?: number | null;
74
+ promoters: number;
75
+ passives: number;
76
+ detractors: number;
77
+ csat_avg?: number | null;
78
+ satisfied?: number | null;
79
+ neutral?: number | null;
80
+ dissatisfied?: number | null;
81
+ response_rate: { rate: number; responded: number; sent: number };
82
+ time_series: TrendBar[];
83
+ }
84
+ export interface Trends { survey_id: string; bars: TrendBar[]; bucket: string; type: string; delivery_channel: string }
85
+
86
+ export interface ResponseRow {
87
+ contact_id: number;
88
+ first_name?: string | null;
89
+ last_name?: string | null;
90
+ score: number;
91
+ classification?: string | null;
92
+ comment?: string | null;
93
+ channel?: string | null;
94
+ responded_at?: string | null;
95
+ response_count?: number;
96
+ trend?: string | null;
97
+ history?: { score: number; responded_at?: string | null; send_id?: number | null }[];
98
+ }
99
+ export interface AppUserResponseRow { app_user_id: string; channel: string; score: number; comment?: string | null; responded_at?: string | null }
100
+
101
+ export interface SendRow {
102
+ id: number;
103
+ status: string;
104
+ channel: string;
105
+ audience_ref?: string | null;
106
+ audience_name?: string | null;
107
+ recipient_count?: number;
108
+ responded_count?: number;
109
+ scheduled_at?: string | null;
110
+ created_at?: string | null;
111
+ trigger_kind?: string | null;
112
+ delivery_breakdown?: { bounced?: number; delivered?: number; failed?: number; opened?: number; pending?: number; sent?: number };
113
+ }
114
+ export interface SendAccepted { send_id: number; status: string; recipient_count: number }
115
+ export interface SendDetail { id: number; status: string; channel: string; recipient_count: number; scheduled_at?: string | null; audience_ref?: string | null; survey_id: string }
116
+
117
+ export interface Trigger {
118
+ id: string;
119
+ survey_id: string;
120
+ trigger_kind: string;
121
+ is_active: boolean;
122
+ sender_email: string;
123
+ sender_name?: string | null;
124
+ reply_to?: string | null;
125
+ subject: string;
126
+ audience_ref?: string | null;
127
+ stage_value?: string | null;
128
+ cadence_days?: number | null;
129
+ }
130
+ export interface SendingDomain { id: number; domain: string; status: "verified" | "pending" | "not_set_up" }
131
+
132
+ export interface OrgSummary {
133
+ bucket: string;
134
+ org_nps: { nps_score?: number | null; promoters: number; passives: number; detractors: number; total_respondents: number };
135
+ org_csat: { csat_avg?: number | null; total_respondents: number };
136
+ in_app_csat_avg?: number | null;
137
+ in_app_respondents?: number;
138
+ per_survey: { survey_id: string; name?: string | null; type: string; status?: string | null; is_active?: boolean; sent_count?: number; total_respondents: number; nps_score?: number | null; csat_avg?: number | null; promoters: number; passives: number; detractors: number }[];
139
+ time_series: TrendBar[];
140
+ }
141
+
142
+ export const isNps = (s: { type?: string | null }): boolean => (s.type ?? "").toLowerCase() === "nps";
143
+ export const typeLabel = (s: { type?: string | null }): string => (isNps(s) ? "NPS" : "CSAT");
144
+ export const scaleOf = (type: SurveyType): { min: number; max: number } => {
145
+ const t = SURVEY_TYPES.find((x) => x.value === type) ?? SURVEY_TYPES[0];
146
+ return { min: t.min, max: t.max };
147
+ };
148
+ export const channelLabel = (v: string | null | undefined): string => DELIVERY_CHANNELS.find((c) => c.value === v)?.label ?? (v || "Not set");
149
+ /** A survey receives answers from the people it is sent to (or the in-app widget) while it is open. */
150
+ export const statusOf = (s: Survey): "open" | "closed" => (s.is_active ? "open" : "closed");
151
+
152
+ /** "42", "-8", or a faint dash when the API has no score (too few answers is not zero). */
153
+ export function scoreText(v: number | null | undefined, digits = 0): string {
154
+ return v == null || !Number.isFinite(v) ? "-" : v.toFixed(digits).replace(/^-0$/, "0");
155
+ }
156
+ export function percent(part: number | null | undefined, whole: number | null | undefined): string {
157
+ return !whole || part == null ? "-" : `${Math.round((part / whole) * 100)}%`;
158
+ }
159
+ export const fullName = (r: { first_name?: string | null; last_name?: string | null; contact_id?: number }): string =>
160
+ [r.first_name, r.last_name].filter(Boolean).join(" ") || (r.contact_id != null ? `Contact ${r.contact_id}` : "Unknown");
161
+ export const when = (v: string | null | undefined): string => (v ? new Date(v).toLocaleString() : "");
162
+
163
+ /** The classification the API may omit, derived from the survey type's published bands (NPS 9-10 / 7-8 / 0-6; CSAT 4-5 / 3 / 1-2). */
164
+ export function bandOf(type: string | null | undefined, score: number, given?: string | null): string {
165
+ if (given) return given;
166
+ if ((type ?? "").toLowerCase() === "csat") return score >= 4 ? "satisfied" : score === 3 ? "neutral" : "dissatisfied";
167
+ return score >= 9 ? "promoter" : score >= 7 ? "passive" : "detractor";
168
+ }
169
+
170
+ /** The audience size a send will reach, at most: the list's contact count, null when the list is unknown. */
171
+ export function audienceSize(list: { contact_count?: number | null; total?: number | null } | null | undefined): number | null {
172
+ const n = list?.contact_count ?? list?.total;
173
+ return typeof n === "number" ? n : null;
174
+ }
175
+
176
+ /** Fields a person can change after creating a survey (the API does not take type, scale or comment mode on update). */
177
+ export interface SurveyFormValues { name: string; question_text: string; thank_you_copy: string; delivery_channel: DeliveryChannel }
178
+
179
+ /**
180
+ * Only the fields that changed. A cleared thank-you line is sent as "" (the API ignores null on update, so null would
181
+ * silently keep the old text); an empty name or question never reaches here because the form blocks saving it.
182
+ */
183
+ export function surveyDiff(before: Survey, next: SurveyFormValues): Record<string, unknown> {
184
+ const diff: Record<string, unknown> = {};
185
+ if (next.name.trim() !== before.name) diff.name = next.name.trim();
186
+ if (next.question_text.trim() !== before.question_text) diff.question_text = next.question_text.trim();
187
+ if (next.thank_you_copy.trim() !== (before.thank_you_copy ?? "").trim()) diff.thank_you_copy = next.thank_you_copy.trim();
188
+ if (next.delivery_channel !== before.delivery_channel) diff.delivery_channel = next.delivery_channel;
189
+ return diff;
190
+ }
191
+
192
+ /** One CSV cell: quoted, quotes doubled, and a leading = + - @ tab or CR neutralised so a spreadsheet never runs a comment as a formula. */
193
+ export function csvCell(value: unknown): string {
194
+ let text = value == null ? "" : String(value);
195
+ if (/^[=+\-@\t\r]/.test(text) && !/^-?\d+(\.\d+)?$/.test(text)) text = `'${text}`;
196
+ return `"${text.replace(/"/g, '""')}"`;
197
+ }
198
+
199
+ export function responsesCsv(type: string, rows: ResponseRow[]): string {
200
+ const head = ["contact_id", "first_name", "last_name", "score", "classification", "comment", "channel", "responded_at", "response_count"];
201
+ const lines = rows.map((r) => [r.contact_id, r.first_name, r.last_name, r.score, bandOf(type, r.score, r.classification), r.comment, r.channel, r.responded_at, r.response_count ?? 1].map(csvCell).join(","));
202
+ return [head.map(csvCell).join(","), ...lines].join("\r\n");
203
+ }
204
+
205
+ /** Hand a text file to the browser. */
206
+ export function downloadText(filename: string, text: string, mime = "text/csv;charset=utf-8"): void {
207
+ const url = URL.createObjectURL(new Blob([text], { type: mime }));
208
+ const a = document.createElement("a");
209
+ a.href = url;
210
+ a.download = filename;
211
+ document.body.appendChild(a);
212
+ a.click();
213
+ a.remove();
214
+ setTimeout(() => URL.revokeObjectURL(url), 0);
215
+ }
216
+
217
+ export const slugName = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "survey";
218
+
219
+ /** The email part of a sender after the @, lower-cased: the domain a send must be verified for. */
220
+ export const domainOf = (email: string): string => email.split("@")[1]?.trim().toLowerCase() ?? "";
221
+ export const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
222
+
223
+ /** The API's `scheduled_at` is a date-time: a `datetime-local` value is read as local time and sent as ISO. */
224
+ export function scheduleIso(local: string): string | undefined {
225
+ if (!local) return undefined;
226
+ const d = new Date(local);
227
+ return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
228
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @title Create an NPS (or CSAT) survey and read back what the API stored
3
+ * @when Setting up the question you will ask. A survey is ONE question with a numeric answer: `type` is "nps" (0 to 10) or
4
+ * "csat" (1 to 5), fixed at creation. Creating it sends nothing; people are asked only when you call sendSurvey.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ const created = await g8.api.surveys.createSurvey({
11
+ body: {
12
+ name: "Relationship NPS",
13
+ type: "nps",
14
+ question_text: "How likely are you to recommend us to a friend or colleague?",
15
+ delivery_channel: "email",
16
+ comment_mode: "link",
17
+ thank_you_copy: "Thank you, that helps us a lot.",
18
+ },
19
+ });
20
+
21
+ const survey = await g8.api.surveys.getSurvey({ path: { survey_id: created.data.id } });
22
+ console.log(survey.data.id, survey.data.type, `${survey.data.scale_min}-${survey.data.scale_max}`, survey.data.is_active);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @title Send a survey to a list and follow the send
3
+ * @when Asking an audience now or later. This EMAILS REAL PEOPLE: have a person confirm the audience size first. The sender
4
+ * address must be on a verified sending domain (listSurveySendingDomains), and `scheduled_at` queues the send for later.
5
+ * The API answers 202 with a send id; dispatch is asynchronous, so poll getSurveySend.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ export async function sendSurvey(surveyId: string, listId: number): Promise<{ sendId: number; status: string; recipients: number }> {
12
+ const domains = await g8.api.surveys.listSurveySendingDomains({});
13
+ const verified = domains.data.find((d) => d.status === "verified");
14
+ if (!verified) throw new Error("No verified sending domain. Verify one first with verifySurveyDomain.");
15
+
16
+ const accepted = await g8.api.surveys.sendSurvey({
17
+ path: { survey_id: surveyId },
18
+ body: { audience_ref: String(listId), channel: "email", sender_email: `survey@${verified.domain}`, subject: "Quick question" },
19
+ });
20
+
21
+ let status = accepted.data.status;
22
+ for (let i = 0; i < 20 && !["sent", "failed", "scheduled"].includes(status); i++) {
23
+ await new Promise((r) => setTimeout(r, 3000));
24
+ status = (await g8.api.surveys.getSurveySend({ path: { send_id: accepted.data.send_id } })).data.status;
25
+ }
26
+ return { sendId: accepted.data.send_id, status, recipients: accepted.data.recipient_count };
27
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @title Report every survey's score, response rate and latest comments
3
+ * @when A weekly digest or a dashboard backend. A null score means too few answers to publish, which is not zero. The
4
+ * responses endpoint returns every answer in one read (it takes no paging), so a very large survey is a large response.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ export async function scoreReport(): Promise<Array<{ name: string; type: string; score: number | null; answered: number; asked: number; comments: string[] }>> {
11
+ const surveys = await g8.api.surveys.listSurveys({});
12
+ const out: Array<{ name: string; type: string; score: number | null; answered: number; asked: number; comments: string[] }> = [];
13
+ for (const s of surveys.data) {
14
+ const summary = await g8.api.surveys.getSurveySummary({ path: { survey_id: s.id }, query: { bucket: "month" } });
15
+ const answers = await g8.api.surveys.getSurveyResponses({ path: { survey_id: s.id } });
16
+ const d = summary.data;
17
+ out.push({
18
+ name: s.name,
19
+ type: s.type,
20
+ score: (s.type === "nps" ? d.nps_score : d.csat_avg) ?? null,
21
+ answered: d.response_rate.responded,
22
+ asked: d.response_rate.sent,
23
+ comments: answers.data.responses.filter((r) => r.comment).slice(0, 5).map((r) => r.comment as string),
24
+ });
25
+ }
26
+ return out;
27
+ }
@@ -18,6 +18,8 @@ import { useState } from "react";
18
18
  import { Button, Skeleton } from "@graph8/sdk/ui";
19
19
  import { g8Get } from "@/g8/lib/g8-client";
20
20
  import { useG8Query } from "@/g8/hooks/use-g8-query";
21
+ import { useAllPages } from "@/g8/hooks/use-all-pages";
22
+ import { hasNextOf } from "@/g8/lib/paging";
21
23
  import { useJobPoll } from "@/g8/hooks/use-job-poll";
22
24
  import { ApiError } from "@/g8/components/api-error";
23
25
  import { EmptyState } from "@/g8/components/empty-state";
@@ -72,7 +74,7 @@ function CodeBlock({ label, code }: { label: string; code: string }) {
72
74
  {copied ? "Copied" : "Copy"}
73
75
  </Button>
74
76
  </div>
75
- <pre className="overflow-x-auto rounded bg-gray-950 p-3 text-xs text-gray-100">
77
+ <pre className="overflow-x-auto rounded bg-muted p-3 text-xs text-foreground">
76
78
  <code>{code}</code>
77
79
  </pre>
78
80
  </div>
@@ -99,33 +101,40 @@ function InstallCheck({ writeKey, onInstalled }: { writeKey: string; onInstalled
99
101
  </Button>
100
102
  {check.error ? <ApiError error={check.error} /> : null}
101
103
  {s?.status === "receiving" ? (
102
- <p role="status" className="text-sm text-green-700">
104
+ <p role="status" className="text-sm text-positive-foreground">
103
105
  Installed: {s.event_count.toLocaleString()} events received.
104
106
  </p>
105
107
  ) : null}
106
108
  {s?.status === "unknown" ? (
107
- <p role="status" className="text-sm text-amber-700">
109
+ <p role="status" className="text-sm text-warning-foreground">
108
110
  Your workspace cannot be probed yet. Open your site once and check the Visitors feed.
109
111
  </p>
110
112
  ) : null}
111
113
  {s?.status === "no_events" && !check.polling ? (
112
- <p role="status" className="text-sm text-gray-600">
114
+ <p role="status" className="text-sm text-muted-foreground">
113
115
  No events yet. Load a page that has the snippet, then check again.
114
116
  </p>
115
117
  ) : null}
116
118
  {s?.status === "no_events" && check.polling ? (
117
- <p className="text-sm text-gray-600">No events yet. Open a page that has the snippet.</p>
119
+ <p className="text-sm text-muted-foreground">No events yet. Open a page that has the snippet.</p>
118
120
  ) : null}
119
121
  </div>
120
122
  );
121
123
  }
122
124
 
125
+ /** Streams per read: every page is read, so the stream picker offers all of them. */
126
+ const STREAM_PAGE = 50;
127
+
123
128
  export function SnippetInstall({ streamId, onInstalled, className }: SnippetInstallProps) {
124
129
  const brand = useBrand();
125
130
  const [selected, setSelected] = useState<string | undefined>(streamId);
126
- const streams = useG8Query((signal) => g8Get<Stream[]>("/streams", { limit: 50 }, { signal }), []);
131
+ const streams = useAllPages<Stream>(async (page, signal) => {
132
+ const env = await g8Get<Stream[]>("/streams", { page, limit: STREAM_PAGE }, { signal });
133
+ const rows = env.data ?? [];
134
+ return { rows, hasNext: hasNextOf(env, STREAM_PAGE, rows.length) };
135
+ }, []);
127
136
  const snippet = useG8Query((signal) => g8Get<SnippetPayload>("/snippet", { stream_id: selected }, { signal }), [selected]);
128
- const list = streams.data?.data ?? [];
137
+ const list = streams.items;
129
138
 
130
139
  if (snippet.error) return <ApiError error={snippet.error} onRetry={() => void snippet.refetch()} className={className} />;
131
140
  if (snippet.loading && !snippet.data) return <Skeleton className={`h-40 w-full ${className ?? ""}`} />;
@@ -145,7 +154,7 @@ export function SnippetInstall({ streamId, onInstalled, className }: SnippetInst
145
154
  <label className="flex items-center gap-2 text-sm">
146
155
  Website
147
156
  <select
148
- className="rounded border border-gray-300 px-2 py-1"
157
+ className="rounded border border-input px-2 py-1"
149
158
  value={selected ?? s.stream_id ?? ""}
150
159
  onChange={(e) => setSelected(e.target.value || undefined)}
151
160
  >
@@ -159,7 +168,7 @@ export function SnippetInstall({ streamId, onInstalled, className }: SnippetInst
159
168
  </label>
160
169
  ) : null}
161
170
  {s.warning ? (
162
- <p role="alert" className="rounded border border-amber-200 bg-amber-50 p-2 text-sm text-amber-900">
171
+ <p role="alert" className="rounded border border-warning-foreground/25 bg-warning p-2 text-sm text-warning-foreground">
163
172
  {s.warning}
164
173
  </p>
165
174
  ) : null}
@@ -62,7 +62,7 @@ export function LineChart({ points, metric }: { points: TrendPoint[]; metric: Me
62
62
  const y = (v: number) => height - (v / max) * (height - 12) - 6;
63
63
  const path = values.map((v, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(" ");
64
64
  return (
65
- <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={`${metric} per day`} className="h-40 w-full text-blue-600">
65
+ <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={`${metric} per day`} className="h-40 w-full text-foreground">
66
66
  <path d={path} fill="none" stroke="currentColor" strokeWidth="2" />
67
67
  {values.map((v, i) => (
68
68
  <circle key={points[i].date} cx={x(i)} cy={y(v)} r="2.5" fill="currentColor">
@@ -113,7 +113,7 @@ export function TrafficChart({ days = 30, writeKey, realtimeSeconds = 10, classN
113
113
  </Button>
114
114
  ))}
115
115
  </div>
116
- <p className="text-sm text-gray-600" aria-live="polite">
116
+ <p className="text-sm text-muted-foreground" aria-live="polite">
117
117
  {now != null ? `${now} on the site now` : ""}
118
118
  </p>
119
119
  </div>
@@ -126,7 +126,7 @@ export function TrafficChart({ days = 30, writeKey, realtimeSeconds = 10, classN
126
126
  <EmptyState title="No traffic yet" description="Install the snippet and visits will chart here." />
127
127
  ) : (
128
128
  <div>
129
- <p className="text-sm text-gray-600">
129
+ <p className="text-sm text-muted-foreground">
130
130
  {total.toLocaleString()} {METRICS.find((m) => m.key === metric)?.label.toLowerCase()} in {days} days
131
131
  </p>
132
132
  <LineChart points={points} metric={metric} />
@@ -136,7 +136,7 @@ export function TrafficChart({ days = 30, writeKey, realtimeSeconds = 10, classN
136
136
  {topPages.length > 0 ? (
137
137
  <table className="w-full text-sm">
138
138
  <thead>
139
- <tr className="text-left text-gray-500">
139
+ <tr className="text-left text-muted-foreground">
140
140
  <th className="py-1">Top pages</th>
141
141
  <th className="py-1 text-right">Views</th>
142
142
  </tr>
@@ -17,7 +17,9 @@ import { useState } from "react";
17
17
  import type { ReactNode } from "react";
18
18
  import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
19
19
  import { g8Get } from "@/g8/lib/g8-client";
20
- import { useG8Query } from "@/g8/hooks/use-g8-query";
20
+ import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
21
+ import { byPage, hasNextOf } from "@/g8/lib/paging";
22
+ import { LoadMore } from "@/g8/components/paging-notices";
21
23
  import { ApiError } from "@/g8/components/api-error";
22
24
  import { companyLabel, pageLabel, pageOf, timeAgo } from "../lib/visitors-utils";
23
25
  import type { FlowEvent, VisitorGroup } from "../lib/visitors-utils";
@@ -32,33 +34,41 @@ export interface VisitorCompanyCardProps {
32
34
  }
33
35
 
34
36
  function PageList({ pages }: { pages: FlowEvent[] }) {
35
- if (pages.length === 0) return <p className="text-sm text-gray-500">No page views recorded.</p>;
37
+ if (pages.length === 0) return <p className="text-sm text-muted-foreground">No page views recorded.</p>;
36
38
  return (
37
39
  <ol className="space-y-1 text-sm">
38
40
  {pages.map((p, i) => (
39
41
  <li key={p.message_id ?? `${p.received_at}-${i}`} className="flex justify-between gap-3">
40
42
  <span className="truncate">
41
43
  {pageLabel(p)}
42
- {p.page_referrer ? <span className="ml-2 text-xs text-gray-400">from {p.page_referrer}</span> : null}
44
+ {p.page_referrer ? <span className="ml-2 text-xs text-muted-foreground">from {p.page_referrer}</span> : null}
43
45
  </span>
44
- <span className="shrink-0 text-xs text-gray-500">{timeAgo(p.received_at)}</span>
46
+ <span className="shrink-0 text-xs text-muted-foreground">{timeAgo(p.received_at)}</span>
45
47
  </li>
46
48
  ))}
47
49
  </ol>
48
50
  );
49
51
  }
50
52
 
53
+ /** Page views per read; "Load more" appends the next page of the journey. */
54
+ const JOURNEY_PAGE = 100;
55
+
51
56
  function Journey({ companyId }: { companyId: number }) {
52
- const journey = useG8Query(
53
- (signal) =>
54
- g8Get("/visitors/companies/events", { show_mode: "raw", filter: JSON.stringify({ company_id: companyId }), limit: 100 }, { signal }),
55
- [companyId],
56
- );
57
- if (journey.loading) return <Skeleton className="h-16 w-full" />;
57
+ const journey = useLoadMoreList(byPage<FlowEvent>(JOURNEY_PAGE, async (page, signal) => {
58
+ const env = await g8Get("/visitors/companies/events", { show_mode: "raw", filter: JSON.stringify({ company_id: companyId }), page, limit: JOURNEY_PAGE }, { signal });
59
+ // Raw mode returns one row per page view.
60
+ const rows = pageOf(env).events as unknown as FlowEvent[];
61
+ return { rows, hasNext: hasNextOf(env, JOURNEY_PAGE, rows.length) };
62
+ }), [companyId]);
63
+ if (journey.loading && !journey.data) return <Skeleton className="h-16 w-full" />;
58
64
  if (journey.error) return <ApiError error={journey.error} onRetry={() => void journey.refetch()} />;
59
- // Raw mode returns one row per page view.
60
- const views = pageOf(journey.data).events as unknown as FlowEvent[];
61
- return <PageList pages={views} />;
65
+ const views = journey.data ?? [];
66
+ return (
67
+ <>
68
+ <PageList pages={views} />
69
+ <LoadMore shown={views.length} noun="page views" hasMore={journey.hasMore} loading={journey.loadingMore} onLoadMore={journey.loadMore} />
70
+ </>
71
+ );
62
72
  }
63
73
 
64
74
  export function VisitorCompanyCard({ visit, previewPages = 5, actions, className }: VisitorCompanyCardProps) {
@@ -69,7 +79,7 @@ export function VisitorCompanyCard({ visit, previewPages = 5, actions, className
69
79
  <CardHeader className="flex flex-row items-start justify-between gap-3">
70
80
  <div>
71
81
  <CardTitle className="text-base">{companyLabel(visit)}</CardTitle>
72
- <p className="text-xs text-gray-500">
82
+ <p className="text-xs text-muted-foreground">
73
83
  {visit.COMPANY_DOMAIN ? `${visit.COMPANY_DOMAIN} - ` : ""}last seen {timeAgo(visit.received_at)} - {plural(pages.length, "page view")}
74
84
  </p>
75
85
  </div>
@@ -131,26 +131,26 @@ export function VisitorFeed({
131
131
  ))}
132
132
  </div>
133
133
  ) : (
134
- <ul className="divide-y rounded border border-gray-200">
134
+ <ul className="divide-y rounded border border-border">
135
135
  {events.map((visit, i) => (
136
136
  <li key={`${visit.CONTACT_ID}-${visit.received_at}-${i}`}>
137
137
  <button
138
138
  type="button"
139
- className="flex w-full items-center justify-between px-3 py-2 text-left text-sm hover:bg-gray-50"
139
+ className="flex w-full items-center justify-between px-3 py-2 text-left text-sm hover:bg-surface-hover"
140
140
  onClick={() => onSelectPerson?.(visit)}
141
141
  >
142
142
  <span>
143
143
  <span className="font-medium">{personLabel(visit)}</span>
144
- <span className="ml-2 text-xs text-gray-500">{visit.children?.[0] ? pageLabel(visit.children[0]) : ""}</span>
144
+ <span className="ml-2 text-xs text-muted-foreground">{visit.children?.[0] ? pageLabel(visit.children[0]) : ""}</span>
145
145
  </span>
146
- <span className="text-xs text-gray-500">{timeAgo(visit.received_at)}</span>
146
+ <span className="text-xs text-muted-foreground">{timeAgo(visit.received_at)}</span>
147
147
  </button>
148
148
  </li>
149
149
  ))}
150
150
  </ul>
151
151
  )}
152
152
 
153
- <div className="flex items-center justify-between text-sm text-gray-600">
153
+ <div className="flex items-center justify-between text-sm text-muted-foreground">
154
154
  <span>{total.toLocaleString()} visits</span>
155
155
  <div className="flex gap-2">
156
156
  <Button size="sm" variant="outline" disabled={page <= 1 || feed.loading} onClick={() => setPage((p) => p - 1)}>
@@ -121,7 +121,7 @@ export function ChatWidgetConfig({ initialBotId, surface = "external" }: ChatWid
121
121
  <div><Label htmlFor="g8-wc-text" className="mb-1">Reply text</Label><Input id="g8-wc-text" placeholder="#111111" value={draft.assistantMessageTextColor ?? ""} onChange={(e) => set({ assistantMessageTextColor: e.target.value })} /></div>
122
122
  <label className="flex items-center gap-2 self-end text-sm"><input type="checkbox" checked={Boolean(draft.darkMode)} onChange={(e) => set({ darkMode: e.target.checked })} />Dark mode</label>
123
123
  </div>
124
- {problem ? <p role="alert" className="text-sm text-red-700">{problem}</p> : null}
124
+ {problem ? <p role="alert" className="text-sm text-error-foreground">{problem}</p> : null}
125
125
  {save.error ? <ApiError error={save.error} title="Could not save the widget settings" /> : null}
126
126
  <div><Button loading={save.loading} disabled={Boolean(problem) || settings.loading || Boolean(settings.error)} onClick={() => void save.mutate(undefined)}>{exists ? "Save changes" : "Create widget settings"}</Button></div>
127
127
  </CardContent>
@@ -47,7 +47,7 @@ export function ConversationList({ linkParam, botId, pageSize = 25, onSelectVisi
47
47
  <div className="grid gap-3">
48
48
  {counts.length > 0 ? (
49
49
  <dl className="grid grid-cols-2 gap-2 sm:grid-cols-4">
50
- {counts.map(([k, v]) => <div key={k} className="rounded-md border p-3"><dt className="text-muted-foreground text-xs">{k.replace(/_/g, " ")}</dt><dd className="text-lg font-semibold">{v.toLocaleString()}</dd></div>)}
50
+ {counts.map(([k, v]) => <div key={k} className="rounded-md border p-3"><dt className="text-muted-foreground text-xs">{k.replace(/_/g, " ")}</dt><dd className="text-lg font-medium">{v.toLocaleString()}</dd></div>)}
51
51
  </dl>
52
52
  ) : null}
53
53
  <form className="flex flex-wrap items-center gap-2" onSubmit={(e) => { e.preventDefault(); setPage(1); setSearch(text.trim()); }}>
@@ -139,7 +139,7 @@ export function ColumnRecipeEditor({ tableId, columns, entityType = "contact", o
139
139
  );
140
140
  })}
141
141
  </fieldset>
142
- {unmapped.length ? <p role="alert" className="text-sm text-amber-700">Not mapped yet: {unmapped.join(", ")}. Use the Map columns tab, or the steps that need them find nothing.</p> : null}
142
+ {unmapped.length ? <p role="alert" className="text-sm text-warning-foreground">Not mapped yet: {unmapped.join(", ")}. Use the Map columns tab, or the steps that need them find nothing.</p> : null}
143
143
  {addWaterfall.error ? <ApiError error={addWaterfall.error} title="Could not save the enrichment column" /> : null}
144
144
  <div><Button size="sm" loading={addWaterfall.loading} disabled={steps.length === 0} onClick={() => void addWaterfall.mutate(undefined)}>Save enrichment column</Button></div>
145
145
  </div>
@@ -94,7 +94,7 @@ export function RunProgress({ tableId, target, rowIds, label = "this column", on
94
94
  if (job) {
95
95
  return (
96
96
  <div className={`flex flex-col gap-2 rounded-md border p-3 ${className ?? ""}`} role="status">
97
- <h3 className="text-sm font-semibold">{poll.done ? `Run ${receipt?.status ?? "finished"}` : "Running..."}</h3>
97
+ <h3 className="text-sm font-medium">{poll.done ? `Run ${receipt?.status ?? "finished"}` : "Running..."}</h3>
98
98
  <p className="text-sm text-muted-foreground">
99
99
  {receipt ? `${receipt.credits_charged ?? 0} credits charged so far.` : "Queued. The first update can take a few seconds."}
100
100
  {Object.entries(distribution).length ? ` Rows: ${Object.entries(distribution).map(([k, v]) => `${v} ${k}`).join(", ")}.` : ""}
@@ -88,7 +88,7 @@ export function WorkbenchGrid({ tableId, pageSize = 100, onOpenRow, activeRowId,
88
88
  <div className={`flex flex-col gap-3 ${className ?? ""}`}>
89
89
  <div className="flex flex-wrap items-center justify-between gap-2">
90
90
  <div>
91
- <h2 className="text-base font-semibold">{table?.name ?? "Workbench"}</h2>
91
+ <h2 className="text-base font-medium">{table?.name ?? "Workbench"}</h2>
92
92
  <p className="text-xs text-muted-foreground">{total.toLocaleString()} rows, {columns.length} columns</p>
93
93
  </div>
94
94
  <div className="flex gap-2" role="toolbar" aria-label="Workbench tools">