@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,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PageTracking: how one landing page is measured. Whether visits are recorded on the workspace stream, on a stream of
|
|
5
|
+
* its own or not at all, which form it reports, whether the chat widget shows, and what counts as a conversion.
|
|
6
|
+
* Changes apply the next time the page is published.
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in the order the screen uses them:
|
|
9
|
+
* 1. getLandingPageTracking GET /landing-pages/{id}/tracking (read)
|
|
10
|
+
* 2. updateLandingPageTracking PATCH /landing-pages/{id}/tracking (write; ONLY the fields you changed are sent)
|
|
11
|
+
* 3. provisionLandingPageTracking POST /landing-pages/{id}/tracking/provision (write; gives the page its own stream)
|
|
12
|
+
* 4. clearLandingPageConsentOverride DELETE /landing-pages/{id}/tracking/consent (destructive, confirmed: drops the page's own consent banner settings)
|
|
13
|
+
*
|
|
14
|
+
* The form is seeded from the loaded record only, and Save stays disabled until that record has arrived, so a blank
|
|
15
|
+
* form can never be written over real settings. "Dedicated" is reached through Provision (the API refuses a direct switch).
|
|
16
|
+
*/
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { Button, Input, Label, Skeleton, Switch } from "@graph8/sdk/ui";
|
|
19
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
20
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
21
|
+
import { useConfirmAction } from "@/g8/components/confirmed-action";
|
|
22
|
+
import { KeyValueList } from "@/g8/components/screen/key-value-list";
|
|
23
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
24
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
25
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
26
|
+
import { g8Delete, g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
|
|
27
|
+
import { asRec, shortDate, text } from "../lib/studio";
|
|
28
|
+
import type { Rec } from "../lib/studio";
|
|
29
|
+
|
|
30
|
+
export interface PageTrackingProps {
|
|
31
|
+
pageId: string;
|
|
32
|
+
onChanged?: () => void;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SELECT = "border-input bg-background h-9 rounded-md border px-2 text-sm";
|
|
37
|
+
const MODES = [
|
|
38
|
+
{ value: "org_default", label: "Workspace stream (default)" },
|
|
39
|
+
{ value: "disabled", label: "Off (no visits recorded)" },
|
|
40
|
+
];
|
|
41
|
+
const GOALS = [
|
|
42
|
+
{ value: "", label: "Not set" },
|
|
43
|
+
{ value: "form_submission", label: "A form is submitted" },
|
|
44
|
+
{ value: "workspace_signup", label: "Someone signs up" },
|
|
45
|
+
{ value: "paid_account", label: "Someone pays" },
|
|
46
|
+
{ value: "traffic_only", label: "Traffic only" },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** The fields that differ from the saved record: the body of the PATCH. Empty means nothing to save. */
|
|
50
|
+
export function trackingChanges(saved: Rec, draft: { mode: string; form_id: string; chat_enabled: boolean; conversion_goal: string }): Rec {
|
|
51
|
+
const out: Rec = {};
|
|
52
|
+
if (draft.mode !== text(saved.mode, "org_default") && draft.mode !== "dedicated") out.mode = draft.mode;
|
|
53
|
+
if (draft.form_id.trim() !== text(saved.form_id)) out.form_id = draft.form_id.trim();
|
|
54
|
+
if (draft.chat_enabled !== (saved.chat_enabled !== false)) out.chat_enabled = draft.chat_enabled;
|
|
55
|
+
if (draft.conversion_goal && draft.conversion_goal !== text(saved.conversion_goal)) out.conversion_goal = draft.conversion_goal;
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function TrackingForm({ pageId, saved, onSaved }: { pageId: string; saved: Rec; onSaved: () => void }) {
|
|
60
|
+
const base = `/landing-pages/${encodeURIComponent(pageId)}/tracking`;
|
|
61
|
+
const { ask, dialog, failure } = useConfirmAction();
|
|
62
|
+
// Only what the person changed is held as `edits`, shown over the saved copy, so a reload never wipes typing.
|
|
63
|
+
const base0 = { mode: text(saved.mode, "org_default"), form_id: text(saved.form_id), chat_enabled: saved.chat_enabled !== false, conversion_goal: text(saved.conversion_goal) };
|
|
64
|
+
const [edits, setEdits] = useState<Partial<typeof base0>>({});
|
|
65
|
+
const draft = { ...base0, ...edits };
|
|
66
|
+
const setDraft = (next: typeof base0): void => setEdits(Object.fromEntries(Object.entries(next).filter(([k, v]) => v !== base0[k as keyof typeof base0])) as Partial<typeof base0>);
|
|
67
|
+
const changes = trackingChanges(saved, draft);
|
|
68
|
+
const dirty = Object.keys(changes).length > 0;
|
|
69
|
+
const save = useG8Mutation(() => g8Patch(base, changes), { onSuccess: () => { setEdits({}); onSaved(); } });
|
|
70
|
+
const provision = useG8Mutation(() => g8Post(`${base}/provision`), { onSuccess: onSaved });
|
|
71
|
+
const dedicated = saved.mode === "dedicated";
|
|
72
|
+
return (
|
|
73
|
+
<div className="grid gap-4">
|
|
74
|
+
<form className="grid gap-3 sm:grid-cols-2" onSubmit={(e) => { e.preventDefault(); if (dirty) void save.mutate(undefined); }}>
|
|
75
|
+
<div>
|
|
76
|
+
<Label htmlFor="pt-mode" className="mb-1">Measure visits</Label>
|
|
77
|
+
{dedicated ? <p className="text-sm">On a stream of its own</p> : (
|
|
78
|
+
<select id="pt-mode" className={`${SELECT} w-full`} value={draft.mode} onChange={(e) => setDraft({ ...draft, mode: e.target.value })}>
|
|
79
|
+
{MODES.map((m) => <option key={m.value} value={m.value}>{m.label}</option>)}
|
|
80
|
+
</select>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
<div>
|
|
84
|
+
<Label htmlFor="pt-goal" className="mb-1">A conversion is</Label>
|
|
85
|
+
<select id="pt-goal" className={`${SELECT} w-full`} value={draft.conversion_goal} onChange={(e) => setDraft({ ...draft, conversion_goal: e.target.value })}>
|
|
86
|
+
{GOALS.map((g) => <option key={g.value} value={g.value} disabled={g.value === "" && Boolean(saved.conversion_goal)}>{g.label}</option>)}
|
|
87
|
+
</select>
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<Label htmlFor="pt-form" className="mb-1">Form label</Label>
|
|
91
|
+
<Input id="pt-form" value={draft.form_id} placeholder="demo-request" onChange={(e) => setDraft({ ...draft, form_id: e.target.value })} />
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex items-end gap-2">
|
|
94
|
+
<Switch id="pt-chat" checked={draft.chat_enabled} onCheckedChange={(v) => setDraft({ ...draft, chat_enabled: v })} aria-label="Show the chat widget" />
|
|
95
|
+
<Label htmlFor="pt-chat">Show the chat widget on this page</Label>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="flex gap-2 sm:col-span-2">
|
|
98
|
+
<Button type="submit" loading={save.loading} disabled={!dirty}>Save tracking</Button>
|
|
99
|
+
{!dedicated ? <Button type="button" variant="outline" loading={provision.loading} onClick={() => void provision.mutate(undefined)}>Give this page its own stream</Button> : null}
|
|
100
|
+
</div>
|
|
101
|
+
</form>
|
|
102
|
+
{save.error ? <ApiError error={save.error} title="Could not save tracking" /> : null}
|
|
103
|
+
{save.data && !dirty ? <p role="status" className="text-sm">Tracking saved. It applies the next time the page is published.</p> : null}
|
|
104
|
+
{provision.error ? <ApiError error={provision.error} title="Could not set up the stream" /> : null}
|
|
105
|
+
<KeyValueList columns={2} items={[
|
|
106
|
+
{ label: "Stream", value: text(saved.stream_name) || "Workspace stream" },
|
|
107
|
+
{ label: "Provisioned", value: shortDate(saved.provisioned_at) },
|
|
108
|
+
{ label: "Page has a form", value: saved.has_forms === true ? "Yes" : saved.has_forms === false ? "No" : "" },
|
|
109
|
+
{ label: "Page has a sign-up", value: saved.has_graph8_signup === true ? "Yes" : saved.has_graph8_signup === false ? "No" : "" },
|
|
110
|
+
]} />
|
|
111
|
+
<div>
|
|
112
|
+
<Button size="sm" variant="ghost" onClick={() => ask({
|
|
113
|
+
tier: "destructive",
|
|
114
|
+
title: "Use the workspace consent banner on this page",
|
|
115
|
+
confirmLabel: "Use workspace setting",
|
|
116
|
+
description: "Drops this page's own consent banner settings. The page follows the workspace default the next time it is published.",
|
|
117
|
+
run: () => g8Delete(`${base}/consent`),
|
|
118
|
+
onDone: onSaved,
|
|
119
|
+
})}>Reset consent to the workspace default</Button>
|
|
120
|
+
</div>
|
|
121
|
+
{failure ? <ApiError error={failure} title="That did not work" /> : null}
|
|
122
|
+
<ConfirmSpendDialog {...dialog} />
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function PageTracking({ pageId, onChanged, className }: PageTrackingProps) {
|
|
128
|
+
const res = useG8Query((signal) => g8Get(`/landing-pages/${encodeURIComponent(pageId)}/tracking`, undefined, { signal }), [pageId]);
|
|
129
|
+
const saved = asRec(res.data?.data);
|
|
130
|
+
return (
|
|
131
|
+
<SectionCard className={className} title="Tracking" description="Applies the next time the page is published.">
|
|
132
|
+
{res.error ? <ApiError error={res.error} title="Could not load tracking" onRetry={() => void res.refetch()} /> : !res.data ? <Skeleton className="h-32 w-full" aria-busy="true" /> : (
|
|
133
|
+
<TrackingForm key={pageId} pageId={pageId} saved={saved} onSaved={() => { void res.refetch(); onChanged?.(); }} />
|
|
134
|
+
)}
|
|
135
|
+
</SectionCard>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Composes: ScreenLayout (title, tabs, stats), SplitView + DetailPanel (landing page list and the selected
|
|
7
7
|
* page's editor, deep link `?id=`), PageList (list, filter, blank or AI create, publish, unpublish, delete),
|
|
8
|
-
* PageEditor (details, sections, preview, versions, publish),
|
|
8
|
+
* PageEditor (details, sections, preview, versions, publish), PageDomains (live URLs, publish to a domain, download),
|
|
9
|
+
* PageTracking, PageAiTools (edit, review, search metadata), PageAnalytics (portfolio tab and per page), PageClone,
|
|
10
|
+
* ContentLibrary, BrandKitEditor, SeoReport.
|
|
9
11
|
*
|
|
10
12
|
* Every record is deep-linkable: `?id=` opens a landing page (Edit and Details tabs) or, on the Content tab
|
|
11
13
|
* (`?tab=content`), a content item with all of its fields and actions; the Brand kit tab is the one brand record.
|
|
@@ -16,30 +18,43 @@
|
|
|
16
18
|
* (confirmed); manage the content inventory (add by URL, edit, SEO enrichment, delete); re-scan the brand kit and
|
|
17
19
|
* choose page layouts; run an SEO audit and track keywords. The editor changes text fields of sections-mode pages;
|
|
18
20
|
* freeform HTML pages can be previewed, published and versioned, and their HTML is edited in the graph8 app.
|
|
21
|
+
* Every page also has Publishing (its URLs, publish to a connected domain, remove a URL, download the HTML), Tracking,
|
|
22
|
+
* AI tools (edit by instruction, review and apply fixes, write search metadata: each AI pass is a confirmed spend) and
|
|
23
|
+
* Analytics tabs; "Clone a page" rebuilds a URL or pasted HTML as a draft (confirmed spend); the Analytics tab shows
|
|
24
|
+
* visits, people and companies across all pages.
|
|
19
25
|
*
|
|
20
|
-
* SDK operations: listLandingPages (
|
|
21
|
-
*
|
|
26
|
+
* SDK operations: listLandingPages (the headline numbers read the API's totals, so they stay right past one page),
|
|
27
|
+
* getLandingPage (the title of a page that is not in the loaded list); everything else lives in the composed parts,
|
|
28
|
+
* which confirm every publish, unpublish, delete, URL removal, AI pass and paid audit.
|
|
22
29
|
*/
|
|
23
30
|
|
|
24
|
-
import { useState } from "react";
|
|
31
|
+
import { useRef, useState } from "react";
|
|
25
32
|
import type { ReactNode } from "react";
|
|
26
33
|
import { Globe } from "lucide-react";
|
|
27
34
|
import { Skeleton } from "@graph8/sdk/ui";
|
|
28
35
|
import { ApiError } from "@/g8/components/api-error";
|
|
36
|
+
import { FirstNotice } from "@/g8/components/paging-notices";
|
|
29
37
|
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
30
38
|
import { KeyValueList } from "@/g8/components/screen/key-value-list";
|
|
31
39
|
import { ScreenLayout } from "@/g8/components/screen/screen-layout";
|
|
32
40
|
import { SplitView } from "@/g8/components/screen/split-view";
|
|
33
41
|
import { useUrlState } from "@/g8/hooks/use-url-state";
|
|
34
42
|
import { g8Get } from "@/g8/lib/g8-client";
|
|
43
|
+
import { useAllPages } from "@/g8/hooks/use-all-pages";
|
|
35
44
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
36
45
|
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
37
46
|
import { asRec, num, rowsOf, shortDate, text } from "../lib/studio";
|
|
38
47
|
import type { LandingPageRow } from "../lib/studio";
|
|
39
48
|
import { BrandKitEditor } from "./brand-kit-editor";
|
|
40
49
|
import { ContentLibrary } from "./content-library";
|
|
50
|
+
import { PageAiTools } from "./page-ai-tools";
|
|
51
|
+
import { PageAnalytics } from "./page-analytics";
|
|
52
|
+
import { PageClone } from "./page-clone";
|
|
53
|
+
import { PageDomains } from "./page-domains";
|
|
41
54
|
import { PageEditor } from "./page-editor";
|
|
55
|
+
import { PageGenerationStatus } from "./page-generation-status";
|
|
42
56
|
import { PageList } from "./page-list";
|
|
57
|
+
import { PageTracking } from "./page-tracking";
|
|
43
58
|
import { SeoReport } from "./seo-report";
|
|
44
59
|
|
|
45
60
|
export interface PagesScreenProps {
|
|
@@ -51,10 +66,10 @@ export interface PagesScreenProps {
|
|
|
51
66
|
className?: string;
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
type Tab = "pages" | "content" | "brand" | "seo";
|
|
69
|
+
type Tab = "pages" | "content" | "brand" | "seo" | "analytics";
|
|
55
70
|
|
|
56
71
|
/** Every field of one landing page (name and slug are edited in the Edit tab). */
|
|
57
|
-
function PageDetails({ pageId }: { pageId: string }) {
|
|
72
|
+
function PageDetails({ pageId, onGenerated }: { pageId: string; onGenerated?: () => void }) {
|
|
58
73
|
const res = useG8Query((signal) => g8Get(`/landing-pages/${encodeURIComponent(pageId)}`, undefined, { signal }), [pageId]);
|
|
59
74
|
if (res.error) return <ApiError error={res.error} title="Could not load the page" onRetry={() => void res.refetch()} />;
|
|
60
75
|
if (!res.data) return <Skeleton className="h-32 w-full" aria-busy="true" />;
|
|
@@ -62,6 +77,8 @@ function PageDetails({ pageId }: { pageId: string }) {
|
|
|
62
77
|
const n = (k: string): string => (d[k] == null || d[k] === "" ? "Not set" : text(d[k]));
|
|
63
78
|
const sections = Array.isArray(d.sections) ? d.sections.length : null;
|
|
64
79
|
return (
|
|
80
|
+
<div className="grid gap-3">
|
|
81
|
+
<PageGenerationStatus pageId={pageId} status={text(d.status)} onDone={onGenerated} />
|
|
65
82
|
<KeyValueList
|
|
66
83
|
columns={2}
|
|
67
84
|
items={[
|
|
@@ -78,23 +95,40 @@ function PageDetails({ pageId }: { pageId: string }) {
|
|
|
78
95
|
...(d.error_message ? [{ label: "Last error", value: text(d.error_message) }] : []),
|
|
79
96
|
]}
|
|
80
97
|
/>
|
|
98
|
+
</div>
|
|
81
99
|
);
|
|
82
100
|
}
|
|
83
101
|
|
|
102
|
+
/** Landing pages per read for the counts; the list itself pages through PageList. */
|
|
103
|
+
const LANDING_PAGE_SIZE = 100;
|
|
104
|
+
|
|
84
105
|
export function PagesScreen({ defaultDomain, renderDetailActions, extraDetailTabs, toolbarActions, className }: PagesScreenProps) {
|
|
85
106
|
const [tabParam, setTabParam] = useUrlState("tab", "pages");
|
|
86
|
-
const tab = (["pages", "content", "brand", "seo"].includes(tabParam) ? tabParam : "pages") as Tab;
|
|
107
|
+
const tab = (["pages", "content", "brand", "seo", "analytics"].includes(tabParam) ? tabParam : "pages") as Tab;
|
|
87
108
|
const [pageId, setPageId] = useUrlState("id", "");
|
|
88
109
|
const [view, setView] = useUrlState("view", "");
|
|
89
110
|
const expandProps = { onExpand: () => setView("full"), onCollapse: view === "full" ? () => setView(null) : undefined };
|
|
90
111
|
// The list owns its own fetch; bumping this remounts it after the editor changed something (publish, rename).
|
|
91
112
|
const [rev, setRev] = useState(0);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
113
|
+
// The stat strip and the open page's name read EVERY landing page (100 per read, offset paged), so the counts are
|
|
114
|
+
// never those of a capped first read. `total` is the API's own count.
|
|
115
|
+
const apiTotal = useRef<number | undefined>(undefined);
|
|
116
|
+
const pages = useAllPages<LandingPageRow>(async (page, signal) => {
|
|
117
|
+
const offset = (page - 1) * LANDING_PAGE_SIZE;
|
|
118
|
+
const data = asRec((await g8Get("/landing-pages", { limit: LANDING_PAGE_SIZE, offset: offset || undefined }, { signal })).data);
|
|
119
|
+
const rows = rowsOf<LandingPageRow>(data, "landing_pages");
|
|
120
|
+
apiTotal.current = num(data.total) ?? apiTotal.current;
|
|
121
|
+
return { rows, hasNext: apiTotal.current != null ? offset + rows.length < apiTotal.current : rows.length >= LANDING_PAGE_SIZE };
|
|
122
|
+
}, [rev]);
|
|
123
|
+
const rows = pages.items;
|
|
124
|
+
const total = apiTotal.current ?? rows.length;
|
|
96
125
|
const published = rows.filter((p) => p.status === "published").length;
|
|
97
126
|
const current = tab === "pages" ? rows.find((p) => p.id === pageId) : undefined;
|
|
127
|
+
const bump = (): void => setRev((r) => r + 1);
|
|
128
|
+
// Pages whose generation "finished" per /status: held HERE, above the child that remounts on every rev, so a page the list still
|
|
129
|
+
// calls generating (a dead worker) refreshes the list once instead of looping.
|
|
130
|
+
const generatedOnce = useRef<Set<string>>(new Set());
|
|
131
|
+
const onGenerated = (id: string): void => { if (generatedOnce.current.has(id)) return; generatedOnce.current.add(id); bump(); };
|
|
98
132
|
|
|
99
133
|
const detail = tab === "pages" && pageId ? (
|
|
100
134
|
<DetailPanel
|
|
@@ -106,7 +140,11 @@ export function PagesScreen({ defaultDomain, renderDetailActions, extraDetailTab
|
|
|
106
140
|
{...expandProps}
|
|
107
141
|
tabs={[
|
|
108
142
|
{ id: "edit", label: "Edit", content: <PageEditor key={pageId} pageId={pageId} onSaved={() => setRev((r) => r + 1)} /> },
|
|
109
|
-
{ id: "details", label: "Details", content: <PageDetails key={`${pageId}:${rev}`} pageId={pageId} /> },
|
|
143
|
+
{ id: "details", label: "Details", content: <PageDetails key={`${pageId}:${rev}`} pageId={pageId} onGenerated={() => onGenerated(pageId)} /> },
|
|
144
|
+
{ id: "publishing", label: "Publishing", content: <PageDomains key={pageId} pageId={pageId} onChanged={bump} /> },
|
|
145
|
+
{ id: "tracking", label: "Tracking", content: <PageTracking key={pageId} pageId={pageId} onChanged={bump} /> },
|
|
146
|
+
{ id: "ai", label: "AI tools", content: <PageAiTools key={pageId} pageId={pageId} onChanged={bump} /> },
|
|
147
|
+
{ id: "analytics", label: "Analytics", content: <PageAnalytics key={pageId} pageId={pageId} /> },
|
|
110
148
|
...(extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(pageId) })),
|
|
111
149
|
]}
|
|
112
150
|
/>
|
|
@@ -118,9 +156,10 @@ export function PagesScreen({ defaultDomain, renderDetailActions, extraDetailTab
|
|
|
118
156
|
title="Pages"
|
|
119
157
|
description="Landing pages, your content library, the brand kit and SEO in one place."
|
|
120
158
|
icon={<Globe className="size-5" />}
|
|
121
|
-
actions={toolbarActions}
|
|
159
|
+
actions={<>{tab === "pages" ? <PageClone onCreated={(id) => { bump(); setPageId(id); }} /> : null}{toolbarActions}</>}
|
|
122
160
|
tabs={[
|
|
123
161
|
{ id: "pages", label: "Landing pages", count: total },
|
|
162
|
+
{ id: "analytics", label: "Analytics" },
|
|
124
163
|
{ id: "content", label: "Content" },
|
|
125
164
|
{ id: "brand", label: "Brand kit" },
|
|
126
165
|
{ id: "seo", label: "SEO" },
|
|
@@ -130,12 +169,13 @@ export function PagesScreen({ defaultDomain, renderDetailActions, extraDetailTab
|
|
|
130
169
|
stats={tab === "pages" ? [
|
|
131
170
|
{ label: "Landing pages", value: total.toLocaleString() },
|
|
132
171
|
{ label: "Published", value: published.toLocaleString(), tone: "positive" },
|
|
133
|
-
{ label: "Drafts", value: Math.max(0,
|
|
172
|
+
{ label: "Drafts", value: Math.max(0, total - published).toLocaleString() },
|
|
134
173
|
] : undefined}
|
|
135
174
|
>
|
|
136
175
|
{tab === "pages" ? (
|
|
137
176
|
<>
|
|
138
|
-
{
|
|
177
|
+
<FirstNotice shown={rows.length} limit={rows.length} force={pages.truncated} noun="landing pages" />
|
|
178
|
+
{pages.error ? <ApiError error={pages.error} title="Could not load landing pages" onRetry={pages.refetch} /> : null}
|
|
139
179
|
<SplitView
|
|
140
180
|
detailOpen={Boolean(pageId)}
|
|
141
181
|
onCloseDetail={() => { setPageId(null); setView(null); }}
|
|
@@ -146,7 +186,8 @@ export function PagesScreen({ defaultDomain, renderDetailActions, extraDetailTab
|
|
|
146
186
|
detail={detail}
|
|
147
187
|
/>
|
|
148
188
|
</>
|
|
149
|
-
) : tab === "
|
|
189
|
+
) : tab === "analytics" ? <PageAnalytics />
|
|
190
|
+
: tab === "content" ? <ContentLibrary selectedId={pageId || null} onSelectedChange={(id) => setPageId(id)} />
|
|
150
191
|
: tab === "brand" ? <BrandKitEditor />
|
|
151
192
|
: <SeoReport defaultDomain={defaultDomain} />}
|
|
152
193
|
</ScreenLayout>
|
|
@@ -26,6 +26,7 @@ import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
|
26
26
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
27
27
|
import { useJobPoll } from "@/g8/hooks/use-job-poll";
|
|
28
28
|
import { ApiError } from "@/g8/components/api-error";
|
|
29
|
+
import { FirstNotice } from "@/g8/components/paging-notices";
|
|
29
30
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
30
31
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
31
32
|
import { asRec, num, rowsOf, shortDate, text } from "../lib/studio";
|
|
@@ -110,12 +111,15 @@ function Keywords({ domain }: { domain: string }) {
|
|
|
110
111
|
);
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/** The audits API has no paging: a full read is announced under the audit chips. */
|
|
115
|
+
const AUDIT_LIMIT = 10;
|
|
116
|
+
|
|
113
117
|
export function SeoReport({ defaultDomain = "", className }: SeoReportProps) {
|
|
114
118
|
const [domain, setDomain] = useState(defaultDomain);
|
|
115
119
|
const [maxPages, setMaxPages] = useState(100);
|
|
116
120
|
const [confirm, setConfirm] = useState(false);
|
|
117
121
|
const [selected, setSelected] = useState<string | null>(null);
|
|
118
|
-
const audits = useG8Query((signal) => g8Get("/seo/audits", { limit:
|
|
122
|
+
const audits = useG8Query((signal) => g8Get("/seo/audits", { limit: AUDIT_LIMIT }, { signal }), []);
|
|
119
123
|
const start = useG8Mutation(() => g8Post("/seo/audits", { domain: domain.trim(), max_pages: maxPages }), {
|
|
120
124
|
onSuccess: (res) => {
|
|
121
125
|
setConfirm(false);
|
|
@@ -143,6 +147,7 @@ export function SeoReport({ defaultDomain = "", className }: SeoReportProps) {
|
|
|
143
147
|
{text(a.domain)} · {shortDate(a.created_at)}
|
|
144
148
|
</Button>
|
|
145
149
|
))}
|
|
150
|
+
<FirstNotice className="w-full" shown={rows.length} limit={AUDIT_LIMIT} noun="audits" />
|
|
146
151
|
</div>
|
|
147
152
|
) : null}
|
|
148
153
|
<div className="space-y-4">
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Nothing here knows more than that, so a field the API adds later never breaks a screen.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { PaginatedResponse } from "@graph8/sdk";
|
|
12
|
+
|
|
11
13
|
export type Rec = Record<string, unknown>;
|
|
12
14
|
|
|
13
15
|
export function asRec(value: unknown): Rec {
|
|
@@ -75,3 +77,8 @@ export function liveUrl(data: unknown): string {
|
|
|
75
77
|
const rec = asRec(data);
|
|
76
78
|
return text(rec.published_url) || text(rec.url) || text(asRec(rec.landing_page).published_url);
|
|
77
79
|
}
|
|
80
|
+
|
|
81
|
+
/** Wrap rows and the API's "next" marker (a cursor or an offset; null or absent when there is no more) for `usePaginated`. */
|
|
82
|
+
export function pageOf<T>(rows: T[], next: unknown): PaginatedResponse<T> {
|
|
83
|
+
return { data: rows, pagination: { next_cursor: next == null || next === "" ? null : String(next) } } as unknown as PaginatedResponse<T>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# phone-numbers: buy, assign, configure and release numbers
|
|
2
|
+
|
|
3
|
+
Every phone number the workspace owns, in one finished screen: see them all, search a carrier for new ones, buy one,
|
|
4
|
+
give it to a teammate or a voice agent, change how it answers, watch its usage against the daily limit, register for text
|
|
5
|
+
messaging (A2P 10DLC), and release it. Install with `npx @graph8/sdk add phone-numbers` (depends on `pickers`, for the
|
|
6
|
+
assignee picker). The dialer, sequencer and voice agents all pick numbers from this inventory.
|
|
7
|
+
|
|
8
|
+
> Buying a number spends credits and real money, releasing one is irreversible, and submitting a text registration
|
|
9
|
+
> reaches the carriers. Each of those opens a confirm dialog first; nothing here buys, releases or submits by itself.
|
|
10
|
+
|
|
11
|
+
## Screens
|
|
12
|
+
|
|
13
|
+
- `PhoneNumbersScreen` (`components/phone-numbers-screen.tsx`): no required props.
|
|
14
|
+
- **Numbers** tab: stat strip (numbers, assigned, unassigned, calls today), search, filters (use, carrier, assignment,
|
|
15
|
+
status, kept in the URL as `n_use=...`), and the list. The list is paged on the client with **Load more** ("Showing 25
|
|
16
|
+
of 140 numbers"): the two list endpoints return the whole inventory and take no `limit`, so nothing is silently cut off.
|
|
17
|
+
A row opens the number beside the list (`?id=%2B14155550123`, add `&view=full` for a full page, `&dtab=usage` for a tab).
|
|
18
|
+
- **Buy a number** (sheet): choose a carrier, number type, area code, and digits or a city; search is free. Choose a number,
|
|
19
|
+
say what it is for (dialer, voice AI, personal work phone) and who gets it, then **Buy for about 250 credits** opens the
|
|
20
|
+
confirm dialog with the price. A 402 says nothing was bought and nothing was charged.
|
|
21
|
+
- **Add a number you own** (sheet): register a number you already hold with the voice platform. It buys nothing.
|
|
22
|
+
- **Text registration** tab: the A2P 10DLC status (brand, campaign, carrier feedback), a form that saves a draft, a
|
|
23
|
+
confirmed **Submit registration**, and **Retry stuck numbers**.
|
|
24
|
+
- The open number has tabs: **Overview**, **Assign** (MemberPicker, Assign to me, Unassign, Re-sync voice record),
|
|
25
|
+
**Usage** (calls today against the daily limit, 7-day calls, connect rate, confirmed counter reset), **Settings**
|
|
26
|
+
(answering mode, Telnyx caller-ID verification, who answers) and **Release** (release, or remove voice routing only).
|
|
27
|
+
- Slots: `toolbarActions`, `renderRowActions`, `renderDetailActions`, `extraDetailTabs`.
|
|
28
|
+
- Parts you can compose yourself: `PhoneNumberList`, `PhoneNumberDetail`, `NumberSearch`, `AssignNumber`, `NumberSettings`,
|
|
29
|
+
`NumberUsage`, `ReleaseNumber`, `RegisterOwnedNumber`, `A2pRegistration`, and the `usePhoneNumbers` hook.
|
|
30
|
+
|
|
31
|
+
## What the API gives, and what it does not
|
|
32
|
+
|
|
33
|
+
- **There is no "list my phone numbers" operation in the `phoneNumbers` namespace.** The inventory is two reads, merged:
|
|
34
|
+
`GET /voice/dialer/numbers` (every number except the ones a voice AI agent holds, with usage) and
|
|
35
|
+
`GET /voice/agent-phone-numbers` (AI-capable numbers and which agent holds each). `usePhoneNumbers` does the merge.
|
|
36
|
+
- **No price read.** Prices on the purchase dialog are estimates: 250 credits held on purchase, then about 250 credits a
|
|
37
|
+
month in rental. The API reports no rental per number, so the block never labels any number "billable". Numbers the
|
|
38
|
+
platform provisioned for you (the onboarding receptionist line) are never rental-billed.
|
|
39
|
+
- **No operations for call forwarding, voicemail greetings or ring-group routing.** Settings says so and links to the
|
|
40
|
+
platform app instead of showing a dead control. Porting a number in is also an app flow.
|
|
41
|
+
- **Admin-only writes.** Reassigning, switching the answering mode and resetting counters are admin-only through the API; a
|
|
42
|
+
403 is explained on the screen.
|
|
43
|
+
- **Telnyx numbers must be verified as a caller ID** before they can place calls; an unverified one shows as "Cannot dial"
|
|
44
|
+
and stays out of the dialer rotation. Twilio numbers need nothing.
|
|
45
|
+
|
|
46
|
+
## Operations
|
|
47
|
+
|
|
48
|
+
| Flow | Operation | Tier |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Inventory | `listDialerNumbers`, `listAgentPhoneNumbers` | read |
|
|
51
|
+
| Search | `searchAvailablePhoneNumbers` | read (free) |
|
|
52
|
+
| Buy | `purchasePhoneNumber` (Idempotency-Key) | **billable**, confirmed |
|
|
53
|
+
| Assign | `updatePhoneAssignee` (CRM + voice), `updateVoiceAssignee` (voice only; `""` clears the voice assignment, the API cannot clear the CRM owner) | write |
|
|
54
|
+
| Answering mode | `updatePhoneCapabilities` (voice AI, dialer), `updateVoiceInboundType` (rep, work phone) | write |
|
|
55
|
+
| Caller ID, who answers | `verifyVoiceCallerId`, `getVoiceAnsweringAgent` | write, read |
|
|
56
|
+
| Register an owned number | `provisionVoiceNumber` | write (buys nothing) |
|
|
57
|
+
| Release | `releasePhoneNumber` | **destructive**, type the number, then confirmed |
|
|
58
|
+
| Remove voice routing | `deleteVoiceNumber` (keeps the number and its carrier bill) | **destructive**, confirmed |
|
|
59
|
+
| Reset counters | `resetVoiceDailyCounts` (every number, lifts the daily cap) | **destructive**, confirmed |
|
|
60
|
+
| Text registration | `getPhoneA2pStatus`, `saveA2pInputs`, `startA2pRegistration`, `reconcileA2pRegistration` | read, write, **external** (submit is confirmed), write |
|
|
61
|
+
|
|
62
|
+
Scopes: `voice:read`, `voice:write`, `voice:run` (buy, submit A2P), `voice:delete` (release, remove routing, reset
|
|
63
|
+
counters) and `team:read` (the assignee picker).
|
|
64
|
+
|
|
65
|
+
## Defaults (no need to ask the human)
|
|
66
|
+
|
|
67
|
+
- Carrier: Twilio (Telnyx is one select away). A new number is an **outbound dialer** number, owned by the identity behind the key unless an assignee is chosen.
|
|
68
|
+
- A number's use maps to the API as: dialer = `dialer: true`; voice AI = `dialer: false`; personal work phone = `dialer: true, work: true`.
|
|
69
|
+
- Answering mode: "Voice AI agent" and "Outbound dialer" go through `update-capabilities`; "Rep answers" and "Personal work phone" go through `inbound-type`. Both rebuild the number's routing, so the change takes a second click on a button that says the number answers nothing for a moment.
|
|
70
|
+
- Releasing needs the number typed first and then a confirm. If the carrier is unknown (a number only the agents list knows), the person chooses it: the wrong carrier releases nothing.
|
|
71
|
+
- Saving A2P details sends the whole object, merged over what was saved, so a field the form does not edit (an address) is never wiped. The form appears only after the saved details loaded.
|
|
72
|
+
|
|
73
|
+
Recipes (`recipes/`): search and buy (human approval required), assign or clear a rep, audit the inventory (read-only), and
|
|
74
|
+
save A2P details and check registration.
|