@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
@@ -1,15 +1,18 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * SocialScreen: the finished social publishing page. Two tabs:
4
+ * SocialScreen: the finished social publishing page. Four tabs:
5
5
  * Posts every post by status (SocialPostList): publish a draft, cancel a scheduled post, delete. "New post" opens the
6
6
  * composer in a side sheet (PostComposer): write or draft with AI, save, schedule or publish.
7
- * Accounts the connected social accounts with their publishing capacity. Connecting an account (LinkedIn sign-in) only
8
- * works in the graph8 app, so this tab links out instead of offering a dead button.
7
+ * Accounts the connected social accounts with their publishing capacity; disconnect one (confirmed). Connecting an account
8
+ * (LinkedIn sign-in) only works in the graph8 app, so this tab links out instead of offering a dead button.
9
+ * Ideas post ideas mined from your signals and content (SocialIdeas); "Draft a post" opens the composer with the idea as the brief.
10
+ * Performance totals and a per-day table for a window (SocialPerformance); a live post also has a Performance tab of its own.
9
11
  *
10
12
  * Every post opens a complete detail screen (PostDetail, `?id=`, full page with `?view=full`).
11
13
  *
12
- * SDK operations: one read of listSocialPosts (page_size 1) for the per-status counts in the stat strip. Composes
14
+ * SDK operations: one read of listSocialPosts (page_size 1) for the per-status counts in the stat strip, and
15
+ * disconnectSocialAccount (DELETE /social/accounts/{id}, destructive, confirmed: publishing from it stops). Composes
13
16
  * PostComposer (billable AI drafts, external schedule/publish, all confirmed), SocialPostList (external + destructive,
14
17
  * confirmed) and useSocialAccounts (read).
15
18
  */
@@ -35,6 +38,11 @@ import { PostComposer } from "./post-composer";
35
38
  import { SocialPostList } from "./social-post-list";
36
39
  import { PostDetail } from "./post-detail";
37
40
  import { brandUrl, useBrand } from "@/g8/hooks/use-brand";
41
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
42
+ import { useConfirmAction } from "@/g8/components/confirmed-action";
43
+ import { g8Delete } from "@/g8/lib/g8-client";
44
+ import { SocialIdeas } from "./social-ideas";
45
+ import { SocialPerformance } from "./social-performance";
38
46
 
