@graph8/sdk 0.370.0 → 0.374.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (646) hide show
  1. package/README.md +22 -3
  2. package/blocks/_foundation/README.md +61 -0
  3. package/blocks/_foundation/block.json +57 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  6. package/blocks/_foundation/components/empty-state.tsx +5 -2
  7. package/blocks/_foundation/components/list-select.tsx +112 -0
  8. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  9. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  10. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  11. package/blocks/_foundation/components/screen/index.ts +1 -0
  12. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  13. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  14. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  15. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  16. package/blocks/_foundation/lib/paging.ts +57 -0
  17. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  18. package/blocks/_foundation/server/auth.ts +815 -0
  19. package/blocks/agents/README.md +81 -4
  20. package/blocks/agents/block.json +311 -2
  21. package/blocks/agents/components/agent-activity.tsx +112 -0
  22. package/blocks/agents/components/agent-approvals.tsx +135 -0
  23. package/blocks/agents/components/agent-builder.tsx +17 -3
  24. package/blocks/agents/components/agent-conversations.tsx +143 -0
  25. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  26. package/blocks/agents/components/agent-operators.tsx +164 -0
  27. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  28. package/blocks/agents/components/agent-runs.tsx +3 -3
  29. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  30. package/blocks/agents/components/agent-test-history.tsx +121 -0
  31. package/blocks/agents/components/agents-screen.tsx +53 -11
  32. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  33. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  34. package/blocks/agents/components/from-template-button.tsx +65 -0
  35. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  36. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  37. package/blocks/agents/components/knowledge-form.tsx +69 -0
  38. package/blocks/agents/components/name-dialog.tsx +32 -0
  39. package/blocks/agents/components/receptionist-button.tsx +46 -0
  40. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  41. package/blocks/agents/components/skill-editor.tsx +147 -0
  42. package/blocks/agents/components/skill-library.tsx +111 -0
  43. package/blocks/agents/lib/agent-types.ts +7 -1
  44. package/blocks/agents/lib/facts.tsx +13 -0
  45. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  46. package/blocks/agents/lib/load-more.tsx +141 -0
  47. package/blocks/agents/lib/skill-utils.ts +91 -0
  48. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  50. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  51. package/blocks/analytics/components/report-builder.tsx +15 -2
  52. package/blocks/appointments/README.md +6 -0
  53. package/blocks/appointments/block.json +27 -0
  54. package/blocks/appointments/components/booking-page.tsx +2 -2
  55. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  56. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  57. package/blocks/appointments/components/meeting-list.tsx +29 -13
  58. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  59. package/blocks/campaigns/README.md +1 -1
  60. package/blocks/campaigns/block.json +34 -1
  61. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  62. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  63. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  64. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  65. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  66. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  67. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  68. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  69. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  70. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  71. package/blocks/coach/components/call-scorecard.tsx +7 -7
  72. package/blocks/company-intelligence/README.md +132 -0
  73. package/blocks/company-intelligence/block.json +318 -0
  74. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  75. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  76. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  77. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  78. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  79. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  80. package/blocks/company-intelligence/components/research-run.tsx +165 -0
  81. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  82. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  83. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  84. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  85. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  86. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  87. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  88. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  89. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  90. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  91. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  92. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  93. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  94. package/blocks/crm/README.md +25 -0
  95. package/blocks/crm/block.json +257 -9
  96. package/blocks/crm/components/bulk-update.tsx +119 -0
  97. package/blocks/crm/components/companies-screen.tsx +23 -9
  98. package/blocks/crm/components/company-detail.tsx +2 -2
  99. package/blocks/crm/components/contact-detail.tsx +4 -4
  100. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  101. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  102. package/blocks/crm/components/contact-lookup-link.tsx +85 -0
  103. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  104. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  105. package/blocks/crm/components/contacts-screen.tsx +77 -18
  106. package/blocks/crm/components/dedupe-review.tsx +6 -1
  107. package/blocks/crm/components/import-wizard.tsx +3 -7
  108. package/blocks/crm/components/list-select.tsx +20 -0
  109. package/blocks/crm/components/owner-assign.tsx +132 -0
  110. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  111. package/blocks/crm/components/record-form.tsx +1 -1
  112. package/blocks/crm/components/record-open-links.tsx +79 -0
  113. package/blocks/crm/components/tag-assign.tsx +112 -0
  114. package/blocks/crm/components/tag-manager.tsx +178 -0
  115. package/blocks/crm/components/tasks-screen.tsx +35 -15
  116. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  117. package/blocks/crm/components/verify-email-button.tsx +93 -0
  118. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  119. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  120. package/blocks/crm/hooks/use-page-query.ts +3 -1
  121. package/blocks/crm/lib/crm-types.ts +14 -0
  122. package/blocks/crm/lib/read-lists.ts +16 -0
  123. package/blocks/crm/lib/record-actions.ts +102 -0
  124. package/blocks/deals/README.md +25 -6
  125. package/blocks/deals/block.json +168 -2
  126. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  127. package/blocks/deals/components/create-deal-button.tsx +109 -0
  128. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  129. package/blocks/deals/components/deal-detail.tsx +7 -5
  130. package/blocks/deals/components/deal-filters.tsx +65 -0
  131. package/blocks/deals/components/deal-line-items.tsx +120 -0
  132. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  133. package/blocks/deals/components/deal-related.tsx +30 -9
  134. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  135. package/blocks/deals/components/deals-screen.tsx +57 -22
  136. package/blocks/deals/components/forecast-view.tsx +4 -4
  137. package/blocks/deals/components/pipeline-board.tsx +8 -6
  138. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  139. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  140. package/blocks/deals/components/stage-editor.tsx +1 -1
  141. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  142. package/blocks/deals/lib/deal-extras.ts +156 -0
  143. package/blocks/dialer/README.md +14 -5
  144. package/blocks/dialer/block.json +17 -2
  145. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  146. package/blocks/dialer/components/call-log-screen.tsx +10 -2
  147. package/blocks/dialer/components/call-log.tsx +16 -4
  148. package/blocks/dialer/components/call-queue.tsx +3 -3
  149. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  150. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  151. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  152. package/blocks/dialer/components/session-start-form.tsx +58 -11
  153. package/blocks/dialer/components/softphone.tsx +1 -1
  154. package/blocks/documents/README.md +86 -0
  155. package/blocks/documents/block.json +378 -0
  156. package/blocks/documents/components/account-bar.tsx +48 -0
  157. package/blocks/documents/components/correspondence-form.tsx +95 -0
  158. package/blocks/documents/components/correspondence-import.tsx +106 -0
  159. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  160. package/blocks/documents/components/documents-screen.tsx +76 -0
  161. package/blocks/documents/components/library-document-detail.tsx +175 -0
  162. package/blocks/documents/components/library-document-form.tsx +105 -0
  163. package/blocks/documents/components/library-folder-form.tsx +74 -0
  164. package/blocks/documents/components/library-panel.tsx +235 -0
  165. package/blocks/documents/components/library-upload.tsx +160 -0
  166. package/blocks/documents/components/question-row.tsx +130 -0
  167. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  168. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  169. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  170. package/blocks/documents/lib/documents-types.ts +252 -0
  171. package/blocks/documents/lib/download.ts +64 -0
  172. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  173. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  174. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  175. package/blocks/email-signatures/README.md +123 -0
  176. package/blocks/email-signatures/block.json +341 -0
  177. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  178. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  179. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  180. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  181. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  182. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  183. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  184. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  185. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  186. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  187. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  188. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  189. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  190. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  191. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  192. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  193. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  194. package/blocks/enrichment/README.md +38 -10
  195. package/blocks/enrichment/block.json +164 -12
  196. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  197. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  198. package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
  199. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  200. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  201. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  202. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  203. package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
  204. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  205. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  206. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  207. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  208. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  209. package/blocks/enrichment/lib/enrichment-types.ts +51 -0
  210. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  211. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  212. package/blocks/forms/README.md +10 -0
  213. package/blocks/forms/block.json +107 -0
  214. package/blocks/forms/components/form-activity.tsx +71 -0
  215. package/blocks/forms/components/form-backfill.tsx +108 -0
  216. package/blocks/forms/components/form-builder.tsx +1 -1
  217. package/blocks/forms/components/form-embed.tsx +1 -1
  218. package/blocks/forms/components/form-groups.tsx +151 -0
  219. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  220. package/blocks/forms/components/form-route-history.tsx +72 -0
  221. package/blocks/forms/components/form-saved-views.tsx +81 -0
  222. package/blocks/forms/components/forms-screen.tsx +57 -20
  223. package/blocks/forms/components/more-rows.tsx +30 -0
  224. package/blocks/forms/components/submissions-export.tsx +67 -0
  225. package/blocks/forms/components/submissions-table.tsx +15 -6
  226. package/blocks/forms/lib/forms-extra.ts +67 -0
  227. package/blocks/inbox/README.md +45 -4
  228. package/blocks/inbox/block.json +145 -8
  229. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  230. package/blocks/inbox/components/compose-message.tsx +193 -0
  231. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  232. package/blocks/inbox/components/inbox-search.tsx +106 -0
  233. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  234. package/blocks/inbox/components/tag-manager.tsx +136 -0
  235. package/blocks/inbox/components/thread-actions.tsx +133 -0
  236. package/blocks/inbox/components/thread-badge.tsx +2 -2
  237. package/blocks/inbox/components/thread-view.tsx +3 -1
  238. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  239. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  240. package/blocks/integrations/README.md +54 -1
  241. package/blocks/integrations/block.json +189 -15
  242. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  243. package/blocks/integrations/components/connection-health.tsx +59 -0
  244. package/blocks/integrations/components/crm-detail.tsx +20 -1
  245. package/blocks/integrations/components/crm-records.tsx +103 -0
  246. package/blocks/integrations/components/field-mappings.tsx +61 -0
  247. package/blocks/integrations/components/integration-list.tsx +4 -4
  248. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  249. package/blocks/integrations/components/migration-panel.tsx +133 -0
  250. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  251. package/blocks/integrations/components/sync-status.tsx +6 -6
  252. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  253. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  254. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  255. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  256. package/blocks/integrations/lib/crm-utils.ts +39 -0
  257. package/blocks/integrations/lib/facts.tsx +13 -0
  258. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  259. package/blocks/integrations/lib/load-more.tsx +141 -0
  260. package/blocks/intent/components/account-intent-score.tsx +5 -5
  261. package/blocks/intent/components/intent-feed.tsx +8 -8
  262. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  263. package/blocks/intent/lib/intent-utils.ts +4 -4
  264. package/blocks/lifecycle/components/account-related.tsx +8 -1
  265. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  266. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  267. package/blocks/lists/README.md +7 -1
  268. package/blocks/lists/block.json +31 -3
  269. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  270. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  271. package/blocks/lists/components/list-picker.tsx +13 -3
  272. package/blocks/lists/components/list-table.tsx +1 -1
  273. package/blocks/lists/components/lists-screen.tsx +4 -2
  274. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  275. package/blocks/lists/components/suppression-manager.tsx +27 -6
  276. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  277. package/blocks/mailboxes/block.json +4 -0
  278. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  279. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  280. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  281. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  282. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  283. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  284. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  285. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  286. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  287. package/blocks/newsletter/README.md +12 -0
  288. package/blocks/newsletter/block.json +117 -0
  289. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  290. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  291. package/blocks/newsletter/components/more-rows.tsx +30 -0
  292. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  293. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  294. package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
  295. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  296. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  297. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  298. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  299. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  300. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  301. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  302. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  303. package/blocks/nurture/components/nurture-form.tsx +5 -8
  304. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  305. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  306. package/blocks/objects/README.md +1 -1
  307. package/blocks/objects/block.json +13 -5
  308. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  309. package/blocks/objects/components/object-record-detail.tsx +8 -1
  310. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  311. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  312. package/blocks/pages/README.md +14 -0
  313. package/blocks/pages/block.json +123 -2
  314. package/blocks/pages/components/more-rows.tsx +30 -0
  315. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  316. package/blocks/pages/components/page-analytics.tsx +176 -0
  317. package/blocks/pages/components/page-clone.tsx +99 -0
  318. package/blocks/pages/components/page-domains.tsx +167 -0
  319. package/blocks/pages/components/page-editor.tsx +1 -1
  320. package/blocks/pages/components/page-generation-status.tsx +48 -0
  321. package/blocks/pages/components/page-tracking.tsx +137 -0
  322. package/blocks/pages/components/pages-screen.tsx +57 -16
  323. package/blocks/pages/components/seo-report.tsx +6 -1
  324. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  325. package/blocks/pages/lib/studio.ts +7 -0
  326. package/blocks/phone-numbers/README.md +74 -0
  327. package/blocks/phone-numbers/block.json +262 -0
  328. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  329. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  330. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  331. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  332. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  333. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  334. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  335. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  336. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  337. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  338. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  339. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  340. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  341. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  342. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  343. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  344. package/blocks/pickers/README.md +23 -0
  345. package/blocks/pickers/block.json +103 -0
  346. package/blocks/pickers/components/company-picker.tsx +90 -0
  347. package/blocks/pickers/components/contact-picker.tsx +131 -0
  348. package/blocks/pickers/components/member-picker.tsx +99 -0
  349. package/blocks/pickers/components/search-picker.tsx +98 -0
  350. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  351. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  352. package/blocks/pickers/lib/use-debounced.ts +13 -0
  353. package/blocks/programmatic-pages/README.md +82 -0
  354. package/blocks/programmatic-pages/block.json +292 -0
  355. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  356. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  357. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  358. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  359. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  360. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  361. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  362. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  363. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  364. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  365. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  366. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  367. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  368. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  369. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  370. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  371. package/blocks/quotes/README.md +38 -1
  372. package/blocks/quotes/block.json +170 -6
  373. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  374. package/blocks/quotes/components/manual-products.tsx +144 -0
  375. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  376. package/blocks/quotes/components/quote-builder.tsx +1 -1
  377. package/blocks/quotes/components/quote-documents.tsx +94 -0
  378. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  379. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  380. package/blocks/quotes/components/quote-list.tsx +28 -8
  381. package/blocks/quotes/components/quote-related.tsx +3 -2
  382. package/blocks/quotes/components/quote-settings.tsx +184 -0
  383. package/blocks/quotes/components/quote-templates.tsx +189 -0
  384. package/blocks/quotes/components/quote-viewer.tsx +23 -5
  385. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  386. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  387. package/blocks/quotes/lib/quote-admin.ts +264 -0
  388. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  389. package/blocks/radar/components/radar-overview.tsx +4 -4
  390. package/blocks/radar/components/radar-screen.tsx +2 -2
  391. package/blocks/sequencer/README.md +61 -0
  392. package/blocks/sequencer/block.json +200 -6
  393. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  394. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  395. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  396. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  397. package/blocks/sequencer/components/sequence-builder.tsx +32 -23
  398. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  399. package/blocks/sequencer/components/sequence-detail.tsx +10 -2
  400. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  401. package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
  402. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  403. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  404. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  405. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  406. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  407. package/blocks/sequencer/components/sequences-screen.tsx +39 -6
  408. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  409. package/blocks/sequencer/components/step-editor.tsx +21 -12
  410. package/blocks/sequencer/components/step-templates.tsx +224 -0
  411. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  412. package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
  413. package/blocks/signals/README.md +39 -0
  414. package/blocks/signals/block.json +287 -0
  415. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  416. package/blocks/signals/components/listener-form.tsx +184 -0
  417. package/blocks/signals/components/matched-posts.tsx +195 -0
  418. package/blocks/signals/components/post-engagements.tsx +114 -0
  419. package/blocks/signals/components/record-social-tab.tsx +49 -0
  420. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  421. package/blocks/signals/components/social-settings.tsx +128 -0
  422. package/blocks/signals/components/source-lists.tsx +83 -0
  423. package/blocks/signals/components/top-engagers.tsx +68 -0
  424. package/blocks/signals/components/x-integration.tsx +129 -0
  425. package/blocks/signals/lib/signals-types.ts +228 -0
  426. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  427. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  428. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  429. package/blocks/social-ads/README.md +8 -0
  430. package/blocks/social-ads/block.json +137 -4
  431. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  432. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  433. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  434. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  435. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  436. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  437. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  438. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  439. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  440. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  441. package/blocks/social-ads/components/post-composer.tsx +4 -2
  442. package/blocks/social-ads/components/post-detail.tsx +3 -0
  443. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  444. package/blocks/social-ads/components/social-performance.tsx +80 -0
  445. package/blocks/social-ads/components/social-screen.tsx +33 -6
  446. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  447. package/blocks/surveys/README.md +91 -0
  448. package/blocks/surveys/block.json +299 -0
  449. package/blocks/surveys/components/account-nps.tsx +69 -0
  450. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  451. package/blocks/surveys/components/org-summary.tsx +82 -0
  452. package/blocks/surveys/components/sending-domains.tsx +113 -0
  453. package/blocks/surveys/components/survey-form.tsx +126 -0
  454. package/blocks/surveys/components/survey-responses.tsx +145 -0
  455. package/blocks/surveys/components/survey-results.tsx +72 -0
  456. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  457. package/blocks/surveys/components/survey-send.tsx +162 -0
  458. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  459. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  460. package/blocks/surveys/lib/surveys-types.ts +228 -0
  461. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  462. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  463. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  464. package/blocks/visitors/components/snippet-install.tsx +18 -9
  465. package/blocks/visitors/components/traffic-chart.tsx +7 -6
  466. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  467. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  468. package/blocks/web-analytics/README.md +102 -0
  469. package/blocks/web-analytics/block.json +292 -0
  470. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  471. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  472. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  473. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  474. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  475. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  476. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  477. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  478. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  479. package/blocks/web-analytics/components/report-card.tsx +71 -0
  480. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  481. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  482. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  483. package/blocks/web-analytics/lib/page-path.ts +14 -0
  484. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  485. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  486. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  487. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  488. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  489. package/blocks/webchat/components/conversation-list.tsx +1 -1
  490. package/blocks/workbench/README.md +54 -2
  491. package/blocks/workbench/block.json +234 -5
  492. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  493. package/blocks/workbench/components/run-progress.tsx +1 -1
  494. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  495. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  496. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  497. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  498. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  499. package/blocks/workbench/components/workbench-grid.tsx +71 -8
  500. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  501. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  502. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  503. package/blocks/workbench/components/workbench-screen.tsx +65 -22
  504. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  505. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  506. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  507. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  508. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  509. package/blocks/workbench/lib/api-status.ts +8 -0
  510. package/blocks/workbench/lib/automation-types.ts +257 -0
  511. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  512. package/dist/cli.js +744 -394
  513. package/dist/index.browser.js +1 -1
  514. package/dist/index.browser.js.map +1 -1
  515. package/dist/index.browser.mjs +1 -1
  516. package/dist/index.browser.mjs.map +1 -1
  517. package/dist/index.d.mts +2 -2
  518. package/dist/index.d.ts +2 -2
  519. package/dist/index.js +1 -1
  520. package/dist/index.js.map +1 -1
  521. package/dist/index.mjs +1 -1
  522. package/dist/index.mjs.map +1 -1
  523. package/dist/react.browser.js +1 -1
  524. package/dist/react.browser.js.map +1 -1
  525. package/dist/react.browser.mjs +1 -1
  526. package/dist/react.browser.mjs.map +1 -1
  527. package/dist/react.d.mts +2 -2
  528. package/dist/react.d.ts +2 -2
  529. package/dist/react.js +1 -1
  530. package/dist/react.js.map +1 -1
  531. package/dist/react.mjs +1 -1
  532. package/dist/react.mjs.map +1 -1
  533. package/dist/types/blocks/auth.d.ts +45 -0
  534. package/dist/types/blocks/brand.d.ts +11 -2
  535. package/dist/types/blocks/create.d.ts +11 -4
  536. package/dist/types/blocks/doctor.d.ts +1 -0
  537. package/dist/types/blocks/flows.d.ts +7 -1
  538. package/dist/types/blocks/orgBrand.d.ts +34 -0
  539. package/dist/types/cli.d.ts +3 -0
  540. package/dist/types/version.d.ts +2 -2
  541. package/docs/api/desk.d.ts +2 -2
  542. package/docs/api/desk.md +3 -3
  543. package/docs/api/forms.md +1 -1
  544. package/package.json +1 -1
  545. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  546. package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
  547. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  548. package/stacks/6sense.json +37 -4
  549. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  550. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  551. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  552. package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
  553. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  554. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  555. package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
  556. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  557. package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
  558. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  559. package/stacks/apollo.json +68 -9
  560. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  561. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  562. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  563. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  564. package/stacks/calendly.json +8 -4
  565. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  566. package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
  567. package/stacks/clay/pages/lists.tsx.tmpl +24 -0
  568. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  569. package/stacks/clay.json +16 -3
  570. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  571. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  572. package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
  573. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  574. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  575. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  576. package/stacks/gong.json +23 -6
  577. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  578. package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
  579. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  580. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  581. package/stacks/hootsuite.json +19 -4
  582. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  583. package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
  584. package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
  585. package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
  586. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  587. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  588. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  589. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  590. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  591. package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
  592. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  593. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  594. package/stacks/hubspot.json +66 -7
  595. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  596. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  597. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  598. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  599. package/stacks/intercom.json +18 -4
  600. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  601. package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
  602. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  603. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  604. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  605. package/stacks/mailchimp.json +22 -6
  606. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  607. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  608. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  609. package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
  610. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  611. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  612. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  613. package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
  614. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  615. package/stacks/outreach.json +38 -9
  616. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  617. package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
  618. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  619. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  620. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  621. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  622. package/stacks/pipedrive.json +14 -2
  623. package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
  624. package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
  625. package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
  626. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  627. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  628. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  629. package/stacks/salesforce.json +17 -3
  630. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  631. package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
  632. package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
  633. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  634. package/stacks/zoominfo.json +36 -3
  635. package/styles/styles.css +1 -1
  636. package/templates/auth/auth-route.ts.tmpl +16 -0
  637. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  638. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  639. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  640. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  641. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  642. package/templates/auth/middleware.ts.tmpl +14 -0
  643. package/templates/next-app/README.md.tmpl +5 -0
  644. package/templates/next-app/app/globals.css.tmpl +349 -64
  645. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  646. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ /**
4
+ * usePickerPages: the paging + search engine behind ContactPicker and CompanyPicker.
5
+ *
6
+ * Page-number lists (`?page&limit`, `pagination.has_next`) with a search box: the text is debounced, a new search
7
+ * starts over from page 1, "load more" appends the next page to the ones already shown, and a response for an
8
+ * earlier search or page is dropped (latest wins). Nothing is fetched until `enabled` (the picker is open).
9
+ *
10
+ * It calls no operation itself: `fetchPage` does, so the caller's component declares the operation.
11
+ */
12
+ import { useEffect, useState } from "react";
13
+ import { useDebounced } from "../lib/use-debounced";
14
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
15
+
16
+ export interface PickerPage<T> {
17
+ rows: T[];
18
+ hasNext: boolean;
19
+ }
20
+
21
+ export interface UsePickerPages<T> {
22
+ search: string;
23
+ setSearch: (value: string) => void;
24
+ /** The debounced, trimmed search text the loaded rows belong to. */
25
+ q: string;
26
+ rows: T[];
27
+ hasMore: boolean;
28
+ /** The first page of this search is loading and nothing is shown yet. */
29
+ loading: boolean;
30
+ /** A following page is loading. */
31
+ loadingMore: boolean;
32
+ error: unknown;
33
+ refetch: () => void;
34
+ loadMore: () => void;
35
+ }
36
+
37
+ interface Loaded<T> extends PickerPage<T> { q: string; page: number }
38
+
39
+ export function usePickerPages<T>(
40
+ enabled: boolean,
41
+ fetchPage: (q: string, page: number, signal: AbortSignal) => Promise<PickerPage<T>>,
42
+ ): UsePickerPages<T> {
43
+ const [search, setSearchText] = useState("");
44
+ const [page, setPage] = useState({ q: "", n: 1 });
45
+ const [store, setStore] = useState<{ q: string | null; pages: T[][]; hasNext: boolean }>({ q: null, pages: [], hasNext: false });
46
+ const q = useDebounced(search.trim());
47
+ const n = page.q === q ? page.n : 1;
48
+ const query = useG8Query(
49
+ async (signal): Promise<Loaded<T>> => ({ ...(await fetchPage(q, n, signal)), q, page: n }),
50
+ [q, n],
51
+ { enabled },
52
+ );
53
+ useEffect(() => {
54
+ const d = query.data;
55
+ if (!d || d.q !== q || d.page !== n) return; // a response for an earlier search or page
56
+ setStore((s) => ({ q, pages: [...(s.q === q ? s.pages.slice(0, n - 1) : []), d.rows], hasNext: d.hasNext }));
57
+ }, [query.data, q, n]);
58
+
59
+ const current = store.q === q;
60
+ const rows = current ? store.pages.flat() : [];
61
+ return {
62
+ search,
63
+ setSearch: (value) => { setSearchText(value); setPage({ q: value.trim(), n: 1 }); },
64
+ q,
65
+ rows,
66
+ hasMore: current && store.hasNext,
67
+ loading: enabled && (query.loading || !current) && rows.length === 0,
68
+ loadingMore: query.loading && rows.length > 0,
69
+ error: query.error,
70
+ refetch: () => { void query.refetch(); },
71
+ loadMore: () => setPage({ q, n: n + 1 }),
72
+ };
73
+ }
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ /** `value`, but only after it has stopped changing for `ms`. The search box of every picker reads through this. */
6
+ export function useDebounced<T>(value: T, ms = 250): T {
7
+ const [v, setV] = useState(value);
8
+ useEffect(() => {
9
+ const t = setTimeout(() => setV(value), ms);
10
+ return () => clearTimeout(t);
11
+ }, [value, ms]);
12
+ return v;
13
+ }
@@ -0,0 +1,82 @@
1
+ # programmatic-pages
2
+
3
+ One **template landing page** plus one **data source** makes **one page per row**: a page per account, per city, per integration. A campaign holds the template, the source (a contact list, or a Workbench, CSV or sitemap-import table), an address pattern such as `{{company}}-{{first_name}}`, and per-variable rules. Rendering is free (no model runs). Putting pages on the live site is a separate, confirmed step.
4
+
5
+ ## Screens
6
+
7
+ `ProgrammaticPagesScreen` (`components/programmatic-pages-screen.tsx`) is the finished page.
8
+
9
+ ```tsx
10
+ import { ProgrammaticPagesScreen } from "@/g8/programmatic-pages/components/programmatic-pages-screen";
11
+ export default function Page() { return <ProgrammaticPagesScreen />; }
12
+ ```
13
+
14
+ Open a campaign (`?id=`) for Overview, Pages, Preview, Variables, Source, Settings, Edit and Details (`?tab=`); `&view=full` shows it as a full page. The Overview links to the template page in the `pages` screen (`/pages?id=`; override with `pagesHref`).
15
+
16
+ Flows: see every campaign with state, source and page counts (search; filter by state and source; the list is complete, the API does not page it); create one from a template page and a list or table; render all pages and follow the run; read, search and filter the generated pages; mark pages ready, regenerate them, replace one page's HTML and undo it; take pages off the live site, delete them, export their addresses, send live ones to Search Console (each confirmed); publish waiting pages, republish everything or roll the last publish back (each confirmed with its page count); preview one row; set a fallback, character limit and cut rule per variable; switch the source or import a sitemap as the source; set regeneration, publishing, search and link settings; edit the name and address pattern; delete a campaign (confirmed). Optional props: `pagesHref`, `renderRowActions`, `renderDetailActions`, `extraDetailTabs`, `toolbarActions`.
17
+
18
+ Parts to mount alone: `ProgrammaticCampaignForm`, `ProgrammaticOverview`, `ProgrammaticGenerate`, `ProgrammaticPublish`, `ProgrammaticGeneratedPages`, `ProgrammaticPreview`, `ProgrammaticVariables`, `ProgrammaticSource`, `ProgrammaticSettings`.
19
+
20
+ ## Install
21
+
22
+ ```
23
+ npx @graph8/sdk add programmatic-pages
24
+ ```
25
+
26
+ Installs `foundation`, `crm` and `pages` first if missing. Everything lands under `g8/programmatic-pages/`.
27
+
28
+ ## Prerequisites (the human must do these)
29
+
30
+ | Where | What | How to verify |
31
+ |---|---|---|
32
+ | graph8 | A **template landing page** with HTML and `{{variables}}` (make it in the pages block). It cannot be changed after the campaign exists. | `GET /api/v1/landing-pages` lists it. |
33
+ | graph8 | A **source** with a column for each variable (a contact list or a Workbench table). | `GET /api/v1/programmatic-campaigns/{id}/variables` shows every variable as found. |
34
+ | graph8 | **Publishing** needs a connected Cloudflare website or hosted publishing. Rendering does not. | A person publishes one test campaign. |
35
+ | graph8 key | `campaigns:read`, `campaigns:write`, `campaigns:delete`; `campaigns:run` for page actions and publish (separate from `campaigns:write`); `lists:read`. | `g8.api.me.describeCurrentKey()` |
36
+ | Your app | `allowedPrefixes` must include `programmatic-campaigns`, `landing-pages`, `workbench`, `lists`. | The route file lists them. |
37
+
38
+ ## The flow, in order (ids passed between steps)
39
+
40
+ 1. **Create**: `createProgrammaticCampaign({ name, template_page_id, data_source_type, list_id | data_source_id, slug_template? })` -> **`campaign.id`** (a UUID). `template_page_id` comes from `listLandingPages`, `list_id` from `listLists`, a table id from `listWorkbenchTables`.
41
+ 2. **Check**: `getProgrammaticCampaignVariables` shows each `{{variable}}`, whether the source has a column for it, and the rule stored for it. `updateProgrammaticWorkspace({ variable_config })` REPLACES the whole array: send the existing entries back with your change.
42
+ 3. **Preview**: `previewProgrammaticPage({ contact_id? })` renders one row and saves nothing.
43
+ 4. **Render**: `startProgrammaticGeneration` (202) then poll `getProgrammaticGenerationStatus` until `completed`, `failed` or `paused`. Free.
44
+ 5. **Review**: `listProgrammaticGeneratedPages` (50 per read, up to 500) and `getProgrammaticWorkspace` (counts, search health, deployment).
45
+ 6. **Page actions**: `runProgrammaticPageAction` (`mark_ready_to_deploy`, `unpublish`, `mark_for_regeneration`, `override`, `reset_override`) and `runProgrammaticBulkPageAction` on a selection.
46
+ 7. **Publish**: `runProgrammaticBulkPageAction({ action: "deploy_pending_changes" | "republish_all" | "rollback_publish" })`, confirmed with the page count.
47
+ 8. **Source**: `updateProgrammaticWorkspace` switches list or table; `importProgrammaticSitemap` REPLACES the source with a table of the sitemap's addresses.
48
+ 9. **Remove**: `deleteProgrammaticCampaign` removes the campaign and its pages (confirmed); live copies are not taken down.
49
+
50
+ ## Default decisions (do not ask the human unless marked)
51
+
52
+ | Decision | Default | Ask? |
53
+ |---|---|---|
54
+ | Data source | None: a person picks the list or table | **yes** |
55
+ | Address pattern | Left blank: the API's own `{{company}}-{{first_name}}` | no |
56
+ | Rendering | Free; makes drafts. Confirmed first when the approval mode is "Publish automatically" | no |
57
+ | Publishing | Never without a person confirming the page count | **yes** |
58
+ | Stopping | Unpublish first; a delete does not take live pages down | no |
59
+
60
+ ## Operation coverage (15 programmaticPages operations, all used)
61
+
62
+ `listProgrammaticCampaigns`, `createProgrammaticCampaign`, `getProgrammaticCampaign`, `updateProgrammaticCampaign`, `deleteProgrammaticCampaign`, `getProgrammaticCampaignVariables`, `previewProgrammaticPage`, `startProgrammaticGeneration`, `getProgrammaticGenerationStatus`, `listProgrammaticGeneratedPages`, `getProgrammaticWorkspace`, `updateProgrammaticWorkspace`, `importProgrammaticSitemap`, `runProgrammaticPageAction`, `runProgrammaticBulkPageAction`. Tiers: `deleteProgrammaticCampaign` is destructive; both page-action operations are external (they can change the live site); none is billable. The block also reads `listLandingPages`, `getLandingPage`, `listWorkbenchTables` and (through the crm `ListSelect`) `listLists`.
63
+
64
+ ## API gaps (found while building; not edited, the backend is colleague-owned)
65
+
66
+ | Asked for | Reality | What the block does |
67
+ |---|---|---|
68
+ | "Generated as drafts; nothing goes live until a deploy" | With approval mode `auto_publish` the render task marks every page `published` with a public address as it renders. | Confirms the render (external, sized by the source rows) in that mode and says so. |
69
+ | Re-render replaces pages | The render does not clear earlier pages and `cb_programmatic_pages` has no unique slug, so a second run can add a second set. | A visible warning before "Generate again"; delete duplicates with the bulk delete. |
70
+ | Read a page's HTML | No endpoint returns a generated page's HTML. | "Replace HTML" asks for the full replacement. |
71
+ | Filter or search generated pages | `listProgrammaticGeneratedPages` takes `offset` and `limit` only. | Search and the status filter act on the loaded pages and say so. |
72
+ | Map a variable to a differently named column | The workspace shows `source_column`, but the renderer matches `{{name}}` to a column of the same name only. | Only the rules the renderer reads are offered: fallback, character limit, cut rule. |
73
+ | Edit a running or finished campaign | `updateProgrammaticCampaign` answers 400 unless the status is draft or failed. | The Edit tab says so; source, variables and settings go through the workspace, which has no such rule. |
74
+ | Change the template | `updateProgrammaticCampaign` has no template field. | Shown read-only; make a new campaign. |
75
+ | Rename the source list | The list source carries no name. | Shown as "Current list" until the options load. |
76
+ | `deploy_pending_changes` count | The workspace counts pages in status `generated`; the API also redeploys live pages whose data changed. | The confirm shows the waiting count and the changed-row count. |
77
+ | Responses | Every programmatic response is an open object (`StudioContentPayload`); the list key is `campaigns` / `pages`, mirrored onto `items`. | Read defensively; a missing value shows a dash. |
78
+
79
+ ## Things that surprise people
80
+
81
+ - Page action `publish` and `regenerate` are aliases (`mark_ready_to_deploy`, `mark_for_regeneration`); marking ready does not put a page on the live site, only a publish does.
82
+ - Preview needs a numeric row id; workbench rows without one can only be previewed as "First row".
@@ -0,0 +1,292 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "programmatic-pages",
4
+ "title": "Programmatic pages",
5
+ "summary": "One template landing page plus a list or table makes a page per row: create campaigns, render, preview, tune variables and source, publish, unpublish and roll back with confirmations.",
6
+ "features": [
7
+ "campaign list with search, state and source filters and stats",
8
+ "create from a template page and a list or table; edit while draft or failed; delete with confirmation",
9
+ "render every page (free), follow the run, preview one row in a sandboxed frame",
10
+ "per-variable fallback, character limit and cut rule; merged into the stored config",
11
+ "switch the source or import a sitemap as the source",
12
+ "generated pages with search, filters, row and bulk actions, HTML replacement, address export",
13
+ "publish waiting pages, republish everything, roll back the last publish, send live pages to Search Console; each confirmed with a real page count",
14
+ "regeneration, publishing, search and link settings"
15
+ ],
16
+ "dependsOn": [
17
+ "crm",
18
+ "pages"
19
+ ],
20
+ "operations": [
21
+ "create_programmatic_campaign_programmatic_campaigns_post",
22
+ "delete_programmatic_campaign_programmatic_campaigns__campaign_id__delete",
23
+ "get_landing_page_landing_pages__landing_page_id__get",
24
+ "get_programmatic_campaign_programmatic_campaigns__campaign_id__get",
25
+ "get_programmatic_campaign_variables_programmatic_campaigns__campaign_id__variables_get",
26
+ "get_programmatic_generation_status_programmatic_campaigns__campaign_id__status_get",
27
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get",
28
+ "import_programmatic_sitemap_programmatic_campaigns__campaign_id__imports_sitemap_post",
29
+ "list_landing_pages_landing_pages_get",
30
+ "list_programmatic_campaigns_programmatic_campaigns_get",
31
+ "list_programmatic_generated_pages_programmatic_campaigns__campaign_id__pages_get",
32
+ "list_workbench_tables_workbench_tables_get",
33
+ "preview_programmatic_page_programmatic_campaigns__campaign_id__preview_post",
34
+ "run_programmatic_bulk_page_action_programmatic_campaigns__campaign_id__pages_actions_post",
35
+ "run_programmatic_page_action_programmatic_campaigns__campaign_id__pages__page_id__actions_post",
36
+ "start_programmatic_generation_programmatic_campaigns__campaign_id__generate_post",
37
+ "update_programmatic_campaign_programmatic_campaigns__campaign_id__patch",
38
+ "update_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_patch"
39
+ ],
40
+ "components": [
41
+ {
42
+ "name": "ProgrammaticPagesScreen",
43
+ "file": "components/programmatic-pages-screen.tsx",
44
+ "purpose": "Finished programmatic pages workspace: campaign list with search, filters and stats, create, delete (confirmed), and per-campaign overview, pages, preview, variables, source, settings, edit and details; deep link ?id= and ?tab=.",
45
+ "operations": [
46
+ "delete_programmatic_campaign_programmatic_campaigns__campaign_id__delete",
47
+ "list_programmatic_campaigns_programmatic_campaigns_get"
48
+ ],
49
+ "tiers": [
50
+ "read",
51
+ "destructive"
52
+ ]
53
+ },
54
+ {
55
+ "name": "ProgrammaticCampaignForm",
56
+ "file": "components/programmatic-campaign-form.tsx",
57
+ "purpose": "Create a campaign (template page, address pattern, list or table source) or edit one; edit reads the full record first and sends only changed fields, and only while the campaign is a draft or failed.",
58
+ "operations": [
59
+ "create_programmatic_campaign_programmatic_campaigns_post",
60
+ "get_programmatic_campaign_programmatic_campaigns__campaign_id__get",
61
+ "list_landing_pages_landing_pages_get",
62
+ "update_programmatic_campaign_programmatic_campaigns__campaign_id__patch"
63
+ ],
64
+ "tiers": [
65
+ "read",
66
+ "write"
67
+ ]
68
+ },
69
+ {
70
+ "name": "ProgrammaticSourceFields",
71
+ "file": "components/programmatic-source-fields.tsx",
72
+ "purpose": "Choose a campaign source: a contact list, or a Workbench, CSV or sitemap-import table, with no typed ids.",
73
+ "operations": [
74
+ "list_workbench_tables_workbench_tables_get"
75
+ ],
76
+ "tiers": [
77
+ "read"
78
+ ]
79
+ },
80
+ {
81
+ "name": "ProgrammaticOverview",
82
+ "file": "components/programmatic-overview.tsx",
83
+ "purpose": "Readiness checks, counts, search health, template link and publish state of one campaign; hosts the generate and publish panels.",
84
+ "operations": [
85
+ "get_landing_page_landing_pages__landing_page_id__get",
86
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get"
87
+ ],
88
+ "tiers": [
89
+ "read"
90
+ ]
91
+ },
92
+ {
93
+ "name": "ProgrammaticGenerate",
94
+ "file": "components/programmatic-generate.tsx",
95
+ "purpose": "Render every page (free) and follow the run; confirmed when the approval mode publishes automatically, sized by the source row count.",
96
+ "operations": [
97
+ "get_programmatic_generation_status_programmatic_campaigns__campaign_id__status_get",
98
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get",
99
+ "start_programmatic_generation_programmatic_campaigns__campaign_id__generate_post"
100
+ ],
101
+ "tiers": [
102
+ "read",
103
+ "write"
104
+ ]
105
+ },
106
+ {
107
+ "name": "ProgrammaticPublish",
108
+ "file": "components/programmatic-publish.tsx",
109
+ "purpose": "Publish waiting pages, republish everything or roll the last publish back, each confirmed with the number of pages it touches.",
110
+ "operations": [
111
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get",
112
+ "run_programmatic_bulk_page_action_programmatic_campaigns__campaign_id__pages_actions_post"
113
+ ],
114
+ "tiers": [
115
+ "read",
116
+ "external"
117
+ ]
118
+ },
119
+ {
120
+ "name": "ProgrammaticGeneratedPages",
121
+ "file": "components/programmatic-generated-pages.tsx",
122
+ "purpose": "Generated pages with search, status filter, selection, row and bulk lifecycle actions (unpublish, delete, Search Console confirmed), HTML replacement and address export.",
123
+ "operations": [
124
+ "list_programmatic_generated_pages_programmatic_campaigns__campaign_id__pages_get",
125
+ "run_programmatic_bulk_page_action_programmatic_campaigns__campaign_id__pages_actions_post",
126
+ "run_programmatic_page_action_programmatic_campaigns__campaign_id__pages__page_id__actions_post"
127
+ ],
128
+ "tiers": [
129
+ "read",
130
+ "external"
131
+ ]
132
+ },
133
+ {
134
+ "name": "ProgrammaticPreview",
135
+ "file": "components/programmatic-preview.tsx",
136
+ "purpose": "Render one page for one source row in a sandboxed frame without saving it.",
137
+ "operations": [
138
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get",
139
+ "preview_programmatic_page_programmatic_campaigns__campaign_id__preview_post"
140
+ ],
141
+ "tiers": [
142
+ "read",
143
+ "write"
144
+ ]
145
+ },
146
+ {
147
+ "name": "ProgrammaticVariables",
148
+ "file": "components/programmatic-variables.tsx",
149
+ "purpose": "Template variables with source matches and per-variable fallback, character limit and cut rule; merges edits into the stored variable config.",
150
+ "operations": [
151
+ "get_programmatic_campaign_programmatic_campaigns__campaign_id__get",
152
+ "get_programmatic_campaign_variables_programmatic_campaigns__campaign_id__variables_get",
153
+ "update_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_patch"
154
+ ],
155
+ "tiers": [
156
+ "read",
157
+ "write"
158
+ ]
159
+ },
160
+ {
161
+ "name": "ProgrammaticSource",
162
+ "file": "components/programmatic-source.tsx",
163
+ "purpose": "Source details with columns and first rows, switching to another list or table, and importing a sitemap as the source.",
164
+ "operations": [
165
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get",
166
+ "import_programmatic_sitemap_programmatic_campaigns__campaign_id__imports_sitemap_post",
167
+ "list_workbench_tables_workbench_tables_get",
168
+ "update_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_patch"
169
+ ],
170
+ "tiers": [
171
+ "read",
172
+ "write"
173
+ ]
174
+ },
175
+ {
176
+ "name": "ProgrammaticSettings",
177
+ "file": "components/programmatic-settings.tsx",
178
+ "purpose": "Regeneration, publishing, search and link settings; sends only changed keys; confirms turning on automatic publishing.",
179
+ "operations": [
180
+ "get_programmatic_campaign_programmatic_campaigns__campaign_id__get",
181
+ "update_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_patch"
182
+ ],
183
+ "tiers": [
184
+ "read",
185
+ "write"
186
+ ]
187
+ }
188
+ ],
189
+ "hooks": [
190
+ {
191
+ "name": "useProgrammaticWorkspace",
192
+ "file": "hooks/use-programmatic-workspace.ts",
193
+ "purpose": "The workspace of one campaign through a shared cache entry, so a save on one tab refreshes the others.",
194
+ "operations": [
195
+ "get_programmatic_workspace_programmatic_campaigns__campaign_id__workspace_get"
196
+ ]
197
+ }
198
+ ],
199
+ "lib": [
200
+ {
201
+ "file": "lib/programmatic-types.ts",
202
+ "purpose": "Campaign, page and workspace types; the API's exact vocabularies; changed-fields diff, bulk target selection, variable-config merge and settings patch helpers."
203
+ }
204
+ ],
205
+ "recipes": [
206
+ {
207
+ "file": "recipes/create-a-programmatic-campaign.ts",
208
+ "title": "Create a campaign and preview one page",
209
+ "when": "Setting up a page per row from a template page and a list; nothing is rendered or published."
210
+ },
211
+ {
212
+ "file": "recipes/render-and-follow.ts",
213
+ "title": "Render every page and follow the run",
214
+ "when": "After the campaign exists; free, but a campaign set to publish automatically needs a person's confirmation first."
215
+ },
216
+ {
217
+ "file": "recipes/review-then-publish.ts",
218
+ "title": "Read the publish numbers, then publish after a person agrees",
219
+ "when": "Putting rendered pages on the live site, which changes what visitors see."
220
+ }
221
+ ],
222
+ "prerequisites": [
223
+ {
224
+ "id": "template-page",
225
+ "description": "A landing page with HTML and {{variables}} where each row's values go (create it with the pages block). The campaign's template cannot be changed later.",
226
+ "where": "api",
227
+ "verify": "GET /api/v1/landing-pages lists the page."
228
+ },
229
+ {
230
+ "id": "source",
231
+ "description": "A contact list or a Workbench table with a column for each {{variable}}. A variable with no matching column renders empty unless it has a fallback.",
232
+ "where": "api",
233
+ "verify": "GET /api/v1/programmatic-campaigns/{id}/variables shows every variable as found."
234
+ },
235
+ {
236
+ "id": "publishing",
237
+ "description": "Publishing needs a connected Cloudflare website or hosted publishing on the workspace; otherwise the publish call answers 500 'not configured'. Rendering needs neither.",
238
+ "where": "api",
239
+ "verify": "A publish of one test campaign succeeds, confirmed by a person."
240
+ },
241
+ {
242
+ "id": "campaign-scopes",
243
+ "description": "The key needs campaigns:read, campaigns:write, campaigns:delete and, for the page actions and publish, campaigns:run (separate from campaigns:write). Picking a list or table needs lists:read.",
244
+ "where": "api",
245
+ "verify": "g8.api.me.describeCurrentKey() lists the scopes."
246
+ },
247
+ {
248
+ "id": "proxy-allowlist",
249
+ "description": "Add \"programmatic-campaigns\", \"landing-pages\", \"workbench\" and \"lists\" to allowedPrefixes in app/api/g8/[...path]/route.ts.",
250
+ "where": "app",
251
+ "verify": "The route file's allowedPrefixes contains them."
252
+ }
253
+ ],
254
+ "scopes": [
255
+ "campaigns:delete",
256
+ "campaigns:read",
257
+ "campaigns:run",
258
+ "campaigns:write",
259
+ "lists:read"
260
+ ],
261
+ "defaults": [
262
+ {
263
+ "decision": "Data source",
264
+ "default": "A person picks the list or table; never guessed",
265
+ "askHuman": true
266
+ },
267
+ {
268
+ "decision": "Address pattern",
269
+ "default": "Left blank: the API's own {{company}}-{{first_name}}",
270
+ "askHuman": false
271
+ },
272
+ {
273
+ "decision": "Rendering",
274
+ "default": "Free; creates drafts. Never run on a campaign set to publish automatically without a person's confirmation",
275
+ "askHuman": false
276
+ },
277
+ {
278
+ "decision": "Publishing",
279
+ "default": "Never without a person confirming the number of pages",
280
+ "askHuman": true
281
+ },
282
+ {
283
+ "decision": "Stopping",
284
+ "default": "Unpublish the pages first; deleting a campaign does not take live pages down",
285
+ "askHuman": false
286
+ }
287
+ ],
288
+ "peerDependencies": {
289
+ "react": ">=18",
290
+ "lucide-react": ">=0.400.0"
291
+ }
292
+ }
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ProgrammaticCampaignForm: create a programmatic campaign (a template landing page plus a data source: one page per row) or
5
+ * edit one.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. listLandingPages (GET /landing-pages) read the template choices (100 per read, offset paged)
9
+ * 2. the source choice: ProgrammaticSourceFields (lists, or listWorkbenchTables for a table source)
10
+ * 3. getProgrammaticCampaign (GET /programmatic-campaigns/{id}) read edit only: the FULL record is read before the form shows
11
+ * 4. createProgrammaticCampaign (POST /programmatic-campaigns) write nothing is rendered yet; free
12
+ * 5. updateProgrammaticCampaign (PATCH /programmatic-campaigns/{id}) write only the fields that changed
13
+ * The template page is fixed once a campaign exists (the update takes none). The API only accepts an edit while the campaign is
14
+ * a draft or failed, so the form says so up front instead of failing on save. A blank URL pattern is left out and the API's own
15
+ * default is used. Nothing here renders or publishes a page.
16
+ */
17
+
18
+ import { useState } from "react";
19
+ import { Button, Input, Label, Skeleton } from "@graph8/sdk/ui";
20
+ import { ApiError } from "@/g8/components/api-error";
21
+ import { FirstNotice } from "@/g8/components/paging-notices";
22
+ import { useAllPages } from "@/g8/hooks/use-all-pages";
23
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
24
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
25
+ import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
26
+ import { asRec, num, rowsOf } from "@/g8/pages/lib/studio";
27
+ import type { LandingPageRow, Rec } from "@/g8/pages/lib/studio";
28
+ import { ProgrammaticSourceFields } from "./programmatic-source-fields";
29
+ import type { SourceValue } from "./programmatic-source-fields";
30
+ import { SOURCE_TYPES, campaignDiff, canEditCampaign, slugVariables, sourceComplete, sourceOf, wording } from "../lib/programmatic-types";
31
+ import type { Campaign, CampaignDraft, SourceType } from "../lib/programmatic-types";
32
+
33
+ export interface ProgrammaticCampaignFormProps {
34
+ /** Edit this campaign; omit to create a new one. */
35
+ campaign?: Campaign;
36
+ /** Called with the saved campaign. */
37
+ onSaved: (campaign: Campaign) => void;
38
+ onCancel?: () => void;
39
+ }
40
+
41
+ const SELECT = "border-input bg-background h-9 w-full rounded-md border px-2 text-sm disabled:opacity-50";
42
+ const SLUG_DEFAULT = "{{company}}-{{first_name}}";
43
+ const PAGE_READ = 100;
44
+ const NAME_MAX = 200;
45
+
46
+ /** Edit mode reads the full campaign first: a list row may omit fields, and saving a form seeded from it would blank them. */
47
+ export function ProgrammaticCampaignForm({ campaign, onSaved, onCancel }: ProgrammaticCampaignFormProps) {
48
+ const full = useG8Query(
49
+ (signal) => g8Get<Campaign>(`/programmatic-campaigns/${encodeURIComponent(campaign!.id)}`, {}, { signal }).then((r) => r.data),
50
+ [campaign?.id],
51
+ { enabled: Boolean(campaign), key: ["programmatic", "detail", campaign?.id ?? ""], placeholderData: campaign },
52
+ );
53
+ if (!campaign) return <CampaignFields onSaved={onSaved} onCancel={onCancel} />;
54
+ if (full.error && (!full.data || full.isPlaceholderData)) return <ApiError error={full.error} title="Could not load this campaign" onRetry={() => void full.refetch()} />;
55
+ if (!full.data || full.isPlaceholderData || full.data.id !== campaign.id) return <Skeleton className="h-40 w-full" aria-label="Loading campaign" />;
56
+ return <CampaignFields key={full.data.id} campaign={full.data} onSaved={onSaved} onCancel={onCancel} />;
57
+ }
58
+
59
+ function CampaignFields({ campaign, onSaved, onCancel }: ProgrammaticCampaignFormProps) {
60
+ const editing = Boolean(campaign);
61
+ const was = campaign ? sourceOf(campaign) : { type: "", id: "", name: "" };
62
+ const locked = campaign ? !canEditCampaign(campaign) : false;
63
+ const [name, setName] = useState(campaign?.name ?? "");
64
+ const [templateId, setTemplateId] = useState(campaign?.template_page_id ?? "");
65
+ const [slug, setSlug] = useState(campaign?.slug_template ?? "");
66
+ const [src, setSrc] = useState<SourceValue>({
67
+ type: (SOURCE_TYPES.find((t) => t.value === was.type)?.value ?? "list") as SourceType,
68
+ listId: was.type === "list" && was.id ? Number(was.id) : null,
69
+ tableId: was.type !== "list" ? was.id : "",
70
+ tableName: was.name,
71
+ });
72
+ const { type: sourceType, listId, tableId, tableName } = src;
73
+ const draft: CampaignDraft = { name, slug_template: slug, source_type: sourceType, list_id: listId, table_id: tableId, table_name: tableName };
74
+
75
+ const templates = useAllPages<LandingPageRow>(async (page, signal) => {
76
+ const offset = (page - 1) * PAGE_READ;
77
+ const data = asRec((await g8Get("/landing-pages", { limit: PAGE_READ, offset: offset || undefined }, { signal })).data);
78
+ const rows = rowsOf<LandingPageRow>(data, "landing_pages");
79
+ const total = num(data.total);
80
+ return { rows, hasNext: total === undefined ? undefined : offset + rows.length < total };
81
+ }, [], { enabled: !editing, pageSize: PAGE_READ });
82
+ const valid = name.trim() !== "" && name.trim().length <= NAME_MAX && sourceComplete(draft) && (editing || templateId !== "");
83
+ const diff = campaign ? campaignDiff(campaign, draft) : {};
84
+ const save = useG8Mutation(async (): Promise<Campaign> => {
85
+ if (campaign) {
86
+ if (Object.keys(diff).length === 0) return campaign;
87
+ return (await g8Patch<Campaign>(`/programmatic-campaigns/${encodeURIComponent(campaign.id)}`, diff)).data;
88
+ }
89
+ const body: Rec = { name: name.trim(), template_page_id: templateId, data_source_type: sourceType };
90
+ if (sourceType === "list") body.list_id = listId;
91
+ else {
92
+ body.data_source_id = tableId;
93
+ if (tableName) body.data_source_name = tableName;
94
+ }
95
+ if (slug.trim()) body.slug_template = slug.trim();
96
+ return (await g8Post<Campaign>("/programmatic-campaigns", body)).data;
97
+ }, { onSuccess: onSaved, invalidates: (saved) => [["programmatic", "list"], ["programmatic", "detail", saved.id], ["programmatic", "workspace", saved.id]] });
98
+
99
+ const names = slugVariables(slug || SLUG_DEFAULT);
100
+ return (
101
+ <form className="grid gap-3" onSubmit={(e) => { e.preventDefault(); if (valid && !locked) void save.mutate(undefined); }}>
102
+ {locked ? <p role="status" className="text-muted-foreground bg-muted rounded-md p-2 text-sm">The API accepts edits while a campaign is a draft or has failed. This one is {wording(campaign?.status).toLowerCase()}: change its source, variables and settings from the other tabs.</p> : null}
103
+ <div>
104
+ <Label htmlFor="g8-pp-name" className="mb-1">Name</Label>
105
+ <Input id="g8-pp-name" value={name} maxLength={NAME_MAX} disabled={locked} onChange={(e) => setName(e.target.value)} placeholder="Integration pages" />
106
+ </div>
107
+ <div>
108
+ <Label htmlFor="g8-pp-template" className="mb-1">Template page</Label>
109
+ {editing ? (
110
+ <p className="text-muted-foreground text-sm">The template is fixed once a campaign exists. To use another template, make a new campaign.</p>
111
+ ) : (
112
+ <>
113
+ <select id="g8-pp-template" className={SELECT} value={templateId} onChange={(e) => setTemplateId(e.target.value)} disabled={templates.loading && templates.items.length === 0}>
114
+ <option value="">{templates.loading && templates.items.length === 0 ? "Loading pages" : "Choose a landing page"}</option>
115
+ {templates.items.map((p) => <option key={p.id} value={p.id}>{p.name} ({wording(p.status).toLowerCase()})</option>)}
116
+ </select>
117
+ {templates.error ? <ApiError error={templates.error} title="Could not load landing pages" onRetry={templates.refetch} /> : null}
118
+ <FirstNotice shown={templates.items.length} limit={PAGE_READ} noun="landing pages" force={templates.truncated} />
119
+ <p className="text-muted-foreground mt-1 text-xs">A landing page with HTML. Put {"{{variable}}"} where each row&apos;s value goes.</p>
120
+ </>
121
+ )}
122
+ </div>
123
+ <div>
124
+ <Label htmlFor="g8-pp-slug" className="mb-1">Page address pattern</Label>
125
+ <Input id="g8-pp-slug" value={slug} disabled={locked} onChange={(e) => setSlug(e.target.value)} placeholder={SLUG_DEFAULT} />
126
+ <p className="text-muted-foreground mt-1 text-xs">Built from each row: {names.map((n) => `{{${n}}}`).join(", ") || "no variables"}. {slug.trim() ? "" : "Left blank, the default pattern shown is used."}</p>
127
+ </div>
128
+ <ProgrammaticSourceFields value={src} onChange={setSrc} disabled={locked} currentName={was.name} />
129
+ {save.error ? <ApiError error={save.error} title={editing ? "Could not save the campaign" : "Could not create the campaign"} /> : null}
130
+ <div className="flex gap-2">
131
+ <Button type="submit" loading={save.loading} disabled={!valid || locked || (editing && Object.keys(diff).length === 0)}>{editing ? "Save changes" : "Create campaign"}</Button>
132
+ {onCancel ? <Button type="button" variant="outline" onClick={onCancel}>Cancel</Button> : null}
133
+ </div>
134
+ </form>
135
+ );
136
+ }