@graph8/sdk 0.303.0 → 0.357.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (664) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +2220 -23
  390. package/dist/index.d.ts +2220 -23
  391. package/dist/index.js +64 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +64 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +2082 -20
  400. package/dist/react.d.ts +2082 -20
  401. package/dist/react.js +63 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +63 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +7 -3
  611. package/docs/api/appObjects.d.ts +3 -1
  612. package/docs/api/appObjects.md +2 -1
  613. package/docs/api/by-path.md +51 -0
  614. package/docs/api/dataPipelines.d.ts +149 -0
  615. package/docs/api/dataPipelines.md +232 -0
  616. package/docs/api/forms.d.ts +16 -0
  617. package/docs/api/forms.md +8 -0
  618. package/docs/api/objects.d.ts +705 -5
  619. package/docs/api/objects.md +1099 -14
  620. package/docs/api/ops.d.ts +1 -1
  621. package/docs/api/ops.md +1 -1
  622. package/docs/api/processLists.d.ts +247 -0
  623. package/docs/api/processLists.md +409 -0
  624. package/docs/api/quotes.d.ts +3 -1
  625. package/docs/api/quotes.md +7 -2
  626. package/docs/api/radar.d.ts +4 -0
  627. package/docs/api/radar.md +1 -0
  628. package/docs/api/reports.d.ts +476 -0
  629. package/docs/api/reports.md +539 -0
  630. package/docs/api/scheduleBuy.d.ts +12 -8
  631. package/docs/api/scheduleBuy.md +13 -10
  632. package/docs/api/seo.d.ts +406 -3
  633. package/docs/api/seo.md +505 -1
  634. package/docs/api/workspaceBackup.d.ts +324 -0
  635. package/docs/api/workspaceBackup.md +427 -0
  636. package/package.json +174 -5
  637. package/stacks/6sense.json +46 -0
  638. package/stacks/README.md +20 -0
  639. package/stacks/apollo.json +128 -0
  640. package/stacks/clay.json +59 -0
  641. package/stacks/gong.json +72 -0
  642. package/stacks/hubspot.json +162 -0
  643. package/stacks/intercom.json +53 -0
  644. package/stacks/mailchimp.json +71 -0
  645. package/stacks/outreach.json +86 -0
  646. package/stacks/salesforce.json +94 -0
  647. package/stacks/stack.schema.json +32 -0
  648. package/stacks/starter.json +22 -0
  649. package/styles/styles.css +2 -0
  650. package/styles/theme.css +181 -0
  651. package/styles/tokens.css +284 -0
  652. package/templates/next-app/README.md.tmpl +35 -0
  653. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  654. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  655. package/templates/next-app/app/globals.css.tmpl +8 -0
  656. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  657. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  658. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  659. package/templates/next-app/dot-gitignore.tmpl +7 -0
  660. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  661. package/templates/next-app/next.config.mjs.tmpl +4 -0
  662. package/templates/next-app/package.json.tmpl +25 -0
  663. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  664. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,361 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "campaigns",
