@graph8/sdk 0.370.0 → 0.374.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 (646) hide show
  1. package/README.md +22 -3
  2. package/blocks/_foundation/README.md +61 -0
  3. package/blocks/_foundation/block.json +57 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  6. package/blocks/_foundation/components/empty-state.tsx +5 -2
  7. package/blocks/_foundation/components/list-select.tsx +112 -0
  8. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  9. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  10. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  11. package/blocks/_foundation/components/screen/index.ts +1 -0
  12. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  13. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  14. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  15. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  16. package/blocks/_foundation/lib/paging.ts +57 -0
  17. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  18. package/blocks/_foundation/server/auth.ts +815 -0
  19. package/blocks/agents/README.md +81 -4
  20. package/blocks/agents/block.json +311 -2
  21. package/blocks/agents/components/agent-activity.tsx +112 -0
  22. package/blocks/agents/components/agent-approvals.tsx +135 -0
  23. package/blocks/agents/components/agent-builder.tsx +17 -3
  24. package/blocks/agents/components/agent-conversations.tsx +143 -0
  25. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  26. package/blocks/agents/components/agent-operators.tsx +164 -0
  27. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  28. package/blocks/agents/components/agent-runs.tsx +3 -3
  29. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  30. package/blocks/agents/components/agent-test-history.tsx +121 -0
  31. package/blocks/agents/components/agents-screen.tsx +53 -11
  32. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  33. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  34. package/blocks/agents/components/from-template-button.tsx +65 -0
  35. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  36. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  37. package/blocks/agents/components/knowledge-form.tsx +69 -0
  38. package/blocks/agents/components/name-dialog.tsx +32 -0
  39. package/blocks/agents/components/receptionist-button.tsx +46 -0
  40. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  41. package/blocks/agents/components/skill-editor.tsx +147 -0
  42. package/blocks/agents/components/skill-library.tsx +111 -0
  43. package/blocks/agents/lib/agent-types.ts +7 -1
  44. package/blocks/agents/lib/facts.tsx +13 -0
  45. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  46. package/blocks/agents/lib/load-more.tsx +141 -0
  47. package/blocks/agents/lib/skill-utils.ts +91 -0
  48. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  50. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  51. package/blocks/analytics/components/report-builder.tsx +15 -2
  52. package/blocks/appointments/README.md +6 -0
  53. package/blocks/appointments/block.json +27 -0
  54. package/blocks/appointments/components/booking-page.tsx +2 -2
  55. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  56. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  57. package/blocks/appointments/components/meeting-list.tsx +29 -13
  58. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  59. package/blocks/campaigns/README.md +1 -1
  60. package/blocks/campaigns/block.json +34 -1
  61. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  62. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  63. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  64. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  65. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  66. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  67. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  68. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  69. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  70. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  71. package/blocks/coach/components/call-scorecard.tsx +7 -7
  72. package/blocks/company-intelligence/README.md +132 -0
  73. package/blocks/company-intelligence/block.json +318 -0
  74. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  75. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  76. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  77. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  78. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  79. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  80. package/blocks/company-intelligence/components/research-run.tsx +165 -0
  81. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  82. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  83. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  84. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  85. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  86. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  87. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  88. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  89. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  90. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  91. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  92. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  93. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  94. package/blocks/crm/README.md +25 -0
  95. package/blocks/crm/block.json +257 -9
  96. package/blocks/crm/components/bulk-update.tsx +119 -0
  97. package/blocks/crm/components/companies-screen.tsx +23 -9
  98. package/blocks/crm/components/company-detail.tsx +2 -2
  99. package/blocks/crm/components/contact-detail.tsx +4 -4
  100. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  101. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  102. package/blocks/crm/components/contact-lookup-link.tsx +85 -0
  103. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  104. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  105. package/blocks/crm/components/contacts-screen.tsx +77 -18
  106. package/blocks/crm/components/dedupe-review.tsx +6 -1
  107. package/blocks/crm/components/import-wizard.tsx +3 -7
  108. package/blocks/crm/components/list-select.tsx +20 -0
  109. package/blocks/crm/components/owner-assign.tsx +132 -0
  110. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  111. package/blocks/crm/components/record-form.tsx +1 -1
  112. package/blocks/crm/components/record-open-links.tsx +79 -0
  113. package/blocks/crm/components/tag-assign.tsx +112 -0
  114. package/blocks/crm/components/tag-manager.tsx +178 -0
  115. package/blocks/crm/components/tasks-screen.tsx +35 -15
  116. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  117. package/blocks/crm/components/verify-email-button.tsx +93 -0
  118. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  119. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  120. package/blocks/crm/hooks/use-page-query.ts +3 -1
  121. package/blocks/crm/lib/crm-types.ts +14 -0
  122. package/blocks/crm/lib/read-lists.ts +16 -0
  123. package/blocks/crm/lib/record-actions.ts +102 -0
  124. package/blocks/deals/README.md +25 -6
  125. package/blocks/deals/block.json +168 -2
  126. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  127. package/blocks/deals/components/create-deal-button.tsx +109 -0
  128. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  129. package/blocks/deals/components/deal-detail.tsx +7 -5
  130. package/blocks/deals/components/deal-filters.tsx +65 -0
  131. package/blocks/deals/components/deal-line-items.tsx +120 -0
  132. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  133. package/blocks/deals/components/deal-related.tsx +30 -9
  134. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  135. package/blocks/deals/components/deals-screen.tsx +57 -22
  136. package/blocks/deals/components/forecast-view.tsx +4 -4
  137. package/blocks/deals/components/pipeline-board.tsx +8 -6
  138. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  139. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  140. package/blocks/deals/components/stage-editor.tsx +1 -1
  141. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  142. package/blocks/deals/lib/deal-extras.ts +156 -0
  143. package/blocks/dialer/README.md +14 -5
  144. package/blocks/dialer/block.json +17 -2
  145. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  146. package/blocks/dialer/components/call-log-screen.tsx +10 -2
  147. package/blocks/dialer/components/call-log.tsx +16 -4
  148. package/blocks/dialer/components/call-queue.tsx +3 -3
  149. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  150. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  151. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  152. package/blocks/dialer/components/session-start-form.tsx +58 -11
  153. package/blocks/dialer/components/softphone.tsx +1 -1
  154. package/blocks/documents/README.md +86 -0
  155. package/blocks/documents/block.json +378 -0
  156. package/blocks/documents/components/account-bar.tsx +48 -0
  157. package/blocks/documents/components/correspondence-form.tsx +95 -0
  158. package/blocks/documents/components/correspondence-import.tsx +106 -0
  159. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  160. package/blocks/documents/components/documents-screen.tsx +76 -0
  161. package/blocks/documents/components/library-document-detail.tsx +175 -0
  162. package/blocks/documents/components/library-document-form.tsx +105 -0
  163. package/blocks/documents/components/library-folder-form.tsx +74 -0
  164. package/blocks/documents/components/library-panel.tsx +235 -0
  165. package/blocks/documents/components/library-upload.tsx +160 -0
  166. package/blocks/documents/components/question-row.tsx +130 -0
  167. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  168. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  169. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  170. package/blocks/documents/lib/documents-types.ts +252 -0
  171. package/blocks/documents/lib/download.ts +64 -0
  172. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  173. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  174. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  175. package/blocks/email-signatures/README.md +123 -0
  176. package/blocks/email-signatures/block.json +341 -0
  177. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  178. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  179. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  180. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  181. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  182. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  183. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  184. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  185. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  186. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  187. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  188. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  189. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  190. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  191. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  192. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  193. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  194. package/blocks/enrichment/README.md +38 -10
  195. package/blocks/enrichment/block.json +164 -12
  196. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  197. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  198. package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
  199. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  200. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  201. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  202. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  203. package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
  204. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  205. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  206. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  207. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  208. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  209. package/blocks/enrichment/lib/enrichment-types.ts +51 -0
  210. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  211. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  212. package/blocks/forms/README.md +10 -0
  213. package/blocks/forms/block.json +107 -0
  214. package/blocks/forms/components/form-activity.tsx +71 -0
  215. package/blocks/forms/components/form-backfill.tsx +108 -0
  216. package/blocks/forms/components/form-builder.tsx +1 -1
  217. package/blocks/forms/components/form-embed.tsx +1 -1
  218. package/blocks/forms/components/form-groups.tsx +151 -0
  219. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  220. package/blocks/forms/components/form-route-history.tsx +72 -0
  221. package/blocks/forms/components/form-saved-views.tsx +81 -0
  222. package/blocks/forms/components/forms-screen.tsx +57 -20
  223. package/blocks/forms/components/more-rows.tsx +30 -0
  224. package/blocks/forms/components/submissions-export.tsx +67 -0
  225. package/blocks/forms/components/submissions-table.tsx +15 -6
  226. package/blocks/forms/lib/forms-extra.ts +67 -0
  227. package/blocks/inbox/README.md +45 -4
  228. package/blocks/inbox/block.json +145 -8
  229. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  230. package/blocks/inbox/components/compose-message.tsx +193 -0
  231. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  232. package/blocks/inbox/components/inbox-search.tsx +106 -0
  233. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  234. package/blocks/inbox/components/tag-manager.tsx +136 -0
  235. package/blocks/inbox/components/thread-actions.tsx +133 -0
  236. package/blocks/inbox/components/thread-badge.tsx +2 -2
  237. package/blocks/inbox/components/thread-view.tsx +3 -1
  238. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  239. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  240. package/blocks/integrations/README.md +54 -1
  241. package/blocks/integrations/block.json +189 -15
  242. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  243. package/blocks/integrations/components/connection-health.tsx +59 -0
  244. package/blocks/integrations/components/crm-detail.tsx +20 -1
  245. package/blocks/integrations/components/crm-records.tsx +103 -0
  246. package/blocks/integrations/components/field-mappings.tsx +61 -0
  247. package/blocks/integrations/components/integration-list.tsx +4 -4
  248. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  249. package/blocks/integrations/components/migration-panel.tsx +133 -0
  250. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  251. package/blocks/integrations/components/sync-status.tsx +6 -6
  252. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  253. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  254. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  255. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  256. package/blocks/integrations/lib/crm-utils.ts +39 -0
  257. package/blocks/integrations/lib/facts.tsx +13 -0
  258. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  259. package/blocks/integrations/lib/load-more.tsx +141 -0
  260. package/blocks/intent/components/account-intent-score.tsx +5 -5
  261. package/blocks/intent/components/intent-feed.tsx +8 -8
  262. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  263. package/blocks/intent/lib/intent-utils.ts +4 -4
  264. package/blocks/lifecycle/components/account-related.tsx +8 -1
  265. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  266. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  267. package/blocks/lists/README.md +7 -1
  268. package/blocks/lists/block.json +31 -3
  269. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  270. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  271. package/blocks/lists/components/list-picker.tsx +13 -3
  272. package/blocks/lists/components/list-table.tsx +1 -1
  273. package/blocks/lists/components/lists-screen.tsx +4 -2
  274. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  275. package/blocks/lists/components/suppression-manager.tsx +27 -6
  276. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  277. package/blocks/mailboxes/block.json +4 -0
  278. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  279. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  280. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  281. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  282. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  283. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  284. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  285. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  286. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  287. package/blocks/newsletter/README.md +12 -0
  288. package/blocks/newsletter/block.json +117 -0
  289. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  290. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  291. package/blocks/newsletter/components/more-rows.tsx +30 -0
  292. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  293. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  294. package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
  295. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  296. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  297. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  298. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  299. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  300. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  301. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  302. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  303. package/blocks/nurture/components/nurture-form.tsx +5 -8
  304. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  305. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  306. package/blocks/objects/README.md +1 -1
  307. package/blocks/objects/block.json +13 -5
  308. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  309. package/blocks/objects/components/object-record-detail.tsx +8 -1
  310. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  311. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  312. package/blocks/pages/README.md +14 -0
  313. package/blocks/pages/block.json +123 -2
  314. package/blocks/pages/components/more-rows.tsx +30 -0
  315. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  316. package/blocks/pages/components/page-analytics.tsx +176 -0
  317. package/blocks/pages/components/page-clone.tsx +99 -0
  318. package/blocks/pages/components/page-domains.tsx +167 -0
  319. package/blocks/pages/components/page-editor.tsx +1 -1
  320. package/blocks/pages/components/page-generation-status.tsx +48 -0
  321. package/blocks/pages/components/page-tracking.tsx +137 -0
  322. package/blocks/pages/components/pages-screen.tsx +57 -16
  323. package/blocks/pages/components/seo-report.tsx +6 -1
  324. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  325. package/blocks/pages/lib/studio.ts +7 -0
  326. package/blocks/phone-numbers/README.md +74 -0
  327. package/blocks/phone-numbers/block.json +262 -0
  328. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  329. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  330. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  331. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  332. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  333. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  334. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  335. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  336. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  337. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  338. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  339. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  340. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  341. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  342. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  343. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  344. package/blocks/pickers/README.md +23 -0
  345. package/blocks/pickers/block.json +103 -0
  346. package/blocks/pickers/components/company-picker.tsx +90 -0
  347. package/blocks/pickers/components/contact-picker.tsx +131 -0
  348. package/blocks/pickers/components/member-picker.tsx +99 -0
  349. package/blocks/pickers/components/search-picker.tsx +98 -0
  350. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  351. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  352. package/blocks/pickers/lib/use-debounced.ts +13 -0
  353. package/blocks/programmatic-pages/README.md +82 -0
  354. package/blocks/programmatic-pages/block.json +292 -0
  355. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  356. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  357. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  358. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  359. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  360. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  361. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  362. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  363. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  364. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  365. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  366. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  367. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  368. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  369. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  370. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  371. package/blocks/quotes/README.md +38 -1
  372. package/blocks/quotes/block.json +170 -6
  373. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  374. package/blocks/quotes/components/manual-products.tsx +144 -0
  375. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  376. package/blocks/quotes/components/quote-builder.tsx +1 -1
  377. package/blocks/quotes/components/quote-documents.tsx +94 -0
  378. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  379. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  380. package/blocks/quotes/components/quote-list.tsx +28 -8
  381. package/blocks/quotes/components/quote-related.tsx +3 -2
  382. package/blocks/quotes/components/quote-settings.tsx +184 -0
  383. package/blocks/quotes/components/quote-templates.tsx +189 -0
  384. package/blocks/quotes/components/quote-viewer.tsx +23 -5
  385. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  386. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  387. package/blocks/quotes/lib/quote-admin.ts +264 -0
  388. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  389. package/blocks/radar/components/radar-overview.tsx +4 -4
  390. package/blocks/radar/components/radar-screen.tsx +2 -2
  391. package/blocks/sequencer/README.md +61 -0
  392. package/blocks/sequencer/block.json +200 -6
  393. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  394. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  395. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  396. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  397. package/blocks/sequencer/components/sequence-builder.tsx +32 -23
  398. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  399. package/blocks/sequencer/components/sequence-detail.tsx +10 -2
  400. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  401. package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
  402. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  403. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  404. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  405. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  406. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  407. package/blocks/sequencer/components/sequences-screen.tsx +39 -6
  408. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  409. package/blocks/sequencer/components/step-editor.tsx +21 -12
  410. package/blocks/sequencer/components/step-templates.tsx +224 -0
  411. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  412. package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
  413. package/blocks/signals/README.md +39 -0
  414. package/blocks/signals/block.json +287 -0
  415. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  416. package/blocks/signals/components/listener-form.tsx +184 -0
  417. package/blocks/signals/components/matched-posts.tsx +195 -0
  418. package/blocks/signals/components/post-engagements.tsx +114 -0
  419. package/blocks/signals/components/record-social-tab.tsx +49 -0
  420. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  421. package/blocks/signals/components/social-settings.tsx +128 -0
  422. package/blocks/signals/components/source-lists.tsx +83 -0
  423. package/blocks/signals/components/top-engagers.tsx +68 -0
  424. package/blocks/signals/components/x-integration.tsx +129 -0
  425. package/blocks/signals/lib/signals-types.ts +228 -0
  426. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  427. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  428. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  429. package/blocks/social-ads/README.md +8 -0
  430. package/blocks/social-ads/block.json +137 -4
  431. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  432. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  433. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  434. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  435. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  436. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  437. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  438. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  439. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  440. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  441. package/blocks/social-ads/components/post-composer.tsx +4 -2
  442. package/blocks/social-ads/components/post-detail.tsx +3 -0
  443. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  444. package/blocks/social-ads/components/social-performance.tsx +80 -0
  445. package/blocks/social-ads/components/social-screen.tsx +33 -6
  446. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  447. package/blocks/surveys/README.md +91 -0
  448. package/blocks/surveys/block.json +299 -0
  449. package/blocks/surveys/components/account-nps.tsx +69 -0
  450. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  451. package/blocks/surveys/components/org-summary.tsx +82 -0
  452. package/blocks/surveys/components/sending-domains.tsx +113 -0
  453. package/blocks/surveys/components/survey-form.tsx +126 -0
  454. package/blocks/surveys/components/survey-responses.tsx +145 -0
  455. package/blocks/surveys/components/survey-results.tsx +72 -0
  456. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  457. package/blocks/surveys/components/survey-send.tsx +162 -0
  458. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  459. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  460. package/blocks/surveys/lib/surveys-types.ts +228 -0
  461. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  462. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  463. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  464. package/blocks/visitors/components/snippet-install.tsx +18 -9
  465. package/blocks/visitors/components/traffic-chart.tsx +7 -6
  466. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  467. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  468. package/blocks/web-analytics/README.md +102 -0
  469. package/blocks/web-analytics/block.json +292 -0
  470. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  471. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  472. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  473. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  474. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  475. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  476. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  477. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  478. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  479. package/blocks/web-analytics/components/report-card.tsx +71 -0
  480. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  481. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  482. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  483. package/blocks/web-analytics/lib/page-path.ts +14 -0
  484. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  485. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  486. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  487. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  488. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  489. package/blocks/webchat/components/conversation-list.tsx +1 -1
  490. package/blocks/workbench/README.md +54 -2
  491. package/blocks/workbench/block.json +234 -5
  492. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  493. package/blocks/workbench/components/run-progress.tsx +1 -1
  494. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  495. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  496. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  497. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  498. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  499. package/blocks/workbench/components/workbench-grid.tsx +71 -8
  500. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  501. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  502. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  503. package/blocks/workbench/components/workbench-screen.tsx +65 -22
  504. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  505. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  506. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  507. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  508. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  509. package/blocks/workbench/lib/api-status.ts +8 -0
  510. package/blocks/workbench/lib/automation-types.ts +257 -0
  511. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  512. package/dist/cli.js +744 -394
  513. package/dist/index.browser.js +1 -1
  514. package/dist/index.browser.js.map +1 -1
  515. package/dist/index.browser.mjs +1 -1
  516. package/dist/index.browser.mjs.map +1 -1
  517. package/dist/index.d.mts +2 -2
  518. package/dist/index.d.ts +2 -2
  519. package/dist/index.js +1 -1
  520. package/dist/index.js.map +1 -1
  521. package/dist/index.mjs +1 -1
  522. package/dist/index.mjs.map +1 -1
  523. package/dist/react.browser.js +1 -1
  524. package/dist/react.browser.js.map +1 -1
  525. package/dist/react.browser.mjs +1 -1
  526. package/dist/react.browser.mjs.map +1 -1
  527. package/dist/react.d.mts +2 -2
  528. package/dist/react.d.ts +2 -2
  529. package/dist/react.js +1 -1
  530. package/dist/react.js.map +1 -1
  531. package/dist/react.mjs +1 -1
  532. package/dist/react.mjs.map +1 -1
  533. package/dist/types/blocks/auth.d.ts +45 -0
  534. package/dist/types/blocks/brand.d.ts +11 -2
  535. package/dist/types/blocks/create.d.ts +11 -4
  536. package/dist/types/blocks/doctor.d.ts +1 -0
  537. package/dist/types/blocks/flows.d.ts +7 -1
  538. package/dist/types/blocks/orgBrand.d.ts +34 -0
  539. package/dist/types/cli.d.ts +3 -0
  540. package/dist/types/version.d.ts +2 -2
  541. package/docs/api/desk.d.ts +2 -2
  542. package/docs/api/desk.md +3 -3
  543. package/docs/api/forms.md +1 -1
  544. package/package.json +1 -1
  545. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  546. package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
  547. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  548. package/stacks/6sense.json +37 -4
  549. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  550. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  551. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  552. package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
  553. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  554. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  555. package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
  556. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  557. package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
  558. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  559. package/stacks/apollo.json +68 -9
  560. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  561. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  562. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  563. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  564. package/stacks/calendly.json +8 -4
  565. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  566. package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
  567. package/stacks/clay/pages/lists.tsx.tmpl +24 -0
  568. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  569. package/stacks/clay.json +16 -3
  570. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  571. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  572. package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
  573. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  574. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  575. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  576. package/stacks/gong.json +23 -6
  577. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  578. package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
  579. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  580. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  581. package/stacks/hootsuite.json +19 -4
  582. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  583. package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
  584. package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
  585. package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
  586. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  587. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  588. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  589. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  590. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  591. package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
  592. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  593. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  594. package/stacks/hubspot.json +66 -7
  595. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  596. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  597. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  598. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  599. package/stacks/intercom.json +18 -4
  600. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  601. package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
  602. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  603. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  604. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  605. package/stacks/mailchimp.json +22 -6
  606. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  607. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  608. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  609. package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
  610. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  611. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  612. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  613. package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
  614. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  615. package/stacks/outreach.json +38 -9
  616. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  617. package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
  618. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  619. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  620. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  621. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  622. package/stacks/pipedrive.json +14 -2
  623. package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
  624. package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
  625. package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
  626. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  627. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  628. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  629. package/stacks/salesforce.json +17 -3
  630. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  631. package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
  632. package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
  633. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  634. package/stacks/zoominfo.json +36 -3
  635. package/styles/styles.css +1 -1
  636. package/templates/auth/auth-route.ts.tmpl +16 -0
  637. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  638. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  639. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  640. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  641. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  642. package/templates/auth/middleware.ts.tmpl +14 -0
  643. package/templates/next-app/README.md.tmpl +5 -0
  644. package/templates/next-app/app/globals.css.tmpl +349 -64
  645. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  646. 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">
