@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.
- package/README.md +33 -10
- package/blocks/_foundation/README.md +78 -1
- package/blocks/_foundation/block.json +123 -6
- package/blocks/_foundation/components/api-error.tsx +9 -7
- package/blocks/_foundation/components/brand-provider.tsx +23 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
- package/blocks/_foundation/components/confirmed-action.tsx +90 -0
- package/blocks/_foundation/components/connect-graph8.tsx +280 -0
- package/blocks/_foundation/components/empty-state.tsx +5 -2
- package/blocks/_foundation/components/list-select.tsx +112 -0
- package/blocks/_foundation/components/paging-notices.tsx +55 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
- package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
- package/blocks/_foundation/components/screen/index.ts +2 -0
- package/blocks/_foundation/components/screen/record-link.tsx +76 -0
- package/blocks/_foundation/components/screen/split-view.tsx +1 -1
- package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
- package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
- package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
- package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
- package/blocks/_foundation/hooks/use-brand.ts +52 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
- package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
- package/blocks/_foundation/hooks/use-paginated.ts +120 -0
- package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
- package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
- package/blocks/_foundation/lib/g8-cache.ts +454 -0
- package/blocks/_foundation/lib/g8-client.ts +1 -1
- package/blocks/_foundation/lib/paging.ts +57 -0
- package/blocks/_foundation/lib/plural.ts +23 -0
- package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
- package/blocks/_foundation/server/connect-route.ts +15 -0
- package/blocks/_foundation/server/connect.ts +127 -0
- package/blocks/_foundation/server/local-key.ts +70 -0
- package/blocks/_foundation/server/proxy.ts +103 -3
- package/blocks/_foundation/server/route.ts +16 -4
- package/blocks/agents/README.md +93 -2
- package/blocks/agents/block.json +343 -2
- package/blocks/agents/components/agent-activity.tsx +112 -0
- package/blocks/agents/components/agent-approvals.tsx +135 -0
- package/blocks/agents/components/agent-builder.tsx +29 -10
- package/blocks/agents/components/agent-conversations.tsx +143 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
- package/blocks/agents/components/agent-operators.tsx +164 -0
- package/blocks/agents/components/agent-run-detail.tsx +119 -0
- package/blocks/agents/components/agent-runs.tsx +19 -10
- package/blocks/agents/components/agent-skills-panel.tsx +7 -7
- package/blocks/agents/components/agent-test-history.tsx +121 -0
- package/blocks/agents/components/agents-screen.tsx +226 -0
- package/blocks/agents/components/assistant-sessions.tsx +150 -0
- package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
- package/blocks/agents/components/from-template-button.tsx +65 -0
- package/blocks/agents/components/knowledge-browser.tsx +107 -0
- package/blocks/agents/components/knowledge-detail.tsx +156 -0
- package/blocks/agents/components/knowledge-form.tsx +69 -0
- package/blocks/agents/components/name-dialog.tsx +32 -0
- package/blocks/agents/components/receptionist-button.tsx +46 -0
- package/blocks/agents/components/save-as-template-button.tsx +32 -0
- package/blocks/agents/components/skill-editor.tsx +147 -0
- package/blocks/agents/components/skill-library.tsx +111 -0
- package/blocks/agents/lib/agent-types.ts +7 -1
- package/blocks/agents/lib/facts.tsx +13 -0
- package/blocks/agents/lib/knowledge-utils.ts +49 -0
- package/blocks/agents/lib/load-more.tsx +141 -0
- package/blocks/agents/lib/skill-utils.ts +91 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
- package/blocks/analytics/README.md +13 -0
- package/blocks/analytics/block.json +45 -1
- package/blocks/analytics/components/analytics-screen.tsx +222 -0
- package/blocks/analytics/components/kpi-tiles.tsx +1 -1
- package/blocks/analytics/components/leaderboard.tsx +14 -7
- package/blocks/analytics/components/report-builder.tsx +38 -11
- package/blocks/analytics/components/report-detail.tsx +84 -0
- package/blocks/analytics/components/trend-chart.tsx +62 -0
- package/blocks/appointments/README.md +6 -0
- package/blocks/appointments/block.json +33 -2
- package/blocks/appointments/components/availability-editor.tsx +17 -5
- package/blocks/appointments/components/booking-page.tsx +2 -2
- package/blocks/appointments/components/event-type-editor.tsx +2 -1
- package/blocks/appointments/components/event-type-related.tsx +4 -2
- package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
- package/blocks/appointments/components/meeting-detail.tsx +2 -2
- package/blocks/appointments/components/meeting-list.tsx +38 -18
- package/blocks/appointments/components/meetings-screen.tsx +7 -4
- package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
- package/blocks/appointments/components/scheduling-screen.tsx +2 -1
- package/blocks/block.schema.json +18 -0
- package/blocks/block.types.ts +17 -0
- package/blocks/campaigns/README.md +41 -11
- package/blocks/campaigns/block.json +105 -42
- package/blocks/campaigns/components/campaign-assets.tsx +17 -6
- package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
- package/blocks/campaigns/components/campaign-list.tsx +7 -3
- package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
- package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
- package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
- package/blocks/campaigns/components/icp-detail.tsx +163 -0
- package/blocks/campaigns/components/icp-editor.tsx +22 -17
- package/blocks/campaigns/components/persona-detail.tsx +181 -0
- package/blocks/campaigns/components/persona-editor.tsx +19 -11
- package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
- package/blocks/coach/README.md +10 -0
- package/blocks/coach/block.json +16 -1
- package/blocks/coach/components/call-scorecard.tsx +7 -7
- package/blocks/coach/components/coach-screen.tsx +222 -0
- package/blocks/company-intelligence/README.md +132 -0
- package/blocks/company-intelligence/block.json +318 -0
- package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
- package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
- package/blocks/company-intelligence/components/company-profile.tsx +68 -0
- package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
- package/blocks/company-intelligence/components/report-detail.tsx +189 -0
- package/blocks/company-intelligence/components/research-reports.tsx +136 -0
- package/blocks/company-intelligence/components/research-run.tsx +161 -0
- package/blocks/company-intelligence/components/source-detail.tsx +117 -0
- package/blocks/company-intelligence/components/source-view.tsx +274 -0
- package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
- package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
- package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
- package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
- package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
- package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
- package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
- package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
- package/blocks/copilot/README.md +13 -0
- package/blocks/copilot/block.json +14 -1
- package/blocks/copilot/components/copilot-memory.tsx +6 -2
- package/blocks/copilot/components/copilot-panel.tsx +14 -5
- package/blocks/copilot/components/copilot-screen.tsx +148 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
- package/blocks/crm/README.md +34 -0
- package/blocks/crm/block.json +107 -2
- package/blocks/crm/components/companies-screen.tsx +359 -0
- package/blocks/crm/components/company-detail.tsx +10 -8
- package/blocks/crm/components/company-table.tsx +11 -4
- package/blocks/crm/components/contact-detail.tsx +10 -7
- package/blocks/crm/components/contact-lookup-link.tsx +44 -0
- package/blocks/crm/components/contact-table.tsx +10 -3
- package/blocks/crm/components/contacts-screen.tsx +377 -0
- package/blocks/crm/components/dedupe-review.tsx +13 -6
- package/blocks/crm/components/import-wizard.tsx +7 -9
- package/blocks/crm/components/list-select.tsx +20 -0
- package/blocks/crm/components/notes-panel.tsx +4 -4
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +6 -4
- package/blocks/crm/components/task-list.tsx +4 -4
- package/blocks/crm/components/tasks-screen.tsx +399 -0
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-custom-fields.ts +6 -1
- package/blocks/crm/hooks/use-page-query.ts +9 -1
- package/blocks/crm/lib/crm-types.ts +26 -2
- package/blocks/crm/lib/read-lists.ts +16 -0
- package/blocks/crm/lib/screen-parts.tsx +105 -0
- package/blocks/deals/README.md +24 -6
- package/blocks/deals/block.json +160 -5
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +25 -13
- package/blocks/deals/components/deal-filters.tsx +65 -0
- package/blocks/deals/components/deal-line-items.tsx +120 -0
- package/blocks/deals/components/deal-notes-tab.tsx +97 -0
- package/blocks/deals/components/deal-related.tsx +31 -10
- package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
- package/blocks/deals/components/deals-screen.tsx +66 -26
- package/blocks/deals/components/forecast-view.tsx +4 -4
- package/blocks/deals/components/pipeline-board.tsx +8 -6
- package/blocks/deals/components/pipeline-editor.tsx +132 -0
- package/blocks/deals/components/record-deals-tab.tsx +62 -0
- package/blocks/deals/components/stage-editor.tsx +5 -5
- package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
- package/blocks/deals/hooks/use-pipelines.ts +1 -1
- package/blocks/deals/lib/deal-extras.ts +156 -0
- package/blocks/dialer/README.md +45 -4
- package/blocks/dialer/block.json +84 -3
- package/blocks/dialer/components/call-contact-button.tsx +269 -0
- package/blocks/dialer/components/call-log-screen.tsx +208 -0
- package/blocks/dialer/components/call-log.tsx +23 -7
- package/blocks/dialer/components/call-queue.tsx +4 -4
- package/blocks/dialer/components/dialer-screen.tsx +361 -0
- package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
- package/blocks/dialer/components/disposition-panel.tsx +1 -1
- package/blocks/dialer/components/live-calls-table.tsx +7 -3
- package/blocks/dialer/components/record-calls-tab.tsx +101 -0
- package/blocks/dialer/components/session-start-form.tsx +61 -12
- package/blocks/dialer/components/softphone.tsx +4 -2
- package/blocks/enrichment/README.md +29 -7
- package/blocks/enrichment/block.json +49 -9
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
- package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
- package/blocks/enrichment/components/enrich-button.tsx +32 -9
- package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
- package/blocks/enrichment/components/waterfall-config.tsx +66 -17
- package/blocks/enrichment/lib/enrichment-types.ts +39 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
- package/blocks/forms/README.md +8 -0
- package/blocks/forms/block.json +139 -18
- package/blocks/forms/components/form-activity.tsx +71 -0
- package/blocks/forms/components/form-backfill.tsx +108 -0
- package/blocks/forms/components/form-builder.tsx +4 -2
- package/blocks/forms/components/form-embed.tsx +4 -2
- package/blocks/forms/components/form-groups.tsx +151 -0
- package/blocks/forms/components/form-mapping-editor.tsx +6 -9
- package/blocks/forms/components/form-route-history.tsx +72 -0
- package/blocks/forms/components/form-saved-views.tsx +81 -0
- package/blocks/forms/components/forms-screen.tsx +56 -19
- package/blocks/forms/components/more-rows.tsx +30 -0
- package/blocks/forms/components/submissions-export.tsx +67 -0
- package/blocks/forms/components/submissions-table.tsx +17 -6
- package/blocks/forms/lib/forms-extra.ts +67 -0
- package/blocks/inbox/README.md +32 -3
- package/blocks/inbox/block.json +29 -2
- package/blocks/inbox/components/inbox-list.tsx +7 -3
- package/blocks/inbox/components/inbox-screen.tsx +294 -27
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/reply-composer.tsx +65 -9
- package/blocks/inbox/components/tag-and-assign.tsx +4 -2
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +1 -1
- package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
- package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
- package/blocks/inbox/lib/inbox-types.ts +47 -1
- package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
- package/blocks/integrations/README.md +63 -1
- package/blocks/integrations/block.json +234 -15
- package/blocks/integrations/components/ad-accounts.tsx +82 -0
- package/blocks/integrations/components/connection-health.tsx +59 -0
- package/blocks/integrations/components/crm-detail.tsx +119 -0
- package/blocks/integrations/components/crm-records.tsx +103 -0
- package/blocks/integrations/components/field-mappings.tsx +61 -0
- package/blocks/integrations/components/integration-list.tsx +13 -8
- package/blocks/integrations/components/integrations-screen.tsx +152 -0
- package/blocks/integrations/components/migration-panel.tsx +133 -0
- package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
- package/blocks/integrations/components/sync-status.tsx +11 -9
- package/blocks/integrations/components/sync-triggers.tsx +134 -0
- package/blocks/integrations/components/webhook-detail.tsx +179 -0
- package/blocks/integrations/components/webhook-manager.tsx +43 -25
- package/blocks/integrations/components/workspace-tools.tsx +127 -0
- package/blocks/integrations/lib/crm-utils.ts +39 -0
- package/blocks/integrations/lib/facts.tsx +13 -0
- package/blocks/integrations/lib/integrations-utils.ts +11 -1
- package/blocks/integrations/lib/load-more.tsx +141 -0
- package/blocks/intent/README.md +14 -0
- package/blocks/intent/block.json +27 -1
- package/blocks/intent/components/account-intent-score.tsx +5 -5
- package/blocks/intent/components/intent-feed.tsx +13 -10
- package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
- package/blocks/intent/components/intent-screen.tsx +265 -0
- package/blocks/intent/components/keyword-detail.tsx +124 -0
- package/blocks/intent/lib/intent-utils.ts +4 -4
- package/blocks/lifecycle/block.json +13 -8
- package/blocks/lifecycle/components/account-related.tsx +8 -1
- package/blocks/lifecycle/components/churn-list.tsx +5 -2
- package/blocks/lifecycle/components/customer-table.tsx +1 -1
- package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
- package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
- package/blocks/lifecycle/lib/lifecycle.ts +1 -1
- package/blocks/lists/README.md +20 -1
- package/blocks/lists/block.json +69 -3
- package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
- package/blocks/lists/components/add-to-list-button.tsx +20 -9
- package/blocks/lists/components/list-picker.tsx +17 -3
- package/blocks/lists/components/list-table.tsx +12 -6
- package/blocks/lists/components/lists-screen.tsx +346 -0
- package/blocks/lists/components/record-lists-tab.tsx +99 -0
- package/blocks/lists/components/suppression-manager.tsx +33 -8
- package/blocks/lists/components/suppressions-screen.tsx +264 -0
- package/blocks/mailboxes/README.md +17 -0
- package/blocks/mailboxes/block.json +69 -17
- package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
- package/blocks/mailboxes/components/domain-detail.tsx +148 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
- package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
- package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
- package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
- package/blocks/mailboxes/components/warmup-health.tsx +24 -22
- package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
- package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
- package/blocks/newsletter/README.md +12 -0
- package/blocks/newsletter/block.json +157 -24
- package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
- package/blocks/newsletter/components/issue-versions.tsx +93 -0
- package/blocks/newsletter/components/more-rows.tsx +30 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
- package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
- package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
- package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
- package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
- package/blocks/newsletter/components/subscriber-table.tsx +5 -7
- package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
- package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
- package/blocks/nurture/block.json +28 -14
- package/blocks/nurture/components/nurture-builder.tsx +13 -3
- package/blocks/nurture/components/nurture-enroll.tsx +12 -10
- package/blocks/nurture/components/nurture-form.tsx +27 -10
- package/blocks/nurture/components/nurture-screen.tsx +26 -11
- package/blocks/nurture/components/nurture-stats.tsx +5 -4
- package/blocks/nurture/lib/nurture-types.ts +1 -1
- package/blocks/objects/README.md +18 -1
- package/blocks/objects/block.json +42 -21
- package/blocks/objects/components/attribute-field-input.tsx +14 -1
- package/blocks/objects/components/object-not-found.tsx +3 -1
- package/blocks/objects/components/object-record-detail.tsx +10 -2
- package/blocks/objects/components/object-schema-editor.tsx +4 -2
- package/blocks/objects/components/object-table.tsx +13 -3
- package/blocks/objects/components/objects-screen.tsx +147 -0
- package/blocks/objects/components/record-roles-panel.tsx +15 -5
- package/blocks/objects/lib/object-types.ts +1 -1
- package/blocks/pages/README.md +14 -0
- package/blocks/pages/block.json +140 -23
- package/blocks/pages/components/brand-kit-editor.tsx +5 -2
- package/blocks/pages/components/content-library.tsx +9 -4
- package/blocks/pages/components/more-rows.tsx +30 -0
- package/blocks/pages/components/page-ai-tools.tsx +196 -0
- package/blocks/pages/components/page-analytics.tsx +176 -0
- package/blocks/pages/components/page-clone.tsx +99 -0
- package/blocks/pages/components/page-domains.tsx +167 -0
- package/blocks/pages/components/page-editor.tsx +1 -1
- package/blocks/pages/components/page-generation-status.tsx +48 -0
- package/blocks/pages/components/page-list.tsx +2 -1
- package/blocks/pages/components/page-tracking.tsx +137 -0
- package/blocks/pages/components/pages-screen.tsx +57 -16
- package/blocks/pages/components/seo-report.tsx +6 -1
- package/blocks/pages/lib/studio.ts +7 -0
- package/blocks/phone-numbers/README.md +74 -0
- package/blocks/phone-numbers/block.json +262 -0
- package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
- package/blocks/phone-numbers/components/assign-number.tsx +104 -0
- package/blocks/phone-numbers/components/number-search.tsx +239 -0
- package/blocks/phone-numbers/components/number-settings.tsx +134 -0
- package/blocks/phone-numbers/components/number-usage.tsx +97 -0
- package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
- package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
- package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
- package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
- package/blocks/phone-numbers/components/release-number.tsx +126 -0
- package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
- package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
- package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
- package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
- package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
- package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
- package/blocks/pickers/README.md +23 -0
- package/blocks/pickers/block.json +103 -0
- package/blocks/pickers/components/company-picker.tsx +90 -0
- package/blocks/pickers/components/contact-picker.tsx +131 -0
- package/blocks/pickers/components/member-picker.tsx +99 -0
- package/blocks/pickers/components/search-picker.tsx +98 -0
- package/blocks/pickers/hooks/use-current-member.ts +104 -0
- package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
- package/blocks/pickers/lib/use-debounced.ts +13 -0
- package/blocks/quotes/README.md +13 -0
- package/blocks/quotes/block.json +65 -13
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/product-picker.tsx +3 -1
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +5 -3
- package/blocks/quotes/components/quote-related.tsx +8 -4
- package/blocks/quotes/components/quote-viewer.tsx +7 -2
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/radar/README.md +10 -0
- package/blocks/radar/block.json +41 -1
- package/blocks/radar/components/competitor-battle-card.tsx +1 -1
- package/blocks/radar/components/competitor-detail.tsx +117 -0
- package/blocks/radar/components/competitor-tracker.tsx +3 -1
- package/blocks/radar/components/radar-overview.tsx +4 -4
- package/blocks/radar/components/radar-report.tsx +4 -2
- package/blocks/radar/components/radar-screen.tsx +214 -0
- package/blocks/radar/components/report-detail.tsx +104 -0
- package/blocks/search/README.md +10 -0
- package/blocks/search/block.json +16 -1
- package/blocks/search/components/filter-builder.tsx +3 -3
- package/blocks/search/components/prospect-results.tsx +5 -3
- package/blocks/search/components/prospect-search-screen.tsx +235 -0
- package/blocks/search/components/saved-searches.tsx +4 -1
- package/blocks/search/hooks/use-filter-options.ts +20 -6
- package/blocks/search/lib/search-types.ts +2 -0
- package/blocks/sequencer/README.md +55 -9
- package/blocks/sequencer/block.json +126 -19
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +224 -60
- package/blocks/sequencer/components/sequence-detail.tsx +261 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
- package/blocks/sequencer/components/sequence-list.tsx +34 -9
- package/blocks/sequencer/components/sequence-stats.tsx +11 -10
- package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
- package/blocks/sequencer/components/sequences-screen.tsx +162 -0
- package/blocks/sequencer/components/step-editor.tsx +14 -12
- package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
- package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
- package/blocks/social-ads/README.md +19 -0
- package/blocks/social-ads/block.json +233 -1
- package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
- package/blocks/social-ads/components/ads-screen.tsx +152 -0
- package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
- package/blocks/social-ads/components/audience-sync.tsx +17 -9
- package/blocks/social-ads/components/campaign-ads.tsx +166 -0
- package/blocks/social-ads/components/campaign-detail.tsx +138 -0
- package/blocks/social-ads/components/campaign-editor.tsx +134 -0
- package/blocks/social-ads/components/creative-concepts.tsx +74 -0
- package/blocks/social-ads/components/creative-detail.tsx +155 -0
- package/blocks/social-ads/components/creative-gallery.tsx +35 -7
- package/blocks/social-ads/components/creative-revisions.tsx +57 -0
- package/blocks/social-ads/components/creative-tools.tsx +176 -0
- package/blocks/social-ads/components/post-composer.tsx +7 -3
- package/blocks/social-ads/components/post-detail.tsx +163 -0
- package/blocks/social-ads/components/social-ideas.tsx +64 -0
- package/blocks/social-ads/components/social-performance.tsx +80 -0
- package/blocks/social-ads/components/social-post-list.tsx +11 -3
- package/blocks/social-ads/components/social-screen.tsx +187 -0
- package/blocks/social-ads/lib/ads-extra.ts +67 -0
- package/blocks/surveys/README.md +91 -0
- package/blocks/surveys/block.json +299 -0
- package/blocks/surveys/components/account-nps.tsx +69 -0
- package/blocks/surveys/components/contact-survey-history.tsx +52 -0
- package/blocks/surveys/components/org-summary.tsx +82 -0
- package/blocks/surveys/components/sending-domains.tsx +113 -0
- package/blocks/surveys/components/survey-form.tsx +126 -0
- package/blocks/surveys/components/survey-responses.tsx +145 -0
- package/blocks/surveys/components/survey-results.tsx +72 -0
- package/blocks/surveys/components/survey-send-history.tsx +58 -0
- package/blocks/surveys/components/survey-send.tsx +162 -0
- package/blocks/surveys/components/survey-triggers.tsx +166 -0
- package/blocks/surveys/components/surveys-screen.tsx +234 -0
- package/blocks/surveys/lib/surveys-types.ts +228 -0
- package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
- package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
- package/blocks/surveys/recipes/survey-score-report.ts +27 -0
- package/blocks/visitors/README.md +10 -0
- package/blocks/visitors/block.json +25 -1
- package/blocks/visitors/components/snippet-install.tsx +21 -10
- package/blocks/visitors/components/traffic-chart.tsx +4 -4
- package/blocks/visitors/components/visitor-company-card.tsx +26 -16
- package/blocks/visitors/components/visitor-detail.tsx +105 -0
- package/blocks/visitors/components/visitor-feed.tsx +16 -7
- package/blocks/visitors/components/visitors-screen.tsx +166 -0
- package/blocks/webchat/README.md +10 -0
- package/blocks/webchat/block.json +21 -7
- package/blocks/webchat/components/chat-widget-config.tsx +8 -5
- package/blocks/webchat/components/conversation-list.tsx +8 -4
- package/blocks/webchat/components/webchat-screen.tsx +245 -0
- package/blocks/workbench/README.md +11 -0
- package/blocks/workbench/block.json +48 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-grid.tsx +10 -4
- package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
- package/blocks/workbench/components/workbench-screen.tsx +202 -0
- package/blocks/workbench/components/workbench-sources.tsx +194 -0
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workflows/README.md +13 -0
- package/blocks/workflows/block.json +28 -9
- package/blocks/workflows/components/run-history.tsx +31 -9
- package/blocks/workflows/components/trigger-config.tsx +3 -1
- package/blocks/workflows/components/workflow-editor.tsx +4 -2
- package/blocks/workflows/components/workflow-list.tsx +13 -6
- package/blocks/workflows/components/workflows-screen.tsx +193 -0
- package/dist/cli.js +3109 -1977
- package/dist/errors.d.mts +34 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.mjs +21 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.browser.js +11 -5
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +11 -5
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +164 -25
- package/dist/index.d.ts +164 -25
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +10 -4
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +10 -4
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +142 -5
- package/dist/react.d.ts +142 -5
- package/dist/react.js +10 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +10 -4
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/add.d.ts +11 -4
- package/dist/types/blocks/brand.d.ts +122 -0
- package/dist/types/blocks/compose.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +27 -20
- package/dist/types/blocks/doctor.d.ts +6 -1
- package/dist/types/blocks/flows.d.ts +7 -1
- package/dist/types/blocks/navfile.d.ts +35 -0
- package/dist/types/blocks/orgBrand.d.ts +34 -0
- package/dist/types/blocks/paths.d.ts +4 -0
- package/dist/types/blocks/prefixes.d.ts +37 -0
- package/dist/types/blocks/registry.d.ts +17 -0
- package/dist/types/blocks/screens.d.ts +53 -0
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/errors.d.ts +33 -0
- package/dist/types/http.d.ts +2 -18
- package/dist/types/version.d.ts +2 -2
- package/docs/agents.md +15 -4
- package/docs/api/README.md +2 -2
- package/docs/api/by-path.md +1 -0
- package/docs/api/desk.d.ts +98 -2
- package/docs/api/desk.md +123 -1
- package/docs/api/inbox.d.ts +10 -2
- package/docs/api/inbox.md +10 -2
- package/docs/api/seo.d.ts +19 -1
- package/docs/api/seo.md +24 -1
- package/package.json +16 -2
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +55 -17
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
- package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
- package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
- package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +147 -37
- package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
- package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
- package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
- package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
- package/stacks/calendly.json +64 -0
- package/stacks/clay/pages/companies.tsx.tmpl +31 -0
- package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
- package/stacks/clay/pages/lists.tsx.tmpl +12 -0
- package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
- package/stacks/clay.json +73 -27
- package/stacks/gong/pages/calls.tsx.tmpl +23 -0
- package/stacks/gong/pages/companies.tsx.tmpl +22 -0
- package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
- package/stacks/gong/pages/lists.tsx.tmpl +12 -0
- package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
- package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
- package/stacks/gong.json +98 -35
- package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
- package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +104 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
- package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
- package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
- package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
- package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
- package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
- package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot.json +181 -64
- package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
- package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
- package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
- package/stacks/intercom.json +52 -20
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +68 -34
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
- package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
- package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
- package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +106 -25
- package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
- package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
- package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
- package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
- package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
- package/stacks/pipedrive.json +117 -0
- package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
- package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
- package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
- package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
- package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
- package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
- package/stacks/salesforce.json +85 -31
- package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +115 -0
- package/styles/styles.css +1 -1
- package/templates/next-app/README.md.tmpl +40 -9
- package/templates/next-app/app/globals.css.tmpl +349 -64
- package/templates/next-app/app/layout.tsx.tmpl +7 -2
- package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
- package/templates/next-app/dot-gitignore.tmpl +2 -0
- package/templates/next-app/package.json.tmpl +1 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "phone-numbers",
|
|
4
|
+
"title": "Phone numbers",
|
|
5
|
+
"summary": "Phone numbers: list, search and buy numbers, assign them to reps or agents, configure how they answer, check usage and text registration, and release them.",
|
|
6
|
+
"features": [
|
|
7
|
+
"every number in one list (dialer and voice agent numbers), searchable and filterable, paged on the client with Load more",
|
|
8
|
+
"search a carrier for available numbers by area code, digits or city, then buy one behind a confirm dialog that shows the price",
|
|
9
|
+
"assign a number to a teammate with a picker (or Assign to me), unassign it, re-sync its voice record",
|
|
10
|
+
"answering mode (voice AI agent, rep, dialer, personal work phone), Telnyx caller-ID verification, who answers",
|
|
11
|
+
"today's usage against the daily limit, 7-day calls and connect rate, confirmed counter reset",
|
|
12
|
+
"A2P 10DLC text registration: status, draft details, confirmed submit, retry stuck numbers",
|
|
13
|
+
"release a number (type it, then confirm) or remove its voice routing only, both confirmed"
|
|
14
|
+
],
|
|
15
|
+
"dependsOn": [
|
|
16
|
+
"foundation",
|
|
17
|
+
"pickers"
|
|
18
|
+
],
|
|
19
|
+
"operations": [
|
|
20
|
+
"delete_voice_number_voice_numbers_delete",
|
|
21
|
+
"get_my_team_member_team_members_me_get",
|
|
22
|
+
"get_phone_a2p_status_phone_numbers_a2p_status_get",
|
|
23
|
+
"get_voice_answering_agent_voice_numbers_answering_agent_get",
|
|
24
|
+
"list_agent_phone_numbers_voice_agent_phone_numbers_get",
|
|
25
|
+
"list_dialer_numbers_voice_dialer_numbers_get",
|
|
26
|
+
"list_team_members_team_members_get",
|
|
27
|
+
"provision_voice_number_voice_numbers_provision_post",
|
|
28
|
+
"purchase_phone_number_phone_numbers_purchase_post",
|
|
29
|
+
"reconcile_a2p_registration_phone_numbers_a2p_reconcile_post",
|
|
30
|
+
"release_phone_number_phone_numbers_release_post",
|
|
31
|
+
"reset_voice_daily_counts_voice_numbers_daily_counts_delete",
|
|
32
|
+
"save_a2p_inputs_phone_numbers_a2p_inputs_post",
|
|
33
|
+
"search_available_phone_numbers_phone_numbers_search_post",
|
|
34
|
+
"start_a2p_registration_phone_numbers_a2p_register_post",
|
|
35
|
+
"update_phone_assignee_phone_numbers_update_assignee_patch",
|
|
36
|
+
"update_phone_capabilities_phone_numbers_update_capabilities_patch",
|
|
37
|
+
"update_voice_assignee_voice_numbers_assignee_patch",
|
|
38
|
+
"update_voice_inbound_type_voice_numbers_inbound_type_patch",
|
|
39
|
+
"verify_voice_caller_id_voice_numbers_verify_caller_id__phone_number__post"
|
|
40
|
+
],
|
|
41
|
+
"components": [
|
|
42
|
+
{
|
|
43
|
+
"name": "PhoneNumbersScreen",
|
|
44
|
+
"file": "components/phone-numbers-screen.tsx",
|
|
45
|
+
"purpose": "The finished Phone numbers page: inventory with stats, search and filters, a detail for each number, buy, add an owned number, and text registration. Composes the parts below.",
|
|
46
|
+
"operations": [],
|
|
47
|
+
"tiers": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "PhoneNumberList",
|
|
51
|
+
"file": "components/phone-number-list.tsx",
|
|
52
|
+
"purpose": "The numbers as a table, paged on the client with Load more so nothing is cut off silently. Reads nothing itself.",
|
|
53
|
+
"operations": [],
|
|
54
|
+
"tiers": []
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "PhoneNumberDetail",
|
|
58
|
+
"file": "components/phone-number-detail.tsx",
|
|
59
|
+
"purpose": "One number in full: Overview, Assign, Usage, Settings and Release tabs, with slots for extra actions and tabs.",
|
|
60
|
+
"operations": [],
|
|
61
|
+
"tiers": []
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "NumberSearch",
|
|
65
|
+
"file": "components/number-search.tsx",
|
|
66
|
+
"purpose": "Search a carrier for available numbers, choose one and buy it. The purchase is billable and opens a confirm dialog with the price estimate first.",
|
|
67
|
+
"operations": [
|
|
68
|
+
"list_team_members_team_members_get",
|
|
69
|
+
"purchase_phone_number_phone_numbers_purchase_post",
|
|
70
|
+
"search_available_phone_numbers_phone_numbers_search_post"
|
|
71
|
+
],
|
|
72
|
+
"tiers": [
|
|
73
|
+
"read",
|
|
74
|
+
"billable"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "AssignNumber",
|
|
79
|
+
"file": "components/assign-number.tsx",
|
|
80
|
+
"purpose": "Assign a number to a teammate with a picker or Assign to me, unassign it, or re-sync its voice record.",
|
|
81
|
+
"operations": [
|
|
82
|
+
"get_my_team_member_team_members_me_get",
|
|
83
|
+
"list_team_members_team_members_get",
|
|
84
|
+
"update_phone_assignee_phone_numbers_update_assignee_patch",
|
|
85
|
+
"update_voice_assignee_voice_numbers_assignee_patch"
|
|
86
|
+
],
|
|
87
|
+
"tiers": [
|
|
88
|
+
"read",
|
|
89
|
+
"write"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "NumberSettings",
|
|
94
|
+
"file": "components/number-settings.tsx",
|
|
95
|
+
"purpose": "Answering mode (voice AI, rep, dialer, work phone), Telnyx caller-ID verification and who answers the number.",
|
|
96
|
+
"operations": [
|
|
97
|
+
"get_voice_answering_agent_voice_numbers_answering_agent_get",
|
|
98
|
+
"update_phone_capabilities_phone_numbers_update_capabilities_patch",
|
|
99
|
+
"update_voice_inbound_type_voice_numbers_inbound_type_patch",
|
|
100
|
+
"verify_voice_caller_id_voice_numbers_verify_caller_id__phone_number__post"
|
|
101
|
+
],
|
|
102
|
+
"tiers": [
|
|
103
|
+
"read",
|
|
104
|
+
"write"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "NumberUsage",
|
|
109
|
+
"file": "components/number-usage.tsx",
|
|
110
|
+
"purpose": "Today's calls against the daily limit, 7-day usage and connect rate, and a confirmed reset of today's counters.",
|
|
111
|
+
"operations": [
|
|
112
|
+
"reset_voice_daily_counts_voice_numbers_daily_counts_delete"
|
|
113
|
+
],
|
|
114
|
+
"tiers": [
|
|
115
|
+
"destructive"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "ReleaseNumber",
|
|
120
|
+
"file": "components/release-number.tsx",
|
|
121
|
+
"purpose": "Release a number back to the carrier (type the number, then confirm) or remove its voice routing without releasing it. Both are confirmed.",
|
|
122
|
+
"operations": [
|
|
123
|
+
"delete_voice_number_voice_numbers_delete",
|
|
124
|
+
"release_phone_number_phone_numbers_release_post"
|
|
125
|
+
],
|
|
126
|
+
"tiers": [
|
|
127
|
+
"destructive"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "RegisterOwnedNumber",
|
|
132
|
+
"file": "components/register-owned-number.tsx",
|
|
133
|
+
"purpose": "Register a number you already own with the voice platform. Buys nothing.",
|
|
134
|
+
"operations": [
|
|
135
|
+
"list_team_members_team_members_get",
|
|
136
|
+
"provision_voice_number_voice_numbers_provision_post"
|
|
137
|
+
],
|
|
138
|
+
"tiers": [
|
|
139
|
+
"read",
|
|
140
|
+
"write"
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "A2pRegistration",
|
|
145
|
+
"file": "components/a2p-registration.tsx",
|
|
146
|
+
"purpose": "A2P 10DLC text-message registration: status, a saved draft of the business details, a confirmed submit and a retry for stuck numbers.",
|
|
147
|
+
"operations": [
|
|
148
|
+
"get_phone_a2p_status_phone_numbers_a2p_status_get",
|
|
149
|
+
"reconcile_a2p_registration_phone_numbers_a2p_reconcile_post",
|
|
150
|
+
"save_a2p_inputs_phone_numbers_a2p_inputs_post",
|
|
151
|
+
"start_a2p_registration_phone_numbers_a2p_register_post"
|
|
152
|
+
],
|
|
153
|
+
"tiers": [
|
|
154
|
+
"read",
|
|
155
|
+
"write",
|
|
156
|
+
"external"
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"hooks": [
|
|
161
|
+
{
|
|
162
|
+
"name": "usePhoneNumbers",
|
|
163
|
+
"file": "hooks/use-phone-numbers.ts",
|
|
164
|
+
"purpose": "Every number of the workspace: the dialer list and the voice agents' numbers read together and merged. Neither endpoint caps or pages, so the whole inventory arrives; a key that cannot read agents still gets the dialer list and the hook says so.",
|
|
165
|
+
"operations": [
|
|
166
|
+
"list_agent_phone_numbers_voice_agent_phone_numbers_get",
|
|
167
|
+
"list_dialer_numbers_voice_dialer_numbers_get"
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"lib": [
|
|
172
|
+
{
|
|
173
|
+
"file": "lib/phone-numbers-utils.ts",
|
|
174
|
+
"purpose": "Row types, the carrier and answering-mode enums, E.164 checks and formatting, the inventory merge, health and A2P tones, price estimates."
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"recipes": [
|
|
178
|
+
{
|
|
179
|
+
"file": "recipes/01-search-and-buy-number.ts",
|
|
180
|
+
"title": "Search for a number and buy it",
|
|
181
|
+
"when": "An agent setting up calling for a workspace. The search is free; the purchase spends credits and needs a human to approve it."
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"file": "recipes/02-assign-number-to-rep.ts",
|
|
185
|
+
"title": "Assign a number to a rep, or clear it",
|
|
186
|
+
"when": "Onboarding a rep: give them a caller ID, or take a number back from someone who left."
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"file": "recipes/03-audit-numbers.ts",
|
|
190
|
+
"title": "Audit the inventory: usage, unassigned numbers and text registration",
|
|
191
|
+
"when": "A health check before a calling campaign; read-only."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"file": "recipes/04-register-text-messaging.ts",
|
|
195
|
+
"title": "Save A2P details and check registration",
|
|
196
|
+
"when": "Before sending texts to US numbers; saving is safe, submitting reaches the carriers and needs approval."
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"prerequisites": [
|
|
200
|
+
{
|
|
201
|
+
"id": "credits",
|
|
202
|
+
"description": "Credits for buying a number: 250 are held on purchase, then about 250 a month in rental (estimates; the API has no price read). A 402 means not enough credits and nothing was charged.",
|
|
203
|
+
"where": "api",
|
|
204
|
+
"verify": "A test purchase of one number returns 200, or 402 with no charge."
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"id": "admin-key",
|
|
208
|
+
"description": "Reassigning a number, switching its answering mode and resetting counters are admin-only through the API. A key without that role gets a 403, which the screens explain.",
|
|
209
|
+
"where": "api",
|
|
210
|
+
"verify": "PATCH /phone-numbers/update-assignee on a test number returns 200, not 403."
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"id": "voice-scopes",
|
|
214
|
+
"description": "Key scopes voice:read, voice:write and voice:run (buy, A2P submit), voice:delete (release, remove routing, reset counters) and team:read for the assignee picker.",
|
|
215
|
+
"where": "api",
|
|
216
|
+
"verify": "`g8.api.me.describeCurrentKey()` lists voice:read, voice:write, voice:run, voice:delete and team:read."
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
"scopes": [
|
|
220
|
+
"team:read",
|
|
221
|
+
"voice:delete",
|
|
222
|
+
"voice:read",
|
|
223
|
+
"voice:run",
|
|
224
|
+
"voice:write"
|
|
225
|
+
],
|
|
226
|
+
"defaults": [
|
|
227
|
+
{
|
|
228
|
+
"decision": "Which carrier to buy from",
|
|
229
|
+
"default": "Twilio. Telnyx is one select away; a Telnyx number must then be verified as a caller ID before it can dial",
|
|
230
|
+
"askHuman": false
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"decision": "What a newly bought number is for",
|
|
234
|
+
"default": "The outbound dialer, unassigned (it is the signed-in key's person unless an assignee is chosen)",
|
|
235
|
+
"askHuman": false
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"decision": "Buying numbers",
|
|
239
|
+
"default": "Always behind a confirm dialog with the price estimate and an Idempotency-Key; never automatic",
|
|
240
|
+
"askHuman": true
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"decision": "Releasing a number",
|
|
244
|
+
"default": "Needs the number typed and a second confirm; it cannot be undone and the number can be bought by anyone",
|
|
245
|
+
"askHuman": true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"decision": "Is a number billable",
|
|
249
|
+
"default": "The block never labels a number billable: the API reports no rental per number, and numbers the platform provisioned itself (the onboarding receptionist) are never rental-billed. Only the purchase dialog shows a price",
|
|
250
|
+
"askHuman": false
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"decision": "Call forwarding, voicemail greetings and routing rules",
|
|
254
|
+
"default": "Not offered: the API has no operations for them. The screen points to the platform app",
|
|
255
|
+
"askHuman": false
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
"peerDependencies": {
|
|
259
|
+
"react": ">=18",
|
|
260
|
+
"lucide-react": ">=0.400.0"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A2pRegistration: US carriers filter text messages from numbers whose business is not registered (A2P 10DLC). This shows
|
|
5
|
+
* where the workspace's registration stands, saves the details as a draft, submits them, and re-runs numbers that got stuck.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. getPhoneA2pStatus GET /phone-numbers/a2p/status (read; brand and campaign status, missing inputs, carrier failures)
|
|
9
|
+
* 2. saveA2pInputs POST /phone-numbers/a2p/inputs (write; saves a DRAFT, sends nothing; it REPLACES what was saved,
|
|
10
|
+
* so the whole object is sent, merged over what the status returned)
|
|
11
|
+
* 3. startA2pRegistration POST /phone-numbers/a2p/register (external: reaches the carrier registry and carries
|
|
12
|
+
* carrier fees, takes days and can be rejected. Idempotent per workspace. Confirmed
|
|
13
|
+
* with ConfirmSpendDialog.)
|
|
14
|
+
* 4. reconcileA2pRegistration POST /phone-numbers/a2p/reconcile (write; re-attaches pending or failed numbers; safe to repeat)
|
|
15
|
+
*
|
|
16
|
+
* The form is shown only once the status has loaded and is seeded from it, so a save never overwrites saved details with blanks.
|
|
17
|
+
* Brand and campaign objects are open: keys this form does not edit (an address, say) are carried through untouched.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useEffect, useState } from "react";
|
|
21
|
+
import { Button, Input, Label, Textarea } from "@graph8/sdk/ui";
|
|
22
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
23
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
24
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
25
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
26
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
27
|
+
import { KeyValueList } from "@/g8/components/screen/key-value-list";
|
|
28
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
29
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
30
|
+
import { Skeleton } from "@graph8/sdk/ui";
|
|
31
|
+
import { a2pTone, asObject, dataOf } from "../lib/phone-numbers-utils";
|
|
32
|
+
|
|
33
|
+
export interface A2pRegistrationProps {
|
|
34
|
+
/** Called when the status was reloaded, so a stat can follow it. */
|
|
35
|
+
onStatus?: (status: A2pStatus) => void;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface A2pStatus {
|
|
40
|
+
brandStatus: string | null;
|
|
41
|
+
campaignStatus: string | null;
|
|
42
|
+
submitted: boolean;
|
|
43
|
+
inputsComplete: boolean;
|
|
44
|
+
missing: string[];
|
|
45
|
+
failures: Array<{ scope?: string | null; code?: number | null; reason?: string | null; fields?: string[] }>;
|
|
46
|
+
brand: Record<string, unknown>;
|
|
47
|
+
campaign: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function a2pStatusOf(envelope: unknown): A2pStatus {
|
|
51
|
+
const d = dataOf(envelope);
|
|
52
|
+
const text = (v: unknown) => (typeof v === "string" && v ? v : null);
|
|
53
|
+
return {
|
|
54
|
+
brandStatus: text(d.brand_status),
|
|
55
|
+
campaignStatus: text(d.campaign_status),
|
|
56
|
+
submitted: Boolean(d.workflow_id),
|
|
57
|
+
inputsComplete: d.inputs_complete === true,
|
|
58
|
+
missing: Array.isArray(d.missing_inputs) ? d.missing_inputs.filter((m): m is string => typeof m === "string") : [],
|
|
59
|
+
failures: (Array.isArray(d.failures) ? d.failures : []).map((f) => asObject(f)) as A2pStatus["failures"],
|
|
60
|
+
brand: asObject(d.brand_inputs),
|
|
61
|
+
campaign: asObject(d.campaign_inputs),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const NAMES: Record<string, string> = {
|
|
66
|
+
legal_business_name: "Legal business name",
|
|
67
|
+
use_case: "Use case",
|
|
68
|
+
message_samples: "Sample messages",
|
|
69
|
+
description: "Campaign description",
|
|
70
|
+
message_flow: "How people opt in",
|
|
71
|
+
privacy_policy_url: "Privacy policy URL",
|
|
72
|
+
terms_and_conditions_url: "Terms of service URL",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const BRAND_FIELDS: Array<[string, string, string]> = [
|
|
76
|
+
["legal_business_name", "Legal business name", "Acme Inc."],
|
|
77
|
+
["ein", "EIN (tax id)", "12-3456789"],
|
|
78
|
+
["website", "Website", "https://acme.example"],
|
|
79
|
+
["business_type", "Business type", "Corporation"],
|
|
80
|
+
["vertical", "Industry", "Technology"],
|
|
81
|
+
];
|
|
82
|
+
const CAMPAIGN_LINES: Array<[string, string, string]> = [
|
|
83
|
+
["use_case", "Use case", "MIXED, MARKETING or ACCOUNT_NOTIFICATION"],
|
|
84
|
+
["privacy_policy_url", "Privacy policy URL", "https://acme.example/privacy"],
|
|
85
|
+
["terms_and_conditions_url", "Terms of service URL", "https://acme.example/terms"],
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const asText = (v: unknown): string => (typeof v === "string" ? v : "");
|
|
89
|
+
const samplesOf = (v: unknown): string => (Array.isArray(v) ? v.filter((x) => typeof x === "string").join("\n") : asText(v));
|
|
90
|
+
|
|
91
|
+
export function A2pRegistration({ onStatus, className }: A2pRegistrationProps) {
|
|
92
|
+
const q = useG8Query(async (signal) => a2pStatusOf(await g8Get("/phone-numbers/a2p/status", undefined, { signal })), []);
|
|
93
|
+
const status = q.data;
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (status) onStatus?.(status);
|
|
96
|
+
}, [status]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
97
|
+
if (q.error && !status) return <ApiError error={q.error} onRetry={() => void q.refetch()} className={className} />;
|
|
98
|
+
if (!status) return <Skeleton className={`h-40 w-full ${className ?? ""}`} />;
|
|
99
|
+
// The form is mounted only once the status has loaded, with its state seeded at the FIRST render from it. (Seeding in an effect
|
|
100
|
+
// let an edit typed right after the form appeared be overwritten by the late seed, and a save then dropped that field.)
|
|
101
|
+
// A reload after a save keeps what is typed; the save merges over the fresh server copy (`status`).
|
|
102
|
+
return <A2pForm status={status} reload={() => q.refetch()} className={className} />;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function A2pForm({ status, reload, className }: { status: A2pStatus; reload: () => Promise<unknown>; className?: string }) {
|
|
106
|
+
const [brand, setBrand] = useState<Record<string, string>>(() => Object.fromEntries(BRAND_FIELDS.map(([k]) => [k, asText(status.brand[k])])));
|
|
107
|
+
const [campaign, setCampaign] = useState<Record<string, string>>(() =>
|
|
108
|
+
Object.fromEntries([...CAMPAIGN_LINES.map(([k]) => k), "description", "message_flow", "opt_in_method"].map((k) => [k, asText(status.campaign[k])])),
|
|
109
|
+
);
|
|
110
|
+
const [samples, setSamples] = useState(() => samplesOf(status.campaign.message_samples));
|
|
111
|
+
const [confirming, setConfirming] = useState(false);
|
|
112
|
+
const [note, setNote] = useState<string | null>(null);
|
|
113
|
+
|
|
114
|
+
const body = () => {
|
|
115
|
+
const clean = (o: Record<string, string>) => Object.fromEntries(Object.entries(o).filter(([, v]) => v.trim() !== "").map(([k, v]) => [k, v.trim()]));
|
|
116
|
+
const lines = samples.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
117
|
+
return {
|
|
118
|
+
brand_inputs: { ...status.brand, ...clean(brand) },
|
|
119
|
+
campaign_inputs: { ...status.campaign, ...clean(campaign), ...(lines.length ? { message_samples: lines } : {}) },
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const save = useG8Mutation(async () => {
|
|
124
|
+
await g8Post("/phone-numbers/a2p/inputs", body());
|
|
125
|
+
setNote("Draft saved. Nothing was sent to the carriers.");
|
|
126
|
+
await reload();
|
|
127
|
+
});
|
|
128
|
+
const submit = useG8Mutation(async () => {
|
|
129
|
+
await g8Post("/phone-numbers/a2p/register", body());
|
|
130
|
+
setNote("Registration submitted. The carriers take days to decide; this page shows the result.");
|
|
131
|
+
setConfirming(false);
|
|
132
|
+
await reload();
|
|
133
|
+
});
|
|
134
|
+
const reconcile = useG8Mutation(async () => {
|
|
135
|
+
await g8Post("/phone-numbers/a2p/reconcile", {});
|
|
136
|
+
setNote("Pending and failed numbers were re-attached to the messaging service.");
|
|
137
|
+
await reload();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const failed = status.failures.filter((f) => f.reason);
|
|
141
|
+
const busy = save.loading || submit.loading || reconcile.loading;
|
|
142
|
+
const field = (id: string, label: string, value: string, set: (v: string) => void, placeholder?: string) => (
|
|
143
|
+
<div className="space-y-1" key={id}>
|
|
144
|
+
<Label htmlFor={`a2p-${id}`}>{label}</Label>
|
|
145
|
+
<Input id={`a2p-${id}`} value={value} placeholder={placeholder} onChange={(e) => set(e.target.value)} />
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className={`space-y-4 ${className ?? ""}`}>
|
|
151
|
+
<SectionCard
|
|
152
|
+
title="Text message registration (A2P 10DLC)"
|
|
153
|
+
description="US carriers filter business text messages from numbers that are not registered."
|
|
154
|
+
actions={
|
|
155
|
+
<StatusPill tone={a2pTone(status.campaignStatus ?? status.brandStatus)} dot>
|
|
156
|
+
{status.campaignStatus ?? status.brandStatus ?? "Not started"}
|
|
157
|
+
</StatusPill>
|
|
158
|
+
}
|
|
159
|
+
>
|
|
160
|
+
<KeyValueList
|
|
161
|
+
columns={2}
|
|
162
|
+
items={[
|
|
163
|
+
{ label: "Brand", value: status.brandStatus ?? "-" },
|
|
164
|
+
{ label: "Campaign", value: status.campaignStatus ?? "-" },
|
|
165
|
+
{ label: "Submitted", value: status.submitted ? "Yes" : "No" },
|
|
166
|
+
{ label: "Details complete", value: status.inputsComplete ? "Yes" : "No" },
|
|
167
|
+
]}
|
|
168
|
+
/>
|
|
169
|
+
{status.missing.length > 0 ? (
|
|
170
|
+
<p className="mt-3 text-sm text-muted-foreground">Still needed: {status.missing.map((m) => NAMES[m] ?? m).join(", ")}.</p>
|
|
171
|
+
) : null}
|
|
172
|
+
{failed.length > 0 ? (
|
|
173
|
+
<ul aria-label="Carrier feedback" className="mt-3 space-y-1 text-sm">
|
|
174
|
+
{failed.map((f, i) => (
|
|
175
|
+
<li key={i} className="rounded-md border border-border bg-muted px-3 py-2">
|
|
176
|
+
{f.scope ? <span className="font-medium capitalize">{f.scope}: </span> : null}
|
|
177
|
+
{f.reason}
|
|
178
|
+
{f.code ? <span className="text-xs text-muted-foreground"> (code {f.code})</span> : null}
|
|
179
|
+
</li>
|
|
180
|
+
))}
|
|
181
|
+
</ul>
|
|
182
|
+
) : null}
|
|
183
|
+
</SectionCard>
|
|
184
|
+
|
|
185
|
+
<SectionCard title="Business and campaign details" description="Saving a draft sends nothing. Submitting registers with the carriers.">
|
|
186
|
+
<form className="space-y-4" onSubmit={(e) => { e.preventDefault(); void save.mutate(undefined); }}>
|
|
187
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
188
|
+
{BRAND_FIELDS.map(([k, label, ph]) => field(`b-${k}`, label, brand[k] ?? "", (v) => setBrand((b) => ({ ...b, [k]: v })), ph))}
|
|
189
|
+
{CAMPAIGN_LINES.map(([k, label, ph]) => field(`c-${k}`, label, campaign[k] ?? "", (v) => setCampaign((c) => ({ ...c, [k]: v })), ph))}
|
|
190
|
+
</div>
|
|
191
|
+
<div className="space-y-1">
|
|
192
|
+
<Label htmlFor="a2p-description">What the messages are (40 characters or more)</Label>
|
|
193
|
+
<Textarea id="a2p-description" rows={2} value={campaign.description ?? ""} onChange={(e) => setCampaign((c) => ({ ...c, description: e.target.value }))} />
|
|
194
|
+
</div>
|
|
195
|
+
<div className="space-y-1">
|
|
196
|
+
<Label htmlFor="a2p-flow">How people opt in (40 characters or more)</Label>
|
|
197
|
+
<Textarea id="a2p-flow" rows={2} value={campaign.message_flow ?? ""} onChange={(e) => setCampaign((c) => ({ ...c, message_flow: e.target.value }))} />
|
|
198
|
+
</div>
|
|
199
|
+
<div className="space-y-1">
|
|
200
|
+
<Label htmlFor="a2p-samples">Sample messages (one per line)</Label>
|
|
201
|
+
<Textarea id="a2p-samples" rows={3} value={samples} onChange={(e) => setSamples(e.target.value)} />
|
|
202
|
+
</div>
|
|
203
|
+
<div className="flex flex-wrap gap-2">
|
|
204
|
+
<Button type="submit" variant="outline" loading={save.loading} disabled={busy}>Save draft</Button>
|
|
205
|
+
<Button type="button" disabled={busy || !status.inputsComplete} onClick={() => setConfirming(true)}>Submit registration</Button>
|
|
206
|
+
<Button type="button" variant="ghost" loading={reconcile.loading} disabled={busy} onClick={() => void reconcile.mutate(undefined)}>Retry stuck numbers</Button>
|
|
207
|
+
</div>
|
|
208
|
+
{!status.inputsComplete ? <p className="text-xs text-muted-foreground">Submitting unlocks once the saved details are complete. Save the draft to check.</p> : null}
|
|
209
|
+
</form>
|
|
210
|
+
{save.error ? <ApiError error={save.error} title="The draft was not saved" onRetry={save.reset} className="mt-3" /> : null}
|
|
211
|
+
{submit.error ? <ApiError error={submit.error} title="The registration was not submitted" onRetry={submit.reset} className="mt-3" /> : null}
|
|
212
|
+
{reconcile.error ? <ApiError error={reconcile.error} title="The numbers were not re-attached" onRetry={reconcile.reset} className="mt-3" /> : null}
|
|
213
|
+
{note ? <p role="status" className="mt-3 text-sm">{note}</p> : null}
|
|
214
|
+
</SectionCard>
|
|
215
|
+
|
|
216
|
+
{confirming ? (
|
|
217
|
+
<ConfirmSpendDialog
|
|
218
|
+
open
|
|
219
|
+
tier="external"
|
|
220
|
+
title="Submit the registration to the carriers"
|
|
221
|
+
description="This sends your business details to the US carrier registry and carries carrier fees. It takes days, the carriers can reject it, and a second submit while one is running does nothing."
|
|
222
|
+
confirmLabel="Submit registration"
|
|
223
|
+
confirming={submit.loading}
|
|
224
|
+
onCancel={() => setConfirming(false)}
|
|
225
|
+
onConfirm={() => void submit.mutate(undefined).then(() => setConfirming(false))}
|
|
226
|
+
/>
|
|
227
|
+
) : null}
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AssignNumber: who a number belongs to. The assignee is not a label: it decides who the number rings, who is told about
|
|
5
|
+
* a missed call, and whose outbound caller-ID pool it joins.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations:
|
|
8
|
+
* updatePhoneAssignee PATCH /phone-numbers/update-assignee (write; moves the number to a person, in the CRM record and in voice)
|
|
9
|
+
* updateVoiceAssignee PATCH /voice/numbers/assignee (write; the VOICE half alone. `assigned_to: ""` clears the
|
|
10
|
+
* assignment, and sending the current assignee again re-syncs voice)
|
|
11
|
+
*
|
|
12
|
+
* The person comes from MemberPicker (no one types an email); "Assign to me" uses the signed-in member. Reassigning is
|
|
13
|
+
* admin-only through the API: a 403 says so instead of failing silently.
|
|
14
|
+
* Unassign is VOICE-ONLY: update-assignee requires an assignee, so there is no operation that clears the CRM record's owner.
|
|
15
|
+
* The UI says so next to the button.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
import { Button } from "@graph8/sdk/ui";
|
|
20
|
+
import { g8Patch } from "@/g8/lib/g8-client";
|
|
21
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
22
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
23
|
+
import { MemberPicker } from "@/g8/pickers/components/member-picker";
|
|
24
|
+
import type { MemberOption } from "@/g8/pickers/components/member-picker";
|
|
25
|
+
import { useCurrentMember } from "@/g8/pickers/hooks/use-current-member";
|
|
26
|
+
import { formatNumber } from "../lib/phone-numbers-utils";
|
|
27
|
+
import type { NumberRow } from "../lib/phone-numbers-utils";
|
|
28
|
+
|
|
29
|
+
export interface AssignNumberProps {
|
|
30
|
+
row: NumberRow;
|
|
31
|
+
/** Called after the assignment changed, so the list can refresh. */
|
|
32
|
+
onChanged?: () => void;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function AssignNumber({ row, onChanged, className }: AssignNumberProps) {
|
|
37
|
+
const me = useCurrentMember();
|
|
38
|
+
const [picked, setPicked] = useState<MemberOption | null>(null);
|
|
39
|
+
const [note, setNote] = useState<string | null>(null);
|
|
40
|
+
|
|
41
|
+
const assign = useG8Mutation(async (member: MemberOption) => {
|
|
42
|
+
if (!member.email) throw new Error(`${member.name} has no email on file, so the number cannot be assigned to them.`);
|
|
43
|
+
await g8Patch("/phone-numbers/update-assignee", { phone_number: row.number, assignee: member.email });
|
|
44
|
+
setNote(`${formatNumber(row.number)} now rings for ${member.name}.`);
|
|
45
|
+
setPicked(null);
|
|
46
|
+
onChanged?.();
|
|
47
|
+
});
|
|
48
|
+
const voice = useG8Mutation(async (assignedTo: string) => {
|
|
49
|
+
await g8Patch("/voice/numbers/assignee", { number: row.number, assigned_to: assignedTo });
|
|
50
|
+
setNote(assignedTo ? "Voice assignment re-synced." : `${formatNumber(row.number)} is unassigned.`);
|
|
51
|
+
onChanged?.();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const error = assign.error ?? voice.error;
|
|
55
|
+
const forbidden = (error as { status?: number } | undefined)?.status === 403;
|
|
56
|
+
const busy = assign.loading || voice.loading;
|
|
57
|
+
const clearError = () => {
|
|
58
|
+
assign.reset();
|
|
59
|
+
voice.reset();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className={`space-y-3 ${className ?? ""}`}>
|
|
64
|
+
<p className="text-sm">
|
|
65
|
+
{row.assignedTo ? (
|
|
66
|
+
<>Assigned to <span className="font-medium">{row.assignedTo}</span>.</>
|
|
67
|
+
) : (
|
|
68
|
+
"Not assigned to anyone."
|
|
69
|
+
)}
|
|
70
|
+
{row.agentName ? <> Held by the voice agent <span className="font-medium">{row.agentName}</span>.</> : null}
|
|
71
|
+
</p>
|
|
72
|
+
<div className="flex flex-wrap items-end gap-2">
|
|
73
|
+
<MemberPicker label="Assign to" value={picked?.id ?? null} selectedName={picked?.name} onChange={setPicked} disabled={busy} />
|
|
74
|
+
<Button disabled={!picked || busy} loading={assign.loading} onClick={() => picked && void assign.mutate(picked)}>
|
|
75
|
+
Assign
|
|
76
|
+
</Button>
|
|
77
|
+
{me.member ? (
|
|
78
|
+
<Button variant="outline" disabled={busy} onClick={() => me.member && void assign.mutate(me.member)}>
|
|
79
|
+
Assign to me
|
|
80
|
+
</Button>
|
|
81
|
+
) : null}
|
|
82
|
+
</div>
|
|
83
|
+
{row.assignedTo ? (
|
|
84
|
+
<div className="flex flex-wrap gap-2">
|
|
85
|
+
<Button size="sm" variant="outline" disabled={busy} onClick={() => void voice.mutate("")}>
|
|
86
|
+
Unassign (voice)
|
|
87
|
+
</Button>
|
|
88
|
+
<Button size="sm" variant="ghost" disabled={busy} onClick={() => void voice.mutate(row.assignedTo ?? "")}>
|
|
89
|
+
Re-sync voice record
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
) : null}
|
|
93
|
+
<p className="text-xs text-muted-foreground">
|
|
94
|
+
Unassign clears the voice assignment only; the API has no way to clear the owner on the CRM record, so assign someone else to change that.
|
|
95
|
+
</p>
|
|
96
|
+
<p className="text-xs text-muted-foreground">
|
|
97
|
+
The assignee decides who the number rings, who hears about a missed call, and whose outbound caller IDs include it.
|
|
98
|
+
Only workspace admins can reassign numbers through the API.
|
|
99
|
+
</p>
|
|
100
|
+
{note ? <p role="status" className="text-sm">{note}</p> : null}
|
|
101
|
+
{error ? <ApiError error={error} title={forbidden ? "Only workspace admins can reassign numbers" : "The assignment did not change"} onRetry={clearError} /> : null}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|