4
+ "title": "AI campaign builder",
5
+ "summary": "Build a company profile, ICPs and personas, generate campaign ideas, then create, staff with steps, attach an audience to and launch a campaign.",
6
+ "features": [
7
+ "company profile, ICP and persona editors with AI build/recommend behind a credit confirmation",
8
+ "campaign wizard: AI ideas (polled) -> create -> attach audience list -> steps and copy -> launch",
9
+ "launch readiness checklist, sender choice and deliverability warning before real outreach",
10
+ "campaign documents: read, edit, rewrite with AI, add and delete",
11
+ "campaign list with delete, and per-campaign metrics"
12
+ ],
13
+ "dependsOn": [
14
+ "lists",
15
+ "mailboxes"
16
+ ],
17
+ "operations": [
18
+ "get_company_profile_company_profile_get",
19
+ "update_company_profile_company_profile_put",
20
+ "generate_company_profile_company_profile_generate_post",
21
+ "get_company_profile_workflow_status_company_profile_workflow_status_get",
22
+ "approve_company_profile_company_profile_approve_post",
23
+ "list_icps_icps_get",
24
+ "create_icp_icps_post",
25
+ "update_icp_icps__icp_id__patch",
26
+ "archive_icp_icps__icp_id__archive_post",
27
+ "list_personas_personas_get",
28
+ "create_persona_personas_post",
29
+ "update_persona_personas__persona_id__patch",
30
+ "archive_persona_personas__persona_id__archive_post",
31
+ "recommend_campaign_personas_campaigns_recommend_personas_post",
32
+ "generate_campaign_ideas_async_campaigns_generate_ideas_async_post",
33
+ "get_campaign_ideas_task_status_campaigns_ideas_status__task_id__get",
34
+ "create_campaign_campaigns_post",
35
+ "update_campaign_campaigns__campaign_id__patch",
36
+ "get_campaign_generation_status_campaigns__campaign_id__status_get",
37
+ "get_campaign_full_campaigns__campaign_id__full_get",
38
+ "attach_audience_to_campaign_campaigns__campaign_id__audience_put",
39
+ "get_campaign_sequence_campaigns__campaign_id__sequence_get",
40
+ "create_campaign_step_campaigns__campaign_id__sequence_steps_post",
41
+ "update_campaign_step_campaigns__campaign_id__sequence_steps__step_id__put",
42
+ "delete_campaign_step_campaigns__campaign_id__sequence_steps__step_id__delete",
43
+ "generate_campaign_step_content_campaigns__campaign_id__sequence_steps__step_id__generate_post",
44
+ "list_mailboxes_mailboxes_get",
45
+ "get_safe_to_send_deliverability_safe_to_send_get",
46
+ "launch_campaign_campaigns__campaign_id__launch_post",
47
+ "list_campaigns_campaigns_get",
48
+ "delete_campaign_campaigns__campaign_id__delete",
49
+ "list_campaigns_for_picker_campaigns_picker_get",
50
+ "get_campaign_metrics_campaigns__campaign_id__metrics_get",
51
+ "list_campaign_documents_campaigns__campaign_id__documents_get",
52
+ "get_campaign_document_campaigns__campaign_id__documents__document_id__get",
53
+ "create_campaign_document_campaigns__campaign_id__documents_post",
54
+ "update_campaign_document_campaigns__campaign_id__documents__document_id__put",
55
+ "delete_campaign_document_campaigns__campaign_id__documents__document_id__delete",
56
+ "regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post"
57
+ ],
58
+ "components": [
59
+ {
60
+ "name": "IcpEditor",
61
+ "file": "components/icp-editor.tsx",
62
+ "purpose": "Company profile (edit, build with AI, approve) and ideal customer profiles: add, edit, archive (confirmed).",
63
+ "operations": [
64
+ "get_company_profile_company_profile_get",
65
+ "update_company_profile_company_profile_put",
66
+ "generate_company_profile_company_profile_generate_post",
67
+ "get_company_profile_workflow_status_company_profile_workflow_status_get",
68
+ "approve_company_profile_company_profile_approve_post",
69
+ "list_icps_icps_get",
70
+ "create_icp_icps_post",
71
+ "update_icp_icps__icp_id__patch",
72
+ "archive_icp_icps__icp_id__archive_post"
73
+ ],
74
+ "tiers": [
75
+ "read",
76
+ "write",
77
+ "billable",
78
+ "destructive"
79
+ ]
80
+ },
81
+ {
82
+ "name": "PersonaEditor",
83
+ "file": "components/persona-editor.tsx",
84
+ "purpose": "Buyer personas: add, edit, archive (confirmed), or have AI suggest a set (confirmed with a credit estimate) and accept the ones you want.",
85
+ "operations": [
86
+ "get_company_profile_company_profile_get",
87
+ "list_personas_personas_get",
88
+ "create_persona_personas_post",
89
+ "update_persona_personas__persona_id__patch",
90
+ "archive_persona_personas__persona_id__archive_post",
91
+ "recommend_campaign_personas_campaigns_recommend_personas_post"
92
+ ],
93
+ "tiers": [
94
+ "read",
95
+ "write",
96
+ "billable",
97
+ "destructive"
98
+ ]
99
+ },
100
+ {
101
+ "name": "CampaignWizard",
102
+ "file": "components/campaign-wizard.tsx",
103
+ "purpose": "Idea -> create campaign -> attach audience -> steps and copy -> launch. Every billable or external step is confirmed; ideas are polled.",
104
+ "operations": [
105
+ "get_company_profile_company_profile_get",
106
+ "list_personas_personas_get",
107
+ "generate_campaign_ideas_async_campaigns_generate_ideas_async_post",
108
+ "get_campaign_ideas_task_status_campaigns_ideas_status__task_id__get",
109
+ "create_campaign_campaigns_post",
110
+ "update_campaign_campaigns__campaign_id__patch",
111
+ "get_campaign_generation_status_campaigns__campaign_id__status_get",
112
+ "get_campaign_full_campaigns__campaign_id__full_get",
113
+ "attach_audience_to_campaign_campaigns__campaign_id__audience_put",
114
+ "get_campaign_sequence_campaigns__campaign_id__sequence_get",
115
+ "create_campaign_step_campaigns__campaign_id__sequence_steps_post",
116
+ "update_campaign_step_campaigns__campaign_id__sequence_steps__step_id__put",
117
+ "delete_campaign_step_campaigns__campaign_id__sequence_steps__step_id__delete",
118
+ "generate_campaign_step_content_campaigns__campaign_id__sequence_steps__step_id__generate_post",
119
+ "list_mailboxes_mailboxes_get",
120
+ "get_safe_to_send_deliverability_safe_to_send_get",
121
+ "launch_campaign_campaigns__campaign_id__launch_post"
122
+ ],
123
+ "tiers": [
124
+ "read",
125
+ "billable",
126
+ "external",
127
+ "destructive"
128
+ ]
129
+ },
130
+ {
131
+ "name": "CampaignAudienceStep",
132
+ "file": "components/campaign-audience-step.tsx",
133
+ "purpose": "Attach (or detach) a contact list as the campaign audience, confirmed with the contact count.",
134
+ "operations": [
135
+ "get_campaign_full_campaigns__campaign_id__full_get",
136
+ "attach_audience_to_campaign_campaigns__campaign_id__audience_put"
137
+ ],
138
+ "tiers": [
139
+ "read",
140
+ "external"
141
+ ]
142
+ },
143
+ {
144
+ "name": "CampaignSequenceStep",
145
+ "file": "components/campaign-sequence-step.tsx",
146
+ "purpose": "Add, edit, remove and AI-write the steps of a campaign sequence; each change is confirmed.",
147
+ "operations": [
148
+ "get_campaign_sequence_campaigns__campaign_id__sequence_get",
149
+ "create_campaign_step_campaigns__campaign_id__sequence_steps_post",
150
+ "update_campaign_step_campaigns__campaign_id__sequence_steps__step_id__put",
151
+ "delete_campaign_step_campaigns__campaign_id__sequence_steps__step_id__delete",
152
+ "generate_campaign_step_content_campaigns__campaign_id__sequence_steps__step_id__generate_post"
153
+ ],
154
+ "tiers": [
155
+ "read",
156
+ "billable",
157
+ "external",
158
+ "destructive"
159
+ ]
160
+ },
161
+ {
162
+ "name": "CampaignLaunchStep",
163
+ "file": "components/campaign-launch-step.tsx",
164
+ "purpose": "Readiness checklist, sender choice and deliverability warning, then a confirmed launch.",
165
+ "operations": [
166
+ "get_campaign_full_campaigns__campaign_id__full_get",
167
+ "list_mailboxes_mailboxes_get",
168
+ "get_safe_to_send_deliverability_safe_to_send_get",
169
+ "launch_campaign_campaigns__campaign_id__launch_post"
170
+ ],
171
+ "tiers": [
172
+ "read",
173
+ "external"
174
+ ]
175
+ },
176
+ {
177
+ "name": "CampaignList",
178
+ "file": "components/campaign-list.tsx",
179
+ "purpose": "All campaigns with status; open one for metrics and documents, or delete it (confirmed). No required props.",
180
+ "operations": [
181
+ "list_campaigns_campaigns_get",
182
+ "delete_campaign_campaigns__campaign_id__delete",
183
+ "list_campaigns_for_picker_campaigns_picker_get",
184
+ "get_campaign_metrics_campaigns__campaign_id__metrics_get",
185
+ "list_campaign_documents_campaigns__campaign_id__documents_get",
186
+ "get_campaign_document_campaigns__campaign_id__documents__document_id__get",
187
+ "create_campaign_document_campaigns__campaign_id__documents_post",
188
+ "update_campaign_document_campaigns__campaign_id__documents__document_id__put",
189
+ "delete_campaign_document_campaigns__campaign_id__documents__document_id__delete",
190
+ "regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post"
191
+ ],
192
+ "tiers": [
193
+ "read",
194
+ "billable",
195
+ "external",
196
+ "destructive"
197
+ ]
198
+ },
199
+ {
200
+ "name": "CampaignAssets",
201
+ "file": "components/campaign-assets.tsx",
202
+ "purpose": "Campaign documents: read, edit, rewrite with AI, add and delete, each confirmed.",
203
+ "operations": [
204
+ "list_campaigns_for_picker_campaigns_picker_get",
205
+ "list_campaign_documents_campaigns__campaign_id__documents_get",
206
+ "get_campaign_document_campaigns__campaign_id__documents__document_id__get",
207
+ "create_campaign_document_campaigns__campaign_id__documents_post",
208
+ "update_campaign_document_campaigns__campaign_id__documents__document_id__put",
209
+ "delete_campaign_document_campaigns__campaign_id__documents__document_id__delete",
210
+ "regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post"
211
+ ],
212
+ "tiers": [
213
+ "read",
214
+ "billable",
215
+ "external",
216
+ "destructive"
217
+ ]
218
+ },
219
+ {
220
+ "name": "CampaignMetrics",
221
+ "file": "components/campaign-metrics.tsx",
222
+ "purpose": "Performance tiles for a campaign over 7, 30, 90 or 365 days. No required props.",
223
+ "operations": [
224
+ "list_campaigns_for_picker_campaigns_picker_get",
225
+ "get_campaign_metrics_campaigns__campaign_id__metrics_get"
226
+ ],
227
+ "tiers": [
228
+ "read"
229
+ ]
230
+ },
231
+ {
232
+ "name": "CampaignSelect",
233
+ "file": "components/campaign-select.tsx",
234
+ "purpose": "Pick a campaign (id and name only); used by the page components when no campaignId is given.",
235
+ "operations": [
236
+ "list_campaigns_for_picker_campaigns_picker_get"
237
+ ],
238
+ "tiers": [
239
+ "read"
240
+ ]
241
+ },
242
+ {
243
+ "name": "FormField",
244
+ "file": "components/form-field.tsx",
245
+ "purpose": "A label around a control; the layout piece the editors share.",
246
+ "operations": [],
247
+ "tiers": []
248
+ }
249
+ ],
250
+ "hooks": [
251
+ {
252
+ "name": "useCompanyProfile",
253
+ "file": "hooks/use-company-profile.ts",
254
+ "purpose": "The saved company profile and its website, which every generation call needs.",
255
+ "operations": [
256
+ "get_company_profile_company_profile_get"
257
+ ]
258
+ }
259
+ ],
260
+ "lib": [
261
+ {
262
+ "file": "lib/campaigns-utils.ts",
263
+ "purpose": "Row types, credit estimate constants, channel-to-mode matrix, UTC date format and response helpers."
264
+ }
265
+ ],
266
+ "recipes": [
267
+ {
268
+ "file": "recipes/set-up-icp-and-personas.ts",
269
+ "title": "Set the company profile, then create an ICP and personas",
270
+ "when": "Before any campaign work; a wrong website makes everything downstream describe the wrong company."
271
+ },
272
+ {
273
+ "file": "recipes/generate-ideas-and-create-campaign.ts",
274
+ "title": "Generate campaign ideas, then create the campaign from one",
275
+ "when": "Starting a campaign; idea generation spends credits and is polled."
276
+ },
277
+ {
278
+ "file": "recipes/attach-audience-and-launch.ts",
279
+ "title": "Attach an audience, check readiness, and launch",
280
+ "when": "The campaign has steps and copy and a human approved reaching the list; launch is real outreach."
281
+ },
282
+ {
283
+ "file": "recipes/campaign-report.ts",
284
+ "title": "Report on every campaign",
285
+ "when": "A daily or weekly summary; reads only."
286
+ }
287
+ ],
288
+ "prerequisites": [
289
+ {
290
+ "id": "company-website",
291
+ "description": "The workspace has a company profile with the real website. Ideas, personas and campaigns are generated from it.",
292
+ "where": "api",
293
+ "verify": "GET /company-profile returns data.profile with website_url."
294
+ },
295
+ {
296
+ "id": "active-mailbox",
297
+ "description": "At least one ACTIVE email mailbox. Google and Microsoft mailboxes are connected by the person in the graph8 app; the API cannot run the OAuth consent screen.",
298
+ "where": "app",
299
+ "verify": "GET /mailboxes lists one with connection_status active."
300
+ },
301
+ {
302
+ "id": "audience-list",
303
+ "description": "A contact list to message, created in the lists block. Search results are masked until saved to a list.",
304
+ "where": "api",
305
+ "verify": "GET /lists returns the list with a non-zero total."
306
+ },
307
+ {
308
+ "id": "credits-for-generation",
309
+ "description": "Credits for AI generation (profile, personas, ideas, step copy, documents). There is no balance read, so the UI shows estimates.",
310
+ "where": "api",
311
+ "verify": "A generation call returns 200 or 202; a 402 means insufficient credits."
312
+ }
313
+ ],
314
+ "scopes": [
315
+ "campaigns:delete",
316
+ "campaigns:launch",
317
+ "campaigns:read",
318
+ "campaigns:run",
319
+ "campaigns:write"
320
+ ],
321
+ "defaults": [
322
+ {
323
+ "decision": "Number of campaign ideas to generate",
324
+ "default": "5",
325
+ "askHuman": false
326
+ },
327
+ {
328
+ "decision": "Campaign goal for idea generation",
329
+ "default": "awareness",
330
+ "askHuman": false
331
+ },
332
+ {
333
+ "decision": "Channels for a new campaign",
334
+ "default": "email (the one-step API launch executes email only)",
335
+ "askHuman": false
336
+ },
337
+ {
338
+ "decision": "Generate campaign documents with AI at creation",
339
+ "default": "No: generate per step and per document so each spend is confirmed",
340
+ "askHuman": false
341
+ },
342
+ {
343
+ "decision": "Sender mailbox for launch",
344
+ "default": "The first active email mailbox",
345
+ "askHuman": false
346
+ },
347
+ {
348
+ "decision": "Whether to launch, and to which list",
349
+ "default": "None: never launch without approval of the recipient count",
350
+ "askHuman": true
351
+ },
352
+ {
353
+ "decision": "Approving the company profile (locks it)",
354
+ "default": "Leave unapproved",
355
+ "askHuman": true
356
+ }
357
+ ],
358
+ "peerDependencies": {
359
+ "react": ">=18"
360
+ }
361
+ }
@@ -0,0 +1,180 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CampaignAssets: the documents behind a campaign (brief, targeting, copy). Read one, edit it, rewrite it with
5
+ * AI, add a new one or delete it. Without `campaignId` it shows a campaign picker first.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. listCampaignsForPicker GET /campaigns/picker (read; only when no `campaignId`)
9
+ * 2. listCampaignDocuments GET /campaigns/{campaign_id}/documents (read)
10
+ * 3. getCampaignDocument GET /campaigns/{campaign_id}/documents/{document_id} (read; full content)
11
+ * 4. updateCampaignDocument PUT /campaigns/{campaign_id}/documents/{document_id} (external, ConfirmSpendDialog)
12
+ * 5. regenerateCampaignDocument POST .../documents/{document_id}/regenerate (billable, ConfirmSpendDialog; 202, POLLED via 3)
13
+ * 6. createCampaignDocument POST /campaigns/{campaign_id}/documents (external, ConfirmSpendDialog)
14
+ * 7. deleteCampaignDocument DELETE .../documents/{document_id} (destructive, ConfirmSpendDialog)
15
+ *
16
+ * Tiers: 4-7 are all confirmed. Regenerating REPLACES the current content, so unsaved edits are discarded;
17
+ * the editor resets whenever the open document or the campaign changes. Content over 5 MiB is refused client
18
+ * side (the proxy would answer 413).
19
+ */
20
+
21
+ import { useState } from "react";
22
+ import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Skeleton, Textarea } from "@graph8/sdk/ui";
23
+ import { ApiError } from "@/g8/components/api-error";
24
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
25
+ import { EmptyState } from "@/g8/components/empty-state";
26
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
27
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
28
+ import { useJobPoll } from "@/g8/hooks/use-job-poll";
29
+ import { g8Delete, g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
30
+ import { CREDIT_ESTIMATES, MAX_BODY_BYTES, byteLength, listOf, objectOf, statusClass, text } from "../lib/campaigns-utils";
31
+ import type { CampaignDocRow } from "../lib/campaigns-utils";
32
+ import { CampaignSelect } from "./campaign-select";
33
+ import { FormField } from "./form-field";
34
+
35
+ export interface CampaignAssetsProps {
36
+ campaignId?: string;
37
+ className?: string;
38
+ }
39
+
40
+ type Pending = { kind: "save" | "regenerate" | "delete" };
41
+ const FILE_TYPES = ["campaign_brief", "targeting", "sequence", "email_copy", "landing_page", "other"];
42
+
43
+ function Editor({ campaignId, doc, onChanged, onClosed }: { campaignId: string; doc: CampaignDocRow; onChanged: () => void; onClosed: () => void }) {
44
+ const base = `/campaigns/${campaignId}/documents/${doc.id}`;
45
+ const [content, setContent] = useState<string | null>(null);
46
+ const [pending, setPending] = useState<Pending | null>(null);
47
+ const full = useG8Query((signal) => g8Get(base, undefined, { signal }), [base]);
48
+ const poll = useJobPoll(async (signal) => objectOf(await g8Get(base, undefined, { signal })), {
49
+ enabled: false,
50
+ isTerminal: (d) => text(d.status) !== "generating",
51
+ onDone: () => { setContent(null); void full.refetch(); onChanged(); },
52
+ });
53
+ const run = useG8Mutation(async (p: Pending) => {
54
+ try {
55
+ if (p.kind === "save") return await g8Put(base, { content: content ?? "" });
56
+ if (p.kind === "regenerate") return await g8Post(`${base}/regenerate`, {});
57
+ if (p.kind !== "delete") return undefined;
58
+ try { return await g8Delete(base); } catch (err) { if ((err as { status?: number }).status === 404) return undefined; throw err; }
59
+ } finally { setPending(null); }
60
+ }, {
61
+ onSuccess: (_r, p) => {
62
+ if (p.kind === "regenerate") poll.start();
63
+ else if (p.kind === "delete") { onChanged(); onClosed(); }
64
+ else { setContent(null); void full.refetch(); onChanged(); }
65
+ },
66
+ });
67
+
68
+ const d = objectOf(full.data);
69
+ const body = content ?? text(d.content);
70
+ const tooBig = byteLength(body) > MAX_BODY_BYTES;
71
+ return (
72
+ <section aria-label={`Document ${doc.display_name ?? doc.id}`} className="space-y-2 rounded-md border p-3">
73
+ <div className="flex items-center justify-between">
74
+ <h4 className="font-medium">{doc.display_name}</h4>
75
+ <Button size="sm" variant="outline" onClick={onClosed}>Close</Button>
76
+ </div>
77
+ {full.error ? <ApiError error={full.error} title="Could not load the document" onRetry={() => void full.refetch()} /> : null}
78
+ {full.loading && !full.data ? <Skeleton className="h-32 w-full" /> : null}
79
+ {run.error ? <ApiError error={run.error} /> : null}
80
+ {poll.polling ? <p role="status" className="text-sm text-muted-foreground">Rewriting the document with AI.</p> : null}
81
+ {full.data ? (
82
+ <>
83
+ <Textarea aria-label="Document content" className="min-h-48 font-mono text-sm" value={body} onChange={(e) => setContent(e.target.value)} />
84
+ {tooBig ? <p role="alert" className="text-sm text-red-700">This document is over 5 MiB and cannot be saved through the proxy.</p> : null}
85
+ <div className="flex gap-2">
86
+ <Button disabled={content === null || tooBig || poll.polling} onClick={() => setPending({ kind: "save" })}>Save changes</Button>
87
+ <Button variant="outline" disabled={poll.polling} onClick={() => setPending({ kind: "regenerate" })}>Rewrite with AI</Button>
88
+ <Button variant="outline" disabled={poll.polling} onClick={() => setPending({ kind: "delete" })}>Delete</Button>
89
+ </div>
90
+ </>
91
+ ) : null}
92
+ <ConfirmSpendDialog
93
+ open={pending !== null}
94
+ tier={pending?.kind === "regenerate" ? "billable" : pending?.kind === "delete" ? "destructive" : "external"}
95
+ title={pending?.kind === "save" ? `Save "${doc.display_name}"` : pending?.kind === "regenerate" ? `Rewrite "${doc.display_name}" with AI` : `Delete "${doc.display_name}"`}
96
+ description={pending?.kind === "regenerate" ? "Replaces the current content, including unsaved edits. Estimate only." : pending?.kind === "delete" ? "The document is removed from the campaign." : "Nothing is sent to contacts."}
97
+ estimatedCredits={pending?.kind === "regenerate" ? CREDIT_ESTIMATES.documentRegenerate : undefined}
98
+ affected={pending?.kind === "delete" ? 1 : undefined}
99
+ confirmLabel={pending?.kind === "save" ? "Save document" : pending?.kind === "regenerate" ? "Rewrite document" : "Delete document"}
100
+ confirming={run.loading}
101
+ onConfirm={() => pending && void run.mutate(pending)}
102
+ onCancel={() => setPending(null)}
103
+ />
104
+ </section>
105
+ );
106
+ }
107
+
108
+ function Documents({ campaignId, className }: { campaignId: string; className?: string }) {
109
+ const docs = useG8Query((signal) => g8Get(`/campaigns/${campaignId}/documents`, undefined, { signal }), [campaignId]);
110
+ const [openId, setOpenId] = useState<string | null>(null);
111
+ const [creating, setCreating] = useState(false);
112
+ const [form, setForm] = useState({ display_name: "", file_type: FILE_TYPES[0], content: "" });
113
+ const [confirmCreate, setConfirmCreate] = useState(false);
114
+ const create = useG8Mutation(() => g8Post(`/campaigns/${campaignId}/documents`, { ...form, status: "draft" }), {
115
+ onSuccess: (res) => { setConfirmCreate(false); setCreating(false); setForm({ display_name: "", file_type: FILE_TYPES[0], content: "" }); void docs.refetch(); setOpenId(text(objectOf(res).id) || null); },
116
+ onError: () => setConfirmCreate(false),
117
+ });
118
+ const rows = listOf<CampaignDocRow>(docs.data);
119
+ const open = rows.find((r) => r.id === openId);
120
+ return (
121
+ <section aria-label="Campaign documents" className={`space-y-3 ${className ?? ""}`}>
122
+ <div className="flex items-center justify-between">
123
+ <h3 className="font-medium">Documents</h3>
124
+ <Button size="sm" onClick={() => { create.reset(); setCreating(true); }}>Add document</Button>
125
+ </div>
126
+ {docs.error ? <ApiError error={docs.error} title="Could not load documents" onRetry={() => void docs.refetch()} /> : null}
127
+ {create.error && !creating ? <ApiError error={create.error} title="Could not add the document" /> : null}
128
+ {docs.loading && !docs.data ? <Skeleton className="h-20 w-full" /> : null}
129
+ {!docs.loading && !docs.error && rows.length === 0 ? <EmptyState title="No documents yet" description="Documents appear once the campaign finishes generating, or add one yourself." /> : null}
130
+ <ul className="divide-y rounded-md border">
131
+ {rows.map((d) => (
132
+ <li key={d.id} className="flex items-center justify-between px-3 py-2 text-sm">
133
+ <button type="button" className="text-left font-medium underline-offset-2 hover:underline" aria-label={`Open ${d.display_name}`} onClick={() => setOpenId(d.id === openId ? null : d.id)}>{d.display_name ?? d.id}</button>
134
+ <span className="flex items-center gap-2 text-xs text-muted-foreground">{d.file_type}<span className={`rounded px-2 py-0.5 ${statusClass(d.status)}`}>{d.status}</span></span>
135
+ </li>
136
+ ))}
137
+ </ul>
138
+ {open ? <Editor key={open.id} campaignId={campaignId} doc={open} onChanged={() => void docs.refetch()} onClosed={() => setOpenId(null)} /> : null}
139
+
140
+ <Dialog open={creating} onOpenChange={setCreating}>
141
+ <DialogContent>
142
+ <DialogHeader><DialogTitle>Add document</DialogTitle></DialogHeader>
143
+ <div className="space-y-3">
144
+ <FormField label="Name" htmlFor="doc-name"><Input id="doc-name" value={form.display_name} onChange={(e) => setForm({ ...form, display_name: e.target.value })} /></FormField>
145
+ <FormField label="Type" htmlFor="doc-type">
146
+ <select id="doc-type" className="h-9 rounded-md border bg-background px-2" value={form.file_type} onChange={(e) => setForm({ ...form, file_type: e.target.value })}>{FILE_TYPES.map((t) => <option key={t}>{t}</option>)}</select>
147
+ </FormField>
148
+ <FormField label="Content" htmlFor="doc-content"><Textarea id="doc-content" value={form.content} onChange={(e) => setForm({ ...form, content: e.target.value })} /></FormField>
149
+ {create.error ? <ApiError error={create.error} title="Could not add the document" /> : null}
150
+ </div>
151
+ <DialogFooter>
152
+ <Button variant="outline" onClick={() => setCreating(false)}>Cancel</Button>
153
+ <Button disabled={!form.display_name.trim() || byteLength(form.content) > MAX_BODY_BYTES} onClick={() => { setCreating(false); setConfirmCreate(true); }}>Review</Button>
154
+ </DialogFooter>
155
+ </DialogContent>
156
+ </Dialog>
157
+ <ConfirmSpendDialog
158
+ open={confirmCreate}
159
+ tier="external"
160
+ title={`Add document "${form.display_name}"`}
161
+ description="Nothing is sent to contacts."
162
+ confirmLabel="Add document"
163
+ confirming={create.loading}
164
+ onConfirm={() => void create.mutate(undefined)}
165
+ onCancel={() => setConfirmCreate(false)}
166
+ />
167
+ </section>
168
+ );
169
+ }
170
+
171
+ export function CampaignAssets({ campaignId, className }: CampaignAssetsProps) {
172
+ const [picked, setPicked] = useState<string | null>(null);
173
+ const id = campaignId ?? picked;
174
+ return (
175
+ <div className={`space-y-3 ${className ?? ""}`}>
176
+ {campaignId ? null : <CampaignSelect value={picked} onChange={setPicked} />}
177
+ {id ? <Documents key={id} campaignId={id} /> : null}
178
+ </div>
179
+ );
180
+ }
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CampaignAudienceStep: attach a contact list to a campaign (or detach it). The list is the audience the
5
+ * campaign will message at launch, so attaching is confirmed with the contact count.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. getCampaignFull GET /campaigns/{campaign_id}/full (read; audience_list_id and the audience summary; plain GET /campaigns/{id} omits them)
9
+ * 2. listLists / createList GET/POST /lists via the lists block's ListPicker (read / write; pick or create the list)
10
+ * 3. attachAudienceToCampaign PUT /campaigns/{campaign_id}/audience (external, ConfirmSpendDialog; { list_id } or null to detach)
11
+ *
12
+ * Tiers: 3 is `external` in the contract. Nothing is sent by attaching, but it fixes who a later launch will
13
+ * reach, so the confirmation states the count. `list_id` is sent as a string. Search results are masked until
14
+ * saved to a list (see the search block), so build the list there first.
15
+ */
16
+
17
+ import { useState } from "react";
18
+ import { Button, Skeleton } from "@graph8/sdk/ui";
19
+ import { ApiError } from "@/g8/components/api-error";
20
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
21
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
22
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
23
+ import { g8Get, g8Put } from "@/g8/lib/g8-client";
24
+ import { ListPicker } from "@/g8/lists/components/list-picker";
25
+ import type { ListSummary } from "@/g8/lists/components/list-picker";
26
+ import { audienceOf } from "../lib/campaigns-utils";
27
+
28
+ export interface CampaignAudienceStepProps {
29
+ campaignId: string;
30
+ /** Called after a successful attach (list) or detach (null). */
31
+ onAttached?: (list: ListSummary | null) => void;
32
+ className?: string;
33
+ }
34
+
35
+ type Pending = { list: ListSummary | null };
36
+
37
+ export function CampaignAudienceStep({ campaignId, onAttached, className }: CampaignAudienceStepProps) {
38
+ const campaign = useG8Query((signal) => g8Get(`/campaigns/${campaignId}/full`, undefined, { signal }), [campaignId]);
39
+ const [picked, setPicked] = useState<ListSummary | null>(null);
40
+ const [pending, setPending] = useState<Pending | null>(null);
41
+
42
+ const attach = useG8Mutation(
43
+ (p: Pending) => g8Put(`/campaigns/${campaignId}/audience`, { list_id: p.list ? String(p.list.id) : null }),
44
+ {
45
+ onSuccess: (_res, p) => {
46
+ setPending(null);
47
+ setPicked(null);
48
+ void campaign.refetch();
49
+ onAttached?.(p.list);
50
+ },
51
+ onError: () => setPending(null),
52
+ },
53
+ );
54
+
55
+ const { listId: attachedId, name: attachedName, size: attachedSize } = audienceOf(campaign.data);
56
+ return (
57
+ <section aria-label="Audience" className={`space-y-3 ${className ?? ""}`}>
58
+ <h3 className="font-medium">Audience</h3>
59
+ {campaign.error ? <ApiError error={campaign.error} title="Could not load the campaign" onRetry={() => void campaign.refetch()} /> : null}
60
+ {campaign.loading && !campaign.data ? <Skeleton className="h-6 w-64" /> : null}
61
+ {!campaign.loading && !campaign.error ? (
62
+ <p className="text-sm" data-testid="attached-audience">
63
+ {attachedId ? <>Attached list: <strong>{attachedName || `#${attachedId}`}</strong>{attachedSize != null ? ` (${attachedSize.toLocaleString()} contacts)` : ""}</> : "No list attached yet."}
64
+ </p>
65
+ ) : null}
66
+ <ListPicker value={picked?.id ?? (attachedId ? Number(attachedId) : null)} onChange={setPicked} allowCreate />
67
+ {attach.error ? <ApiError error={attach.error} title="Could not change the audience" /> : null}
68
+ <div className="flex gap-2">
69
+ <Button disabled={!picked || String(picked.id) === attachedId} onClick={() => picked && setPending({ list: picked })}>
70
+ Attach {picked ? `"${picked.title}"` : "list"}
71
+ </Button>
72
+ <Button variant="outline" disabled={!attachedId} onClick={() => setPending({ list: null })}>Detach list</Button>
73
+ </div>
74
+ <ConfirmSpendDialog
75
+ open={pending !== null}
76
+ tier="external"
77
+ title={pending?.list ? `Attach "${pending.list.title}" to this campaign` : "Detach the audience from this campaign"}
78
+ description={
79
+ pending?.list
80
+ ? "The campaign will message this list when you launch it. Nothing is sent by attaching."
81
+ : "The campaign will have no audience and cannot launch until you attach one."
82
+ }
83
+ recipients={pending?.list ? (pending.list.total ?? pending.list.contact_count ?? undefined) : undefined}
84
+ confirmLabel={pending?.list ? "Attach list" : "Detach list"}
85
+ confirming={attach.loading}
86
+ onConfirm={() => pending && void attach.mutate(pending)}
87
+ onCancel={() => setPending(null)}
88
+ />
89
+ </section>
90
+ );
91
+ }