@graph8/sdk 0.308.1 → 0.357.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (654) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +1440 -21
  390. package/dist/index.d.ts +1440 -21
  391. package/dist/index.js +42 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +42 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +1340 -18
  400. package/dist/react.d.ts +1340 -18
  401. package/dist/react.js +41 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +41 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +6 -3
  611. package/docs/api/appObjects.d.ts +1 -1
  612. package/docs/api/appObjects.md +1 -1
  613. package/docs/api/by-path.md +29 -0
  614. package/docs/api/objects.d.ts +72 -5
  615. package/docs/api/objects.md +84 -7
  616. package/docs/api/processLists.d.ts +247 -0
  617. package/docs/api/processLists.md +409 -0
  618. package/docs/api/reports.d.ts +476 -0
  619. package/docs/api/reports.md +539 -0
  620. package/docs/api/scheduleBuy.d.ts +12 -8
  621. package/docs/api/scheduleBuy.md +13 -10
  622. package/docs/api/seo.d.ts +406 -3
  623. package/docs/api/seo.md +505 -1
  624. package/docs/api/workspaceBackup.d.ts +324 -0
  625. package/docs/api/workspaceBackup.md +427 -0
  626. package/package.json +174 -5
  627. package/stacks/6sense.json +46 -0
  628. package/stacks/README.md +20 -0
  629. package/stacks/apollo.json +128 -0
  630. package/stacks/clay.json +59 -0
  631. package/stacks/gong.json +72 -0
  632. package/stacks/hubspot.json +162 -0
  633. package/stacks/intercom.json +53 -0
  634. package/stacks/mailchimp.json +71 -0
  635. package/stacks/outreach.json +86 -0
  636. package/stacks/salesforce.json +94 -0
  637. package/stacks/stack.schema.json +32 -0
  638. package/stacks/starter.json +22 -0
  639. package/styles/styles.css +2 -0
  640. package/styles/theme.css +181 -0
  641. package/styles/tokens.css +284 -0
  642. package/templates/next-app/README.md.tmpl +35 -0
  643. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  644. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  645. package/templates/next-app/app/globals.css.tmpl +8 -0
  646. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  647. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  648. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  649. package/templates/next-app/dot-gitignore.tmpl +7 -0
  650. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  651. package/templates/next-app/next.config.mjs.tmpl +4 -0
  652. package/templates/next-app/package.json.tmpl +25 -0
  653. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  654. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,284 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ConnectMailbox: add a sending mailbox three ways: connect one you own over IMAP/SMTP, connect Google or