39
47
  export interface SocialScreenProps {
40
48
  /** Where "Connect" sends people. Default: graph8 Settings > Integrations. */
@@ -49,6 +57,7 @@ const APP_PATH = "/studio/settings?tab=integrations";
49
57
  function Accounts({ connectUrl }: { connectUrl: string }) {
50
58
  const brand = useBrand();
51
59
  const { accounts, loading, error, refetch } = useSocialAccounts();
60
+ const { ask, dialog, failure } = useConfirmAction();
52
61
  if (error) return <ApiError error={error} onRetry={() => void refetch()} />;
53
62
  if (loading && accounts.length === 0) return <p className="text-sm text-muted-foreground" role="status">Loading accounts...</p>;
54
63
  if (accounts.length === 0) {
@@ -61,6 +70,9 @@ function Accounts({ connectUrl }: { connectUrl: string }) {
61
70
  );
62
71
  }
63
72
  return (
73
+ <>
74
+ {failure ? <ApiError error={failure} title="Could not disconnect the account" /> : null}
75
+ <ConfirmSpendDialog {...dialog} />
64
76
  <ul className="grid gap-3 md:grid-cols-2">
65
77
  {accounts.map((a) => (
66
78
  <li key={a.id}>
@@ -74,11 +86,17 @@ function Accounts({ connectUrl }: { connectUrl: string }) {
74
86
  </p>
75
87
  </div>
76
88
  {a.profile_url ? <a className="text-sm underline" href={a.profile_url} target="_blank" rel="noreferrer">Profile</a> : null}
89
+ <Button size="sm" variant="ghost" aria-label={`Disconnect ${a.display_name ?? a.provider}`} onClick={() => ask({
90
+ tier: "destructive", title: `Disconnect ${a.display_name ?? a.provider}`, confirmLabel: "Disconnect", affected: 1,
91
+ description: `Publishing and scheduled posts from this account stop. Posts already live stay live. Connect it again in ${brand.linkLabel} to resume.`,
92
+ tolerateStatus: [404], run: () => g8Delete(`/social/accounts/${encodeURIComponent(a.id)}`), onDone: () => void refetch(),
93
+ })}>Disconnect</Button>
77
94
  </div>
78
95
  </SectionCard>
79
96
  </li>
80
97
  ))}
81
98
  </ul>
99
+ </>
82
100
  );
83
101
  }
84
102
 
@@ -86,11 +104,12 @@ export function SocialScreen(props: SocialScreenProps) {
86
104
  const brand = useBrand();
87
105
  const connectUrl = props.connectUrl ?? brandUrl(brand, APP_PATH);
88
106
  const [tabValue, setTab] = useUrlState("tab", "posts");
89
- const tab = tabValue === "accounts" ? "accounts" : "posts";
107
+ const tab = tabValue === "accounts" || tabValue === "ideas" || tabValue === "performance" ? tabValue : "posts";
90
108
  const [id, setId] = useUrlState("id", "");
91
109
  const [view, setView] = useUrlState("view", "");
92
110
  const expanded = view === "full" && Boolean(id);
93
111
  const [composing, setComposing] = useState(false);
112
+ const [composeBrief, setComposeBrief] = useState("");
94
113
  const [refreshKey, setRefreshKey] = useState(0);
95
114
  const counts = useG8Query(async (signal) => (await g8Get<SocialPostPage>("/social/posts", { page: 1, page_size: 1 }, { signal })).data, [refreshKey]);
96
115
 
@@ -112,13 +131,15 @@ export function SocialScreen(props: SocialScreenProps) {
112
131
  actions={
113
132
  <>
114
133
  {props.toolbarActions}
115
- <Button onClick={() => setComposing(true)}>
134
+ <Button onClick={() => { setComposeBrief(""); setComposing(true); }}>
116
135
  <Plus className="mr-1.5 size-4" /> New post
117
136
  </Button>
118
137
  </>
119
138
  }
120
139
  tabs={[
121
140
  { id: "posts", label: "Posts" },
141
+ { id: "ideas", label: "Ideas" },
142
+ { id: "performance", label: "Performance" },
122
143
  { id: "accounts", label: "Accounts" },
123
144
  ]}
124
145
  activeTab={tab}
@@ -144,11 +165,17 @@ export function SocialScreen(props: SocialScreenProps) {
144
165
  onChanged={() => setRefreshKey((k) => k + 1)} />
145
166
  ) : null}
146
167
  />
168
+ ) : tab === "ideas" ? (
169
+ <SocialIdeas onUse={(brief) => { setComposeBrief(brief); setComposing(true); }} />
170
+ ) : tab === "performance" ? (
171
+ <SocialPerformance />
147
172
  ) : (
148
173
  <Accounts connectUrl={connectUrl} />
149
174
  )}
150
175
  <FormSheet open={composing} onOpenChange={setComposing} title="New post" description="Write it, draft it with AI, then save, schedule or publish.">
151
176
  <PostComposer
177
+ key={composeBrief}
178
+ initialBrief={composeBrief || undefined}
152
179
  onSaved={() => {
153
180
  setComposing(false);
154
181
  setRefreshKey((k) => k + 1);
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Helpers for the ads and social management components (campaign editor, campaign ads, creative tools, revisions,
5
+ * concepts, performance, ideas). These endpoints return open dicts, so every reader accepts what is present.
6
+ */
7
+ export type Rec = Record<string, unknown>;
8
+
9
+ export function asRec(value: unknown): Rec {
10
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Rec) : {};
11
+ }
12
+
13
+ export function str(value: unknown, fallback = ""): string {
14
+ return typeof value === "string" ? value : value == null ? fallback : String(value);
15
+ }
16
+
17
+ export function num(value: unknown): number | undefined {
18
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
19
+ }
20
+
21
+ export function rowsOf<T = Rec>(data: unknown, ...keys: string[]): T[] {
22
+ if (Array.isArray(data)) return data as T[];
23
+ const rec = asRec(data);
24
+ for (const key of [...keys, "items"]) if (Array.isArray(rec[key])) return rec[key] as T[];
25
+ return [];
26
+ }
27
+
28
+ export const whenText = (v: unknown): string => {
29
+ const d = new Date(str(v));
30
+ return Number.isNaN(d.getTime()) ? "" : d.toLocaleString();
31
+ };
32
+
33
+ /** What the API's `/editable` map says about one field. A field it does not list is treated as not editable. */
34
+ export interface FieldCapability { editable: boolean; reason?: string; consequences: string[] }
35
+
36
+ export function capabilityOf(editable: unknown, field: string): FieldCapability {
37
+ const f = asRec(asRec(asRec(editable).fields)[field]);
38
+ return { editable: f.editable === true, reason: str(f.reason) || undefined, consequences: rowsOf<string>(f.consequences).map(String) };
39
+ }
40
+
41
+ /** A money string from an input: a positive number with at most two decimals, else undefined. */
42
+ export function parseMoney(raw: string): number | undefined {
43
+ const t = raw.trim();
44
+ if (!/^\d+(\.\d{1,2})?$/.test(t)) return undefined;
45
+ const n = Number(t);
46
+ return n > 0 ? n : undefined;
47
+ }
48
+
49
+ /** `YYYY-MM-DD` from an API date or midnight-UTC timestamp, for a date input. Never goes through the local zone. */
50
+ export function dateInput(value: unknown): string {
51
+ const t = str(value);
52
+ return /^\d{4}-\d{2}-\d{2}/.test(t) ? t.slice(0, 10) : "";
53
+ }
54
+
55
+ export interface CampaignEdit { name?: string; daily_budget?: number; end_date?: string; spend_cap?: number }
56
+
57
+ /** The body of an edit: only fields that differ from the saved campaign. Empty means nothing to save. */
58
+ export function campaignChanges(saved: { name?: unknown; daily_budget?: unknown; end_date?: unknown }, draft: { name: string; daily_budget: string; end_date: string; spend_cap: string }): CampaignEdit {
59
+ const out: CampaignEdit = {};
60
+ if (draft.name.trim() && draft.name.trim() !== str(saved.name)) out.name = draft.name.trim();
61
+ const budget = parseMoney(draft.daily_budget);
62
+ if (budget !== undefined && budget !== num(saved.daily_budget)) out.daily_budget = budget;
63
+ if (/^\d{4}-\d{2}-\d{2}$/.test(draft.end_date) && draft.end_date !== dateInput(saved.end_date)) out.end_date = draft.end_date;
64
+ const cap = parseMoney(draft.spend_cap);
65
+ if (cap !== undefined) out.spend_cap = cap;
66
+ return out;
67
+ }
@@ -0,0 +1,91 @@
1
+ # surveys
2
+
3
+ NPS and CSAT surveys: ask customers **one scored question**, read the score, the answers and the trend. A survey is one question
4
+ with a numeric answer (NPS 0 to 10, CSAT 1 to 5) and an optional comment. It is sent to an audience **list** by email or chat
5
+ message, shown **in-app** to your own team, or sent automatically (on a schedule, when someone joins a list, when a record reaches
6
+ a stage). A send reaches real people, so it is tier `external` and is confirmed with the audience size.
7
+
8
+ ## Screens
9
+
10
+ `SurveysScreen` (`components/surveys-screen.tsx`) is the finished page: list, create, results, answers, send, automatic sends, settings, org overview, sending domains and account lookups.
11
+
12
+ ```tsx
13
+ import { SurveysScreen } from "@/g8/surveys/components/surveys-screen";
14
+ export default function Page() { return <SurveysScreen />; }
15
+ ```
16
+
17
+ Tabs (`?tab=`): **Surveys**, **Overview**, **Sending domains**, **Accounts and people**. Open a survey (`?id=`) for Results, Responses, Send, Automatic sends, Settings and Details; `&view=full` shows it as a full page.
18
+
19
+ Flows: see every survey with its type, state and channel (search; filter by type, open or closed, channel; the list is complete: the API does not page it), create one, open it to read the score, response rate, split and trend (by day, week or month), read and search its answers (filter by promoter, passive, detractor), open one answer, export the answers on screen as CSV, send it to a list now or later (confirmed with the list's size), add or remove automatic sends (both confirmed), edit the question, thank-you line and channel, **close** it (stops new sends, keeps every answer) and reopen it, delete it (confirmed), verify a sending domain (confirmed, returns the DNS records), read org-wide NPS and CSAT (all contacts or one list), look up one company's NPS and one person's answers. Optional props: `renderRowActions`, `renderDetailActions`, `renderContact`, `extraDetailTabs`, `toolbarActions`.
20
+
21
+ Parts you can mount alone: `SurveyForm`, `SurveySend`, `SurveySendHistory`, `SurveyTriggers`, `SurveyResults`, `SurveyResponses`, `SendingDomains`, `OrgSummary`, `AccountNps` (pass `companyId` on a company page), `ContactSurveyHistory` (pass `contactId` on a contact page).
22
+
23
+ ## Install
24
+
25
+ ```
26
+ npx @graph8/sdk add surveys
27
+ ```
28
+
29
+ Installs `foundation`, `crm`, `lists` and `pickers` first if missing. Everything lands under `g8/surveys/`.
30
+
31
+ ## Prerequisites (the human must do these)
32
+
33
+ | Where | What | How to verify |
34
+ |---|---|---|
35
+ | graph8 | A **verified sending domain** for email surveys. Verify one in the Sending domains tab (it returns DNS records), publish them at your DNS provider, then press Check status. | `GET /api/v1/feedback/sending-domains` lists it as `verified`. |
36
+ | The business | An **audience list** of people you may ask. A person picks it and confirms the size; unsubscribed and suppressed contacts are skipped by the API. | The owner can say who is on the list and why. |
37
+ | graph8 key | `surveys:read`, `surveys:write`, `surveys:delete`; `surveys:run` to send or verify a domain (separate from `surveys:write`); `lists:read`. | `g8.api.me.describeCurrentKey()` |
38
+ | Your app | `allowedPrefixes` must include `feedback`, `lists`, `companies`, `contacts`. The survey endpoints live under `/feedback`, not `/surveys`. | The route file lists them. |
39
+
40
+ ## The flow, in order (IDs passed between steps)
41
+
42
+ 1. **Create**: `createSurvey({ name, type: "nps" | "csat", question_text, delivery_channel, comment_mode, thank_you_copy? })` -> **`survey.id`** (a UUID string). `type`, the scale (`scale_min`/`scale_max`) and `comment_mode` are fixed now; `updateSurvey` takes none of them.
43
+ 2. **Sender**: `listSurveySendingDomains` -> pick a `verified` domain; the sender address must be on it. A new domain: `verifySurveyDomain({ domain })` then `surveyDomainStatus({ domain })` until verified.
44
+ 3. **Audience**: a list id (`listLists`, or build one with the `lists` block) -> **`list.id`**, sent as the string `audience_ref`.
45
+ 4. **Send**: `sendSurvey(survey.id, { audience_ref, channel, sender_email, subject, scheduled_at? })` behind `ConfirmSpendDialog` showing the list's size -> **`send_id`**. It answers 202; poll `getSurveySend(send_id)` until `sent`, `failed` or `scheduled`.
46
+ 5. **Automate** (optional): `createSurveyTrigger(survey.id, { trigger_kind, sender_email, subject, audience_ref | stage_value, cadence_days })`. `scheduled` needs `audience_ref` + `cadence_days`, `list_entry` needs `audience_ref`, `stage_entry` needs `stage_value`. There is no update: delete and recreate.
47
+ 6. **Read**: `getSurveySummary` (score, response rate, split), `getSurveyTrends`, `getSurveyResponses` (every answer), `getSurveyAppUserResponses` (your team, in-app), `getSurveySends` (history), `getSurveyOrgSummary`, `getCompanySurveyNps`, `getContactSurveyHistory`.
48
+ 7. **Stop**: `updateSurvey({ is_active: false })` closes it and keeps every answer. `deleteSurvey` removes the survey **and its answers** (confirmed).
49
+
50
+ ## Default decisions (do not ask the human unless marked)
51
+
52
+ | Decision | Default | Ask? |
53
+ |---|---|---|
54
+ | Survey type | NPS (0 to 10) for relationship feedback, CSAT (1 to 5) for one interaction | no |
55
+ | Question | The standard question for the type, with the brand name | no |
56
+ | Comment capture | Score only (`none`) | no |
57
+ | Delivery channel | Email | no |
58
+ | Which list is asked | None: a person always picks the audience | **yes** |
59
+ | Sending | Never without the audience size and sender domain confirmed | no |
60
+ | Stopping | Close, do not delete | no |
61
+ | Trend bucket | Weekly for one survey, monthly for the org overview | no |
62
+
63
+ ## Operation coverage (21 surveys operations, all used)
64
+
65
+ Every operation in the `surveys` namespace is reachable from `SurveysScreen`:
66
+ `listSurveys`, `getSurvey`, `createSurvey`, `updateSurvey`, `deleteSurvey`, `sendSurvey`, `getSurveySend`, `getSurveySends`, `createSurveyTrigger`, `listSurveyTriggers`, `deleteSurveyTrigger`, `getSurveySummary`, `getSurveyTrends`, `getSurveyResponses`, `getSurveyAppUserResponses`, `getSurveyOrgSummary`, `getCompanySurveyNps`, `getContactSurveyHistory`, `listSurveySendingDomains`, `verifySurveyDomain`, `surveyDomainStatus`.
67
+ The block also reads `listLists`, `listCompanies` and `listContacts` for its pickers.
68
+
69
+ ## API gaps (what the request asked for that the API does not have)
70
+
71
+ | Asked for | Reality | What the block does |
72
+ |---|---|---|
73
+ | Several questions, question types, reordering | A survey is **one** question; "type" is only `nps` or `csat`. There is no question list or question endpoint. | Create takes the one question; the type is chosen at creation. |
74
+ | Publish / close | No publish endpoint; `updateSurvey({ is_active })` is the switch. | Close / Reopen buttons. |
75
+ | Edit type, scale, comment mode, template | `UpdateSurveyRequest` takes `name`, `question_text`, `thank_you_copy`, `delivery_channel`, `template_id`, `is_active` only. | Shown read-only with "create a new survey to change it". Templates have no list endpoint, so `template_id` is not offered. |
76
+ | Clear the thank-you line | The API ignores `null` on update; only `""` clears it. | A cleared line is sent as `""`. |
77
+ | Share link and embed code | None. The respondent link is signed per recipient inside the emailed survey; the in-app widget has no embed endpoint. | In-app surveys explain they appear inside the product; email is the way out. |
78
+ | Export responses | No export endpoint. | CSV built in the browser from the answers on screen (formula-safe). |
79
+ | AI generation (questions, summaries) | None for surveys. | Nothing to confirm or bill; no button. |
80
+ | Paging and server filters | `listSurveys`, `getSurveyResponses` and `getSurveySends` take no paging or filter parameters and return everything. | The UI says "All N" and filters client-side; a survey with a very large number of answers is one large read. |
81
+ | Pause or edit an automatic send | Triggers can only be created, listed and deleted. | Delete and recreate. |
82
+ | Respond as a contact (`/surveys/{id}/respond`, `/in-app/pending`) | Browser-session and signed-link surfaces, deliberately not in the developer API. | Out of scope. |
83
+
84
+ ## Things that surprise people
85
+
86
+ - The endpoints are under **`/feedback`**, not `/surveys` (the in-app router owns `/surveys`). Add `feedback` to the proxy allowlist.
87
+ - Scores are lowercase types (`nps`, `csat`); sending channels are `email` and `bridge` only (`in_app` is a delivery channel for showing, not a send channel).
88
+ - A score is `null` when there are too few answers (the API suppresses it). The UI shows a dash, not 0.
89
+ - `response_rate.rate` is a fraction (0 to 1).
90
+ - A send is dispatched asynchronously: `202` with a `send_id`, then poll. A send scheduled for later stays `scheduled`.
91
+ - `contact_id` on an answer and in the history endpoint is the contact's primary key (the mashup contact id), not the business `CONTACT_ID` key.
@@ -0,0 +1,299 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "surveys",
4
+ "title": "Surveys (NPS and CSAT)",
5
+ "summary": "Ask customers one scored question (NPS or CSAT): create and edit surveys, close, send to a list with confirmation, automate sends, read scores and answers, export, verify a sending domain.",
6
+ "features": [
7
+ "create NPS and CSAT surveys with a question, channel, comment capture and thank-you line",
8
+ "close a survey (keeps answers) and reopen it, or delete it with confirmation",
9
+ "send to an audience list now or later, confirmed with the audience size",
10
+ "automatic sends on a schedule, on list entry or on stage entry",
11
+ "score, response rate, answer split and trend by day, week or month",
12
+ "every answer with search, band filter, detail and CSV export",
13
+ "org-wide NPS and CSAT, account NPS and a person's survey history",
14
+ "sending domain verification with the DNS records to publish"
15
+ ],
16
+ "dependsOn": [
17
+ "lists",
18
+ "pickers"
19
+ ],
20
+ "operations": [
21
+ "create_survey_feedback_surveys_post",
22
+ "create_survey_trigger_feedback_surveys__survey_id__triggers_post",
23
+ "delete_survey_feedback_surveys__survey_id__delete",
24
+ "delete_survey_trigger_feedback_surveys__survey_id__triggers__trigger_id__delete",
25
+ "get_company_survey_nps_feedback_companies__company_id__nps_get",
26
+ "get_contact_survey_history_feedback_contacts__contact_id__history_get",
27
+ "get_survey_app_user_responses_feedback_surveys__survey_id__app_user_responses_get",
28
+ "get_survey_feedback_surveys__survey_id__get",
29
+ "get_survey_org_summary_feedback_org_summary_get",
30
+ "get_survey_responses_feedback_surveys__survey_id__responses_get",
31
+ "get_survey_send_feedback_sends__send_id__get",
32
+ "get_survey_sends_feedback_surveys__survey_id__sends_get",
33
+ "get_survey_summary_feedback_surveys__survey_id__summary_get",
34
+ "get_survey_trends_feedback_surveys__survey_id__trends_get",
35
+ "list_companies_companies_get",
36
+ "list_contacts_contacts_get",
37
+ "list_lists_lists_get",
38
+ "list_survey_sending_domains_feedback_sending_domains_get",
39
+ "list_survey_triggers_feedback_surveys__survey_id__triggers_get",
40
+ "list_surveys_feedback_surveys_get",
41
+ "send_survey_feedback_surveys__survey_id__send_post",
42
+ "survey_domain_status_feedback_sending_domains_status_get",
43
+ "update_survey_feedback_surveys__survey_id__patch",
44
+ "verify_survey_domain_feedback_sending_domains_verify_post"
45
+ ],
46
+ "components": [
47
+ {
48
+ "name": "SurveysScreen",
49
+ "file": "components/surveys-screen.tsx",
50
+ "purpose": "Finished NPS and CSAT workspace: survey list with filters, create, close and reopen, delete, and per-survey results, answers, send, automatic sends and settings; org overview, sending domains and account lookups.",
51
+ "operations": [
52
+ "delete_survey_feedback_surveys__survey_id__delete",
53
+ "list_surveys_feedback_surveys_get",
54
+ "update_survey_feedback_surveys__survey_id__patch"
55
+ ],
56
+ "tiers": [
57
+ "read",
58
+ "write",
59
+ "destructive"
60
+ ]
61
+ },
62
+ {
63
+ "name": "SurveyForm",
64
+ "file": "components/survey-form.tsx",
65
+ "purpose": "Create a survey (name, NPS or CSAT, question, channel, comments, thank-you line, scale) or edit one; edit reads the full record first and sends only changed fields.",
66
+ "operations": [
67
+ "create_survey_feedback_surveys_post",
68
+ "get_survey_feedback_surveys__survey_id__get",
69
+ "update_survey_feedback_surveys__survey_id__patch"
70
+ ],
71
+ "tiers": [
72
+ "read",
73
+ "write"
74
+ ]
75
+ },
76
+ {
77
+ "name": "SurveySend",
78
+ "file": "components/survey-send.tsx",
79
+ "purpose": "Send a survey to an audience list now or later, sized from the list and confirmed; follows the send until it settles.",
80
+ "operations": [
81
+ "get_survey_send_feedback_sends__send_id__get",
82
+ "list_lists_lists_get",
83
+ "list_survey_sending_domains_feedback_sending_domains_get",
84
+ "send_survey_feedback_surveys__survey_id__send_post"
85
+ ],
86
+ "tiers": [
87
+ "read",
88
+ "external"
89
+ ]
90
+ },
91
+ {
92
+ "name": "SurveySendHistory",
93
+ "file": "components/survey-send-history.tsx",
94
+ "purpose": "Every send of one survey with status, audience, reach and answers.",
95
+ "operations": [
96
+ "get_survey_sends_feedback_surveys__survey_id__sends_get"
97
+ ],
98
+ "tiers": [
99
+ "read"
100
+ ]
101
+ },
102
+ {
103
+ "name": "SurveyTriggers",
104
+ "file": "components/survey-triggers.tsx",
105
+ "purpose": "Automatic sends for a survey: on a schedule, when someone joins a list or when a record reaches a stage; add (confirmed) and remove (confirmed).",
106
+ "operations": [
107
+ "create_survey_trigger_feedback_surveys__survey_id__triggers_post",
108
+ "delete_survey_trigger_feedback_surveys__survey_id__triggers__trigger_id__delete",
109
+ "list_lists_lists_get",
110
+ "list_survey_sending_domains_feedback_sending_domains_get",
111
+ "list_survey_triggers_feedback_surveys__survey_id__triggers_get"
112
+ ],
113
+ "tiers": [
114
+ "read",
115
+ "write",
116
+ "destructive"
117
+ ]
118
+ },
119
+ {
120
+ "name": "SurveyResults",
121
+ "file": "components/survey-results.tsx",
122
+ "purpose": "Headline NPS or CSAT, response rate, answer split and trend by day, week or month.",
123
+ "operations": [
124
+ "get_survey_summary_feedback_surveys__survey_id__summary_get",
125
+ "get_survey_trends_feedback_surveys__survey_id__trends_get"
126
+ ],
127
+ "tiers": [
128
+ "read"
129
+ ]
130
+ },
131
+ {
132
+ "name": "SurveyResponses",
133
+ "file": "components/survey-responses.tsx",
134
+ "purpose": "Every answer with search, band filter, per-answer detail and CSV export, plus the team's in-app answers.",
135
+ "operations": [
136
+ "get_survey_app_user_responses_feedback_surveys__survey_id__app_user_responses_get",
137
+ "get_survey_responses_feedback_surveys__survey_id__responses_get"
138
+ ],
139
+ "tiers": [
140
+ "read"
141
+ ]
142
+ },
143
+ {
144
+ "name": "SendingDomains",
145
+ "file": "components/sending-domains.tsx",
146
+ "purpose": "Domains surveys may be emailed from, their verification state, and verify a new domain (confirmed) to get its DNS records.",
147
+ "operations": [
148
+ "list_survey_sending_domains_feedback_sending_domains_get",
149
+ "survey_domain_status_feedback_sending_domains_status_get",
150
+ "verify_survey_domain_feedback_sending_domains_verify_post"
151
+ ],
152
+ "tiers": [
153
+ "read",
154
+ "external"
155
+ ]
156
+ },
157
+ {
158
+ "name": "OrgSummary",
159
+ "file": "components/org-summary.tsx",
160
+ "purpose": "Org-wide NPS and CSAT for all contacts or one audience list, with a rollup per survey.",
161
+ "operations": [
162
+ "get_survey_org_summary_feedback_org_summary_get",
163
+ "list_lists_lists_get"
164
+ ],
165
+ "tiers": [
166
+ "read"
167
+ ]
168
+ },
169
+ {
170
+ "name": "AccountNps",
171
+ "file": "components/account-nps.tsx",
172
+ "purpose": "One company's NPS, coverage and each contact's latest score; takes a companyId or finds a company by name.",
173
+ "operations": [
174
+ "get_company_survey_nps_feedback_companies__company_id__nps_get",
175
+ "list_companies_companies_get"
176
+ ],
177
+ "tiers": [
178
+ "read"
179
+ ]
180
+ },
181
+ {
182
+ "name": "ContactSurveyHistory",
183
+ "file": "components/contact-survey-history.tsx",
184
+ "purpose": "Every survey answer one person has given; takes a contactId or finds a person by name.",
185
+ "operations": [
186
+ "get_contact_survey_history_feedback_contacts__contact_id__history_get",
187
+ "list_contacts_contacts_get"
188
+ ],
189
+ "tiers": [
190
+ "read"
191
+ ]
192
+ }
193
+ ],
194
+ "hooks": [],
195
+ "lib": [
196
+ {
197
+ "file": "lib/surveys-types.ts",
198
+ "purpose": "Survey, summary, response, send and trigger types; the API's exact vocabularies (types, channels, comment modes, trigger kinds); score and band helpers; the changed-fields diff; CSV building with formula-injection guard."
199
+ }
200
+ ],
201
+ "recipes": [
202
+ {
203
+ "file": "recipes/create-an-nps-survey.ts",
204
+ "title": "Create an NPS or CSAT survey and read it back",
205
+ "when": "Setting up the question you will ask; nothing is sent."
206
+ },
207
+ {
208
+ "file": "recipes/send-a-survey-and-follow-it.ts",
209
+ "title": "Send a survey to a list and follow the send",
210
+ "when": "Asking an audience now or later; emails real people, so a person confirms the audience size first."
211
+ },
212
+ {
213
+ "file": "recipes/survey-score-report.ts",
214
+ "title": "Report every survey's score, response rate and recent comments",
215
+ "when": "A weekly digest or a dashboard backend."
216
+ }
217
+ ],
218
+ "prerequisites": [
219
+ {
220
+ "id": "sending-domain",
221
+ "description": "Email surveys need a VERIFIED sending domain: the sender address must be on one. Verify a domain in the Sending domains tab (it returns the DNS records), publish those records at your DNS provider, then check its status.",
222
+ "where": "api",
223
+ "verify": "GET /api/v1/feedback/sending-domains lists the domain with status verified."
224
+ },
225
+ {
226
+ "id": "consented-audience",
227
+ "description": "A list of people who may be asked for feedback. A send reaches real people and cannot be recalled; a person confirms the audience size first. Unsubscribed and suppressed contacts are skipped by the API.",
228
+ "where": "app",
229
+ "verify": "The owner confirmed the list is people you may email."
230
+ },
231
+ {
232
+ "id": "survey-scopes",
233
+ "description": "The key needs surveys:read, surveys:write, surveys:delete and, to send or verify a domain, surveys:run (an external operation separate from surveys:write). Listing audiences needs lists:read.",
234
+ "where": "api",
235
+ "verify": "g8.api.me.describeCurrentKey() lists the scopes."
236
+ },
237
+ {
238
+ "id": "proxy-allowlist",
239
+ "description": "Add \"feedback\", \"lists\", \"companies\" and \"contacts\" to allowedPrefixes in app/api/g8/[...path]/route.ts. The survey endpoints live under /feedback, not /surveys.",
240
+ "where": "app",
241
+ "verify": "The route file's allowedPrefixes contains them."
242
+ }
243
+ ],
244
+ "scopes": [
245
+ "companies:read",
246
+ "contacts:read",
247
+ "lists:read",
248
+ "surveys:delete",
249
+ "surveys:read",
250
+ "surveys:run",
251
+ "surveys:write"
252
+ ],
253
+ "defaults": [
254
+ {
255
+ "decision": "Survey type",
256
+ "default": "NPS (0 to 10) for relationship feedback, CSAT (1 to 5) for a specific interaction",
257
+ "askHuman": false
258
+ },
259
+ {
260
+ "decision": "Question wording",
261
+ "default": "The standard question for the type, with the brand name; the person may rewrite it",
262
+ "askHuman": false
263
+ },
264
+ {
265
+ "decision": "Comment capture",
266
+ "default": "Score only (none); choose link to ask for a comment after the score",
267
+ "askHuman": false
268
+ },
269
+ {
270
+ "decision": "Delivery channel",
271
+ "default": "Email",
272
+ "askHuman": false
273
+ },
274
+ {
275
+ "decision": "Which list is asked",
276
+ "default": "None: a person always picks the audience",
277
+ "askHuman": true
278
+ },
279
+ {
280
+ "decision": "Sending",
281
+ "default": "Never without the person confirming the audience size and the sender domain",
282
+ "askHuman": false
283
+ },
284
+ {
285
+ "decision": "Stopping a survey",
286
+ "default": "Close it (keeps every answer); delete only when asked, after a confirmation",
287
+ "askHuman": false
288
+ },
289
+ {
290
+ "decision": "Trend bucket",
291
+ "default": "Weekly for one survey, monthly for the org overview",
292
+ "askHuman": false
293
+ }
294
+ ],
295
+ "peerDependencies": {
296
+ "react": ">=18",
297
+ "lucide-react": ">=0.400.0"
298
+ }
299
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ /**
4
+ * AccountNps: how the people at one company scored you.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. listCompanies, through CompanyPicker read find the company by name (skipped when you pass `companyId`)
8
+ * 2. getCompanySurveyNps (GET /feedback/companies/{company_id}/nps) read account NPS, split, coverage and each contact's latest score
9
+ * Pass `companyId` to show a company you already have open (for example in a company page). The account score is hidden by the API when there are too few answers.
10
+ */
11
+
12
+ import { useState } from "react";
13
+ import { Label, Skeleton } from "@graph8/sdk/ui";
14
+ import { ApiError } from "@/g8/components/api-error";
15
+ import { EmptyState } from "@/g8/components/empty-state";
16
+ import { SectionCard } from "@/g8/components/screen/section-card";
17
+ import { StatStrip } from "@/g8/components/screen/stat-strip";
18
+ import { CompanyPicker } from "@/g8/pickers/components/company-picker";
19
+ import type { CompanyOption } from "@/g8/pickers/components/company-picker";
20
+ import { g8Get } from "@/g8/lib/g8-client";
21
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
22
+ import { plural } from "@/g8/lib/plural";
23
+ import { bandOf, fullName, percent, scoreText, when } from "../lib/surveys-types";
24
+
25
+ interface CompanyNps {
26
+ company_id: number;
27
+ nps_score?: number | null;
28
+ suppressed: boolean;
29
+ promoters: number;
30
+ passives: number;
31
+ detractors: number;
32
+ total_respondents: number;
33
+ coverage: { rate: number; responded: number; total_contacts: number };
34
+ contacts: { contact_id: number; first_name?: string | null; last_name?: string | null; score?: number | null; classification?: string | null; responded?: boolean; last_responded_at?: string | null }[];
35
+ }
36
+ export interface AccountNpsProps { companyId?: number; className?: string }
37
+
38
+ export function AccountNps({ companyId, className }: AccountNpsProps) {
39
+ const [picked, setPicked] = useState<CompanyOption | null>(null);
40
+ const id = companyId ?? picked?.id;
41
+ const nps = useG8Query((signal) => g8Get<CompanyNps>(`/feedback/companies/${id}/nps`, {}, { signal }).then((r) => r.data), [id], { enabled: id != null, key: ["surveys", "company-nps", id ?? ""] });
42
+ const d = nps.data;
43
+ return (
44
+ <SectionCard title="Account NPS" description="How the people at one company scored you." className={className}>
45
+ <div className="grid gap-3">
46
+ {companyId == null ? <div><Label htmlFor="g8-sva-company" className="mb-1">Company</Label><CompanyPicker id="g8-sva-company" value={picked} onChange={setPicked} /></div> : null}
47
+ {id == null ? <EmptyState compact title="Choose a company" description="Its NPS, coverage and each contact's latest score appear here." /> : nps.error ? <ApiError error={nps.error} title="Could not load the account NPS" onRetry={() => void nps.refetch()} /> : !d ? <Skeleton className="h-24 w-full" aria-busy="true" /> : (
48
+ <>
49
+ <StatStrip items={[
50
+ { label: "Account NPS", value: d.suppressed ? "-" : scoreText(d.nps_score, 0), hint: d.suppressed ? "Hidden: too few answers to publish a score." : `${plural(d.total_respondents, "person", "people")} answered.` },
51
+ { label: "Coverage", value: percent(d.coverage.responded, d.coverage.total_contacts), hint: `${d.coverage.responded} of ${d.coverage.total_contacts} contacts answered.` },
52
+ { label: "Split", value: `${d.promoters} / ${d.passives} / ${d.detractors}`, hint: "Promoters, passives, detractors." },
53
+ ]} />
54
+ {d.contacts.length === 0 ? <p className="text-muted-foreground text-sm">No contact at this company has answered.</p> : (
55
+ <ul className="grid gap-1 text-sm">
56
+ {d.contacts.map((c) => (
57
+ <li key={c.contact_id} className="flex items-center justify-between gap-2 rounded-md border p-2">
58
+ <span>{fullName(c)}</span>
59
+ <span className="text-muted-foreground text-xs">{c.responded && c.score != null ? `${c.score} · ${bandOf("nps", c.score, c.classification)} · ${when(c.last_responded_at)}` : "No answer yet"}</span>
60
+ </li>
61
+ ))}
62
+ </ul>
63
+ )}
64
+ </>
65
+ )}
66
+ </div>
67
+ </SectionCard>
68
+ );
69
+ }