@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
@@ -11,6 +11,8 @@
11
11
  * 4. reinstateSuppressions POST /contacts/suppressions/reinstate { contact_ids, reason } (write, but it lets
12
12
  * sequences and campaigns contact people again, so it goes through ConfirmSpendDialog as "external")
13
13
  *
14
+ * No ids to type: contacts to suppress and the contact to check are chosen with a ContactPicker (search by name or work
15
+ * email; GET /contacts, read). Pasting numeric ids still works under "Paste ids instead" / "Check by id instead".
14
16
  * The ledger holds ids, not names or emails (no personal data): resolve a name with ContactDetail.
15
17
  * Suppressing only ever REDUCES outreach, so it runs without a confirmation.
16
18
  */
@@ -20,6 +22,8 @@ import { Button, Input, Label, Skeleton, Table, TableBody, TableCell, TableHead,
20
22
  import { ApiError } from "@/g8/components/api-error";
21
23
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
22
24
  import { EmptyState } from "@/g8/components/empty-state";
25
+ import { ContactPicker } from "@/g8/pickers/components/contact-picker";
26
+ import type { ContactOption } from "@/g8/pickers/components/contact-picker";
23
27
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
24
28
  import { useG8Query } from "@/g8/hooks/use-g8-query";
25
29
  import { g8Get, g8Post } from "@/g8/lib/g8-client";
@@ -59,6 +63,8 @@ export function SuppressionManager({ pageSize = 50, onOpenContact, className }:
59
63
  const [category, setCategory] = useState("");
60
64
  const [offset, setOffset] = useState(0);
61
65
  const [idsText, setIdsText] = useState("");
66
+ const [chosen, setChosen] = useState<ContactOption[]>([]);
67
+ const [checkChosen, setCheckChosen] = useState<ContactOption | null>(null);
62
68
  const [addChannel, setAddChannel] = useState("all");
63
69
  const [reason, setReason] = useState("");
64
70
  const [picked, setPicked] = useState<Set<number>>(new Set());
@@ -68,7 +74,7 @@ export function SuppressionManager({ pageSize = 50, onOpenContact, className }:
68
74
 
69
75
  const ledger = useG8Query((signal) => g8Get<SuppressionRecord[]>("/contacts/suppressions", { channel: channel || undefined, category: category || undefined, limit: pageSize, offset }, { signal }), [channel, category, offset, pageSize]);
70
76
  const add = useG8Mutation((ids: number[]) => g8Post<BulkAdd>("/contacts/suppressions/bulk-add", { contact_ids: ids, channel: addChannel, reason: reason.trim() || undefined }), {
71
- onSuccess: () => { setIdsText(""); setReason(""); void ledger.refetch(); },
77
+ onSuccess: () => { setIdsText(""); setChosen([]); setReason(""); void ledger.refetch(); },
72
78
  });
73
79
  const reinstate = useG8Mutation((ids: number[]) => g8Post<Reinstate>("/contacts/suppressions/reinstate", { contact_ids: ids, reason: reinstateReason.trim() || undefined }), {
74
80
  onSuccess: () => { setConfirmReinstate(false); setReinstateReason(""); setPicked(new Set()); void ledger.refetch(); },
@@ -76,15 +82,23 @@ export function SuppressionManager({ pageSize = 50, onOpenContact, className }:
76
82
  const check = useG8Mutation((id: number) => g8Get<ContactStatus>(`/contacts/${id}/suppression`));
77
83
 
78
84
  const rows = (ledger.data?.data ?? []) as SuppressionRecord[];
79
- const ids = parseIds(idsText);
85
+ const ids = [...new Set([...chosen.map((c) => c.id), ...parseIds(idsText)])];
86
+ const checkTarget = checkChosen?.id ?? Number(checkId);
80
87
  const selectedContacts = [...picked];
81
88
  const togglePick = (contactId: number) => setPicked((p) => { const n = new Set(p); if (!n.delete(contactId)) n.add(contactId); return n; });
82
89
 
83
90
  return (
84
91
  <div className={`flex flex-col gap-5 ${className ?? ""}`}>
85
92
  <section className="flex flex-col gap-2" aria-label="Suppress contacts">
86
- <Label htmlFor="suppress-ids">Suppress contacts by id</Label>
87
- <Textarea id="suppress-ids" rows={2} placeholder="101, 102, 103" value={idsText} onChange={(e) => setIdsText(e.target.value)} />
93
+ <span className="text-sm font-medium leading-none">Who to suppress</span>
94
+ <ContactPicker multiple label="Contacts to suppress" selected={chosen} onSelectedChange={setChosen} />
95
+ <details className="text-sm">
96
+ <summary className="cursor-pointer text-muted-foreground">Paste ids instead</summary>
97
+ <div className="mt-1 flex flex-col gap-1">
98
+ <Label htmlFor="suppress-ids">Suppress contacts by id</Label>
99
+ <Textarea id="suppress-ids" rows={2} placeholder="101, 102, 103" value={idsText} onChange={(e) => setIdsText(e.target.value)} />
100
+ </div>
101
+ </details>
88
102
  <div className="flex flex-wrap items-center gap-2">
89
103
  <select aria-label="Channel to suppress" className="h-9 rounded-md border bg-background px-2 text-sm" value={addChannel} onChange={(e) => setAddChannel(e.target.value)}>
90
104
  <option value="all">All channels</option><option value="email">Email</option><option value="phone">Phone</option><option value="linkedin">LinkedIn</option>
@@ -97,8 +111,15 @@ export function SuppressionManager({ pageSize = 50, onOpenContact, className }:
97
111
  </section>
98
112
 
99
113
  <section className="flex items-end gap-2" aria-label="Check a contact">
100
- <div className="flex flex-col gap-1"><Label htmlFor="check-id">Check one contact</Label><Input id="check-id" inputMode="numeric" placeholder="Contact id" className="w-40" value={checkId} onChange={(e) => setCheckId(e.target.value)} /></div>
101
- <Button variant="outline" size="sm" loading={check.loading} disabled={!Number(checkId)} onClick={() => void check.mutate(Number(checkId))}>Check</Button>
114
+ <div className="flex flex-col gap-1">
115
+ <span className="text-sm font-medium leading-none">Check one contact</span>
116
+ <ContactPicker label="Contact to check" value={checkChosen} onChange={(c) => { setCheckChosen(c); setCheckId(""); }} />
117
+ <details className="text-sm">
118
+ <summary className="cursor-pointer text-muted-foreground">Check by id instead</summary>
119
+ <div className="mt-1 flex flex-col gap-1"><Label htmlFor="check-id">Check one contact</Label><Input id="check-id" inputMode="numeric" placeholder="Contact id" className="w-40" value={checkId} onChange={(e) => { setCheckId(e.target.value); setCheckChosen(null); }} /></div>
120
+ </details>
121
+ </div>
122
+ <Button variant="outline" size="sm" loading={check.loading} disabled={!checkTarget} onClick={() => void check.mutate(checkTarget)}>Check</Button>
102
123
  {check.data ? <p role="status" className="text-sm">{check.data.data.is_suppressed ? `Suppressed on: ${check.data.data.active_channels.join(", ")}` : "Not suppressed"}</p> : null}
103
124
  {check.error ? <ApiError error={check.error} title="Could not check that contact" /> : null}
104
125
  </section>
@@ -9,9 +9,10 @@
9
9
  *
10
10
  * User flows:
11
11
  * - filter the ledger by channel and reason, page through it, see how many are suppressed on each channel
12
- * - Suppress contacts: paste ids, or pick contacts from the CRM, choose the channel (default all) and a reason
12
+ * - Suppress contacts: search and tick people (ContactPicker), or pick them from the CRM table, or paste ids under
13
+ * "Paste ids instead"; choose the channel (default all) and a reason
13
14
  * - open a ledger row: the contact's record plus its live suppression status (read-only check)
14
- * - check any contact id without opening it
15
+ * - check any contact without opening it (pick them by name; no ids to type)
15
16
  * - tick rows and Reinstate (confirmed: it lets sequences, campaigns and newsletters contact them again)
16
17
  *
17
18
  * SDK operations, in order:
@@ -24,7 +25,7 @@
24
25
 
25
26
  import { useEffect, useState } from "react";
26
27
  import type { ReactNode } from "react";
27
- import { BadgeCheck, Ban, Search, ShieldOff, Undo2 } from "lucide-react";
28
+ import { BadgeCheck, Ban, ShieldOff, Undo2 } from "lucide-react";
28
29
  import { Button, Input, Label, Skeleton, Textarea } from "@graph8/sdk/ui";
29
30
  import { ApiError } from "@/g8/components/api-error";
30
31
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
@@ -37,6 +38,8 @@ import { SectionCard } from "@/g8/components/screen/section-card";
37
38
  import { SplitView } from "@/g8/components/screen/split-view";
38
39
  import { StatusPill } from "@/g8/components/screen/status-pill";
39
40
  import { Toolbar } from "@/g8/components/screen/toolbar";
41
+ import { ContactPicker } from "@/g8/pickers/components/contact-picker";
42
+ import type { ContactOption } from "@/g8/pickers/components/contact-picker";
40
43
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
41
44
  import { useG8Query } from "@/g8/hooks/use-g8-query";
42
45
  import { useUrlState } from "@/g8/hooks/use-url-state";
@@ -85,6 +88,7 @@ export function SuppressionsScreen({ pageSize = 50, toolbarActions, renderRowAct
85
88
  const full = view === "full";
86
89
  const [adding, setAdding] = useState(false);
87
90
  const [idsText, setIdsText] = useState("");
91
+ const [pickedContacts, setPickedContacts] = useState<ContactOption[]>([]);
88
92
  const [addChannel, setAddChannel] = useState("all");
89
93
  const [reason, setReason] = useState("");
90
94
  const [picked, setPicked] = useState<Set<number>>(new Set());
@@ -92,8 +96,7 @@ export function SuppressionsScreen({ pageSize = 50, toolbarActions, renderRowAct
92
96
  /** Set when Reinstate is started from a row's panel: the confirm acts on that contact only and leaves the ticked rows alone. */
93
97
  const [panelTarget, setPanelTarget] = useState<number | null>(null);
94
98
  const [reinstateReason, setReinstateReason] = useState("");
95
- const [checkId, setCheckId] = useState("");
96
- const [checked, setChecked] = useState<number | null>(null);
99
+ const [checked, setChecked] = useState<ContactOption | null>(null);
97
100
 
98
101
  const ledger = useG8Query((signal) => g8Get<SuppressionRecord[]>("/contacts/suppressions", { channel: channel || undefined, category: category || undefined, limit: pageSize, offset }, { signal }), [channel, category, offset, pageSize]);
99
102
  const rows = (ledger.data?.data ?? []) as SuppressionRecord[];
@@ -101,13 +104,13 @@ export function SuppressionsScreen({ pageSize = 50, toolbarActions, renderRowAct
101
104
  useEffect(() => { setPicked((p) => new Set([...p].filter((id) => rows.some((r) => r.contact_id === id)))); }, [ledger.data]); // eslint-disable-line react-hooks/exhaustive-deps
102
105
 
103
106
  const add = useG8Mutation((v: { ids: number[] }) => g8Post<BulkAdd>("/contacts/suppressions/bulk-add", { contact_ids: v.ids, channel: addChannel, reason: reason.trim() || undefined }), {
104
- onSuccess: () => { setIdsText(""); setReason(""); void ledger.refetch(); },
107
+ onSuccess: () => { setIdsText(""); setPickedContacts([]); setReason(""); void ledger.refetch(); },
105
108
  });
106
109
  const reinstate = useG8Mutation((ids: number[]) => g8Post<Reinstate>("/contacts/suppressions/reinstate", { contact_ids: ids, reason: reinstateReason.trim() || undefined }), {
107
110
  onSuccess: () => { setConfirmReinstate(false); setReinstateReason(""); if (panelTarget != null) { const done = panelTarget; setPicked((p) => { const n = new Set(p); n.delete(done); return n; }); } else setPicked(new Set()); setPanelTarget(null); void ledger.refetch(); },
108
111
  });
109
112
 
110
- const ids = parseIds(idsText);
113
+ const ids = [...new Set([...pickedContacts.map((c) => c.id), ...parseIds(idsText)])];
111
114
  const chosen = panelTarget != null ? [panelTarget] : [...picked];
112
115
  const togglePick = (id: number) => setPicked((p) => { const n = new Set(p); if (!n.delete(id)) n.add(id); return n; });
113
116
  const byChannel = (c: string) => rows.filter((r) => r.channel === c).length;
@@ -205,9 +208,8 @@ export function SuppressionsScreen({ pageSize = 50, toolbarActions, renderRowAct
205
208
  <select aria-label="Filter by reason" className={SELECT} value={category} onChange={(e) => { setCategory(e.target.value); resetPage(); }}>
206
209
  <option value="">Any reason</option><option value="contact_initiated">Contact asked</option><option value="org_initiated">Our team</option><option value="system">System</option>
207
210
  </select>
208
- <Input aria-label="Check a contact id" inputMode="numeric" placeholder="Check contact id" className="w-40" value={checkId} onChange={(e) => setCheckId(e.target.value)} />
209
- <Button variant="outline" size="sm" disabled={!Number(checkId)} onClick={() => setChecked(Number(checkId))}><Search className="mr-1 size-4" aria-hidden />Check</Button>
210
- {checked ? <span className="flex items-center gap-1"><ContactStatusPanel key={checked} contactId={checked} /><span className="text-xs text-muted-foreground">#{checked}</span></span> : null}
211
+ <ContactPicker label="Contact to check" placeholder="Check a contact" value={checked} onChange={setChecked} />
212
+ {checked ? <span className="flex items-center gap-1"><ContactStatusPanel key={checked.id} contactId={checked.id} /><span className="text-xs text-muted-foreground">{checked.name}</span></span> : null}
211
213
  </>)}
212
214
  actions={<Button variant="outline" size="sm" disabled={chosen.length === 0} onClick={() => { setPanelTarget(null); setConfirmReinstate(true); }}><BadgeCheck className="mr-1 size-4" aria-hidden />Reinstate {chosen.length || ""}</Button>}
213
215
  />
@@ -224,8 +226,15 @@ export function SuppressionsScreen({ pageSize = 50, toolbarActions, renderRowAct
224
226
  <div className="flex min-w-40 flex-1 flex-col gap-1"><Label htmlFor="suppress-reason">Reason (optional)</Label><Input id="suppress-reason" value={reason} onChange={(e) => setReason(e.target.value)} /></div>
225
227
  </div>
226
228
  <div className="flex flex-col gap-2">
227
- <Label htmlFor="suppress-ids">Contact ids</Label>
228
- <Textarea id="suppress-ids" rows={3} placeholder="101, 102, 103" value={idsText} onChange={(e) => setIdsText(e.target.value)} />
229
+ <span className="text-sm font-medium leading-none">Search and add people</span>
230
+ <ContactPicker multiple label="Contacts to suppress" selected={pickedContacts} onSelectedChange={setPickedContacts} />
231
+ <details className="text-sm">
232
+ <summary className="cursor-pointer text-muted-foreground">Paste ids instead</summary>
233
+ <div className="mt-1 flex flex-col gap-1">
234
+ <Label htmlFor="suppress-ids">Contact ids</Label>
235
+ <Textarea id="suppress-ids" rows={3} placeholder="101, 102, 103" value={idsText} onChange={(e) => setIdsText(e.target.value)} />
236
+ </div>
237
+ </details>
229
238
  <Button size="sm" loading={add.loading} disabled={ids.length === 0} onClick={() => void add.mutate({ ids })}>{ids.length ? `Suppress ${plural(ids.length, "contact")}` : "Suppress contacts"}</Button>
230
239
  </div>
231
240
  <div className="flex flex-col gap-2">
@@ -157,6 +157,10 @@
157
157
  {
158
158
  "file": "lib/mailboxes-utils.ts",
159
159
  "purpose": "Mailbox and domain row types, credit estimate constants, status colors and idempotency-key helper."
160
+ },
161
+ {
162
+ "file": "lib/use-provider-domains.ts",
163
+ "purpose": "Every sending domain, read page after page (the domains API pages) instead of one capped call."
160
164
  }
161
165
  ],
162
166
  "recipes": [
@@ -25,6 +25,7 @@ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
25
  import { ApiError } from "@/g8/components/api-error";
26
26
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
27
27
  import { DEFAULT_MAILBOX_CREDITS, GRAPH8_APP_MAILBOXES_PATH, listOf, newIdempotencyKey, objectOf } from "../lib/mailboxes-utils";
28
+ import { useProviderDomains } from "../lib/use-provider-domains";
28
29
  import type { DomainRow } from "../lib/mailboxes-utils";
29
30
  import { plural, pluralWord } from "@/g8/lib/plural";
30
31
  import { brandUrl, useBrand } from "@/g8/hooks/use-brand";
@@ -135,7 +136,7 @@ function ImapForm({ onConnected }: { onConnected?: (id: number | string) => void
135
136
  </Button>
136
137
  {connect.error ? <ApiError error={connect.error} onRetry={connect.reset} /> : null}
137
138
  {result ? (
138
- <p role="status" className="text-sm text-gray-700">
139
+ <p role="status" className="text-sm text-foreground">
139
140
  {result}
140
141
  </p>
141
142
  ) : null}
@@ -145,7 +146,7 @@ function ImapForm({ onConnected }: { onConnected?: (id: number | string) => void
145
146
 
146
147
  function BuyForm({ onPurchased, domainId: initialDomainId }: { onPurchased?: (id: number | string) => void; domainId?: number | string }) {
147
148
  const brand = useBrand();
148
- const domains = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 100 }, { signal }), []);
149
+ const domains = useProviderDomains([]);
149
150
  const pricing = useG8Query((signal) => g8Get("/provider-domains/pricing", undefined, { signal }), []);
150
151
  const [domainId, setDomainId] = useState(initialDomainId != null ? String(initialDomainId) : "");
151
152
  const [usernames, setUsernames] = useState("");
@@ -170,19 +171,19 @@ function BuyForm({ onPurchased, domainId: initialDomainId }: { onPurchased?: (id
170
171
  setDone(`Accepted ${plural(Number(res.count ?? names.length), "mailbox", "mailboxes")}; provisioning continues in the background.`);
171
172
  onPurchased?.(domainId);
172
173
  });
173
- const connected = listOf<DomainRow>(domains.data).filter((d) => d.provider_connected);
174
+ const connected = domains.items.filter((d) => d.provider_connected);
174
175
  const ready = domainId && names.length > 0 && person.first_name.trim() && person.last_name.trim();
175
176
 
176
177
  return (
177
178
  <div className="space-y-3">
178
179
  {domains.error ? <ApiError error={domains.error} onRetry={() => void domains.refetch()} /> : null}
179
180
  {!domains.loading && connected.length === 0 ? (
180
- <p className="text-sm text-gray-600">No connected domain yet. Buy and connect one with DomainPurchase first.</p>
181
+ <p className="text-sm text-muted-foreground">No connected domain yet. Buy and connect one with DomainPurchase first.</p>
181
182
  ) : (
182
183
  <div className="grid gap-2 sm:grid-cols-2">
183
184
  <select
184
185
  aria-label="Domain"
185
- className="h-9 rounded-md border border-gray-300 px-2 text-sm"
186
+ className="h-9 rounded-md border border-input px-2 text-sm"
186
187
  value={domainId}
187
188
  onChange={(e) => setDomainId(e.target.value)}
188
189
  >
@@ -195,7 +196,7 @@ function BuyForm({ onPurchased, domainId: initialDomainId }: { onPurchased?: (id
195
196
  </select>
196
197
  <select
197
198
  aria-label="Platform"
198
- className="h-9 rounded-md border border-gray-300 px-2 text-sm"
199
+ className="h-9 rounded-md border border-input px-2 text-sm"
199
200
  value={platform}
200
201
  onChange={(e) => setPlatform(e.target.value as "GOOGLE" | "MICROSOFT")}
201
202
  >
@@ -231,7 +232,7 @@ function BuyForm({ onPurchased, domainId: initialDomainId }: { onPurchased?: (id
231
232
  </Button>
232
233
  {buy.error ? <ApiError error={buy.error} onRetry={buy.reset} /> : null}
233
234
  {done ? (
234
- <p role="status" className="text-sm text-green-700">
235
+ <p role="status" className="text-sm text-positive-foreground">
235
236
  {done}
236
237
  </p>
237
238
  ) : null}
@@ -276,7 +277,7 @@ export function ConnectMailbox({ appUrl: appUrlProp, onConnected, onPurchased, i
276
277
  </div>
277
278
  {mode === "imap" ? <ImapForm onConnected={onConnected} /> : null}
278
279
  {mode === "oauth" ? (
279
- <div className="space-y-2 rounded border border-gray-200 p-3 text-sm">
280
+ <div className="space-y-2 rounded border border-border p-3 text-sm">
280
281
  <p>
281
282
  Google and Microsoft mailboxes connect through a sign-in screen that only runs inside the {brand.linkLabel} app. Connect it there, then
282
283
  come back: it appears in your mailbox list.
@@ -18,6 +18,7 @@ import { useState } from "react";
18
18
  import type { ReactNode } from "react";
19
19
  import { Button, Skeleton } from "@graph8/sdk/ui";
20
20
  import { ApiError } from "@/g8/components/api-error";
21
+ import { FirstNotice } from "@/g8/components/paging-notices";
21
22
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
22
23
  import { DetailPanel } from "@/g8/components/screen/detail-panel";
23
24
  import { KeyValueList } from "@/g8/components/screen/key-value-list";
@@ -27,6 +28,7 @@ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
27
28
  import { useG8Query } from "@/g8/hooks/use-g8-query";
28
29
  import { g8Get, g8Post } from "@/g8/lib/g8-client";
29
30
  import { fieldItems, listOf, objectOf } from "../lib/mailboxes-utils";
31
+ import { useProviderDomains } from "../lib/use-provider-domains";
30
32
  import type { DomainRow, MailboxRow } from "../lib/mailboxes-utils";
31
33
 
32
34
  export interface DomainDetailProps {
@@ -46,13 +48,16 @@ export interface DomainDetailProps {
46
48
 
47
49
  type Pending = "connect" | "propagation";
48
50
 
51
+ /** The mailboxes API has no paging: a full read is announced where the mailboxes are listed. */
52
+ const MAILBOX_LIMIT = 200;
53
+
49
54
  export function DomainDetail({ domainId, onClose, onExpand, onCollapse, onChanged, onBuyMailboxes, onOpenMailbox, renderActions, className }: DomainDetailProps) {
50
55
  const [pending, setPending] = useState<Pending | null>(null);
51
56
  const [note, setNote] = useState<string | null>(null);
52
- const domains = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 100 }, { signal }), [domainId]);
53
- const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: 200 }, { signal }), [domainId]);
57
+ const domains = useProviderDomains([domainId]);
58
+ const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: MAILBOX_LIMIT }, { signal }), [domainId]);
54
59
 
55
- const row = listOf<DomainRow>(domains.data).find((d) => String(d.id) === String(domainId));
60
+ const row = domains.items.find((d) => String(d.id) === String(domainId));
56
61
  const run = useG8Mutation(async (p: Pending) => {
57
62
  if (p === "connect") {
58
63
  await g8Post(`/provider-domains/${domainId}/connect-provider`, {});
@@ -67,7 +72,7 @@ export function DomainDetail({ domainId, onClose, onExpand, onCollapse, onChange
67
72
  }, { onError: () => setPending(null) });
68
73
 
69
74
  if (domains.error) return <ApiError error={domains.error} title="Could not load the domain" onRetry={() => void domains.refetch()} className={className} />;
70
- if (domains.loading && !domains.data) return <Skeleton className={`h-48 w-full ${className ?? ""}`} />;
75
+ if (domains.loading && !domains.loaded) return <Skeleton className={`h-48 w-full ${className ?? ""}`} />;
71
76
  if (!row) return <p className={`p-4 text-sm ${className ?? ""}`}>This domain no longer exists.</p>;
72
77
 
73
78
  const onDomain = listOf<MailboxRow>(mailboxes.data).filter((m) => (m.email ?? "").toLowerCase().endsWith(`@${row.domain.toLowerCase()}`));
@@ -133,6 +138,7 @@ export function DomainDetail({ domainId, onClose, onExpand, onCollapse, onChange
133
138
  ))}
134
139
  </ul>
135
140
  )}
141
+ <FirstNotice shown={listOf<MailboxRow>(mailboxes.data).length} limit={MAILBOX_LIMIT} noun="mailboxes" />
136
142
  </SectionCard>
137
143
  ),
138
144
  },
@@ -24,9 +24,11 @@ import { g8Get, g8Post } from "@/g8/lib/g8-client";
24
24
  import { useG8Query } from "@/g8/hooks/use-g8-query";
25
25
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
26
26
  import { ApiError } from "@/g8/components/api-error";
27
+ import { FirstNotice } from "@/g8/components/paging-notices";
27
28
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
28
29
  import { EmptyState } from "@/g8/components/empty-state";
29
30
  import { DEFAULT_DOMAIN_CREDITS, listOf, newIdempotencyKey, objectOf, statusClass } from "../lib/mailboxes-utils";
31
+ import { useProviderDomains } from "../lib/use-provider-domains";
30
32
  import type { DomainRow } from "../lib/mailboxes-utils";
31
33
  import { plural } from "@/g8/lib/plural";
32
34
  import { useBrand } from "@/g8/hooks/use-brand";
@@ -66,7 +68,7 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
66
68
  const [note, setNote] = useState<string | null>(null);
67
69
  const idemKey = useRef<{ sig: string; key: string } | null>(null);
68
70
 
69
- const domains = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 50 }, { signal }), []);
71
+ const domains = useProviderDomains([]);
70
72
  const suggest = useG8Mutation(async () => {
71
73
  setNames(suggestionsOf(await g8Post("/provider-domains/generate-domains", { keyword: keyword.trim(), count: 10 })));
72
74
  setSelected([]);
@@ -106,7 +108,7 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
106
108
  const toggle = (d: string) => setSelected((s) => (s.includes(d) ? s.filter((x) => x !== d) : [...s, d]));
107
109
  const buyable = selected.filter((d) => avail[d]?.available);
108
110
  const contactOk = contact.first_name.trim() && contact.last_name.trim() && /\S+@\S+/.test(contact.email);
109
- const rows = listOf<DomainRow>(domains.data);
111
+ const rows = domains.items;
110
112
 
111
113
  const dialog = (() => {
112
114
  if (!pending) return null;
@@ -141,8 +143,8 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
141
143
 
142
144
  return (
143
145
  <div className={`space-y-5 ${className ?? ""}`}>
144
- <section className="space-y-2 rounded border border-gray-200 p-3">
145
- <h3 className="text-sm font-semibold">Find a domain</h3>
146
+ <section className="space-y-2 rounded border border-border p-3">
147
+ <h3 className="text-sm font-medium">Find a domain</h3>
146
148
  <form
147
149
  className="flex gap-2"
148
150
  onSubmit={(e) => {
@@ -165,7 +167,7 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
165
167
  {d}
166
168
  {avail[d] ? (
167
169
  <span
168
- className={`rounded px-1.5 text-xs ${avail[d].available ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}`}
170
+ className={`rounded px-1.5 text-xs ${avail[d].available ? "bg-positive text-positive-foreground" : "bg-error text-error-foreground"}`}
169
171
  >
170
172
  {avail[d].available
171
173
  ? `available${avail[d].price != null ? ` ${avail[d].price}` : ""}`
@@ -189,8 +191,8 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
189
191
  </Button>
190
192
  </section>
191
193
 
192
- <section className="space-y-2 rounded border border-gray-200 p-3">
193
- <h3 className="text-sm font-semibold">Registrant</h3>
194
+ <section className="space-y-2 rounded border border-border p-3">
195
+ <h3 className="text-sm font-medium">Registrant</h3>
194
196
  <div className="grid gap-2 sm:grid-cols-3">
195
197
  <Input
196
198
  aria-label="First name"
@@ -217,7 +219,7 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
217
219
  </section>
218
220
 
219
221
  {note ? (
220
- <p role="status" className="text-sm text-gray-700">
222
+ <p role="status" className="text-sm text-foreground">
221
223
  {note}
222
224
  </p>
223
225
  ) : null}
@@ -226,18 +228,18 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
226
228
  <section aria-label="Your domains">
227
229
  {domains.error ? (
228
230
  <ApiError error={domains.error} onRetry={() => void domains.refetch()} />
229
- ) : domains.loading && !domains.data ? (
231
+ ) : domains.loading && !domains.loaded ? (
230
232
  <Skeleton className="h-16 w-full" />
231
233
  ) : rows.length === 0 ? (
232
234
  <EmptyState title="No sending domains yet" description={`Buy one above, or add a domain you already own in the ${brand.linkLabel} app.`} />
233
235
  ) : (
234
- <ul className="divide-y rounded border border-gray-200">
236
+ <ul className="divide-y rounded border border-border">
235
237
  {rows.map((d) => (
236
238
  <li key={d.id} className="flex flex-wrap items-center justify-between gap-2 px-3 py-2 text-sm">
237
239
  <span>
238
240
  <span className="font-medium">{d.domain}</span>{" "}
239
241
  <span className={`rounded px-1.5 text-xs ${statusClass(d.status)}`}>{d.status ?? "unknown"}</span>{" "}
240
- <span className="text-xs text-gray-500">{d.mailbox_count ?? 0} mailboxes</span>
242
+ <span className="text-xs text-muted-foreground">{d.mailbox_count ?? 0} mailboxes</span>
241
243
  </span>
242
244
  <span className="flex gap-1">
243
245
  {!d.provider_connected && !d.is_external ? (
@@ -254,6 +256,7 @@ export function DomainPurchase({ onPurchased, className }: DomainPurchaseProps)
254
256
  ))}
255
257
  </ul>
256
258
  )}
259
+ <FirstNotice shown={rows.length} limit={rows.length} force={domains.truncated} noun="domains" />
257
260
  </section>
258
261
 
259
262
  {pending && dialog ? (
@@ -29,6 +29,7 @@ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
29
29
  import { useG8Query } from "@/g8/hooks/use-g8-query";
30
30
  import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
31
31
  import { fieldItems, listOf, objectOf } from "../lib/mailboxes-utils";
32
+ import { useProviderDomains } from "../lib/use-provider-domains";
32
33
  import type { DomainRow, MailboxRow } from "../lib/mailboxes-utils";
33
34
  import { WarmupHealth } from "./warmup-health";
34
35
  import { useBrand } from "@/g8/hooks/use-brand";
@@ -59,7 +60,7 @@ export function MailboxDetail({ mailboxId, tab, onTabChange, onClose, onExpand,
59
60
  const [pending, setPending] = useState<Pending | null>(null);
60
61
  const [testNote, setTestNote] = useState<string | null>(null);
61
62
  const q = useG8Query((signal) => g8Get(`/mailboxes/${mailboxId}`, undefined, { signal }), [mailboxId]);
62
- const domains = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 100 }, { signal }), []);
63
+ const domains = useProviderDomains([]);
63
64
 
64
65
  const test = useG8Mutation(async () => {
65
66
  const r = objectOf(await g8Post(`/mailboxes/${mailboxId}/test-connection`, {})) as TestResult;
@@ -84,7 +85,7 @@ export function MailboxDetail({ mailboxId, tab, onTabChange, onClose, onExpand,
84
85
  const raw = objectOf(q.data);
85
86
  const m = { id: String(mailboxId), email: null, ...raw } as MailboxRow;
86
87
  const domain = (m.email ?? "").split("@")[1]?.toLowerCase();
87
- const domainRow = listOf<DomainRow>(domains.data).find((d) => d.domain?.toLowerCase() === domain);
88
+ const domainRow = domains.items.find((d) => d.domain?.toLowerCase() === domain);
88
89
  const status = m.connection_status ?? "unknown";
89
90
  const name = m.email ?? `Mailbox ${mailboxId}`;
90
91
 
@@ -22,9 +22,13 @@ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
22
22
  import { ApiError } from "@/g8/components/api-error";
23
23
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
24
24
  import { EmptyState } from "@/g8/components/empty-state";
25
+ import { FirstNotice } from "@/g8/components/paging-notices";
25
26
  import { listOf, objectOf, statusClass } from "../lib/mailboxes-utils";
26
27
  import type { MailboxRow } from "../lib/mailboxes-utils";
27
28
 
29
+ /** The read cap. The mailboxes API has no paging, so a full read is announced ("Showing the first 200"). */
30
+ const MAILBOX_LIMIT = 200;
31
+
28
32
  export interface MailboxListProps {
29
33
  includeArchived?: boolean;
30
34
  /** Called when a row is opened (for example to show WarmupHealth for it). */
@@ -47,7 +51,7 @@ export function MailboxList({ includeArchived = false, onSelect, emptyAction, cl
47
51
  const [tests, setTests] = useState<Record<string, string>>({});
48
52
  const [testing, setTesting] = useState<ReadonlySet<string>>(new Set());
49
53
  const list = useG8Query(
50
- (signal) => g8Get("/mailboxes", { limit: 200, include_archived: includeArchived }, { signal }),
54
+ (signal) => g8Get("/mailboxes", { limit: MAILBOX_LIMIT, include_archived: includeArchived }, { signal }),
51
55
  [includeArchived],
52
56
  );
53
57
 
@@ -114,13 +118,13 @@ export function MailboxList({ includeArchived = false, onSelect, emptyAction, cl
114
118
  <button type="button" className="text-left font-medium hover:underline" onClick={() => onSelect?.(m)}>
115
119
  {m.email ?? `Mailbox ${m.id}`}
116
120
  </button>
117
- <div className="text-xs text-gray-500">
121
+ <div className="text-xs text-muted-foreground">
118
122
  {m.display_name ?? ""}
119
123
  {m.provider ? ` - ${m.provider}` : ""}
120
124
  {m.is_archived ? " - archived" : ""}
121
125
  </div>
122
126
  {tests[m.id] ? (
123
- <div role="status" className="text-xs text-gray-700">
127
+ <div role="status" className="text-xs text-foreground">
124
128
  {tests[m.id]}
125
129
  </div>
126
130
  ) : null}
@@ -163,6 +167,7 @@ export function MailboxList({ includeArchived = false, onSelect, emptyAction, cl
163
167
  ))}
164
168
  </TableBody>
165
169
  </Table>
170
+ <FirstNotice shown={rows.length} limit={MAILBOX_LIMIT} noun="mailboxes" />
166
171
  {pending ? (
167
172
  <ConfirmSpendDialog
168
173
  open
@@ -38,7 +38,9 @@ import { useUrlState } from "@/g8/hooks/use-url-state";
38
38
  import { useG8Query } from "@/g8/hooks/use-g8-query";
39
39
  import { g8Get } from "@/g8/lib/g8-client";
40
40
  import { listOf, objectOf } from "../lib/mailboxes-utils";
41
+ import { useProviderDomains } from "../lib/use-provider-domains";
41
42
  import type { DomainRow, MailboxRow } from "../lib/mailboxes-utils";
43
+ import { FirstNotice } from "@/g8/components/paging-notices";
42
44
  import { ConnectMailbox } from "./connect-mailbox";
43
45
  import { DomainPurchase } from "./domain-purchase";
44
46
  import { DomainDetail } from "./domain-detail";
@@ -83,11 +85,12 @@ export function fleetStats(summary: Record<string, unknown>): StatItem[] {
83
85
  }
84
86
 
85
87
  function DomainList({ version, selectedId, onOpen }: { version: number; selectedId: string; onOpen: (d: DomainRow) => void }) {
86
- const q = useG8Query((signal) => g8Get("/provider-domains", { page: 1, limit: 100 }, { signal }), [version]);
87
- const rows = listOf<DomainRow>(q.data);
88
- if (q.loading && !q.data) return <Skeleton className="h-24 w-full" />;
88
+ const q = useProviderDomains([version]);
89
+ const rows = q.items;
90
+ if (q.loading && !q.loaded) return <Skeleton className="h-24 w-full" />;
89
91
  if (rows.length === 0) return null;
90
92
  return (
93
+ <>
91
94
  <ul aria-label="Your domains" className="divide-y rounded-md border">
92
95
  {rows.map((d) => (
93
96
  <li key={d.id} className={`flex items-center justify-between px-3 py-2 text-sm ${String(d.id) === selectedId ? "bg-muted" : ""}`}>
@@ -99,6 +102,8 @@ function DomainList({ version, selectedId, onOpen }: { version: number; selected
99
102
  </li>
100
103
  ))}
101
104
  </ul>
105
+ <FirstNotice shown={rows.length} limit={rows.length} force={q.truncated} noun="domains" />
106
+ </>
102
107
  );
103
108
  }
104
109