@graph8/sdk 0.362.0 → 0.373.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 (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -0,0 +1,110 @@
1
+ "use client";
2
+
3
+ /**
4
+ * PhoneNumberList: the workspace's numbers as a table, paged on the client with a "Load more" so a long inventory is never
5
+ * cut off silently. It reads nothing itself: the screen passes the filtered rows from usePhoneNumbers.
6
+ *
7
+ * SDK operations: none (rows come from usePhoneNumbers: listDialerNumbers + listAgentPhoneNumbers).
8
+ */
9
+
10
+ import { useEffect, useState } from "react";
11
+ import type { ReactNode } from "react";
12
+ import { Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
13
+ import { ApiError } from "@/g8/components/api-error";
14
+ import { EmptyState } from "@/g8/components/empty-state";
15
+ import { LoadMore } from "@/g8/components/paging-notices";
16
+ import { RecordLink } from "@/g8/components/screen/record-link";
17
+ import { StatusPill } from "@/g8/components/screen/status-pill";
18
+ import { rowActivationProps } from "@/g8/hooks/use-row-activation";
19
+ import { formatNumber, numberHealth, typeLabel } from "../lib/phone-numbers-utils";
20
+ import type { NumberRow } from "../lib/phone-numbers-utils";
21
+
22
+ export const PAGE_SIZE = 25;
23
+ const COLUMNS = 6;
24
+
25
+ export interface PhoneNumberListProps {
26
+ rows: NumberRow[];
27
+ loading?: boolean;
28
+ error?: unknown;
29
+ onRetry?: () => void;
30
+ /** The open number (E.164). */
31
+ selected?: string;
32
+ onSelect: (row: NumberRow) => void;
33
+ /** Extra buttons in each row's last cell. */
34
+ renderRowActions?: (row: NumberRow) => ReactNode;
35
+ /** Shown when there are no rows at all. */
36
+ emptyAction?: ReactNode;
37
+ /** Shown instead of the empty state when filters hid every row. */
38
+ filtered?: boolean;
39
+ /** Bump to go back to the first page (filters changed). */
40
+ resetKey?: string;
41
+ className?: string;
42
+ }
43
+
44
+ export function PhoneNumberList({ rows, loading, error, onRetry, selected, onSelect, renderRowActions, emptyAction, filtered, resetKey, className }: PhoneNumberListProps) {
45
+ const [shown, setShown] = useState(PAGE_SIZE);
46
+ useEffect(() => setShown(PAGE_SIZE), [resetKey]);
47
+
48
+ if (error) return <ApiError error={error} onRetry={onRetry} className={className} />;
49
+ if (loading && rows.length === 0) {
50
+ return (
51
+ <Table className={className}>
52
+ <TableBody>
53
+ {Array.from({ length: 5 }, (_, i) => (
54
+ <TableRow key={i}>
55
+ <TableCell colSpan={COLUMNS}><Skeleton className="h-5 w-full" /></TableCell>
56
+ </TableRow>
57
+ ))}
58
+ </TableBody>
59
+ </Table>
60
+ );
61
+ }
62
+ if (rows.length === 0) {
63
+ return filtered ? (
64
+ <EmptyState className={className} title="No numbers match" description="Clear a filter or change the search to see more." />
65
+ ) : (
66
+ <EmptyState className={className} title="No phone numbers yet" description="Buy a number to call from, to give a voice agent, or as a rep's work phone." action={emptyAction} />
67
+ );
68
+ }
69
+ const visible = rows.slice(0, shown);
70
+
71
+ return (
72
+ <div className={`space-y-2 ${className ?? ""}`}>
73
+ <Table>
74
+ <TableHeader>
75
+ <TableRow>
76
+ <TableHead>Number</TableHead>
77
+ <TableHead>Use</TableHead>
78
+ <TableHead>Assigned to</TableHead>
79
+ <TableHead>Carrier</TableHead>
80
+ <TableHead className="text-right">Calls today</TableHead>
81
+ <TableHead>Status</TableHead>
82
+ </TableRow>
83
+ </TableHeader>
84
+ <TableBody>
85
+ {visible.map((r) => {
86
+ const health = numberHealth(r);
87
+ return (
88
+ <TableRow key={r.number} data-state={selected === r.number ? "selected" : undefined} className="cursor-pointer" {...rowActivationProps(() => onSelect(r))}>
89
+ <TableCell className="font-medium tabular-nums">
90
+ <RecordLink param="id" id={r.number} onOpen={() => onSelect(r)}>{formatNumber(r.number)}</RecordLink>
91
+ </TableCell>
92
+ <TableCell>{typeLabel(r.inboundType)}{r.agentName ? <span className="block text-xs text-muted-foreground">{r.agentName}</span> : null}</TableCell>
93
+ <TableCell>{r.assignedTo ?? <span className="text-muted-foreground">-</span>}</TableCell>
94
+ <TableCell>{r.provider === "telnyx" ? "Telnyx" : r.provider === "twilio" ? "Twilio" : <span className="text-muted-foreground">-</span>}</TableCell>
95
+ <TableCell className="text-right tabular-nums">{r.callsToday ?? "-"}{r.dailyLimit ? <span className="text-xs text-muted-foreground"> / {r.dailyLimit}</span> : null}</TableCell>
96
+ <TableCell>
97
+ <span className="flex items-center justify-between gap-2">
98
+ <StatusPill tone={health.tone} dot>{health.label}</StatusPill>
99
+ {renderRowActions ? <span data-row-ignore>{renderRowActions(r)}</span> : null}
100
+ </span>
101
+ </TableCell>
102
+ </TableRow>
103
+ );
104
+ })}
105
+ </TableBody>
106
+ </Table>
107
+ <LoadMore shown={visible.length} noun={`of ${rows.length.toLocaleString()} numbers`} hasMore={visible.length < rows.length} onLoadMore={() => setShown((n) => n + PAGE_SIZE)} />
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,201 @@
1
+ "use client";
2
+
3
+ /**
4
+ * PhoneNumbersScreen: the finished "Phone numbers" page. Every number the workspace owns, who has it, what it is for, and
5
+ * how to get, assign, configure and give back numbers.
6
+ *
7
+ * Composes: PhoneNumberList, PhoneNumberDetail (Overview, Assign, Usage, Settings, Release), NumberSearch (search and
8
+ * buy), RegisterOwnedNumber, A2pRegistration.
9
+ *
10
+ * User flows:
11
+ * - See the inventory at a glance (stat strip), search it and filter by use, carrier, assignment and status. The list is
12
+ * paged on the client ("Load more"); the API returns the whole inventory, so nothing is cut off.
13
+ * - Open a number (deep link `?id=<number>`, `?tab=`, full page with `?view=full`): assign it to a teammate (MemberPicker,
14
+ * "Assign to me", unassign), see today's usage against the daily limit, change what answers it, verify a Telnyx caller
15
+ * ID, see who answers, remove its voice routing (confirmed) or release it (type the number, then confirm).
16
+ * - "Buy a number" opens a sheet: search a carrier by area code, digits or city, choose one, and buy it. The purchase is
17
+ * billable, so it opens ConfirmSpendDialog with the price first (an estimate: the API has no price read).
18
+ * - "Add a number you own" registers an owned number with the voice platform (spends nothing).
19
+ * - Text registration tab: A2P 10DLC status, a draft of the business details, a confirmed submit, and a retry for stuck numbers.
20
+ *
21
+ * SDK operations: none of its own. The list is read by usePhoneNumbers; billable, external and destructive calls live in the
22
+ * composed parts, each of which opens ConfirmSpendDialog itself.
23
+ *
24
+ * Billing: numbers the platform provisioned for you (the onboarding receptionist line) are never rental-billed, and nothing
25
+ * here labels any number as billable. The purchase dialog is the only place a price appears.
26
+ */
27
+
28
+ import { useState } from "react";
29
+ import type { ReactNode } from "react";
30
+ import { Phone, Plus } from "lucide-react";
31
+ import { Button } from "@graph8/sdk/ui";
32
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
33
+ import { SelectFilter } from "@/g8/components/screen/filter-controls";
34
+ import { ScreenLayout } from "@/g8/components/screen/screen-layout";
35
+ import { SplitView } from "@/g8/components/screen/split-view";
36
+ import type { StatItem } from "@/g8/components/screen/stat-strip";
37
+ import { Toolbar } from "@/g8/components/screen/toolbar";
38
+ import { useUrlFilters } from "@/g8/hooks/use-url-filters";
39
+ import { useUrlState } from "@/g8/hooks/use-url-state";
40
+ import { usePhoneNumbers } from "../hooks/use-phone-numbers";
41
+ import { digits, numberHealth, typeLabel, INBOUND_TYPES } from "../lib/phone-numbers-utils";
42
+ import type { NumberRow } from "../lib/phone-numbers-utils";
43
+ import { A2pRegistration } from "./a2p-registration";
44
+ import { NumberSearch } from "./number-search";
45
+ import { PhoneNumberDetail } from "./phone-number-detail";
46
+ import { PhoneNumberList } from "./phone-number-list";
47
+ import { RegisterOwnedNumber } from "./register-owned-number";
48
+
49
+ export interface PhoneNumbersScreenProps {
50
+ /** Extra buttons in the toolbar. */
51
+ toolbarActions?: ReactNode;
52
+ /** Extra buttons in each row. */
53
+ renderRowActions?: (row: NumberRow) => ReactNode;
54
+ /** Extra buttons in the open number's header. */
55
+ renderDetailActions?: (row: NumberRow) => ReactNode;
56
+ /** Extra tabs in the open number's panel. */
57
+ extraDetailTabs?: Array<{ id: string; label: string; render: (row: NumberRow) => ReactNode }>;
58
+ className?: string;
59
+ }
60
+
61
+ const TABS = [
62
+ { id: "numbers", label: "Numbers" },
63
+ { id: "texting", label: "Text registration" },
64
+ ];
65
+ const FILTERS = ["q", "use", "carrier", "assigned", "status"] as const;
66
+
67
+ /** Rows that match the filters (all client-side: the API returns the whole inventory). */
68
+ export function filterNumbers(rows: NumberRow[], f: Record<(typeof FILTERS)[number], string>): NumberRow[] {
69
+ const q = f.q.trim().toLowerCase();
70
+ const qDigits = digits(f.q);
71
+ return rows.filter((r) => {
72
+ if (q) {
73
+ const hay = [r.assignedTo, r.agentName, typeLabel(r.inboundType)].join(" ").toLowerCase();
74
+ if (!(hay.includes(q) || (qDigits && digits(r.number).includes(qDigits)))) return false;
75
+ }
76
+ if (f.use && r.inboundType !== f.use) return false;
77
+ if (f.carrier && r.provider !== f.carrier) return false;
78
+ if (f.assigned === "assigned" && !r.assignedTo) return false;
79
+ if (f.assigned === "unassigned" && r.assignedTo) return false;
80
+ if (f.status && numberHealth(r).label !== f.status) return false;
81
+ return true;
82
+ });
83
+ }
84
+
85
+ export function PhoneNumbersScreen({ toolbarActions, renderRowActions, renderDetailActions, extraDetailTabs, className }: PhoneNumbersScreenProps) {
86
+ const [tab, setTab] = useUrlState("tab", "numbers");
87
+ const [selected, setSelected] = useUrlState("id", "");
88
+ const [detailTab, setDetailTab] = useUrlState("dtab", "");
89
+ const [view, setView] = useUrlState("view", "");
90
+ const [sheet, setSheet] = useState<"buy" | "own" | null>(null);
91
+ const numbers = usePhoneNumbers();
92
+ const uf = useUrlFilters(FILTERS, { prefix: "n_" });
93
+ const rows = filterNumbers(numbers.rows, uf.filters);
94
+ const open = numbers.rows.find((r) => digits(r.number) === digits(selected));
95
+
96
+ const close = () => { setSelected(null); setDetailTab(null); setView(null); };
97
+ const openRow = (r: NumberRow) => { setSelected(r.number); setDetailTab(null); setView(null); };
98
+ const assigned = numbers.rows.filter((r) => r.assignedTo).length;
99
+ const stats: StatItem[] = numbers.loaded
100
+ ? [
101
+ { label: "Numbers", value: numbers.rows.length.toLocaleString() },
102
+ { label: "Assigned", value: assigned.toLocaleString() },
103
+ { label: "Unassigned", value: (numbers.rows.length - assigned).toLocaleString(), tone: numbers.rows.length - assigned > 0 ? "warning" : undefined },
104
+ { label: "Calls today", value: numbers.rows.reduce((n, r) => n + (r.callsToday ?? 0), 0).toLocaleString() },
105
+ ]
106
+ : [];
107
+
108
+ const actions = (
109
+ <>
110
+ <Button variant="outline" onClick={() => setSheet("own")}>Add a number you own</Button>
111
+ <Button onClick={() => setSheet("buy")}>
112
+ <Plus className="mr-1 h-4 w-4" aria-hidden /> Buy a number
113
+ </Button>
114
+ </>
115
+ );
116
+ const resetKey = JSON.stringify(uf.filters);
117
+
118
+ return (
119
+ <ScreenLayout
120
+ className={className}
121
+ title="Phone numbers"
122
+ description="The numbers you call from, answer on and text from: buy, assign, configure and release."
123
+ icon={<Phone className="h-5 w-5" aria-hidden />}
124
+ actions={actions}
125
+ tabs={TABS}
126
+ activeTab={TABS.some((t) => t.id === tab) ? tab : "numbers"}
127
+ onTabChange={(id) => { setTab(id === "numbers" ? null : id); close(); }}
128
+ stats={stats.length > 0 ? stats : undefined}
129
+ statsLoading={numbers.loading && !numbers.loaded}
130
+ >
131
+ {tab === "texting" ? (
132
+ <A2pRegistration />
133
+ ) : (
134
+ <div className="space-y-3">
135
+ <Toolbar
136
+ search={{ value: uf.draft.q, onChange: (v) => uf.set("q", v), debounceMs: 0, placeholder: "Search by number, person or agent" }}
137
+ actions={toolbarActions}
138
+ filters={
139
+ <>
140
+ <SelectFilter label="use" value={uf.draft.use} onChange={(v) => uf.set("use", v)} any="Any use" options={INBOUND_TYPES.map((t) => ({ value: t, label: typeLabel(t) }))} />
141
+ <SelectFilter label="carrier" value={uf.draft.carrier} onChange={(v) => uf.set("carrier", v)} any="Any carrier" options={[{ value: "twilio", label: "Twilio" }, { value: "telnyx", label: "Telnyx" }]} />
142
+ <SelectFilter label="assignment" value={uf.draft.assigned} onChange={(v) => uf.set("assigned", v)} any="Assigned or not" options={[{ value: "assigned", label: "Assigned" }, { value: "unassigned", label: "Unassigned" }]} />
143
+ <SelectFilter label="status" value={uf.draft.status} onChange={(v) => uf.set("status", v)} any="Any status" options={["Ready", "Near limit", "At daily limit", "Cannot dial"]} />
144
+ </>
145
+ }
146
+ />
147
+ {numbers.agentsUnavailable ? (
148
+ <p role="note" className="text-xs text-muted-foreground">
149
+ Numbers held by voice agents could not be read with this key, so they may be missing from the list.
150
+ </p>
151
+ ) : null}
152
+ <SplitView
153
+ expanded={view === "full"}
154
+ onExpandedChange={(e) => setView(e ? "full" : null)}
155
+ detailOpen={Boolean(selected)}
156
+ onCloseDetail={close}
157
+ list={
158
+ <PhoneNumberList
159
+ rows={rows}
160
+ loading={numbers.loading && !numbers.loaded}
161
+ error={numbers.error}
162
+ onRetry={numbers.refetch}
163
+ selected={open?.number}
164
+ onSelect={openRow}
165
+ renderRowActions={renderRowActions}
166
+ filtered={uf.active}
167
+ resetKey={resetKey}
168
+ emptyAction={<Button onClick={() => setSheet("buy")}>Buy a number</Button>}
169
+ />
170
+ }
171
+ detail={
172
+ selected && open ? (
173
+ <PhoneNumberDetail
174
+ key={open.number}
175
+ row={open}
176
+ tab={detailTab || undefined}
177
+ onTabChange={(t) => setDetailTab(t === "overview" ? null : t)}
178
+ onClose={close}
179
+ onExpand={view === "full" ? undefined : () => setView("full")}
180
+ onCollapse={view === "full" ? () => setView(null) : undefined}
181
+ onChanged={numbers.refetch}
182
+ onReleased={() => { close(); numbers.refetch(); }}
183
+ renderActions={renderDetailActions}
184
+ extraTabs={extraDetailTabs}
185
+ />
186
+ ) : selected && numbers.loaded ? (
187
+ <p className="p-4 text-sm text-muted-foreground">No number matches this link. It may have been released.</p>
188
+ ) : null
189
+ }
190
+ />
191
+ </div>
192
+ )}
193
+ <FormSheet width="xl" open={sheet === "buy"} onOpenChange={(o) => !o && setSheet(null)} title="Buy a number" description="Search for a number, choose one, and confirm the purchase.">
194
+ {sheet === "buy" ? <NumberSearch onPurchased={() => numbers.refetch()} /> : null}
195
+ </FormSheet>
196
+ <FormSheet width="md" open={sheet === "own"} onOpenChange={(o) => !o && setSheet(null)} title="Add a number you own" description="Register a number you already hold with the voice platform.">
197
+ {sheet === "own" ? <RegisterOwnedNumber onRegistered={() => { setSheet(null); numbers.refetch(); }} /> : null}
198
+ </FormSheet>
199
+ </ScreenLayout>
200
+ );
201
+ }
@@ -0,0 +1,98 @@
1
+ "use client";
2
+
3
+ /**
4
+ * RegisterOwnedNumber: wire a number you already own (bought outside, or whose voice half failed) into the voice platform.
5
+ *
6
+ * SDK operations:
7
+ * provisionVoiceNumber POST /voice/numbers/provision (write; BUYS NOTHING and charges nothing: it creates SIP trunks
8
+ * and dispatch rules for a number you already hold. Idempotent: a number already registered
9
+ * comes back as a success.)
10
+ *
11
+ * The person comes from MemberPicker, not a typed email. A personal work phone needs someone assigned, or no one can
12
+ * reach it by name.
13
+ */
14
+
15
+ import { useState } from "react";
16
+ import { Button, Input, Label } from "@graph8/sdk/ui";
17
+ import { g8Post } from "@/g8/lib/g8-client";
18
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
19
+ import { ApiError } from "@/g8/components/api-error";
20
+ import { MemberPicker } from "@/g8/pickers/components/member-picker";
21
+ import type { MemberOption } from "@/g8/pickers/components/member-picker";
22
+ import { INBOUND_LABEL, INBOUND_TYPES, PROVIDERS, formatNumber, isE164 } from "../lib/phone-numbers-utils";
23
+ import type { InboundType, Provider } from "../lib/phone-numbers-utils";
24
+
25
+ export interface RegisterOwnedNumberProps {
26
+ onRegistered?: (phoneNumber: string) => void;
27
+ className?: string;
28
+ }
29
+
30
+ const SELECT = "h-9 w-full rounded-md border border-border bg-background px-2 text-sm";
31
+
32
+ export function RegisterOwnedNumber({ onRegistered, className }: RegisterOwnedNumberProps) {
33
+ const [number, setNumber] = useState("");
34
+ const [provider, setProvider] = useState<Provider>("twilio");
35
+ const [mode, setMode] = useState<InboundType>("ai");
36
+ const [assignee, setAssignee] = useState<MemberOption | null>(null);
37
+ const [note, setNote] = useState<string | null>(null);
38
+ const valid = isE164(number);
39
+ const needsPerson = mode === "work" && !assignee?.email;
40
+
41
+ const register = useG8Mutation(async () => {
42
+ const n = number.trim();
43
+ await g8Post("/voice/numbers/provision", {
44
+ number: n,
45
+ provider,
46
+ inbound_type: mode,
47
+ dialer: mode === "dialer",
48
+ work: mode === "work",
49
+ ...(assignee?.email ? { assigned_to: assignee.email } : {}),
50
+ });
51
+ setNote(`${formatNumber(n)} is registered with the voice platform.`);
52
+ setNumber("");
53
+ onRegistered?.(n);
54
+ });
55
+
56
+ return (
57
+ <form
58
+ className={`space-y-4 ${className ?? ""}`}
59
+ onSubmit={(e) => {
60
+ e.preventDefault();
61
+ if (valid && !needsPerson) void register.mutate(undefined);
62
+ }}
63
+ >
64
+ <p className="text-sm text-muted-foreground">Use this for a number you already own. Nothing is bought and no credits are spent.</p>
65
+ <div className="space-y-1">
66
+ <Label htmlFor="ro-number">Phone number (E.164)</Label>
67
+ <Input id="ro-number" placeholder="+14155550123" value={number} onChange={(e) => setNumber(e.target.value)} aria-invalid={number !== "" && !valid} />
68
+ {number !== "" && !valid ? <p className="text-xs text-error">Use the international format: a plus sign, the country code, then the number.</p> : null}
69
+ </div>
70
+ <div className="grid gap-3 sm:grid-cols-2">
71
+ <div className="space-y-1">
72
+ <Label htmlFor="ro-provider">Carrier that holds it</Label>
73
+ <select id="ro-provider" className={SELECT} value={provider} onChange={(e) => setProvider(e.target.value as Provider)}>
74
+ {PROVIDERS.map((p) => (
75
+ <option key={p} value={p}>{p === "twilio" ? "Twilio" : "Telnyx"}</option>
76
+ ))}
77
+ </select>
78
+ </div>
79
+ <div className="space-y-1">
80
+ <Label htmlFor="ro-mode">How it answers</Label>
81
+ <select id="ro-mode" className={SELECT} value={mode} onChange={(e) => setMode(e.target.value as InboundType)}>
82
+ {INBOUND_TYPES.map((t) => (
83
+ <option key={t} value={t}>{INBOUND_LABEL[t]}</option>
84
+ ))}
85
+ </select>
86
+ </div>
87
+ </div>
88
+ <div className="space-y-1">
89
+ <Label htmlFor="ro-assignee">Assign to {mode === "work" ? "" : "(optional)"}</Label>
90
+ <MemberPicker id="ro-assignee" label="Assign to" value={assignee?.id ?? null} selectedName={assignee?.name} onChange={setAssignee} />
91
+ {needsPerson ? <p className="text-xs text-muted-foreground">A personal work phone needs a person, or no one can reach it by name.</p> : null}
92
+ </div>
93
+ {register.error ? <ApiError error={register.error} title="The number was not registered" onRetry={register.reset} /> : null}
94
+ {note ? <p role="status" className="text-sm">{note}</p> : null}
95
+ <Button type="submit" loading={register.loading} disabled={!valid || needsPerson}>Register number</Button>
96
+ </form>
97
+ );
98
+ }
@@ -0,0 +1,126 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ReleaseNumber: give a number back, or take it off the voice platform without giving it back.
5
+ *
6
+ * SDK operations:
7
+ * releasePhoneNumber POST /phone-numbers/release (destructive: IRREVERSIBLE. The number returns to the carrier's pool
8
+ * and anyone can buy it, including a competitor; calls and messages stop at once. Confirmed with
9
+ * ConfirmSpendDialog after the person types the number.)
10
+ * deleteVoiceNumber DELETE /voice/numbers (destructive: tears down the number's SIP trunks, dispatch rules and
11
+ * carrier routing. It does NOT release the number: you keep owning it and the carrier keeps billing
12
+ * it. Confirmed with ConfirmSpendDialog.)
13
+ *
14
+ * The carrier must match where the number was bought: releasing a Telnyx number as Twilio reaches the wrong provider. The
15
+ * list knows it for every dialer number; when it does not, the person chooses and the form says why.
16
+ */
17
+
18
+ import { useState } from "react";
19
+ import { Button, Input, Label } from "@graph8/sdk/ui";
20
+ import { g8Delete, g8Post } from "@/g8/lib/g8-client";
21
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
22
+ import { ApiError } from "@/g8/components/api-error";
23
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
24
+ import { PROVIDERS, dataOf, digits, formatNumber } from "../lib/phone-numbers-utils";
25
+ import type { NumberRow, Provider } from "../lib/phone-numbers-utils";
26
+
27
+ export interface ReleaseNumberProps {
28
+ row: NumberRow;
29
+ /** Called after the number was released (the record is gone: close its detail). */
30
+ onReleased?: () => void;
31
+ /** Called after the voice registration was removed (the number is still owned). */
32
+ onChanged?: () => void;
33
+ className?: string;
34
+ }
35
+
36
+ type Pending = "release" | "deregister";
37
+
38
+ export function ReleaseNumber({ row, onReleased, onChanged, className }: ReleaseNumberProps) {
39
+ const known = (PROVIDERS as readonly string[]).includes(row.provider);
40
+ const [provider, setProvider] = useState<Provider | "">(known ? (row.provider as Provider) : "");
41
+ const [typed, setTyped] = useState("");
42
+ const [pending, setPending] = useState<Pending | null>(null);
43
+ const [note, setNote] = useState<string | null>(null);
44
+
45
+ const release = useG8Mutation(async () => {
46
+ if (!provider) return;
47
+ const out = dataOf(await g8Post("/phone-numbers/release", { phone_number: row.number, provider }));
48
+ const detail = typeof out.message === "string" ? ` ${out.message}` : "";
49
+ setNote(`${formatNumber(row.number)} was released.${detail}`);
50
+ setPending(null);
51
+ onReleased?.();
52
+ });
53
+ const deregister = useG8Mutation(async () => {
54
+ await g8Delete("/voice/numbers", { body: { number: row.number, ...(provider ? { provider } : {}) } });
55
+ setNote(`${formatNumber(row.number)} was removed from the voice platform. You still own it.`);
56
+ setPending(null);
57
+ onChanged?.();
58
+ });
59
+
60
+ const matches = digits(typed) === digits(row.number);
61
+ const assignedNote = row.agentName ? ` The voice agent ${row.agentName} stops answering on it.` : "";
62
+
63
+ return (
64
+ <div className={`space-y-6 ${className ?? ""}`}>
65
+ {!known ? (
66
+ <div className="space-y-1">
67
+ <Label htmlFor="rn-provider">Carrier it was bought from</Label>
68
+ <select id="rn-provider" className="h-9 rounded-md border border-border bg-background px-2 text-sm" value={provider} onChange={(e) => setProvider(e.target.value as Provider | "")}>
69
+ <option value="">Choose the carrier</option>
70
+ {PROVIDERS.map((p) => (
71
+ <option key={p} value={p}>{p === "twilio" ? "Twilio" : "Telnyx"}</option>
72
+ ))}
73
+ </select>
74
+ <p className="text-xs text-muted-foreground">The carrier is not known for this number. It must match, or the wrong provider is asked to release it.</p>
75
+ </div>
76
+ ) : null}
77
+
78
+ <section className="space-y-2" aria-label="Release this number">
79
+ <h3 className="text-sm font-medium">Release this number</h3>
80
+ <p className="text-sm text-muted-foreground">
81
+ Gives the number back to the carrier. It returns to the public pool and anyone can buy it, including a competitor. Calls and messages to it stop
82
+ at once, and monthly rental billing for it ends. There is no undo.
83
+ </p>
84
+ <div className="flex flex-wrap items-end gap-2">
85
+ <div className="space-y-1">
86
+ <Label htmlFor="rn-confirm">Type {row.number} to enable release</Label>
87
+ <Input id="rn-confirm" value={typed} placeholder={row.number} onChange={(e) => setTyped(e.target.value)} />
88
+ </div>
89
+ <Button variant="destructive" disabled={!matches || !provider} onClick={() => setPending("release")}>
90
+ Release number
91
+ </Button>
92
+ </div>
93
+ {release.error ? <ApiError error={release.error} title="The number was not released" onRetry={release.reset} /> : null}
94
+ </section>
95
+
96
+ <section className="space-y-2" aria-label="Remove from voice">
97
+ <h3 className="text-sm font-medium">Remove from the voice platform</h3>
98
+ <p className="text-sm text-muted-foreground">
99
+ Tears down the number&apos;s call routing so nothing answers it. You still own the number and the carrier still bills for it. Use Release to give it up.
100
+ </p>
101
+ <Button variant="outline" onClick={() => setPending("deregister")}>Remove voice routing</Button>
102
+ {deregister.error ? <ApiError error={deregister.error} title="The voice routing was not removed" onRetry={deregister.reset} /> : null}
103
+ </section>
104
+
105
+ {note ? <p role="status" className="text-sm">{note}</p> : null}
106
+
107
+ {pending ? (
108
+ <ConfirmSpendDialog
109
+ open
110
+ tier="destructive"
111
+ title={pending === "release" ? `Release ${formatNumber(row.number)}` : `Remove voice routing for ${formatNumber(row.number)}`}
112
+ description={
113
+ pending === "release"
114
+ ? `The number goes back to the carrier and can be bought by anyone, including a competitor. Inbound calls and messages stop at once.${assignedNote}`
115
+ : `Nothing will answer this number until it is set up again. You keep owning it and the carrier keeps billing it.${assignedNote}`
116
+ }
117
+ confirmLabel={pending === "release" ? "Release number" : "Remove routing"}
118
+ affected={1}
119
+ confirming={release.loading || deregister.loading}
120
+ onCancel={() => setPending(null)}
121
+ onConfirm={() => void (pending === "release" ? release.mutate(undefined) : deregister.mutate(undefined)).then(() => setPending(null))}
122
+ />
123
+ ) : null}
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ /**
4
+ * usePhoneNumbers: every phone number of the workspace, in full.
5
+ *
6
+ * SDK operations, in order (both read, in parallel):
7
+ * 1. listDialerNumbers GET /voice/dialer/numbers every number except the ones a voice AI agent holds, with
8
+ * usage (calls today / 7 days, connect rate) and the daily limit
9
+ * 2. listAgentPhoneNumbers GET /voice/agent-phone-numbers the AI-capable numbers and which agent holds each
10
+ *
11
+ * Neither endpoint pages or caps: each answers the whole inventory, so nothing is cut off and the screen pages the list
12
+ * on the client ("Load more"). A key that cannot read agents (403) still gets the dialer list; `agentsUnavailable` says
13
+ * so, and the AI-held numbers are then missing and the screen says that rather than hiding it.
14
+ */
15
+ import { g8Get } from "@/g8/lib/g8-client";
16
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
17
+ import { agentNumbers, dialerRows, mergeInventory } from "../lib/phone-numbers-utils";
18
+ import type { NumberRow } from "../lib/phone-numbers-utils";
19
+
20
+ export interface UsePhoneNumbers {
21
+ rows: NumberRow[];
22
+ loading: boolean;
23
+ /** The first read has finished. */
24
+ loaded: boolean;
25
+ /** The dialer list failed (the agents list failing alone is not an error, see `agentsUnavailable`). */
26
+ error: unknown;
27
+ /** Numbers held by voice AI agents could not be read, so they may be missing from `rows`. */
28
+ agentsUnavailable: boolean;
29
+ refetch: () => void;
30
+ }
31
+
32
+ export function usePhoneNumbers(deps: readonly unknown[] = []): UsePhoneNumbers {
33
+ const q = useG8Query(
34
+ async (signal) => {
35
+ const [dialer, agents] = await Promise.allSettled([
36
+ g8Get("/voice/dialer/numbers", undefined, { signal }),
37
+ g8Get("/voice/agent-phone-numbers", undefined, { signal }),
38
+ ]);
39
+ if (dialer.status === "rejected") throw dialer.reason;
40
+ const agentsOk = agents.status === "fulfilled";
41
+ return {
42
+ rows: mergeInventory(dialerRows(dialer.value), agentsOk ? agentNumbers(agents.value) : { available: [], booked: [] }),
43
+ agentsUnavailable: !agentsOk,
44
+ };
45
+ },
46
+ [...deps],
47
+ );
48
+ return {
49
+ rows: q.data?.rows ?? EMPTY,
50
+ loading: q.loading,
51
+ loaded: q.data !== undefined,
52
+ error: q.error,
53
+ agentsUnavailable: q.data?.agentsUnavailable ?? false,
54
+ refetch: () => void q.refetch(),
55
+ };
56
+ }
57
+
58
+ const EMPTY: NumberRow[] = [];