@@ -75,7 +75,8 @@ export function LineChart({ points, metric }: { points: TrendPoint[]; metric: Me
75
75
 
76
76
  function activeNow(envelope: unknown): number | undefined {
77
77
  const data = (envelope as { data?: Record<string, unknown> } | null)?.data;
78
- const value = data?.active_users_5min ?? data?.active_5min ?? data?.active_users ?? data?.five_min ?? data?.active_now;
78
+ // The realtime report answers `active_5m` (reports/analytics/queries/realtime.py); the older names stay as fallbacks.
79
+ const value = data?.active_5m ?? data?.active_users_5min ?? data?.active_5min ?? data?.active_users ?? data?.five_min ?? data?.active_now;
79
80
  return typeof value === "number" ? value : undefined;
80
81
  }
81
82
 
@@ -113,8 +114,8 @@ export function TrafficChart({ days = 30, writeKey, realtimeSeconds = 10, classN
113
114
  </Button>
114
115
  ))}
115
116
  </div>
116
- <p className="text-sm text-gray-600" aria-live="polite">
117
- {now != null ? `${now} on the site now` : ""}
117
+ <p className="text-sm text-muted-foreground" aria-live="polite">
118
+ {now != null ? `${now} on the site now` : live.data || live.error ? "On the site now: -" : ""}
118
119
  </p>
119
120
  </div>
120
121
 
@@ -126,7 +127,7 @@ export function TrafficChart({ days = 30, writeKey, realtimeSeconds = 10, classN
126
127
  <EmptyState title="No traffic yet" description="Install the snippet and visits will chart here." />
127
128
  ) : (
128
129
  <div>
129
- <p className="text-sm text-gray-600">
130
+ <p className="text-sm text-muted-foreground">
130
131
  {total.toLocaleString()} {METRICS.find((m) => m.key === metric)?.label.toLowerCase()} in {days} days
131
132
  </p>
132
133
  <LineChart points={points} metric={metric} />
@@ -136,7 +137,7 @@ export function TrafficChart({ days = 30, writeKey, realtimeSeconds = 10, classN
136
137
  {topPages.length > 0 ? (
137
138
  <table className="w-full text-sm">
138
139
  <thead>
139
- <tr className="text-left text-gray-500">
140
+ <tr className="text-left text-muted-foreground">
140
141
  <th className="py-1">Top pages</th>
141
142
  <th className="py-1 text-right">Views</th>
142
143
  </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)}>
@@ -0,0 +1,102 @@
1
+ # web-analytics
2
+
3
+ Website analytics for your own sites: **traffic, acquisition, audience, pages, engagement, conversions and realtime**, read from the
4
+ tracking pipeline (the same reports as the graph8 Analytics pages). Every call is a free read. Nothing here is billable, external
5
+ or destructive, so there are no confirmations.
6
+
7
+ ## Screens
8
+
9
+ `WebAnalyticsScreen` (`components/web-analytics-screen.tsx`) is the finished page.
10
+
11
+ ```tsx
12
+ import { WebAnalyticsScreen } from "@/g8/web-analytics/components/web-analytics-screen";
13
+ export default function Page() { return <WebAnalyticsScreen />; }
14
+ ```
15
+
16
+ One **site picker** and one **window** drive every tab and live in the URL, so a link opens the same view:
17
+ `?site=pubKeyA,pubKeyB` (none = all sites), `?days=7|30|90|365` or `?from=YYYY-MM-DD&to=YYYY-MM-DD`, `?bots=1`, `?internal=1`, `?tab=`.
18
+
19
+ | Tab | What it shows |
20
+ |---|---|
21
+ | Overview | visitors, new visitors, sessions, page views; daily traffic; conversion rate; top pages (a row opens the page detail) |
22
+ | Acquisition | channels, channel trend, sources and mediums, UTM campaigns, referrals, social networks, search engines |
23
+ | Audience | top countries, cities and languages; every country or city; languages; browsers, operating systems, devices; new or returning |
24
+ | Pages | top, landing and exit pages; custom events; a page detail panel (`?page=/path`) with views, visitors, form submissions, bookings |
25
+ | Engagement | sessions, pages per session, time on site, bounce and engaged share against the previous period; trend; depth and time distributions; by channel, campaign, landing page |
26
+ | Conversions | conversion rate; funnel (visitors, viewed a form, submitted, booked); goals and their trend; forms, form trend, form analysis |
27
+ | Realtime | active visitors in the last 5 and 30 minutes, active pages, traffic sources, locations; re-read every 10 seconds while visible |
28
+
29
+ Optional props: `resolveLandingPage(path)` returns the id of the landing page at exactly that address (one match only), and `onOpenLandingPage(id)` opens it
30
+ (the hubspot stack wires both to the pages block). `refreshSeconds` (default 10; 0 turns the realtime poll off), `toolbarActions`, `className`.
31
+
32
+ ## Rules the screen keeps
33
+
34
+ - **Loading is visible.** Changing a site, the window or a switch dims every card (and a first load shows a skeleton). A failed or refused request clears the card and shows the error with Retry; the previous numbers never stay under an error.
35
+ - **Unavailable is not zero.** The reports swallow a failed ClickHouse query and answer `meta.stale: true` with empty data. A card in that state says it is unavailable (with Retry), and the tiles that belong to that card go with it.
36
+ - **Unknown rates are dashes.** A conversion rate the API sends as null (no visitors, no viewers) and any rate whose denominator is 0 shows a dash, never zero percent.
37
+ - **No request the API refuses.** `days` 1 to 365; a custom range needs both days, real dates, start not after end, at most 365 days (Apply is disabled with the reason otherwise); site keys are letters, digits, `-`, `_`; `limit` never exceeds each report's maximum (languages 100, form analysis 100, engagement by landing page 100, realtime pages and sources 50, the rest 200); a page path must start with `/` and use plain URL characters.
38
+ - **Sort, then cut.** Tables sort the whole list a read returned and only then show the first 10 ("Show all N"). A read that came back full says "Showing the first N".
39
+ - **No invented defaults.** The window defaults to the API's own 30 days; bots and internal traffic stay excluded (the flags are sent only when switched on).
40
+
41
+ ## Types
42
+
43
+ The contract answers every web analytics report with `AnalyticsResponse { success, data: object | array, meta }`: `data` is **untyped**. The row types in
44
+ `lib/web-analytics-types.ts` are typed from the backend instead (the column aliases of `reports/analytics/queries/{audience,acquisition,behavior,conversions,engagement,realtime}.py`).
45
+ Every field is optional and read defensively.
46
+
47
+ ## Install
48
+
49
+ ```
50
+ npx @graph8/sdk add web-analytics
51
+ ```
52
+
53
+ Installs `foundation`, `crm` and `visitors` first if missing. Everything lands under `g8/web-analytics/`. Your app's proxy must allow the `analytics` and `streams` prefixes.
54
+
55
+ ## Prerequisites (the human must do these)
56
+
57
+ | Where | What | How to verify |
58
+ |---|---|---|
59
+ | The website | The graph8 tracking snippet is pasted into the site. Without it every report is empty. | The visitors block's SnippetInstall reports events received. |
60
+ | graph8 key | `analytics:read`, and `integrations:read` for the site list | `g8.api.me.describeCurrentKey()` |
61
+
62
+ ## Operations (39)
63
+
64
+ - `get_acquisition_campaigns_analytics_acquisition_campaigns_get`
65
+ - `get_acquisition_channels_analytics_acquisition_channels_get`
66
+ - `get_acquisition_channels_trend_analytics_acquisition_channels_trend_get`
67
+ - `get_acquisition_referrals_analytics_acquisition_referrals_get`
68
+ - `get_acquisition_search_analytics_acquisition_search_get`
69
+ - `get_acquisition_social_analytics_acquisition_social_get`
70
+ - `get_acquisition_sources_analytics_acquisition_sources_get`
71
+ - `get_audience_behavior_analytics_audience_behavior_get`
72
+ - `get_audience_demographics_analytics_audience_demographics_get`
73
+ - `get_audience_languages_analytics_audience_languages_get`
74
+ - `get_audience_overview_analytics_audience_overview_get`
75
+ - `get_audience_trend_analytics_audience_trend_get`
76
+ - `get_conversion_rate_analytics_conversions_rate_get`
77
+ - `get_engagement_by_campaign_analytics_engagement_by_campaign_get`
78
+ - `get_engagement_by_channel_analytics_engagement_by_channel_get`
79
+ - `get_engagement_by_landing_page_analytics_engagement_by_landing_page_get`
80
+ - `get_engagement_overview_analytics_engagement_overview_get`
81
+ - `get_engagement_trend_analytics_engagement_trend_get`
82
+ - `get_events_analytics_behavior_events_get`
83
+ - `get_exit_pages_analytics_behavior_exit_pages_get`
84
+ - `get_form_analysis_analytics_conversions_forms_analysis_get`
85
+ - `get_forms_analytics_conversions_forms_get`
86
+ - `get_forms_trend_analytics_conversions_forms_trend_get`
87
+ - `get_funnel_analytics_conversions_funnel_get`
88
+ - `get_geo_cities_analytics_audience_geo_cities_get`
89
+ - `get_geo_countries_analytics_audience_geo_get`
90
+ - `get_goals_analytics_conversions_goals_get`
91
+ - `get_goals_trend_analytics_conversions_goals_trend_get`
92
+ - `get_landing_pages_analytics_behavior_landing_pages_get`
93
+ - `get_page_metrics_analytics_behavior_page_metrics_get`
94
+ - `get_realtime_analytics_realtime_get`
95
+ - `get_realtime_locations_analytics_realtime_locations_get`
96
+ - `get_realtime_pages_analytics_realtime_pages_get`
97
+ - `get_realtime_sources_analytics_realtime_sources_get`
98
+ - `get_session_depth_distribution_analytics_engagement_depth_distribution_get`
99
+ - `get_technology_overview_analytics_audience_technology_get`
100
+ - `get_time_on_site_distribution_analytics_engagement_time_distribution_get`
101
+ - `get_top_pages_analytics_behavior_pages_get`
102
+ - `list_org_streams_streams_get`