@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,53 @@
1
+ "use client";
2
+
3
+ /**
4
+ * WorkbenchTableSettings: rename a workbench or change its description, in a side sheet. Sends only the fields that changed. The
5
+ * handler ignores a null field but applies an empty string, so a cleared description is sent as "" (a null would silently keep the old one).
6
+ *
7
+ * SDK operations:
8
+ * 1. updateWorkbenchTable PUT /workbench/tables/{table_id} { name?, description? } (write, undoable by editing again)
9
+ */
10
+
11
+ import { useEffect, useState } from "react";
12
+ import { Button, Input, Label, Textarea } from "@graph8/sdk/ui";
13
+ import { ApiError } from "@/g8/components/api-error";
14
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
15
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
16
+ import { g8Put } from "@/g8/lib/g8-client";
17
+ import type { WorkbenchTable } from "../lib/workbench-types";
18
+
19
+ export interface WorkbenchTableSettingsProps {
20
+ tableId: string;
21
+ /** The loaded table; the form is seeded from it only once it is loaded. */
22
+ table: Pick<WorkbenchTable, "name" | "description"> | null | undefined;
23
+ open: boolean;
24
+ onOpenChange: (open: boolean) => void;
25
+ /** Called after a save so the caller can re-read the table. */
26
+ onSaved?: () => void;
27
+ }
28
+
29
+ export function WorkbenchTableSettings({ tableId, table, open, onOpenChange, onSaved }: WorkbenchTableSettingsProps) {
30
+ const [name, setName] = useState("");
31
+ const [description, setDescription] = useState("");
32
+ useEffect(() => { if (open && table) { setName(table.name ?? ""); setDescription(table.description ?? ""); } }, [open, table?.name, table?.description]); // eslint-disable-line react-hooks/exhaustive-deps
33
+
34
+ const originalName = table?.name ?? "";
35
+ const originalDescription = table?.description ?? "";
36
+ const nameChanged = name.trim() !== originalName && name.trim().length > 0;
37
+ const descChanged = description.trim() !== originalDescription;
38
+ const save = useG8Mutation(async () => (await g8Put(`/workbench/tables/${tableId}`, { ...(nameChanged ? { name: name.trim() } : {}), ...(descChanged ? { description: description.trim() } : {}) })).data, {
39
+ onSuccess: () => { onOpenChange(false); onSaved?.(); },
40
+ });
41
+
42
+ return (
43
+ <FormSheet open={open} onOpenChange={onOpenChange} title="Workbench details" description="Rename it or change what it is for."
44
+ footer={(<><Button variant="ghost" onClick={() => onOpenChange(false)}>Cancel</Button><Button loading={save.loading} disabled={!table || name.trim().length === 0 || (!nameChanged && !descChanged)} onClick={() => void save.mutate(undefined)}>Save details</Button></>)}>
45
+ <div className="flex flex-col gap-3">
46
+ <div className="flex flex-col gap-1"><Label htmlFor="wb-set-name">Name</Label><Input id="wb-set-name" value={name} onChange={(e) => setName(e.target.value)} /></div>
47
+ <div className="flex flex-col gap-1"><Label htmlFor="wb-set-desc">Description</Label><Textarea id="wb-set-desc" rows={3} value={description} onChange={(e) => setDescription(e.target.value)} />
48
+ </div>
49
+ {save.error ? <ApiError error={save.error} title="Could not save the details" /> : null}
50
+ </div>
51
+ </FormSheet>
52
+ );
53
+ }
@@ -0,0 +1,178 @@
1
+ "use client";
2
+
3
+ /**
4
+ * WorkbenchTriggers: the automations that fire when something happens to a row of one workbench. A trigger is "when a row
5
+ * arrives (or an AI column finishes, or a status changes), optionally only if a column matches, do this": run the AI
6
+ * columns, recompute formulas, send the row to a list, or call a webhook. Lists the triggers with a switch each, creates
7
+ * one in a side sheet, and deletes with a confirm.
8
+ *
9
+ * SDK operations, in order:
10
+ * 1. listWorkbenchTriggers GET /workbench/tables/{table_id}/triggers (read)
11
+ * 2. createWorkbenchTrigger POST /workbench/tables/{table_id}/triggers (billable tier: ARMS spend when the action runs AI)
12
+ * 3. updateWorkbenchTrigger PUT /workbench/tables/{table_id}/triggers/{trigger_id} (billable tier: enabling an AI trigger arms spend)
13
+ * 4. deleteWorkbenchTrigger DELETE /workbench/tables/{table_id}/triggers/{trigger_id} (destructive, ConfirmSpendDialog; a 404 means already gone)
14
+ *
15
+ * Nothing charges on the call itself. An enabled "Run the AI columns" trigger enriches, and charges for, every matching row as
16
+ * it arrives. graph8 publishes no total up front for that, so the confirm says so and shows no number. Disabling and every
17
+ * other action need no confirm. The destination list of an "export" trigger comes from ListPicker (lists block).
18
+ */
19
+
20
+ import { useState } from "react";
21
+ import { Webhook, Zap } from "lucide-react";
22
+ import { Button, Input, Label, Skeleton, Switch } 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 { FormSheet } from "@/g8/components/screen/form-sheet";
27
+ import { SectionCard } from "@/g8/components/screen/section-card";
28
+ import { StatusPill } from "@/g8/components/screen/status-pill";
29
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
30
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
31
+ import { g8Delete, g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
32
+ import { ListPicker } from "@/g8/lists/components/list-picker";
33
+ import { isNotFound } from "../lib/api-status";
34
+ import {
35
+ CONDITION_OPERATORS, TRIGGER_ACTIONS, TRIGGER_EVENTS, describeRule, labelOf, rowsOf, triggerConfigProblem, triggerRules, triggerSpends,
36
+ } from "../lib/automation-types";
37
+ import type { WorkbenchTrigger } from "../lib/automation-types";
38
+ import type { WorkbenchColumn } from "../lib/workbench-types";
39
+
40
+ export interface WorkbenchTriggersProps {
41
+ tableId: string;
42
+ /** The table's columns, offered for the optional "only when" condition. */
43
+ columns?: WorkbenchColumn[];
44
+ /** Rows are people or companies; picks the lists an "export" trigger can send to. Default people. */
45
+ entityType?: "contact" | "company";
46
+ className?: string;
47
+ }
48
+
49
+ const selectClass = "h-9 w-full rounded-md border border-border bg-background px-2 text-sm";
50
+ const SPEND_NOTE = "Every matching row is enriched and charged at the AI column rate. No total is published up front, so none is shown: check the AI columns first.";
51
+
52
+ interface Draft {
53
+ name: string;
54
+ event_type: string;
55
+ action_type: string;
56
+ webhook_url: string;
57
+ list_id: number | null;
58
+ field: string;
59
+ operator: string;
60
+ value: string;
61
+ enabled: boolean;
62
+ }
63
+ const BLANK: Draft = { name: "", event_type: "ingest", action_type: "run_formula", webhook_url: "", list_id: null, field: "", operator: "eq", value: "", enabled: true };
64
+
65
+ function toBody(d: Draft) {
66
+ const cond = CONDITION_OPERATORS.find((o) => o.value === d.operator);
67
+ const rule = d.field ? (cond?.needsValue ? { field: d.field, operator: d.operator, value: d.value } : { field: d.field, operator: d.operator }) : null;
68
+ return {
69
+ name: d.name.trim(), event_type: d.event_type, action_type: d.action_type,
70
+ action_config: d.action_type === "webhook_notify" ? { webhook_url: d.webhook_url.trim() } : d.action_type === "export" ? { list_id: d.list_id } : {},
71
+ enabled: d.enabled, ...(rule ? { conditions: { rules: [rule] } } : {}),
72
+ };
73
+ }
74
+
75
+ export function WorkbenchTriggers({ tableId, columns = [], entityType = "contact", className }: WorkbenchTriggersProps) {
76
+ const path = `/workbench/tables/${tableId}/triggers`;
77
+ const list = useG8Query(async (signal) => rowsOf<WorkbenchTrigger>((await g8Get(path, undefined, { signal })).data), [tableId]);
78
+ const [open, setOpen] = useState(false);
79
+ const [draft, setDraft] = useState<Draft>(BLANK);
80
+ const [arming, setArming] = useState<{ kind: "create" } | { kind: "enable"; trigger: WorkbenchTrigger } | null>(null);
81
+ const [deleting, setDeleting] = useState<WorkbenchTrigger | null>(null);
82
+ const [busyId, setBusyId] = useState<number | null>(null);
83
+
84
+ const create = useG8Mutation(async (d: Draft) => (await g8Post(path, toBody(d))).data, {
85
+ onSuccess: () => { setOpen(false); setArming(null); setDraft(BLANK); void list.refetch(); },
86
+ onError: () => setArming(null),
87
+ });
88
+ const toggle = useG8Mutation(async (v: { trigger: WorkbenchTrigger; enabled: boolean }) => { setBusyId(v.trigger.id); return (await g8Put(`${path}/${v.trigger.id}`, { enabled: v.enabled })).data; }, {
89
+ onSuccess: () => { setBusyId(null); setArming(null); void list.refetch(); },
90
+ onError: () => { setBusyId(null); setArming(null); },
91
+ });
92
+ const remove = useG8Mutation(async (t: WorkbenchTrigger) => {
93
+ try { await g8Delete(`${path}/${t.id}`); } catch (e) { if (!isNotFound(e)) throw e; }
94
+ }, { onSuccess: () => { setDeleting(null); void list.refetch(); } });
95
+
96
+ const problem = triggerConfigProblem(draft.action_type, { webhook_url: draft.webhook_url, list_id: draft.list_id, new_list_name: "" });
97
+ const needsValue = CONDITION_OPERATORS.find((o) => o.value === draft.operator)?.needsValue !== false;
98
+ const valid = draft.name.trim().length > 0 && !problem && (!draft.field || !needsValue || draft.value.trim() !== "");
99
+ const submit = () => { if (valid && !create.loading) { if (triggerSpends(draft.action_type) && draft.enabled) setArming({ kind: "create" }); else void create.mutate(draft); } };
100
+ const items = list.data ?? [];
101
+
102
+ return (
103
+ <SectionCard
104
+ className={className}
105
+ title="Triggers"
106
+ icon={<Zap aria-hidden />}
107
+ description="Do something automatically when a row arrives, an AI column finishes, or a status changes."
108
+ actions={<Button size="sm" onClick={() => { setDraft(BLANK); create.reset(); setOpen(true); }}>New trigger</Button>}
109
+ >
110
+ {list.error ? <ApiError error={list.error} title="Could not load triggers" onRetry={() => void list.refetch()} /> : null}
111
+ {toggle.error ? <ApiError error={toggle.error} title="Could not change the trigger" /> : null}
112
+ {remove.error ? <ApiError error={remove.error} title="Could not delete the trigger" /> : null}
113
+ {list.loading && !list.data ? <Skeleton className="h-16 w-full" aria-label="Loading triggers" /> : null}
114
+ {!list.loading && !list.error && items.length === 0 ? (
115
+ <EmptyState compact icon={<Zap strokeWidth={1.5} />} title="No triggers yet" description="Send new rows to a list, call a webhook, or run your AI columns the moment a row arrives." />
116
+ ) : null}
117
+ <ul className="flex flex-col divide-y divide-border">
118
+ {items.map((t) => {
119
+ const rules = triggerRules(t);
120
+ return (
121
+ <li key={t.id} className="flex flex-wrap items-center gap-3 py-3">
122
+ <div className="min-w-0 flex-1">
123
+ <p className="flex items-center gap-2 text-sm font-medium">
124
+ {t.action_type === "webhook_notify" ? <Webhook className="size-4 text-text-color-muted" aria-hidden /> : <Zap className="size-4 text-text-color-muted" aria-hidden />}
125
+ <span className="truncate">{t.name}</span>
126
+ {triggerSpends(t.action_type) ? <StatusPill tone="warning">Uses credits</StatusPill> : null}
127
+ </p>
128
+ <p className="text-xs text-text-color-muted">{labelOf(TRIGGER_EVENTS, t.event_type)}, then {labelOf(TRIGGER_ACTIONS, t.action_type).toLowerCase()}</p>
129
+ {rules.length ? <p className="text-xs text-text-color-muted">Only when {rules.map((r, i) => <span key={i}>{i ? " and " : ""}{describeRule(r)}</span>)}</p> : null}
130
+ </div>
131
+ <Switch aria-label={`Enable trigger ${t.name}`} checked={t.enabled === true} disabled={busyId === t.id}
132
+ onCheckedChange={(on) => { if (on && triggerSpends(t.action_type)) setArming({ kind: "enable", trigger: t }); else void toggle.mutate({ trigger: t, enabled: on }); }} />
133
+ <Button size="sm" variant="ghost" aria-label={`Delete trigger ${t.name}`} onClick={() => setDeleting(t)}>Delete</Button>
134
+ </li>
135
+ );
136
+ })}
137
+ </ul>
138
+
139
+ <FormSheet open={open} onOpenChange={setOpen} title="New trigger" description="A trigger watches one workbench. It starts working as soon as it is saved and switched on."
140
+ footer={(<><Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button><Button loading={create.loading} disabled={!valid} onClick={submit}>Create trigger</Button></>)}>
141
+ <div className="flex flex-col gap-3">
142
+ <div className="flex flex-col gap-1"><Label htmlFor="wbt-name">Trigger name</Label><Input id="wbt-name" placeholder="Send new leads to the hot list" value={draft.name} onChange={(e) => setDraft({ ...draft, name: e.target.value })} /></div>
143
+ <div className="flex flex-col gap-1"><Label htmlFor="wbt-when">When</Label>
144
+ <select id="wbt-when" className={selectClass} value={draft.event_type} onChange={(e) => setDraft({ ...draft, event_type: e.target.value })}>{TRIGGER_EVENTS.map((x) => <option key={x.value} value={x.value}>{x.label}</option>)}</select></div>
145
+ <div className="flex flex-col gap-1"><Label htmlFor="wbt-then">Then</Label>
146
+ <select id="wbt-then" className={selectClass} value={draft.action_type} onChange={(e) => setDraft({ ...draft, action_type: e.target.value })}>{TRIGGER_ACTIONS.map((x) => <option key={x.value} value={x.value}>{x.label}</option>)}</select></div>
147
+ {draft.action_type === "webhook_notify" ? (
148
+ <div className="flex flex-col gap-1"><Label htmlFor="wbt-url">Webhook URL</Label><Input id="wbt-url" inputMode="url" placeholder="https://hooks.example.com/notify" value={draft.webhook_url} onChange={(e) => setDraft({ ...draft, webhook_url: e.target.value })} /></div>
149
+ ) : null}
150
+ {draft.action_type === "export" ? (
151
+ <div className="flex flex-col gap-1"><Label>Destination list</Label><ListPicker value={draft.list_id} onChange={(l) => setDraft({ ...draft, list_id: l.id })} type={entityType === "company" ? "companies" : "contacts"} allowCreate /></div>
152
+ ) : null}
153
+ {draft.name.trim() && problem ? <p role="status" className="text-xs text-text-color-muted">{problem}</p> : null}
154
+ <fieldset className="flex flex-col gap-2 rounded-md border border-border p-3">
155
+ <legend className="px-1 text-xs text-text-color-muted">Optional condition</legend>
156
+ <div className="flex flex-col gap-1"><Label htmlFor="wbt-field">Only when column</Label>
157
+ <select id="wbt-field" className={selectClass} value={draft.field} onChange={(e) => setDraft({ ...draft, field: e.target.value })}>
158
+ <option value="">Always</option>{columns.map((c) => <option key={c.id} value={c.name}>{c.display_name || c.name}</option>)}</select></div>
159
+ {draft.field ? (<>
160
+ <div className="flex flex-col gap-1"><Label htmlFor="wbt-op">Condition</Label>
161
+ <select id="wbt-op" className={selectClass} value={draft.operator} onChange={(e) => setDraft({ ...draft, operator: e.target.value })}>{CONDITION_OPERATORS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}</select></div>
162
+ {needsValue ? <div className="flex flex-col gap-1"><Label htmlFor="wbt-val">Condition value</Label><Input id="wbt-val" value={draft.value} onChange={(e) => setDraft({ ...draft, value: e.target.value })} /></div> : null}
163
+ </>) : null}
164
+ </fieldset>
165
+ <div className="flex items-center justify-between gap-3"><Label htmlFor="wbt-on">Switch it on when saved</Label><Switch id="wbt-on" checked={draft.enabled} onCheckedChange={(on) => setDraft({ ...draft, enabled: on })} /></div>
166
+ {create.error ? <ApiError error={create.error} title="Could not create the trigger" /> : null}
167
+ </div>
168
+ </FormSheet>
169
+
170
+ <ConfirmSpendDialog open={arming != null} tier="billable" title={arming?.kind === "enable" ? `Arm "${arming.trigger.name}"` : `Arm "${draft.name.trim()}"`}
171
+ description={SPEND_NOTE} confirmLabel="Arm trigger" confirming={create.loading || toggle.loading}
172
+ onConfirm={() => { if (arming?.kind === "enable") void toggle.mutate({ trigger: arming.trigger, enabled: true }); else if (arming) void create.mutate(draft); }}
173
+ onCancel={() => setArming(null)} />
174
+ <ConfirmSpendDialog open={deleting != null} tier="destructive" title={`Delete "${deleting?.name ?? ""}"`} description="The trigger stops firing. Rows it already handled are not changed."
175
+ confirmLabel="Delete trigger" confirming={remove.loading} onConfirm={() => deleting && void remove.mutate(deleting)} onCancel={() => setDeleting(null)} />
176
+ </SectionCard>
177
+ );
178
+ }
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
4
+ import { g8Get } from "@/g8/lib/g8-client";
5
+ import { rowsOf } from "../lib/automation-types";
6
+
7
+ export interface ConnectedCrm {
8
+ provider: string;
9
+ connection_id: string;
10
+ }
11
+
12
+ interface CrmRow { provider?: string; status?: string; connection_id?: string | null }
13
+
14
+ const LABELS: Record<string, string> = { hubspot: "HubSpot", salesforce: "Salesforce", pipedrive: "Pipedrive", zoho: "Zoho CRM", sugarcrm: "SugarCRM" };
15
+
16
+ /** A CRM's display name: "HubSpot" for hubspot, the key with spaces for one this block does not know. */
17
+ export function crmLabel(provider: string): string {
18
+ return LABELS[provider] ?? provider.replace(/[-_]/g, " ");
19
+ }
20
+
21
+ /**
22
+ * The CRMs this workspace has connected (`GET /crm-syncs`): only rows that carry a connection id are offered, because both the
23
+ * one-off import and the scheduled import need it. `data` is undefined until the read lands.
24
+ */
25
+ export function useConnectedCrms(options: { enabled?: boolean } = {}) {
26
+ const read = useG8Query(async (signal) => rowsOf<CrmRow>((await g8Get("/crm-syncs", undefined, { signal })).data), [], { enabled: options.enabled !== false });
27
+ const connected = read.data?.filter((s): s is ConnectedCrm => Boolean(s.provider && s.connection_id));
28
+ return { connected, loading: read.loading, error: read.error, refetch: read.refetch };
29
+ }
@@ -0,0 +1,8 @@
1
+ "use client";
2
+
3
+ import { G8Error } from "@graph8/sdk/errors";
4
+
5
+ /** The API answered 404: the thing is not there. For a read that means "none yet"; for a delete it means "already gone". */
6
+ export function isNotFound(error: unknown): boolean {
7
+ return error instanceof G8Error && error.status === 404;
8
+ }
@@ -0,0 +1,257 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Shapes and small helpers for the Workbench automation components (triggers, pipelines, schedule, auto-enrich,
5
+ * connections, imports, send to sequence).
6
+ *
7
+ * The dev API answers these with OPEN payloads, so everything here is optional and read defensively. The rules the
8
+ * helpers encode are copied from the handlers (enrichment/interfaces/fastapi/staging_*_dtos.py, domain/services/
9
+ * staging_trigger_contract.py), never guessed:
10
+ * - trigger event_type: ingest | ai_complete | status_change; action_type: run_ai | run_formula | export | webhook_notify
11
+ * - a webhook trigger needs an http(s) URL; an export trigger needs an existing list or a new list name
12
+ * - trigger conditions are `{ rules: [{ field, operator, value }] }`, operators eq | neq | gt | lt | exists | not_exists | contains
13
+ * (exists and not_exists take no value)
14
+ * - schedule action_type: run_ai | run_formula | crm_import; needs a cron expression or a frequency (hourly | daily | weekly)
15
+ * - pipeline start type: email_pdf | webhook | scraper_bot | ingest; step type: ai_group | waterfall | formula | branch | export_to_list
16
+ */
17
+ export type StatusTone = "neutral" | "info" | "positive" | "warning" | "negative";
18
+
19
+ export interface WorkbenchTrigger {
20
+ id: number;
21
+ name: string;
22
+ event_type?: string | null;
23
+ action_type?: string | null;
24
+ action_config?: Record<string, unknown> | null;
25
+ conditions?: { rules?: TriggerRule[] } | TriggerRule[] | null;
26
+ enabled?: boolean | null;
27
+ created_at?: string | null;
28
+ updated_at?: string | null;
29
+ }
30
+
31
+ export interface TriggerRule {
32
+ field?: string;
33
+ operator?: string;
34
+ value?: unknown;
35
+ logic?: string;
36
+ }
37
+
38
+ export const TRIGGER_EVENTS = [
39
+ { value: "ingest", label: "A row arrives" },
40
+ { value: "ai_complete", label: "An AI column finishes" },
41
+ { value: "status_change", label: "A row's status changes" },
42
+ ] as const;
43
+
44
+ export const TRIGGER_ACTIONS = [
45
+ { value: "run_ai", label: "Run the AI columns", spends: true },
46
+ { value: "run_formula", label: "Recompute formulas", spends: false },
47
+ { value: "export", label: "Send the row to a list", spends: false },
48
+ { value: "webhook_notify", label: "Call a webhook", spends: false },
49
+ ] as const;
50
+
51
+ export const CONDITION_OPERATORS = [
52
+ { value: "eq", label: "equals", needsValue: true },
53
+ { value: "neq", label: "does not equal", needsValue: true },
54
+ { value: "gt", label: "is greater than", needsValue: true },
55
+ { value: "lt", label: "is less than", needsValue: true },
56
+ { value: "contains", label: "contains", needsValue: true },
57
+ { value: "exists", label: "has a value", needsValue: false },
58
+ { value: "not_exists", label: "is empty", needsValue: false },
59
+ ] as const;
60
+
61
+ export function labelOf(list: readonly { value: string; label: string }[], value: string | null | undefined): string {
62
+ return list.find((x) => x.value === value)?.label ?? (value ? value.replace(/_/g, " ") : "Unknown");
63
+ }
64
+
65
+ /** Does this trigger action charge credits when it fires? Only an AI pass does. */
66
+ export function triggerSpends(actionType: string | null | undefined): boolean {
67
+ return TRIGGER_ACTIONS.find((a) => a.value === actionType)?.spends === true;
68
+ }
69
+
70
+ export function triggerRules(t: Pick<WorkbenchTrigger, "conditions">): TriggerRule[] {
71
+ const c = t.conditions;
72
+ if (!c) return [];
73
+ return Array.isArray(c) ? c : Array.isArray(c.rules) ? c.rules : [];
74
+ }
75
+
76
+ /** "Status equals failed" for a rule; the row field name is the API's own, so it is shown as written. */
77
+ export function describeRule(rule: TriggerRule): string {
78
+ const op = CONDITION_OPERATORS.find((o) => o.value === rule.operator);
79
+ if (!rule.field || !op) return "Custom condition";
80
+ return op.needsValue ? `${rule.field} ${op.label} ${String(rule.value ?? "")}` : `${rule.field} ${op.label}`;
81
+ }
82
+
83
+ /** The validation the handler runs on action_config, so the form blocks what the API would refuse. Returns a message or null. */
84
+ export function triggerConfigProblem(actionType: string, config: { webhook_url?: string; list_id?: number | null; new_list_name?: string }): string | null {
85
+ if (actionType === "webhook_notify") {
86
+ try {
87
+ const u = new URL(config.webhook_url ?? "");
88
+ if (u.protocol !== "http:" && u.protocol !== "https:") return "Enter a full http or https webhook URL.";
89
+ } catch {
90
+ return "Enter a full http or https webhook URL.";
91
+ }
92
+ }
93
+ if (actionType === "export" && !(config.list_id && config.list_id > 0) && !config.new_list_name?.trim()) return "Choose a list, or name a new one.";
94
+ return null;
95
+ }
96
+
97
+ export interface WorkbenchPipelineStep {
98
+ id: string;
99
+ type: string;
100
+ name: string;
101
+ enabled?: boolean;
102
+ group_id?: string | null;
103
+ column_id?: number | null;
104
+ }
105
+
106
+ export interface WorkbenchPipeline {
107
+ id: string;
108
+ name: string;
109
+ enabled?: boolean | null;
110
+ start_type?: string | null;
111
+ start?: { type?: string | null; source_types?: string[]; expected_fields?: unknown[] } | null;
112
+ steps?: WorkbenchPipelineStep[] | null;
113
+ created_at?: string | null;
114
+ updated_at?: string | null;
115
+ }
116
+
117
+ export const PIPELINE_STARTS = [
118
+ { value: "ingest", label: "Rows added to the table" },
119
+ { value: "webhook", label: "A webhook call" },
120
+ { value: "email_pdf", label: "An email with a PDF" },
121
+ { value: "scraper_bot", label: "A scraper run" },
122
+ ] as const;
123
+
124
+ export const STEP_TYPES = [
125
+ { value: "ai_group", label: "AI columns" },
126
+ { value: "waterfall", label: "Provider waterfall" },
127
+ { value: "formula", label: "Formula" },
128
+ { value: "branch", label: "If / else" },
129
+ { value: "export_to_list", label: "Send to a list" },
130
+ ] as const;
131
+
132
+ /** Steps that call a model or a data provider, so an enabled pipeline charges for them on every new row. */
133
+ export function stepSpends(step: Pick<WorkbenchPipelineStep, "type" | "enabled">): boolean {
134
+ return step.enabled !== false && (step.type === "ai_group" || step.type === "waterfall");
135
+ }
136
+
137
+ export function pipelineSpends(p: Pick<WorkbenchPipeline, "steps">): boolean {
138
+ return (p.steps ?? []).some(stepSpends);
139
+ }
140
+
141
+ export interface WorkbenchSchedule {
142
+ action_type?: string | null;
143
+ cron_expression?: string | null;
144
+ frequency?: string | null;
145
+ action_config?: Record<string, unknown> | null;
146
+ enabled?: boolean | null;
147
+ scheduler_state?: string | null;
148
+ scheduler_error?: string | null;
149
+ next_run_at?: string | null;
150
+ last_run_at?: string | null;
151
+ last_outcome?: string | null;
152
+ owner_label?: string | null;
153
+ updated_by_label?: string | null;
154
+ timezone?: string | null;
155
+ }
156
+
157
+ export const SCHEDULE_ACTIONS = [
158
+ { value: "run_ai", label: "Run the AI columns", spends: true },
159
+ { value: "run_formula", label: "Recompute formulas", spends: false },
160
+ { value: "crm_import", label: "Pull records from the CRM", spends: false },
161
+ ] as const;
162
+
163
+ export const SCHEDULE_FREQUENCIES = [
164
+ { value: "hourly", label: "Every hour" },
165
+ { value: "daily", label: "Every day" },
166
+ { value: "weekly", label: "Every week" },
167
+ ] as const;
168
+
169
+ export function scheduleSpends(actionType: string | null | undefined): boolean {
170
+ return SCHEDULE_ACTIONS.find((a) => a.value === actionType)?.spends === true;
171
+ }
172
+
173
+ const CRON_FIELD = /^[A-Za-z0-9*/?,#LW-]+$/;
174
+
175
+ /**
176
+ * Mirrors the server's `_validate_cron` (set_staging_schedule/handler.py): five fields of cron characters, and the MINUTE must be a
177
+ * plain number from 0 to 59, so a schedule runs at most once an hour (that is the recurring-spend bound). `*\/5 * * * *` and
178
+ * `0,30 * * * *` are refused there, so they are refused here.
179
+ */
180
+ export function validCron(expr: string): boolean {
181
+ const parts = expr.trim().split(/\s+/);
182
+ if (parts.length !== 5 || !parts.every((p) => CRON_FIELD.test(p))) return false;
183
+ return /^\d{1,2}$/.test(parts[0]) && Number(parts[0]) <= 59;
184
+ }
185
+
186
+ export const CRON_HINT = "Five fields: minute, hour, day of month, month, day of week. The minute must be a single number from 0 to 59 (schedules run at most once an hour).";
187
+
188
+ /** The most records a scheduled CRM import may pull per run (the one-off import allows 10,000). */
189
+ export const MAX_CRM_SCHEDULE_LIMIT = 1000;
190
+
191
+ export function describeSchedule(s: WorkbenchSchedule): string {
192
+ const when = s.frequency ? labelOf(SCHEDULE_FREQUENCIES, s.frequency) : s.cron_expression ? `On cron ${s.cron_expression}` : "No cadence set";
193
+ return `${labelOf(SCHEDULE_ACTIONS, s.action_type)}. ${when}${s.timezone ? ` (${s.timezone})` : ""}.`;
194
+ }
195
+
196
+ export function schedulerTone(state: string | null | undefined): StatusTone {
197
+ if (state === "active") return "positive";
198
+ if (state === "paused") return "neutral";
199
+ return "warning"; // missing, unavailable and anything unknown need a look; unknown is never shown as healthy
200
+ }
201
+
202
+ export interface EnrichmentConfig {
203
+ id?: string | number;
204
+ type?: string | null;
205
+ name?: string | null;
206
+ display_name?: string | null;
207
+ column_name?: string | null;
208
+ group_id?: string | null;
209
+ is_active?: boolean | null;
210
+ }
211
+
212
+ export function configName(c: EnrichmentConfig): string {
213
+ return c.display_name || c.name || c.column_name || (c.type ? `${c.type.replace(/_/g, " ")} configuration` : "Configuration");
214
+ }
215
+
216
+ export interface WorkbenchConnection {
217
+ id: number;
218
+ source_table_id?: string;
219
+ destination_table_id?: string;
220
+ sync_mode?: string | null;
221
+ field_mapping?: Record<string, string> | null;
222
+ filter_conditions?: Record<string, unknown> | null;
223
+ enabled?: boolean | null;
224
+ last_synced_row_id?: number | null;
225
+ created_at?: string | null;
226
+ }
227
+
228
+ export interface MappingSuggestion {
229
+ status?: string | null;
230
+ job_id?: string | null;
231
+ mappings?: { source?: string; target?: string | null; confidence?: number }[] | null;
232
+ message?: string | null;
233
+ }
234
+
235
+ /** Statuses of a mapping job that will not change any more. Anything else (pending, an unknown word) keeps polling. */
236
+ export function mappingDone(status: string | null | undefined): boolean {
237
+ return status === "completed" || status === "failed";
238
+ }
239
+
240
+ /** The first non-empty list found under any of the keys the endpoints use, or an empty list. */
241
+ export function rowsOf<T>(data: unknown): T[] {
242
+ if (Array.isArray(data)) return data as T[];
243
+ const d = (data ?? {}) as Record<string, unknown>;
244
+ for (const key of ["items", "rows", "configs", "connections", "triggers", "pipelines"]) if (Array.isArray(d[key])) return d[key] as T[];
245
+ return [];
246
+ }
247
+
248
+ /** Whole-number sequence ids only: the send-to-sequence body takes an integer, the list read answers a string. */
249
+ export function sequenceNumber(id: string | number | null | undefined): number | null {
250
+ const n = typeof id === "number" ? id : /^\d+$/.test(String(id ?? "")) ? Number(id) : NaN;
251
+ return Number.isInteger(n) && n > 0 ? n : null;
252
+ }
253
+
254
+ /** Sequences that can take new people: running ones. Paused, ended and draft sequences are not offered. */
255
+ export function sequenceAcceptsPeople(status: string | null | undefined): boolean {
256
+ return ["live", "waiting", "running", "active"].includes((status ?? "").toLowerCase());
257
+ }
@@ -107,7 +107,7 @@ export function WorkflowEditor({ actionId, onSaved, className }: WorkflowEditorP
107
107
  return (
108
108
  <div className={className}>
109
109
  <div className="mb-4 flex flex-wrap items-center gap-2">
110
- <h2 className="text-lg font-semibold">{str(wf.workflow.name, "Workflow")}</h2>
110
+ <h2 className="text-lg font-medium">{str(wf.workflow.name, "Workflow")}</h2>
111
111
  <span className="text-muted-foreground text-sm">{wf.workflow.enabled === false ? "Disabled" : "Enabled"}</span>
112
112
  <div className="ml-auto flex items-center gap-2">
113
113
  {draft ? <span className="text-sm">Unsaved changes</span> : null}