5
+ * Microsoft (done in the graph8 app), or buy new mailboxes on a domain you hold.
6
+ *
7
+ * SDK operations, in order:
8
+ * IMAP/SMTP: 1. createMailbox POST /mailboxes (write; credentials are write-only)
9
+ * 2. testMailboxConnection POST /mailboxes/{mailbox_id}/test-connection (write; proves SMTP and IMAP)
10
+ * Google/Microsoft: no API call. OAuth consent only runs in the graph8 app: a link button sends the person there.
11
+ * Buy: 1. listProviderDomains GET /provider-domains (read; connected domains only)
12
+ * 2. getSendingPricing GET /provider-domains/pricing (read; credits per mailbox and warmup month)
13
+ * 3. checkoutPurchaseMailboxes POST /provider-domains/{domain_id}/checkout-mailboxes
14
+ * (billable: SPENDS REAL CREDITS, confirmed with an estimate, sent with an Idempotency-Key)
15
+ *
16
+ * The `id` that createMailbox returns feeds testMailboxConnection. A domain's `id` from listProviderDomains
17
+ * feeds the purchase. The domain must be connected first (DomainPurchase does that).
18
+ */
19
+
20
+ import { useRef, useState } from "react";
21
+ import { Button, Input } from "@graph8/sdk/ui";
22
+ import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
23
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
24
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
+ import { ApiError } from "@/g8/components/api-error";
26
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
27
+ import { DEFAULT_MAILBOX_CREDITS, GRAPH8_APP_MAILBOXES_URL, listOf, newIdempotencyKey, objectOf } from "../lib/mailboxes-utils";
28
+ import type { DomainRow } from "../lib/mailboxes-utils";
29
+
30
+ export interface ConnectMailboxProps {
31
+ /** Where the person connects Google or Microsoft. Default: graph8 Settings > Purchase > Mailboxes. */
32
+ appUrl?: string;
33
+ /** Called with the new mailbox id once an IMAP/SMTP mailbox is saved AND its connection test passes. */
34
+ onConnected?: (mailboxId: number | string) => void;
35
+ /** Called with the domain id after mailboxes were bought on it. */
36
+ onPurchased?: (domainId: number | string) => void;
37
+ className?: string;
38
+ }
39
+
40
+ type Mode = "imap" | "oauth" | "buy";
41
+
42
+ const EMPTY = {
43
+ email: "",
44
+ display_name: "",
45
+ smtp_address: "",
46
+ smtp_port: "465",
47
+ smtp_username: "",
48
+ smtp_password: "",
49
+ imap_address: "",
50
+ imap_port: "993",
51
+ imap_username: "",
52
+ imap_password: "",
53
+ daily_limit: "40",
54
+ };
55
+
56
+ function ImapForm({ onConnected }: { onConnected?: (id: number | string) => void }) {
57
+ const [form, setForm] = useState(EMPTY);
58
+ const [result, setResult] = useState<string | null>(null);
59
+ // Step 1 (create) must not repeat when step 2 (test) fails, even if the person then fixes a password.
60
+ // The created mailbox is remembered by its email address; changed credentials are PATCHed onto it.
61
+ const created = useRef<{ email: string; id: number; sig: string } | null>(null);
62
+ const connect = useG8Mutation(async () => {
63
+ const body = {
64
+ ...form,
65
+ smtp_port: Number(form.smtp_port),
66
+ imap_port: Number(form.imap_port),
67
+ daily_limit: Number(form.daily_limit),
68
+ smtp_username: form.smtp_username || form.email,
69
+ imap_username: form.imap_username || form.email,
70
+ display_name: form.display_name || undefined,
71
+ };
72
+ const sig = JSON.stringify(body);
73
+ const known = created.current?.email === form.email.trim().toLowerCase() ? created.current : null;
74
+ if (!known) {
75
+ const res = await g8Post<{ id: number }>("/mailboxes", body);
76
+ if (res.data?.id == null) throw new Error("graph8 did not return the new mailbox id");
77
+ created.current = { email: form.email.trim().toLowerCase(), id: res.data.id, sig };
78
+ } else if (known.sig !== sig) {
79
+ const { email: _email, ...changes } = body;
80
+ await g8Patch(`/mailboxes/${known.id}`, changes);
81
+ created.current = { ...known, sig };
82
+ }
83
+ const id = created.current!.id;
84
+ const test = objectOf(await g8Post(`/mailboxes/${id}/test-connection`, {})) as { success?: boolean };
85
+ setResult(
86
+ test.success ? "Connected: SMTP and IMAP both work." : "Saved, but the connection test failed. Check the host, port and password.",
87
+ );
88
+ // Only report a mailbox that actually connects; a saved-but-failing one is not yet usable.
89
+ if (id != null && test.success) onConnected?.(id);
90
+ });
91
+ const set = (key: keyof typeof EMPTY) => (e: React.ChangeEvent<HTMLInputElement>) => setForm({ ...form, [key]: e.target.value });
92
+ const ready = form.email && form.smtp_address && form.smtp_password && form.imap_address && form.imap_password;
93
+ return (
94
+ <form
95
+ className="space-y-3"
96
+ onSubmit={(e) => {
97
+ e.preventDefault();
98
+ if (ready) void connect.mutate(undefined);
99
+ }}
100
+ >
101
+ <div className="grid gap-2 sm:grid-cols-2">
102
+ <Input aria-label="Email address" placeholder="you@yourdomain.com" value={form.email} onChange={set("email")} />
103
+ <Input aria-label="Display name" placeholder="Display name (optional)" value={form.display_name} onChange={set("display_name")} />
104
+ <Input aria-label="SMTP host" placeholder="smtp.yourhost.com" value={form.smtp_address} onChange={set("smtp_address")} />
105
+ <Input aria-label="SMTP port" value={form.smtp_port} onChange={set("smtp_port")} />
106
+ <Input aria-label="SMTP username" placeholder="Defaults to the email" value={form.smtp_username} onChange={set("smtp_username")} />
107
+ <Input
108
+ aria-label="SMTP password"
109
+ type="password"
110
+ autoComplete="new-password"
111
+ value={form.smtp_password}
112
+ onChange={set("smtp_password")}
113
+ />
114
+ <Input aria-label="IMAP host" placeholder="imap.yourhost.com" value={form.imap_address} onChange={set("imap_address")} />
115
+ <Input aria-label="IMAP port" value={form.imap_port} onChange={set("imap_port")} />
116
+ <Input aria-label="IMAP username" placeholder="Defaults to the email" value={form.imap_username} onChange={set("imap_username")} />
117
+ <Input
118
+ aria-label="IMAP password"
119
+ type="password"
120
+ autoComplete="new-password"
121
+ value={form.imap_password}
122
+ onChange={set("imap_password")}
123
+ />
124
+ <Input aria-label="Daily send limit" value={form.daily_limit} onChange={set("daily_limit")} />
125
+ </div>
126
+ <Button type="submit" disabled={!ready || connect.loading}>
127
+ {connect.loading ? "Connecting..." : "Connect mailbox"}
128
+ </Button>
129
+ {connect.error ? <ApiError error={connect.error} onRetry={connect.reset} /> : null}
130
+ {result ? (
131
+ <p role="status" className="text-sm text-gray-700">
132
+ {result}
133
+ </p>
134
+ ) : null}
135
+ </form>
136
+ );
137
+ }
138
+
139
+ function BuyForm({ onPurchased }: { onPurchased?: (id: number | string) => void }) {
140
+ const domains = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 100 }, { signal }), []);
141
+ const pricing = useG8Query((signal) => g8Get("/provider-domains/pricing", undefined, { signal }), []);
142
+ const [domainId, setDomainId] = useState("");
143
+ const [usernames, setUsernames] = useState("");
144
+ const [person, setPerson] = useState({ first_name: "", last_name: "" });
145
+ const [platform, setPlatform] = useState<"GOOGLE" | "MICROSOFT">("GOOGLE");
146
+ const [warmup, setWarmup] = useState(true);
147
+ const [confirming, setConfirming] = useState(false);
148
+ const key = useRef<{ sig: string; value: string } | null>(null);
149
+ const [done, setDone] = useState<string | null>(null);
150
+
151
+ const names = usernames
152
+ .split(/[\s,]+/)
153
+ .map((n) => n.trim())
154
+ .filter(Boolean);
155
+ const perMailbox = Number(objectOf(pricing.data).credits_per_mailbox) || DEFAULT_MAILBOX_CREDITS;
156
+ const buy = useG8Mutation(async () => {
157
+ const body = { mailboxes: names.map((username) => ({ username, ...person })), enable_warmup: warmup, provider: "managed", platform };
158
+ // Same purchase retried (a timeout, a double click) reuses its key so it cannot be bought twice.
159
+ const sig = `${domainId}:${JSON.stringify(body)}`;
160
+ if (key.current?.sig !== sig) key.current = { sig, value: newIdempotencyKey() };
161
+ const res = objectOf(await g8Post(`/provider-domains/${domainId}/checkout-mailboxes`, body, { idempotencyKey: key.current.value }));
162
+ setDone(`Accepted ${res.count ?? names.length} mailbox${names.length === 1 ? "" : "es"}; provisioning continues in the background.`);
163
+ onPurchased?.(domainId);
164
+ });
165
+ const connected = listOf<DomainRow>(domains.data).filter((d) => d.provider_connected);
166
+ const ready = domainId && names.length > 0 && person.first_name.trim() && person.last_name.trim();
167
+
168
+ return (
169
+ <div className="space-y-3">
170
+ {domains.error ? <ApiError error={domains.error} onRetry={() => void domains.refetch()} /> : null}
171
+ {!domains.loading && connected.length === 0 ? (
172
+ <p className="text-sm text-gray-600">No connected domain yet. Buy and connect one with DomainPurchase first.</p>
173
+ ) : (
174
+ <div className="grid gap-2 sm:grid-cols-2">
175
+ <select
176
+ aria-label="Domain"
177
+ className="h-9 rounded-md border border-gray-300 px-2 text-sm"
178
+ value={domainId}
179
+ onChange={(e) => setDomainId(e.target.value)}
180
+ >
181
+ <option value="">Choose a domain</option>
182
+ {connected.map((d) => (
183
+ <option key={d.id} value={d.id}>
184
+ {d.domain}
185
+ </option>
186
+ ))}
187
+ </select>
188
+ <select
189
+ aria-label="Platform"
190
+ className="h-9 rounded-md border border-gray-300 px-2 text-sm"
191
+ value={platform}
192
+ onChange={(e) => setPlatform(e.target.value as "GOOGLE" | "MICROSOFT")}
193
+ >
194
+ <option value="GOOGLE">Google</option>
195
+ <option value="MICROSOFT">Microsoft</option>
196
+ </select>
197
+ <Input
198
+ aria-label="Mailbox usernames"
199
+ placeholder="sales, hello (the part before @)"
200
+ value={usernames}
201
+ onChange={(e) => setUsernames(e.target.value)}
202
+ />
203
+ <label className="flex items-center gap-2 text-sm">
204
+ <input type="checkbox" checked={warmup} onChange={(e) => setWarmup(e.target.checked)} />
205
+ Start warmup
206
+ </label>
207
+ <Input
208
+ aria-label="Sender first name"
209
+ placeholder="First name"
210
+ value={person.first_name}
211
+ onChange={(e) => setPerson({ ...person, first_name: e.target.value })}
212
+ />
213
+ <Input
214
+ aria-label="Sender last name"
215
+ placeholder="Last name"
216
+ value={person.last_name}
217
+ onChange={(e) => setPerson({ ...person, last_name: e.target.value })}
218
+ />
219
+ </div>
220
+ )}
221
+ <Button disabled={!ready} onClick={() => setConfirming(true)}>
222
+ Buy {names.length || ""} mailbox{names.length === 1 ? "" : "es"}
223
+ </Button>
224
+ {buy.error ? <ApiError error={buy.error} onRetry={buy.reset} /> : null}
225
+ {done ? (
226
+ <p role="status" className="text-sm text-green-700">
227
+ {done}
228
+ </p>
229
+ ) : null}
230
+ {confirming ? (
231
+ <ConfirmSpendDialog
232
+ open
233
+ tier="billable"
234
+ title={`Buy ${names.length} mailbox${names.length === 1 ? "" : "es"}: ${names.join(", ")}`}
235
+ description="Mailboxes are billed monthly while they exist, plus warmup if enabled. This is an estimate: graph8 has no balance read."
236
+ estimatedCredits={names.length * perMailbox}
237
+ confirmLabel="Buy mailboxes"
238
+ confirming={buy.loading}
239
+ onCancel={() => setConfirming(false)}
240
+ onConfirm={() => void buy.mutate(undefined).then(() => setConfirming(false))}
241
+ />
242
+ ) : null}
243
+ </div>
244
+ );
245
+ }
246
+
247
+ export function ConnectMailbox({ appUrl = GRAPH8_APP_MAILBOXES_URL, onConnected, onPurchased, className }: ConnectMailboxProps) {
248
+ const [mode, setMode] = useState<Mode>("imap");
249
+ return (
250
+ <div className={`space-y-4 ${className ?? ""}`}>
251
+ <div className="inline-flex gap-1" role="group" aria-label="How to add a mailbox">
252
+ <Button size="sm" variant={mode === "imap" ? "default" : "outline"} aria-pressed={mode === "imap"} onClick={() => setMode("imap")}>
253
+ IMAP / SMTP
254
+ </Button>
255
+ <Button
256
+ size="sm"
257
+ variant={mode === "oauth" ? "default" : "outline"}
258
+ aria-pressed={mode === "oauth"}
259
+ onClick={() => setMode("oauth")}
260
+ >
261
+ Google / Microsoft
262
+ </Button>
263
+ <Button size="sm" variant={mode === "buy" ? "default" : "outline"} aria-pressed={mode === "buy"} onClick={() => setMode("buy")}>
264
+ Buy new
265
+ </Button>
266
+ </div>
267
+ {mode === "imap" ? <ImapForm onConnected={onConnected} /> : null}
268
+ {mode === "oauth" ? (
269
+ <div className="space-y-2 rounded border border-gray-200 p-3 text-sm">
270
+ <p>
271
+ Google and Microsoft mailboxes connect through a sign-in screen that only runs inside the graph8 app. Connect it there, then
272
+ come back: it appears in your mailbox list.
273
+ </p>
274
+ <Button asChild>
275
+ <a href={appUrl} target="_blank" rel="noreferrer">
276
+ Open graph8 to connect
277
+ </a>
278
+ </Button>
279
+ </div>
280
+ ) : null}
281
+ {mode === "buy" ? <BuyForm onPurchased={onPurchased} /> : null}
282
+ </div>
283
+ );
284
+ }
@@ -0,0 +1,271 @@
1
+ "use client";
2
+
3
+ /**
4
+ * DomainPurchase: find, check and buy sending domains, then connect them so mailboxes can be bought on them.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. generateDomainNames POST /provider-domains/generate-domains (read; ideas around a keyword)
8
+ * 2. checkDomainAvailability POST /provider-domains/check-availability (billable tier but documented to
9
+ * spend NO credits; still confirmed once per batch, the dialog says 0 credits)
10
+ * 3. checkoutPurchaseDomains POST /provider-domains/checkout-purchase (billable: SPENDS REAL CREDITS,
11
+ * confirmed with the estimate; sent with an Idempotency-Key)
12
+ * 4. listProviderDomains GET /provider-domains (read; status, mailbox_count, connected)
13
+ * 5. connectProvider POST /provider-domains/{domain_id}/connect-provider (billable: confirmed)
14
+ * checkDomainPropagation POST /provider-domains/{domain_id}/check-propagation (billable: confirmed)
15
+ *
16
+ * Ids passed between steps: names from 1 feed 2; the available ones feed 3; the `id` of a row from 4 feeds 5.
17
+ * There is no credit-balance read: the dialog shows an ESTIMATE (2,500 credits held per domain). A 402
18
+ * means the hold could not clear; a 422 means the plan cannot fund purchases.
19
+ */
20
+
21
+ import { useRef, useState } from "react";
22
+ import { Button, Input, Skeleton } from "@graph8/sdk/ui";
23
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
24
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
25
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
26
+ import { ApiError } from "@/g8/components/api-error";
27
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
28
+ import { EmptyState } from "@/g8/components/empty-state";
29
+ import { DEFAULT_DOMAIN_CREDITS, listOf, newIdempotencyKey, objectOf, statusClass } from "../lib/mailboxes-utils";
30
+ import type { DomainRow } from "../lib/mailboxes-utils";
31
+
32
+ export interface DomainPurchaseProps {
33
+ /** Called after a purchase was accepted (provisioning continues in the background). */
34
+ onPurchased?: (domains: string[]) => void;
35
+ className?: string;
36
+ }
37
+
38
+ interface Availability {
39
+ domain: string;
40
+ available?: boolean;
41
+ price?: number | null;
42
+ banned?: boolean;
43
+ }
44
+
45
+ type Pending =
46
+ { kind: "check"; domains: string[] } | { kind: "buy"; domains: string[] } | { kind: "connect" | "propagation"; domain: DomainRow };
47
+
48
+ function suggestionsOf(envelope: unknown): string[] {
49
+ const data = (envelope as { data?: unknown } | null)?.data;
50
+ const raw = Array.isArray(data)
51
+ ? data
52
+ : ((data as Record<string, unknown> | null)?.domains ?? (data as Record<string, unknown> | null)?.suggestions ?? []);
53
+ return (raw as unknown[]).map((x) => (typeof x === "string" ? x : String((x as { domain?: string }).domain ?? ""))).filter(Boolean);
54
+ }
55
+
56
+ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps) {
57
+ const [keyword, setKeyword] = useState("");
58
+ const [names, setNames] = useState<string[]>([]);
59
+ const [selected, setSelected] = useState<string[]>([]);
60
+ const [avail, setAvail] = useState<Record<string, Availability>>({});
61
+ const [contact, setContact] = useState({ first_name: "", last_name: "", email: "" });
62
+ const [pending, setPending] = useState<Pending | null>(null);
63
+ const [note, setNote] = useState<string | null>(null);
64
+ const idemKey = useRef<{ sig: string; key: string } | null>(null);
65
+
66
+ const domains = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 50 }, { signal }), []);
67
+ const suggest = useG8Mutation(async () => {
68
+ setNames(suggestionsOf(await g8Post("/provider-domains/generate-domains", { keyword: keyword.trim(), count: 10 })));
69
+ setSelected([]);
70
+ setAvail({});
71
+ });
72
+ const run = useG8Mutation(async (p: Pending) => {
73
+ if (p.kind === "check") {
74
+ const results = await Promise.allSettled(
75
+ p.domains.map((domain) => g8Post("/provider-domains/check-availability", { domain, provider: "registrar" })),
76
+ );
77
+ const found: Record<string, Availability> = {};
78
+ results.forEach((r, i) => {
79
+ if (r.status === "fulfilled") found[p.domains[i]] = { domain: p.domains[i], ...(objectOf(r.value) as Partial<Availability>) };
80
+ });
81
+ setAvail((a) => ({ ...a, ...found }));
82
+ const failed = results.length - Object.keys(found).length;
83
+ setNote(failed ? `Checked ${Object.keys(found).length} of ${results.length} domains; ${failed} failed. Check those again.` : null);
84
+ } else if (p.kind === "buy") {
85
+ const body = { domains: p.domains, period: 1, contact_info: contact, provider: "registrar" };
86
+ const sig = JSON.stringify(body);
87
+ if (idemKey.current?.sig !== sig) idemKey.current = { sig, key: newIdempotencyKey() };
88
+ await g8Post("/provider-domains/checkout-purchase", body, { idempotencyKey: idemKey.current.key });
89
+ setNote(`Purchase accepted for ${p.domains.join(", ")}. Provisioning continues in the background.`);
90
+ setSelected([]);
91
+ onPurchased?.(p.domains);
92
+ await domains.refetch();
93
+ } else if (p.kind === "connect") {
94
+ await g8Post(`/provider-domains/${p.domain.id}/connect-provider`, {});
95
+ setNote(`${p.domain.domain} is connecting. Check propagation in a few minutes.`);
96
+ await domains.refetch();
97
+ } else {
98
+ const status = objectOf(await g8Post(`/provider-domains/${p.domain.id}/check-propagation`, {}));
99
+ setNote(status.propagated ? `${p.domain.domain} has propagated.` : `${p.domain.domain} has not propagated yet.`);
100
+ }
101
+ });
102
+
103
+ const toggle = (d: string) => setSelected((s) => (s.includes(d) ? s.filter((x) => x !== d) : [...s, d]));
104
+ const buyable = selected.filter((d) => avail[d]?.available);
105
+ const contactOk = contact.first_name.trim() && contact.last_name.trim() && /\S+@\S+/.test(contact.email);
106
+ const rows = listOf<DomainRow>(domains.data);
107
+
108
+ const dialog = (() => {
109
+ if (!pending) return null;
110
+ if (pending.kind === "check")
111
+ return {
112
+ title: `Check ${pending.domains.length} domain${pending.domains.length === 1 ? "" : "s"}`,
113
+ description: "Looks up availability and price. The API documents no charge for this check.",
114
+ credits: 0,
115
+ label: "Check availability",
116
+ };
117
+ if (pending.kind === "buy")
118
+ return {
119
+ title: `Buy ${pending.domains.join(", ")}`,
120
+ description: "Registers the domains for 1 year and holds credits now.",
121
+ credits: pending.domains.length * DEFAULT_DOMAIN_CREDITS,
122
+ label: "Buy domains",
123
+ };
124
+ if (pending.kind === "connect")
125
+ return {
126
+ title: `Connect ${pending.domain.domain}`,
127
+ description: "Connects the domain to mailbox provisioning. Required before buying mailboxes on it.",
128
+ credits: undefined,
129
+ label: "Connect domain",
130
+ };
131
+ return {
132
+ title: `Check DNS propagation for ${pending.domain.domain}`,
133
+ description: "Asks the provider whether the nameservers have propagated.",
134
+ credits: undefined,
135
+ label: "Check propagation",
136
+ };
137
+ })();
138
+
139
+ return (
140
+ <div className={`space-y-5 ${className ?? ""}`}>
141
+ <section className="space-y-2 rounded border border-gray-200 p-3">
142
+ <h3 className="text-sm font-semibold">Find a domain</h3>
143
+ <form
144
+ className="flex gap-2"
145
+ onSubmit={(e) => {
146
+ e.preventDefault();
147
+ if (keyword.trim()) void suggest.mutate(undefined);
148
+ }}
149
+ >
150
+ <Input aria-label="Brand keyword" placeholder="acme" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
151
+ <Button type="submit" variant="outline" disabled={suggest.loading}>
152
+ Suggest names
153
+ </Button>
154
+ </form>
155
+ {suggest.error ? <ApiError error={suggest.error} onRetry={suggest.reset} /> : null}
156
+ {names.length > 0 ? (
157
+ <ul className="grid gap-1 sm:grid-cols-2">
158
+ {names.map((d) => (
159
+ <li key={d}>
160
+ <label className="flex items-center gap-2 text-sm">
161
+ <input type="checkbox" checked={selected.includes(d)} onChange={() => toggle(d)} />
162
+ {d}
163
+ {avail[d] ? (
164
+ <span
165
+ className={`rounded px-1.5 text-xs ${avail[d].available ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}`}
166
+ >
167
+ {avail[d].available
168
+ ? `available${avail[d].price != null ? ` ${avail[d].price}` : ""}`
169
+ : avail[d].banned
170
+ ? "blocked"
171
+ : "taken"}
172
+ </span>
173
+ ) : null}
174
+ </label>
175
+ </li>
176
+ ))}
177
+ </ul>
178
+ ) : null}
179
+ <Button
180
+ size="sm"
181
+ variant="outline"
182
+ disabled={selected.length === 0}
183
+ onClick={() => setPending({ kind: "check", domains: selected })}
184
+ >
185
+ Check availability ({selected.length})
186
+ </Button>
187
+ </section>
188
+
189
+ <section className="space-y-2 rounded border border-gray-200 p-3">
190
+ <h3 className="text-sm font-semibold">Registrant</h3>
191
+ <div className="grid gap-2 sm:grid-cols-3">
192
+ <Input
193
+ aria-label="First name"
194
+ placeholder="First name"
195
+ value={contact.first_name}
196
+ onChange={(e) => setContact({ ...contact, first_name: e.target.value })}
197
+ />
198
+ <Input
199
+ aria-label="Last name"
200
+ placeholder="Last name"
201
+ value={contact.last_name}
202
+ onChange={(e) => setContact({ ...contact, last_name: e.target.value })}
203
+ />
204
+ <Input
205
+ aria-label="Registrant email"
206
+ placeholder="Email"
207
+ value={contact.email}
208
+ onChange={(e) => setContact({ ...contact, email: e.target.value })}
209
+ />
210
+ </div>
211
+ <Button disabled={buyable.length === 0 || !contactOk} onClick={() => setPending({ kind: "buy", domains: buyable })}>
212
+ Buy {buyable.length} domain{buyable.length === 1 ? "" : "s"}
213
+ </Button>
214
+ </section>
215
+
216
+ {note ? (
217
+ <p role="status" className="text-sm text-gray-700">
218
+ {note}
219
+ </p>
220
+ ) : null}
221
+ {run.error ? <ApiError error={run.error} onRetry={run.reset} /> : null}
222
+
223
+ <section aria-label="Your domains">
224
+ {domains.error ? (
225
+ <ApiError error={domains.error} onRetry={() => void domains.refetch()} />
226
+ ) : domains.loading && !domains.data ? (
227
+ <Skeleton className="h-16 w-full" />
228
+ ) : rows.length === 0 ? (
229
+ <EmptyState title="No sending domains yet" description="Buy one above, or add a domain you already own in the graph8 app." />
230
+ ) : (
231
+ <ul className="divide-y rounded border border-gray-200">
232
+ {rows.map((d) => (
233
+ <li key={d.id} className="flex flex-wrap items-center justify-between gap-2 px-3 py-2 text-sm">
234
+ <span>
235
+ <span className="font-medium">{d.domain}</span>{" "}
236
+ <span className={`rounded px-1.5 text-xs ${statusClass(d.status)}`}>{d.status ?? "unknown"}</span>{" "}
237
+ <span className="text-xs text-gray-500">{d.mailbox_count ?? 0} mailboxes</span>
238
+ </span>
239
+ <span className="flex gap-1">
240
+ {!d.provider_connected && !d.is_external ? (
241
+ <Button size="sm" variant="outline" onClick={() => setPending({ kind: "connect", domain: d })}>
242
+ Connect
243
+ </Button>
244
+ ) : (
245
+ <Button size="sm" variant="ghost" onClick={() => setPending({ kind: "propagation", domain: d })}>
246
+ Check DNS
247
+ </Button>
248
+ )}
249
+ </span>
250
+ </li>
251
+ ))}
252
+ </ul>
253
+ )}
254
+ </section>
255
+
256
+ {pending && dialog ? (
257
+ <ConfirmSpendDialog
258
+ open
259
+ tier="billable"
260
+ title={dialog.title}
261
+ description={dialog.description}
262
+ estimatedCredits={dialog.credits}
263
+ confirmLabel={dialog.label}
264
+ confirming={run.loading}
265
+ onCancel={() => setPending(null)}
266
+ onConfirm={() => void run.mutate(pending).then(() => setPending(null))}
267
+ />
268
+ ) : null}
269
+ </div>
270
+ );
271
+ }