@graph8/sdk 0.370.0 → 0.374.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (646) hide show
  1. package/README.md +22 -3
  2. package/blocks/_foundation/README.md +61 -0
  3. package/blocks/_foundation/block.json +57 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  6. package/blocks/_foundation/components/empty-state.tsx +5 -2
  7. package/blocks/_foundation/components/list-select.tsx +112 -0
  8. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  9. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  10. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  11. package/blocks/_foundation/components/screen/index.ts +1 -0
  12. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  13. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  14. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  15. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  16. package/blocks/_foundation/lib/paging.ts +57 -0
  17. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  18. package/blocks/_foundation/server/auth.ts +815 -0
  19. package/blocks/agents/README.md +81 -4
  20. package/blocks/agents/block.json +311 -2
  21. package/blocks/agents/components/agent-activity.tsx +112 -0
  22. package/blocks/agents/components/agent-approvals.tsx +135 -0
  23. package/blocks/agents/components/agent-builder.tsx +17 -3
  24. package/blocks/agents/components/agent-conversations.tsx +143 -0
  25. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  26. package/blocks/agents/components/agent-operators.tsx +164 -0
  27. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  28. package/blocks/agents/components/agent-runs.tsx +3 -3
  29. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  30. package/blocks/agents/components/agent-test-history.tsx +121 -0
  31. package/blocks/agents/components/agents-screen.tsx +53 -11
  32. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  33. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  34. package/blocks/agents/components/from-template-button.tsx +65 -0
  35. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  36. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  37. package/blocks/agents/components/knowledge-form.tsx +69 -0
  38. package/blocks/agents/components/name-dialog.tsx +32 -0
  39. package/blocks/agents/components/receptionist-button.tsx +46 -0
  40. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  41. package/blocks/agents/components/skill-editor.tsx +147 -0
  42. package/blocks/agents/components/skill-library.tsx +111 -0
  43. package/blocks/agents/lib/agent-types.ts +7 -1
  44. package/blocks/agents/lib/facts.tsx +13 -0
  45. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  46. package/blocks/agents/lib/load-more.tsx +141 -0
  47. package/blocks/agents/lib/skill-utils.ts +91 -0
  48. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  50. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  51. package/blocks/analytics/components/report-builder.tsx +15 -2
  52. package/blocks/appointments/README.md +6 -0
  53. package/blocks/appointments/block.json +27 -0
  54. package/blocks/appointments/components/booking-page.tsx +2 -2
  55. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  56. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  57. package/blocks/appointments/components/meeting-list.tsx +29 -13
  58. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  59. package/blocks/campaigns/README.md +1 -1
  60. package/blocks/campaigns/block.json +34 -1
  61. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  62. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  63. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  64. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  65. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  66. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  67. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  68. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  69. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  70. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  71. package/blocks/coach/components/call-scorecard.tsx +7 -7
  72. package/blocks/company-intelligence/README.md +132 -0
  73. package/blocks/company-intelligence/block.json +318 -0
  74. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  75. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  76. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  77. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  78. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  79. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  80. package/blocks/company-intelligence/components/research-run.tsx +165 -0
  81. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  82. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  83. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  84. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  85. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  86. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  87. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  88. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  89. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  90. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  91. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  92. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  93. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  94. package/blocks/crm/README.md +25 -0
  95. package/blocks/crm/block.json +257 -9
  96. package/blocks/crm/components/bulk-update.tsx +119 -0
  97. package/blocks/crm/components/companies-screen.tsx +23 -9
  98. package/blocks/crm/components/company-detail.tsx +2 -2
  99. package/blocks/crm/components/contact-detail.tsx +4 -4
  100. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  101. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  102. package/blocks/crm/components/contact-lookup-link.tsx +85 -0
  103. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  104. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  105. package/blocks/crm/components/contacts-screen.tsx +77 -18
  106. package/blocks/crm/components/dedupe-review.tsx +6 -1
  107. package/blocks/crm/components/import-wizard.tsx +3 -7
  108. package/blocks/crm/components/list-select.tsx +20 -0
  109. package/blocks/crm/components/owner-assign.tsx +132 -0
  110. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  111. package/blocks/crm/components/record-form.tsx +1 -1
  112. package/blocks/crm/components/record-open-links.tsx +79 -0
  113. package/blocks/crm/components/tag-assign.tsx +112 -0
  114. package/blocks/crm/components/tag-manager.tsx +178 -0
  115. package/blocks/crm/components/tasks-screen.tsx +35 -15
  116. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  117. package/blocks/crm/components/verify-email-button.tsx +93 -0
  118. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  119. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  120. package/blocks/crm/hooks/use-page-query.ts +3 -1
  121. package/blocks/crm/lib/crm-types.ts +14 -0
  122. package/blocks/crm/lib/read-lists.ts +16 -0
  123. package/blocks/crm/lib/record-actions.ts +102 -0
  124. package/blocks/deals/README.md +25 -6
  125. package/blocks/deals/block.json +168 -2
  126. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  127. package/blocks/deals/components/create-deal-button.tsx +109 -0
  128. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  129. package/blocks/deals/components/deal-detail.tsx +7 -5
  130. package/blocks/deals/components/deal-filters.tsx +65 -0
  131. package/blocks/deals/components/deal-line-items.tsx +120 -0
  132. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  133. package/blocks/deals/components/deal-related.tsx +30 -9
  134. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  135. package/blocks/deals/components/deals-screen.tsx +57 -22
  136. package/blocks/deals/components/forecast-view.tsx +4 -4
  137. package/blocks/deals/components/pipeline-board.tsx +8 -6
  138. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  139. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  140. package/blocks/deals/components/stage-editor.tsx +1 -1
  141. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  142. package/blocks/deals/lib/deal-extras.ts +156 -0
  143. package/blocks/dialer/README.md +14 -5
  144. package/blocks/dialer/block.json +17 -2
  145. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  146. package/blocks/dialer/components/call-log-screen.tsx +10 -2
  147. package/blocks/dialer/components/call-log.tsx +16 -4
  148. package/blocks/dialer/components/call-queue.tsx +3 -3
  149. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  150. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  151. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  152. package/blocks/dialer/components/session-start-form.tsx +58 -11
  153. package/blocks/dialer/components/softphone.tsx +1 -1
  154. package/blocks/documents/README.md +86 -0
  155. package/blocks/documents/block.json +378 -0
  156. package/blocks/documents/components/account-bar.tsx +48 -0
  157. package/blocks/documents/components/correspondence-form.tsx +95 -0
  158. package/blocks/documents/components/correspondence-import.tsx +106 -0
  159. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  160. package/blocks/documents/components/documents-screen.tsx +76 -0
  161. package/blocks/documents/components/library-document-detail.tsx +175 -0
  162. package/blocks/documents/components/library-document-form.tsx +105 -0
  163. package/blocks/documents/components/library-folder-form.tsx +74 -0
  164. package/blocks/documents/components/library-panel.tsx +235 -0
  165. package/blocks/documents/components/library-upload.tsx +160 -0
  166. package/blocks/documents/components/question-row.tsx +130 -0
  167. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  168. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  169. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  170. package/blocks/documents/lib/documents-types.ts +252 -0
  171. package/blocks/documents/lib/download.ts +64 -0
  172. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  173. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  174. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  175. package/blocks/email-signatures/README.md +123 -0
  176. package/blocks/email-signatures/block.json +341 -0
  177. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  178. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  179. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  180. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  181. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  182. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  183. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  184. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  185. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  186. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  187. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  188. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  189. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  190. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  191. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  192. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  193. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  194. package/blocks/enrichment/README.md +38 -10
  195. package/blocks/enrichment/block.json +164 -12
  196. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  197. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  198. package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
  199. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  200. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  201. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  202. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  203. package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
  204. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  205. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  206. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  207. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  208. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  209. package/blocks/enrichment/lib/enrichment-types.ts +51 -0
  210. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  211. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  212. package/blocks/forms/README.md +10 -0
  213. package/blocks/forms/block.json +107 -0
  214. package/blocks/forms/components/form-activity.tsx +71 -0
  215. package/blocks/forms/components/form-backfill.tsx +108 -0
  216. package/blocks/forms/components/form-builder.tsx +1 -1
  217. package/blocks/forms/components/form-embed.tsx +1 -1
  218. package/blocks/forms/components/form-groups.tsx +151 -0
  219. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  220. package/blocks/forms/components/form-route-history.tsx +72 -0
  221. package/blocks/forms/components/form-saved-views.tsx +81 -0
  222. package/blocks/forms/components/forms-screen.tsx +57 -20
  223. package/blocks/forms/components/more-rows.tsx +30 -0
  224. package/blocks/forms/components/submissions-export.tsx +67 -0
  225. package/blocks/forms/components/submissions-table.tsx +15 -6
  226. package/blocks/forms/lib/forms-extra.ts +67 -0
  227. package/blocks/inbox/README.md +45 -4
  228. package/blocks/inbox/block.json +145 -8
  229. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  230. package/blocks/inbox/components/compose-message.tsx +193 -0
  231. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  232. package/blocks/inbox/components/inbox-search.tsx +106 -0
  233. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  234. package/blocks/inbox/components/tag-manager.tsx +136 -0
  235. package/blocks/inbox/components/thread-actions.tsx +133 -0
  236. package/blocks/inbox/components/thread-badge.tsx +2 -2
  237. package/blocks/inbox/components/thread-view.tsx +3 -1
  238. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  239. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  240. package/blocks/integrations/README.md +54 -1
  241. package/blocks/integrations/block.json +189 -15
  242. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  243. package/blocks/integrations/components/connection-health.tsx +59 -0
  244. package/blocks/integrations/components/crm-detail.tsx +20 -1
  245. package/blocks/integrations/components/crm-records.tsx +103 -0
  246. package/blocks/integrations/components/field-mappings.tsx +61 -0
  247. package/blocks/integrations/components/integration-list.tsx +4 -4
  248. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  249. package/blocks/integrations/components/migration-panel.tsx +133 -0
  250. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  251. package/blocks/integrations/components/sync-status.tsx +6 -6
  252. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  253. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  254. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  255. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  256. package/blocks/integrations/lib/crm-utils.ts +39 -0
  257. package/blocks/integrations/lib/facts.tsx +13 -0
  258. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  259. package/blocks/integrations/lib/load-more.tsx +141 -0
  260. package/blocks/intent/components/account-intent-score.tsx +5 -5
  261. package/blocks/intent/components/intent-feed.tsx +8 -8
  262. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  263. package/blocks/intent/lib/intent-utils.ts +4 -4
  264. package/blocks/lifecycle/components/account-related.tsx +8 -1
  265. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  266. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  267. package/blocks/lists/README.md +7 -1
  268. package/blocks/lists/block.json +31 -3
  269. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  270. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  271. package/blocks/lists/components/list-picker.tsx +13 -3
  272. package/blocks/lists/components/list-table.tsx +1 -1
  273. package/blocks/lists/components/lists-screen.tsx +4 -2
  274. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  275. package/blocks/lists/components/suppression-manager.tsx +27 -6
  276. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  277. package/blocks/mailboxes/block.json +4 -0
  278. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  279. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  280. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  281. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  282. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  283. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  284. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  285. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  286. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  287. package/blocks/newsletter/README.md +12 -0
  288. package/blocks/newsletter/block.json +117 -0
  289. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  290. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  291. package/blocks/newsletter/components/more-rows.tsx +30 -0
  292. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  293. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  294. package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
  295. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  296. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  297. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  298. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  299. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  300. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  301. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  302. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  303. package/blocks/nurture/components/nurture-form.tsx +5 -8
  304. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  305. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  306. package/blocks/objects/README.md +1 -1
  307. package/blocks/objects/block.json +13 -5
  308. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  309. package/blocks/objects/components/object-record-detail.tsx +8 -1
  310. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  311. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  312. package/blocks/pages/README.md +14 -0
  313. package/blocks/pages/block.json +123 -2
  314. package/blocks/pages/components/more-rows.tsx +30 -0
  315. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  316. package/blocks/pages/components/page-analytics.tsx +176 -0
  317. package/blocks/pages/components/page-clone.tsx +99 -0
  318. package/blocks/pages/components/page-domains.tsx +167 -0
  319. package/blocks/pages/components/page-editor.tsx +1 -1
  320. package/blocks/pages/components/page-generation-status.tsx +48 -0
  321. package/blocks/pages/components/page-tracking.tsx +137 -0
  322. package/blocks/pages/components/pages-screen.tsx +57 -16
  323. package/blocks/pages/components/seo-report.tsx +6 -1
  324. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  325. package/blocks/pages/lib/studio.ts +7 -0
  326. package/blocks/phone-numbers/README.md +74 -0
  327. package/blocks/phone-numbers/block.json +262 -0
  328. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  329. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  330. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  331. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  332. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  333. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  334. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  335. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  336. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  337. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  338. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  339. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  340. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  341. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  342. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  343. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  344. package/blocks/pickers/README.md +23 -0
  345. package/blocks/pickers/block.json +103 -0
  346. package/blocks/pickers/components/company-picker.tsx +90 -0
  347. package/blocks/pickers/components/contact-picker.tsx +131 -0
  348. package/blocks/pickers/components/member-picker.tsx +99 -0
  349. package/blocks/pickers/components/search-picker.tsx +98 -0
  350. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  351. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  352. package/blocks/pickers/lib/use-debounced.ts +13 -0
  353. package/blocks/programmatic-pages/README.md +82 -0
  354. package/blocks/programmatic-pages/block.json +292 -0
  355. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  356. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  357. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  358. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  359. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  360. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  361. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  362. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  363. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  364. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  365. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  366. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  367. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  368. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  369. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  370. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  371. package/blocks/quotes/README.md +38 -1
  372. package/blocks/quotes/block.json +170 -6
  373. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  374. package/blocks/quotes/components/manual-products.tsx +144 -0
  375. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  376. package/blocks/quotes/components/quote-builder.tsx +1 -1
  377. package/blocks/quotes/components/quote-documents.tsx +94 -0
  378. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  379. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  380. package/blocks/quotes/components/quote-list.tsx +28 -8
  381. package/blocks/quotes/components/quote-related.tsx +3 -2
  382. package/blocks/quotes/components/quote-settings.tsx +184 -0
  383. package/blocks/quotes/components/quote-templates.tsx +189 -0
  384. package/blocks/quotes/components/quote-viewer.tsx +23 -5
  385. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  386. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  387. package/blocks/quotes/lib/quote-admin.ts +264 -0
  388. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  389. package/blocks/radar/components/radar-overview.tsx +4 -4
  390. package/blocks/radar/components/radar-screen.tsx +2 -2
  391. package/blocks/sequencer/README.md +61 -0
  392. package/blocks/sequencer/block.json +200 -6
  393. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  394. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  395. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  396. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  397. package/blocks/sequencer/components/sequence-builder.tsx +32 -23
  398. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  399. package/blocks/sequencer/components/sequence-detail.tsx +10 -2
  400. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  401. package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
  402. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  403. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  404. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  405. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  406. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  407. package/blocks/sequencer/components/sequences-screen.tsx +39 -6
  408. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  409. package/blocks/sequencer/components/step-editor.tsx +21 -12
  410. package/blocks/sequencer/components/step-templates.tsx +224 -0
  411. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  412. package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
  413. package/blocks/signals/README.md +39 -0
  414. package/blocks/signals/block.json +287 -0
  415. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  416. package/blocks/signals/components/listener-form.tsx +184 -0
  417. package/blocks/signals/components/matched-posts.tsx +195 -0
  418. package/blocks/signals/components/post-engagements.tsx +114 -0
  419. package/blocks/signals/components/record-social-tab.tsx +49 -0
  420. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  421. package/blocks/signals/components/social-settings.tsx +128 -0
  422. package/blocks/signals/components/source-lists.tsx +83 -0
  423. package/blocks/signals/components/top-engagers.tsx +68 -0
  424. package/blocks/signals/components/x-integration.tsx +129 -0
  425. package/blocks/signals/lib/signals-types.ts +228 -0
  426. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  427. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  428. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  429. package/blocks/social-ads/README.md +8 -0
  430. package/blocks/social-ads/block.json +137 -4
  431. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  432. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  433. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  434. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  435. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  436. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  437. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  438. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  439. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  440. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  441. package/blocks/social-ads/components/post-composer.tsx +4 -2
  442. package/blocks/social-ads/components/post-detail.tsx +3 -0
  443. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  444. package/blocks/social-ads/components/social-performance.tsx +80 -0
  445. package/blocks/social-ads/components/social-screen.tsx +33 -6
  446. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  447. package/blocks/surveys/README.md +91 -0
  448. package/blocks/surveys/block.json +299 -0
  449. package/blocks/surveys/components/account-nps.tsx +69 -0
  450. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  451. package/blocks/surveys/components/org-summary.tsx +82 -0
  452. package/blocks/surveys/components/sending-domains.tsx +113 -0
  453. package/blocks/surveys/components/survey-form.tsx +126 -0
  454. package/blocks/surveys/components/survey-responses.tsx +145 -0
  455. package/blocks/surveys/components/survey-results.tsx +72 -0
  456. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  457. package/blocks/surveys/components/survey-send.tsx +162 -0
  458. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  459. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  460. package/blocks/surveys/lib/surveys-types.ts +228 -0
  461. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  462. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  463. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  464. package/blocks/visitors/components/snippet-install.tsx +18 -9
  465. package/blocks/visitors/components/traffic-chart.tsx +7 -6
  466. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  467. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  468. package/blocks/web-analytics/README.md +102 -0
  469. package/blocks/web-analytics/block.json +292 -0
  470. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  471. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  472. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  473. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  474. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  475. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  476. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  477. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  478. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  479. package/blocks/web-analytics/components/report-card.tsx +71 -0
  480. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  481. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  482. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  483. package/blocks/web-analytics/lib/page-path.ts +14 -0
  484. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  485. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  486. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  487. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  488. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  489. package/blocks/webchat/components/conversation-list.tsx +1 -1
  490. package/blocks/workbench/README.md +54 -2
  491. package/blocks/workbench/block.json +234 -5
  492. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  493. package/blocks/workbench/components/run-progress.tsx +1 -1
  494. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  495. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  496. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  497. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  498. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  499. package/blocks/workbench/components/workbench-grid.tsx +71 -8
  500. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  501. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  502. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  503. package/blocks/workbench/components/workbench-screen.tsx +65 -22
  504. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  505. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  506. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  507. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  508. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  509. package/blocks/workbench/lib/api-status.ts +8 -0
  510. package/blocks/workbench/lib/automation-types.ts +257 -0
  511. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  512. package/dist/cli.js +744 -394
  513. package/dist/index.browser.js +1 -1
  514. package/dist/index.browser.js.map +1 -1
  515. package/dist/index.browser.mjs +1 -1
  516. package/dist/index.browser.mjs.map +1 -1
  517. package/dist/index.d.mts +2 -2
  518. package/dist/index.d.ts +2 -2
  519. package/dist/index.js +1 -1
  520. package/dist/index.js.map +1 -1
  521. package/dist/index.mjs +1 -1
  522. package/dist/index.mjs.map +1 -1
  523. package/dist/react.browser.js +1 -1
  524. package/dist/react.browser.js.map +1 -1
  525. package/dist/react.browser.mjs +1 -1
  526. package/dist/react.browser.mjs.map +1 -1
  527. package/dist/react.d.mts +2 -2
  528. package/dist/react.d.ts +2 -2
  529. package/dist/react.js +1 -1
  530. package/dist/react.js.map +1 -1
  531. package/dist/react.mjs +1 -1
  532. package/dist/react.mjs.map +1 -1
  533. package/dist/types/blocks/auth.d.ts +45 -0
  534. package/dist/types/blocks/brand.d.ts +11 -2
  535. package/dist/types/blocks/create.d.ts +11 -4
  536. package/dist/types/blocks/doctor.d.ts +1 -0
  537. package/dist/types/blocks/flows.d.ts +7 -1
  538. package/dist/types/blocks/orgBrand.d.ts +34 -0
  539. package/dist/types/cli.d.ts +3 -0
  540. package/dist/types/version.d.ts +2 -2
  541. package/docs/api/desk.d.ts +2 -2
  542. package/docs/api/desk.md +3 -3
  543. package/docs/api/forms.md +1 -1
  544. package/package.json +1 -1
  545. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  546. package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
  547. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  548. package/stacks/6sense.json +37 -4
  549. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  550. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  551. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  552. package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
  553. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  554. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  555. package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
  556. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  557. package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
  558. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  559. package/stacks/apollo.json +68 -9
  560. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  561. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  562. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  563. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  564. package/stacks/calendly.json +8 -4
  565. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  566. package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
  567. package/stacks/clay/pages/lists.tsx.tmpl +24 -0
  568. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  569. package/stacks/clay.json +16 -3
  570. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  571. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  572. package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
  573. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  574. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  575. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  576. package/stacks/gong.json +23 -6
  577. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  578. package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
  579. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  580. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  581. package/stacks/hootsuite.json +19 -4
  582. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  583. package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
  584. package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
  585. package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
  586. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  587. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  588. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  589. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  590. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  591. package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
  592. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  593. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  594. package/stacks/hubspot.json +66 -7
  595. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  596. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  597. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  598. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  599. package/stacks/intercom.json +18 -4
  600. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  601. package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
  602. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  603. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  604. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  605. package/stacks/mailchimp.json +22 -6
  606. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  607. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  608. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  609. package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
  610. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  611. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  612. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  613. package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
  614. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  615. package/stacks/outreach.json +38 -9
  616. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  617. package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
  618. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  619. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  620. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  621. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  622. package/stacks/pipedrive.json +14 -2
  623. package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
  624. package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
  625. package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
  626. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  627. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  628. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  629. package/stacks/salesforce.json +17 -3
  630. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  631. package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
  632. package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
  633. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  634. package/stacks/zoominfo.json +36 -3
  635. package/styles/styles.css +1 -1
  636. package/templates/auth/auth-route.ts.tmpl +16 -0
  637. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  638. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  639. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  640. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  641. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  642. package/templates/auth/middleware.ts.tmpl +14 -0
  643. package/templates/next-app/README.md.tmpl +5 -0
  644. package/templates/next-app/app/globals.css.tmpl +349 -64
  645. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  646. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
@@ -0,0 +1,134 @@
1
+ "use client";
2
+
3
+ /**
4
+ * NumberSettings: how one number behaves. Answering mode, caller-ID verification, and who picks up.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. getVoiceAnsweringAgent GET /voice/numbers/answering-agent?number= (read; who picks up when it is called)
8
+ * 2. updatePhoneCapabilities PATCH /phone-numbers/update-capabilities (write; switches a number between the
9
+ * dialer and a voice AI agent and re-provisions it; admin-only)
10
+ * 3. updateVoiceInboundType PATCH /voice/numbers/inbound-type (write; retypes among ai, sdr, dialer
11
+ * and work on the voice side)
12
+ * 4. verifyVoiceCallerId POST /voice/numbers/verify-caller-id/{number} (write; Telnyx numbers only)
13
+ *
14
+ * Mapping: "Voice AI agent" and "Outbound dialer" go through 2 (the app's own derivation, which also updates the CRM
15
+ * record); "Rep answers" and "Personal work phone" go through 3. Changing the mode tears down and rebuilds the number's
16
+ * routing, so the number answers nothing for a moment: the change takes a second click on a button that says so.
17
+ * Call forwarding, voicemail greetings and ring-group routing have no API operations, so they are not offered here.
18
+ */
19
+
20
+ import { useState } from "react";
21
+ import { ExternalLink } from "lucide-react";
22
+ import { Button } from "@graph8/sdk/ui";
23
+ import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
24
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
26
+ import { useBrand, brandUrl } from "@/g8/hooks/use-brand";
27
+ import { ApiError } from "@/g8/components/api-error";
28
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
29
+ import { StatusPill } from "@/g8/components/screen/status-pill";
30
+ import { INBOUND_LABEL, INBOUND_TYPES, dataOf, formatNumber, scalarItems, typeLabel } from "../lib/phone-numbers-utils";
31
+ import type { InboundType, NumberRow } from "../lib/phone-numbers-utils";
32
+
33
+ export interface NumberSettingsProps {
34
+ row: NumberRow;
35
+ onChanged?: () => void;
36
+ className?: string;
37
+ }
38
+
39
+ const SELECT = "h-9 rounded-md border border-border bg-background px-2 text-sm";
40
+
41
+ export function NumberSettings({ row, onChanged, className }: NumberSettingsProps) {
42
+ const brand = useBrand();
43
+ // An unreported mode is "" (never guessed as voice AI), so every real mode can be applied to it.
44
+ const current: InboundType | "" = (INBOUND_TYPES as readonly string[]).includes(row.inboundType) ? (row.inboundType as InboundType) : "";
45
+ const [mode, setMode] = useState<InboundType | "">(current);
46
+ const [armed, setArmed] = useState(false);
47
+ const [note, setNote] = useState<string | null>(null);
48
+
49
+ const who = useG8Query(async (signal) => dataOf(await g8Get("/voice/numbers/answering-agent", { number: row.number }, { signal })), [row.number]);
50
+
51
+ const retype = useG8Mutation(async (next: InboundType) => {
52
+ if (next === "ai" || next === "dialer") await g8Patch("/phone-numbers/update-capabilities", { phone_number: row.number, dialer: next === "dialer", voice: next === "ai" });
53
+ else await g8Patch("/voice/numbers/inbound-type", { number: row.number, inbound_type: next });
54
+ setNote(`${formatNumber(row.number)} is now: ${INBOUND_LABEL[next]}.`);
55
+ setArmed(false);
56
+ onChanged?.();
57
+ void who.refetch();
58
+ });
59
+ const verify = useG8Mutation(async () => {
60
+ const res = dataOf(await g8Post(`/voice/numbers/verify-caller-id/${encodeURIComponent(row.number)}`, {}));
61
+ const state = typeof res.status === "string" ? res.status : typeof res.result === "string" ? res.result : "verification_started";
62
+ setNote(state.includes("already") ? "Already verified as a caller ID." : "Verification started. It takes a few minutes; this page shows the result once it lands.");
63
+ onChanged?.();
64
+ });
65
+
66
+ const changed = mode !== "" && mode !== current;
67
+ const telnyx = row.provider === "telnyx";
68
+ const answering = scalarItems(who.data ?? {});
69
+
70
+ return (
71
+ <div className={`space-y-6 ${className ?? ""}`}>
72
+ <section className="space-y-2" aria-label="Answering mode">
73
+ <h3 className="text-sm font-medium">Answering mode</h3>
74
+ <div className="flex flex-wrap items-center gap-2">
75
+ <select aria-label="Answering mode" className={SELECT} value={mode} onChange={(e) => { setMode(e.target.value as InboundType | ""); setArmed(false); retype.reset(); }}>
76
+ {current === "" ? <option value="" disabled>Not reported</option> : null}
77
+ {INBOUND_TYPES.map((t) => (
78
+ <option key={t} value={t}>{INBOUND_LABEL[t]}</option>
79
+ ))}
80
+ </select>
81
+ {!armed ? (
82
+ <Button variant="outline" disabled={!changed} onClick={() => setArmed(true)}>Change mode</Button>
83
+ ) : (
84
+ <Button loading={retype.loading} onClick={() => mode !== "" && void retype.mutate(mode)}>Switch now: the number answers nothing for a moment</Button>
85
+ )}
86
+ {armed ? <Button variant="ghost" onClick={() => setArmed(false)}>Cancel</Button> : null}
87
+ </div>
88
+ <p className="text-xs text-muted-foreground">
89
+ Now: {typeLabel(row.inboundType)}. Switching rebuilds how calls reach the number, so calls in that moment are missed. Admin-only.
90
+ </p>
91
+ {retype.error ? <ApiError error={retype.error} title="The mode did not change" onRetry={retype.reset} /> : null}
92
+ </section>
93
+
94
+ <section className="space-y-2" aria-label="Caller ID">
95
+ <h3 className="text-sm font-medium">Caller ID</h3>
96
+ {telnyx ? (
97
+ <div className="flex flex-wrap items-center gap-2">
98
+ <StatusPill tone={row.callerIdVerified === true ? "positive" : row.callerIdVerified === false ? "warning" : "neutral"} dot>
99
+ {row.callerIdVerified === true ? "Verified" : row.callerIdVerified === false ? "Not verified" : "Unknown"}
100
+ </StatusPill>
101
+ <Button size="sm" variant="outline" loading={verify.loading} disabled={row.callerIdVerified === true} onClick={() => void verify.mutate(undefined)}>
102
+ Verify as caller ID
103
+ </Button>
104
+ </div>
105
+ ) : row.provider === "twilio" ? (
106
+ <p className="text-sm text-muted-foreground">Twilio numbers are valid caller IDs as they are. Nothing to verify.</p>
107
+ ) : (
108
+ <p className="text-sm text-muted-foreground">The carrier is not reported for this number, so caller-ID verification cannot be offered.</p>
109
+ )}
110
+ {telnyx ? <p className="text-xs text-muted-foreground">An unverified number cannot place calls, so it stays out of the dialer rotation.</p> : null}
111
+ {verify.error ? <ApiError error={verify.error} title="Verification did not start" onRetry={verify.reset} /> : null}
112
+ </section>
113
+
114
+ <section className="space-y-2" aria-label="Who answers">
115
+ <h3 className="text-sm font-medium">Who answers</h3>
116
+ {who.error ? (
117
+ <ApiError compact error={who.error} onRetry={() => void who.refetch()} />
118
+ ) : answering.length > 0 ? (
119
+ <KeyValueList items={answering} />
120
+ ) : (
121
+ <p className="text-sm text-muted-foreground">{who.loading ? "Looking up who answers." : "No answering agent is set for this number."}</p>
122
+ )}
123
+ </section>
124
+
125
+ {note ? <p role="status" className="text-sm">{note}</p> : null}
126
+ <p className="text-xs text-muted-foreground">
127
+ Call forwarding, voicemail greetings and ring-group routing are not available through the API yet.{" "}
128
+ <a className="inline-flex items-center gap-1 underline" href={brandUrl(brand)} target="_blank" rel="noreferrer">
129
+ Manage them in {brand.linkLabel} <ExternalLink className="h-3 w-3" aria-hidden />
130
+ </a>
131
+ </p>
132
+ </div>
133
+ );
134
+ }
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ /**
4
+ * NumberUsage: how hard one number is being used today, against the daily cap that keeps carriers from flagging it.
5
+ *
6
+ * SDK operations:
7
+ * resetVoiceDailyCounts DELETE /voice/numbers/daily-counts (destructive: clears today's outbound call counters for EVERY
8
+ * number in the workspace, which lets numbers dial past a cap that exists to protect their
9
+ * reputation. Admin-only. Confirmed with ConfirmSpendDialog.)
10
+ *
11
+ * The figures come from the number list (GET /voice/dialer/numbers), so this component reads nothing itself. Counters
12
+ * reset on their own at midnight US Eastern. Costs: the API has no per-number cost read; the rental is shown as an
13
+ * estimate on the purchase dialog only, and a number the platform provisioned for you is never rental-billed.
14
+ */
15
+
16
+ import { useState } from "react";
17
+ import { Button } from "@graph8/sdk/ui";
18
+ import { g8Delete } from "@/g8/lib/g8-client";
19
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
20
+ import { ApiError } from "@/g8/components/api-error";
21
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
22
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
23
+ import { StatusPill } from "@/g8/components/screen/status-pill";
24
+ import { dataOf, numberHealth, percent } from "../lib/phone-numbers-utils";
25
+ import type { NumberRow } from "../lib/phone-numbers-utils";
26
+
27
+ export interface NumberUsageProps {
28
+ row: NumberRow;
29
+ onChanged?: () => void;
30
+ className?: string;
31
+ }
32
+
33
+ const show = (n: number | null) => (n == null ? "-" : n.toLocaleString());
34
+
35
+ export function NumberUsage({ row, onChanged, className }: NumberUsageProps) {
36
+ const [confirming, setConfirming] = useState(false);
37
+ const [note, setNote] = useState<string | null>(null);
38
+ const reset = useG8Mutation(async () => {
39
+ const out = dataOf(await g8Delete("/voice/numbers/daily-counts"));
40
+ const n = typeof out.deleted === "number" ? out.deleted : null;
41
+ setNote(n === null ? "Today's counters were cleared." : `Cleared ${n.toLocaleString()} counter${n === 1 ? "" : "s"}.`);
42
+ setConfirming(false);
43
+ onChanged?.();
44
+ });
45
+ const health = numberHealth(row);
46
+ const used = row.callsToday ?? 0;
47
+ const pct = row.dailyLimit ? Math.min(100, Math.round((used / row.dailyLimit) * 100)) : 0;
48
+
49
+ return (
50
+ <div className={`space-y-5 ${className ?? ""}`}>
51
+ <div className="flex items-center gap-2">
52
+ <StatusPill tone={health.tone} dot>{health.label}</StatusPill>
53
+ {row.isValid === false ? <span className="text-xs text-muted-foreground">The carrier would refuse calls from this number.</span> : null}
54
+ </div>
55
+ <div className="space-y-1">
56
+ <div className="flex items-baseline justify-between text-sm">
57
+ <span>Outbound calls today</span>
58
+ <span className="tabular-nums">{show(row.callsToday)} of {row.dailyLimit ? row.dailyLimit.toLocaleString() : "no limit set"}</span>
59
+ </div>
60
+ <div role="progressbar" aria-label="Daily limit used" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100} className="h-2 overflow-hidden rounded-full bg-muted">
61
+ <div className={`h-full ${pct >= 100 ? "bg-error" : pct >= 80 ? "bg-warning" : "bg-primary"}`} style={{ width: `${pct}%` }} />
62
+ </div>
63
+ </div>
64
+ <KeyValueList
65
+ columns={2}
66
+ items={[
67
+ { label: "Calls, last 7 days", value: show(row.calls7d) },
68
+ { label: "Connect rate, 7 days", value: percent(row.connectRate7d) },
69
+ { label: "Daily limit", value: row.dailyLimit ? show(row.dailyLimit) : "-" },
70
+ { label: "Carrier", value: row.provider ? (row.provider === "telnyx" ? "Telnyx" : "Twilio") : "-" },
71
+ ]}
72
+ />
73
+ <section className="space-y-2 rounded-md border border-border p-3" aria-label="Reset counters">
74
+ <h3 className="text-sm font-medium">Reset today&apos;s counters</h3>
75
+ <p className="text-sm text-muted-foreground">
76
+ Clears today&apos;s call counts for every number in the workspace, not only this one, so they can dial past the daily limit that protects them from being flagged
77
+ as spam. They reset on their own at midnight US Eastern. Admin-only.
78
+ </p>
79
+ <Button variant="outline" onClick={() => setConfirming(true)}>Reset counters</Button>
80
+ {reset.error ? <ApiError error={reset.error} title="The counters were not reset" onRetry={reset.reset} /> : null}
81
+ {note ? <p role="status" className="text-sm">{note}</p> : null}
82
+ </section>
83
+ {confirming ? (
84
+ <ConfirmSpendDialog
85
+ open
86
+ tier="destructive"
87
+ title="Reset today's call counters for every number"
88
+ description="Numbers that have reached their daily limit can dial again today. The limit exists to keep carriers from flagging a number as spam."
89
+ confirmLabel="Reset counters"
90
+ confirming={reset.loading}
91
+ onCancel={() => setConfirming(false)}
92
+ onConfirm={() => void reset.mutate(undefined).then(() => setConfirming(false))}
93
+ />
94
+ ) : null}
95
+ </div>
96
+ );
97
+ }
@@ -0,0 +1,84 @@
1
+ "use client";
2
+
3
+ /**
4
+ * PhoneNumberDetail: one number in full, as tabs: Overview, Assign, Usage, Settings and Release. Each tab is its own
5
+ * component (AssignNumber, NumberUsage, NumberSettings, ReleaseNumber) that owns its operations and confirm dialogs.
6
+ *
7
+ * SDK operations: none of its own (the composed parts call them).
8
+ *
9
+ * Billing, stated honestly: the API reports no rental per number, so Overview does not guess one. A number bought here is
10
+ * rented monthly after purchase (see the purchase dialog for the estimate); a number the platform set up for you, such as
11
+ * the onboarding receptionist line, is never rental-billed.
12
+ */
13
+
14
+ import type { ReactNode } from "react";
15
+ import { Phone } from "lucide-react";
16
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
17
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
18
+ import { StatusPill } from "@/g8/components/screen/status-pill";
19
+ import { AssignNumber } from "./assign-number";
20
+ import { NumberSettings } from "./number-settings";
21
+ import { NumberUsage } from "./number-usage";
22
+ import { ReleaseNumber } from "./release-number";
23
+ import { formatNumber, numberHealth, shortDate, typeLabel } from "../lib/phone-numbers-utils";
24
+ import type { NumberRow } from "../lib/phone-numbers-utils";
25
+
26
+ export interface PhoneNumberDetailProps {
27
+ row: NumberRow;
28
+ tab?: string;
29
+ onTabChange?: (tab: string) => void;
30
+ onClose?: () => void;
31
+ onExpand?: () => void;
32
+ onCollapse?: () => void;
33
+ /** The list should reload (something about the number changed). */
34
+ onChanged?: () => void;
35
+ /** The number was released: the record is gone. */
36
+ onReleased?: () => void;
37
+ renderActions?: (row: NumberRow) => ReactNode;
38
+ extraTabs?: Array<{ id: string; label: string; render: (row: NumberRow) => ReactNode }>;
39
+ }
40
+
41
+ export function PhoneNumberDetail({ row, tab, onTabChange, onClose, onExpand, onCollapse, onChanged, onReleased, renderActions, extraTabs }: PhoneNumberDetailProps) {
42
+ const health = numberHealth(row);
43
+ const overview = (
44
+ <div className="space-y-4">
45
+ <KeyValueList
46
+ columns={2}
47
+ items={[
48
+ { label: "Number", value: row.number, mono: true },
49
+ { label: "Use", value: typeLabel(row.inboundType) },
50
+ { label: "Carrier", value: row.provider === "telnyx" ? "Telnyx" : row.provider === "twilio" ? "Twilio" : "-" },
51
+ { label: "Assigned to", value: row.assignedTo ?? "-" },
52
+ { label: "Voice agent", value: row.agentName ?? "-" },
53
+ { label: "Added", value: shortDate(row.createdAt) },
54
+ ]}
55
+ />
56
+ <p className="text-sm text-muted-foreground">
57
+ The API does not report a rental price per number. A number bought here is rented monthly after purchase; a number the platform set up for you is never
58
+ rental-billed.
59
+ </p>
60
+ </div>
61
+ );
62
+ return (
63
+ <DetailPanel
64
+ title={formatNumber(row.number)}
65
+ subtitle={typeLabel(row.inboundType)}
66
+ avatar={<Phone className="h-5 w-5" aria-hidden />}
67
+ badges={<StatusPill tone={health.tone} dot>{health.label}</StatusPill>}
68
+ actions={renderActions?.(row)}
69
+ activeTab={tab || "overview"}
70
+ onTabChange={onTabChange}
71
+ onClose={onClose}
72
+ onExpand={onExpand}
73
+ onCollapse={onCollapse}
74
+ tabs={[
75
+ { id: "overview", label: "Overview", content: overview },
76
+ { id: "assign", label: "Assign", content: <AssignNumber row={row} onChanged={onChanged} /> },
77
+ { id: "usage", label: "Usage", content: <NumberUsage row={row} onChanged={onChanged} /> },
78
+ { id: "settings", label: "Settings", content: <NumberSettings row={row} onChanged={onChanged} /> },
79
+ { id: "release", label: "Release", content: <ReleaseNumber row={row} onChanged={onChanged} onReleased={onReleased} /> },
80
+ ...(extraTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(row) })),
81
+ ]}
82
+ />
83
+ );
84
+ }
@@ -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
+ }