@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
|
@@ -6,17 +6,26 @@
|
|
|
6
6
|
* is launched (SequenceList does that).
|
|
7
7
|
*
|
|
8
8
|
* SDK operations, in order:
|
|
9
|
-
* create: 1. listMailboxes GET /mailboxes (read; the sender)
|
|
9
|
+
* create: 1. listMailboxes GET /mailboxes (read; the email sender, with `source`)
|
|
10
10
|
* 2. listLists GET /lists (read; the audience, becomes associated_list_id)
|
|
11
|
-
* 3.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* 3. listSequencerPhoneNumbers GET /sequencer/channels/phone_numbers (read; only with call steps)
|
|
12
|
+
* listLinkedinAccounts GET /sequencer/channels/linkedin_accounts (read; only with LinkedIn steps)
|
|
13
|
+
* 4. createSequence POST /sequences (external tier: confirmed; steps + senders in ONE call)
|
|
14
|
+
* edit: 1. getSequence / listSequenceSteps / listSequenceChannels GET /sequences/{id}, /steps, /channels (read)
|
|
15
|
+
* 2. addSequenceChannels POST /sequences/{id}/channels (write; a sender new steps need, if picked)
|
|
16
|
+
* 3. addSequenceSteps POST /sequences/{id}/steps (write; appended steps)
|
|
17
|
+
* 4. updateSequenceStep PATCH /sequences/{id}/steps/{step_id} (external: confirmed; replaces step_data)
|
|
18
|
+
* 5. deleteSequenceStep DELETE /sequences/{id}/steps/{step_id} (destructive: confirmed)
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* Every step type needs its own sender or POST /sequences/{id}/run refuses the launch with 400: email steps a
|
|
21
|
+
* mailbox, AI call steps a Voice AI number, call tasks a dialer number, LinkedIn steps a LinkedIn account. The
|
|
22
|
+
* builder asks for each one the steps need and will not create the draft without them. The audience list is
|
|
23
|
+
* required too: the API has no way to set it later, and a sequence without one cannot launch.
|
|
24
|
+
*
|
|
25
|
+
* Ids passed between steps: the chosen mailbox `id` + `email` (channel type from its `source`), phone `id` +
|
|
26
|
+
* `phone_number`, LinkedIn account `id` become the sequence channels; the list `id` becomes `associated_list_id`;
|
|
27
|
+
* createSequence returns the new sequence `id` (handed to `onSaved`). Changing a LIVE sequence's copy changes
|
|
28
|
+
* what real people receive, which is why the update dialog says so.
|
|
20
29
|
*/
|
|
21
30
|
|
|
22
31
|
import { useRef, useState } from "react";
|
|
@@ -25,23 +34,29 @@ import { g8Delete, g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
|
|
|
25
34
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
26
35
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
27
36
|
import { ApiError } from "@/g8/components/api-error";
|
|
37
|
+
import { ListSelect } from "@/g8/crm/components/list-select";
|
|
38
|
+
import { FirstNotice } from "@/g8/components/paging-notices";
|
|
28
39
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
29
40
|
import type { ConfirmTier } from "@/g8/components/confirm-spend-dialog";
|
|
30
41
|
import { StepEditor } from "./step-editor";
|
|
31
42
|
import {
|
|
32
|
-
channelTypeFor,
|
|
33
43
|
fromApiStep,
|
|
34
44
|
KIND_LABEL,
|
|
35
45
|
KINDS,
|
|
36
46
|
listOf,
|
|
47
|
+
mailboxChannelType,
|
|
48
|
+
mailboxUnsupportedReason,
|
|
37
49
|
newIdempotencyKey,
|
|
38
50
|
newStep,
|
|
39
51
|
objectOf,
|
|
52
|
+
requiredSenders,
|
|
40
53
|
statusGroup,
|
|
41
54
|
stepIssues,
|
|
42
55
|
toApiStep,
|
|
43
56
|
} from "../lib/sequencer-utils";
|
|
44
57
|
import type { ApiStep, DraftStep, StepKind } from "../lib/sequencer-utils";
|
|
58
|
+
import { pluralWord } from "@/g8/lib/plural";
|
|
59
|
+
import { useBrand } from "@/g8/hooks/use-brand";
|
|
45
60
|
|
|
46
61
|
export interface SequenceBuilderProps {
|
|
47
62
|
/** Edit this sequence's steps. Omit to build a new draft. */
|
|
@@ -57,12 +72,40 @@ interface MailboxOption {
|
|
|
57
72
|
id: string;
|
|
58
73
|
email: string | null;
|
|
59
74
|
provider?: string | null;
|
|
75
|
+
source?: string | null;
|
|
76
|
+
}
|
|
77
|
+
interface PhoneOption {
|
|
78
|
+
id: number | string;
|
|
79
|
+
phone_number: string;
|
|
80
|
+
/** "ai" (Voice AI) or "dialer"; the API matches AI call steps to "ai" and call tasks to "dialer". */
|
|
81
|
+
inbound_type?: string | null;
|
|
82
|
+
}
|
|
83
|
+
interface LinkedinOption {
|
|
84
|
+
id: number | string;
|
|
85
|
+
name?: string | null;
|
|
86
|
+
display_name?: string | null;
|
|
87
|
+
public_identifier?: string | null;
|
|
88
|
+
account_id?: string | null;
|
|
89
|
+
email?: string | null;
|
|
90
|
+
}
|
|
91
|
+
interface SenderChannel {
|
|
92
|
+
channel_id: number;
|
|
93
|
+
channel_value: string;
|
|
94
|
+
channel_type: string;
|
|
60
95
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
|
|
97
|
+
/** Every bucket of a picker response (available, active, drafted), each id once. */
|
|
98
|
+
function bucketed<T extends { id: number | string }>(envelope: unknown, keys: string[]): T[] {
|
|
99
|
+
const data = objectOf(envelope);
|
|
100
|
+
const all = keys.flatMap((k) => (Array.isArray(data[k]) ? (data[k] as T[]) : []));
|
|
101
|
+
return all.filter((item, i) => all.findIndex((o) => String(o.id) === String(item.id)) === i);
|
|
65
102
|
}
|
|
103
|
+
const PHONE_KEYS = ["phone_numbers", "active_phone_numbers", "drafted_phone_numbers"];
|
|
104
|
+
const LINKEDIN_KEYS = ["linkedin_accounts", "active_linkedin_accounts", "drafted_linkedin_accounts"];
|
|
105
|
+
const phoneFits = (p: PhoneOption, type: "ai" | "dialer") => !p.inbound_type || p.inbound_type === type;
|
|
106
|
+
const linkedinLabel = (a: LinkedinOption) => a.name || a.display_name || a.public_identifier || a.email || `Account ${a.id}`;
|
|
107
|
+
/** The mailboxes API has no paging: a full read is announced under the sender dropdown. */
|
|
108
|
+
const MAILBOX_LIMIT = 200;
|
|
66
109
|
type Pending = { kind: "create" } | { kind: "update" | "delete"; step: DraftStep };
|
|
67
110
|
|
|
68
111
|
function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string; description: string; label: string } {
|
|
@@ -70,7 +113,7 @@ function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string
|
|
|
70
113
|
return {
|
|
71
114
|
tier: "external",
|
|
72
115
|
title: "Create this sequence as a draft",
|
|
73
|
-
description: "Saves the steps and sender. Nobody is contacted until you launch it.",
|
|
116
|
+
description: "Saves the steps, the audience list and a sender for every step type. Nobody is contacted until you launch it.",
|
|
74
117
|
label: "Create draft",
|
|
75
118
|
};
|
|
76
119
|
if (p.kind === "update")
|
|
@@ -91,9 +134,13 @@ function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string
|
|
|
91
134
|
}
|
|
92
135
|
|
|
93
136
|
export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved, className }: SequenceBuilderProps) {
|
|
137
|
+
const brand = useBrand();
|
|
94
138
|
const editing = Boolean(sequenceId);
|
|
95
139
|
const [name, setName] = useState("");
|
|
96
140
|
const [mailboxId, setMailboxId] = useState("");
|
|
141
|
+
const [aiPhoneId, setAiPhoneId] = useState("");
|
|
142
|
+
const [dialerPhoneId, setDialerPhoneId] = useState("");
|
|
143
|
+
const [linkedinId, setLinkedinId] = useState("");
|
|
97
144
|
const [listId, setListId] = useState(defaultListId ? String(defaultListId) : "");
|
|
98
145
|
const [steps, setSteps] = useState<DraftStep[]>([]);
|
|
99
146
|
const [loadedFor, setLoadedFor] = useState<string | null>(null);
|
|
@@ -106,9 +153,10 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
106
153
|
if (keys.current[slot]?.sig !== sig) keys.current[slot] = { sig, value: newIdempotencyKey() };
|
|
107
154
|
return keys.current[slot].value;
|
|
108
155
|
};
|
|
156
|
+
// Senders already attached in edit mode by an earlier (failed) append: never attach them twice.
|
|
157
|
+
const attached = useRef<Set<string>>(new Set());
|
|
109
158
|
|
|
110
|
-
const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit:
|
|
111
|
-
const lists = useG8Query((signal) => g8Get("/lists", { limit: 100 }, { signal }), [], { enabled: !editing });
|
|
159
|
+
const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: MAILBOX_LIMIT }, { signal }), [], { enabled: !editing });
|
|
112
160
|
const existing = useG8Query(
|
|
113
161
|
async (signal) => {
|
|
114
162
|
const [seq, list] = await Promise.all([
|
|
@@ -120,24 +168,71 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
120
168
|
[sequenceId],
|
|
121
169
|
{ enabled: editing },
|
|
122
170
|
);
|
|
171
|
+
// Which senders the sequence already has (edit mode). Unknown when this read fails: nothing is then asked for.
|
|
172
|
+
const current = useG8Query(
|
|
173
|
+
async (signal) => listOf<{ channel_type?: string | null }>(await g8Get(`/sequences/${sequenceId}/channels`, undefined, { signal })),
|
|
174
|
+
[sequenceId],
|
|
175
|
+
{ enabled: editing },
|
|
176
|
+
);
|
|
123
177
|
if (editing && existing.data && loadedFor !== sequenceId) {
|
|
124
178
|
setLoadedFor(sequenceId ?? null);
|
|
125
179
|
setSteps(existing.data.steps);
|
|
126
180
|
}
|
|
127
181
|
const live = statusGroup(existing.data?.status) === "live";
|
|
128
182
|
|
|
183
|
+
// The senders the steps being written need. In edit mode only new steps count, minus what is attached.
|
|
184
|
+
const fresh = steps.filter((s) => !s.id);
|
|
185
|
+
const has = new Set((current.data ?? []).map((c) => (c.channel_type ?? "").toUpperCase()));
|
|
186
|
+
const want = requiredSenders((editing ? fresh : steps).map((s) => s.kind));
|
|
187
|
+
const need = editing
|
|
188
|
+
? {
|
|
189
|
+
// Mailboxes are attached with the sequence; edit mode has no mailbox picker (the launch check names the gap).
|
|
190
|
+
email: false,
|
|
191
|
+
aiPhone: want.aiPhone && Boolean(current.data) && !has.has("PHONE"),
|
|
192
|
+
dialerPhone: want.dialerPhone && Boolean(current.data) && !has.has("PHONE"),
|
|
193
|
+
linkedin: want.linkedin && Boolean(current.data) && !has.has("LINKEDIN") && !has.has("LINKEDIN_NETRION"),
|
|
194
|
+
}
|
|
195
|
+
: want;
|
|
196
|
+
const phones = useG8Query((signal) => g8Get("/sequencer/channels/phone_numbers", undefined, { signal }), [], {
|
|
197
|
+
enabled: need.aiPhone || need.dialerPhone,
|
|
198
|
+
});
|
|
199
|
+
const linkedin = useG8Query((signal) => g8Get("/sequencer/channels/linkedin_accounts", undefined, { signal }), [], {
|
|
200
|
+
enabled: need.linkedin,
|
|
201
|
+
});
|
|
202
|
+
const provider = String(objectOf(linkedin.data).provider ?? "HEYREACH").toUpperCase();
|
|
203
|
+
const linkedinStepType = provider === "NETRION" ? "NETRION" : "HEYREACH";
|
|
204
|
+
const phoneOptions = bucketed<PhoneOption>(phones.data, PHONE_KEYS);
|
|
205
|
+
const linkedinOptions = bucketed<LinkedinOption>(linkedin.data, LINKEDIN_KEYS);
|
|
206
|
+
|
|
207
|
+
/** The sender channels picked for what `need` asks, in the shape POST /sequences and /channels take. */
|
|
208
|
+
const senderChannels = (): SenderChannel[] => {
|
|
209
|
+
const out: SenderChannel[] = [];
|
|
210
|
+
const mailbox = listOf<MailboxOption>(mailboxes.data).find((m) => m.id === mailboxId);
|
|
211
|
+
const type = mailbox ? mailboxChannelType(mailbox) : null;
|
|
212
|
+
if (mailbox && type) out.push({ channel_id: Number(mailbox.id), channel_value: mailbox.email ?? "", channel_type: type });
|
|
213
|
+
for (const id of new Set([need.aiPhone ? aiPhoneId : "", need.dialerPhone ? dialerPhoneId : ""].filter(Boolean))) {
|
|
214
|
+
const phone = phoneOptions.find((p) => String(p.id) === id);
|
|
215
|
+
if (phone) out.push({ channel_id: Number(phone.id), channel_value: phone.phone_number, channel_type: "PHONE" });
|
|
216
|
+
}
|
|
217
|
+
const account = need.linkedin ? linkedinOptions.find((a) => String(a.id) === linkedinId) : undefined;
|
|
218
|
+
if (account)
|
|
219
|
+
out.push({
|
|
220
|
+
channel_id: Number(account.id),
|
|
221
|
+
channel_value: provider === "NETRION" ? (account.public_identifier ?? account.display_name ?? account.account_id ?? "") : linkedinLabel(account),
|
|
222
|
+
channel_type: provider === "NETRION" ? "LINKEDIN_NETRION" : "LINKEDIN",
|
|
223
|
+
});
|
|
224
|
+
return out;
|
|
225
|
+
};
|
|
226
|
+
|
|
129
227
|
const run = useG8Mutation(async (p: Pending) => {
|
|
130
228
|
if (p.kind === "create") {
|
|
131
|
-
const mailbox = listOf<MailboxOption>(mailboxes.data).find((m) => m.id === mailboxId);
|
|
132
229
|
const body = {
|
|
133
230
|
name: name.trim(),
|
|
134
231
|
user_email: ownerEmail,
|
|
135
232
|
associated_list_id: listId ? Number(listId) : undefined,
|
|
136
233
|
finish_on_reply: true,
|
|
137
|
-
steps: steps.map((s, i) => toApiStep(s, i + 1)),
|
|
138
|
-
channels:
|
|
139
|
-
? [{ channel_id: Number(mailbox.id), channel_value: mailbox.email ?? "", channel_type: channelTypeFor(mailbox.provider) }]
|
|
140
|
-
: [],
|
|
234
|
+
steps: steps.map((s, i) => toApiStep(s, i + 1, linkedinStepType)),
|
|
235
|
+
channels: senderChannels(),
|
|
141
236
|
};
|
|
142
237
|
const res = await g8Post<{ id: string }>("/sequences", body, { idempotencyKey: keyFor("create", body) });
|
|
143
238
|
setSaved(`Draft "${body.name}" created. Launch it from the sequence list.`);
|
|
@@ -157,9 +252,22 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
157
252
|
}
|
|
158
253
|
});
|
|
159
254
|
const append = useG8Mutation(async () => {
|
|
255
|
+
// Attach the senders the new steps need first (each one once, even across retries), then the steps.
|
|
256
|
+
const channels = senderChannels().filter((c) => !attached.current.has(`${c.channel_type}:${c.channel_id}`));
|
|
257
|
+
if (channels.length > 0) {
|
|
258
|
+
await g8Post(`/sequences/${sequenceId}/channels`, { channels });
|
|
259
|
+
channels.forEach((c) => attached.current.add(`${c.channel_type}:${c.channel_id}`));
|
|
260
|
+
void current.refetch();
|
|
261
|
+
}
|
|
160
262
|
const fresh = steps.filter((s) => !s.id);
|
|
161
263
|
const offset = steps.length - fresh.length;
|
|
162
|
-
|
|
264
|
+
// New LinkedIn steps follow the sequence's own provider: an existing LinkedIn step's type, else the LinkedIn
|
|
265
|
+
// sender already attached, else the org's provider. A HEYREACH step next to a LINKEDIN_NETRION sender
|
|
266
|
+
// (or the reverse) could never launch.
|
|
267
|
+
const existingLinkedin = steps.find((s) => s.id && s.kind === "linkedin")?.step_type;
|
|
268
|
+
const sequenceLinkedin =
|
|
269
|
+
existingLinkedin ?? (has.has("LINKEDIN_NETRION") ? "NETRION" : has.has("LINKEDIN") ? "HEYREACH" : linkedinStepType);
|
|
270
|
+
const body = { steps: fresh.map((s, i) => toApiStep(s, offset + i + 1, sequenceLinkedin)) };
|
|
163
271
|
await g8Post(`/sequences/${sequenceId}/steps`, body, { idempotencyKey: keyFor("append", body) });
|
|
164
272
|
// The steps are saved: drop the unsaved copies at once so a second click cannot append them again,
|
|
165
273
|
// then reload the real list (with ids) instead of trusting the response shape.
|
|
@@ -179,53 +287,99 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
179
287
|
return next;
|
|
180
288
|
});
|
|
181
289
|
const invalid = steps.some((s) => stepIssues(s).length > 0);
|
|
182
|
-
|
|
183
|
-
const
|
|
290
|
+
// What is still missing before the draft can be created (shown under the button, so it is never a mystery).
|
|
291
|
+
const missing = [
|
|
292
|
+
!name.trim() && "a name",
|
|
293
|
+
!listId && "an audience list",
|
|
294
|
+
steps.length === 0 && "a step",
|
|
295
|
+
need.email && !mailboxId && "a mailbox for the email steps",
|
|
296
|
+
need.aiPhone && !aiPhoneId && "a Voice AI number for the AI call steps",
|
|
297
|
+
need.dialerPhone && !dialerPhoneId && "a dialer number for the call tasks",
|
|
298
|
+
need.linkedin && !linkedinId && "a LinkedIn account for the LinkedIn steps",
|
|
299
|
+
].filter(Boolean) as string[];
|
|
300
|
+
const canCreate = missing.length === 0 && !invalid;
|
|
184
301
|
|
|
185
302
|
if (editing && existing.error) return <ApiError error={existing.error} onRetry={() => void existing.refetch()} className={className} />;
|
|
186
303
|
if (editing && existing.loading && !existing.data) return <Skeleton className={`h-48 w-full ${className ?? ""}`} />;
|
|
187
304
|
const copy = pending ? describe(pending, live) : null;
|
|
305
|
+
const select = "h-9 rounded-md border border-border bg-background px-2 text-sm";
|
|
188
306
|
|
|
189
307
|
return (
|
|
190
308
|
<div className={`space-y-4 ${className ?? ""}`}>
|
|
191
309
|
{!editing ? (
|
|
192
310
|
<div className="grid gap-2 sm:grid-cols-3">
|
|
193
311
|
<Input aria-label="Sequence name" placeholder="Q4 outbound" value={name} onChange={(e) => setName(e.target.value)} />
|
|
194
|
-
<select
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
))}
|
|
312
|
+
<select aria-label="Send from" className={select} value={mailboxId} onChange={(e) => setMailboxId(e.target.value)}>
|
|
313
|
+
<option value="">{steps.length > 0 && !want.email ? "Mailbox (no email steps)" : "Send from..."}</option>
|
|
314
|
+
{listOf<MailboxOption>(mailboxes.data).map((m) => {
|
|
315
|
+
const reason = mailboxUnsupportedReason(m);
|
|
316
|
+
return (
|
|
317
|
+
<option key={m.id} value={m.id} disabled={Boolean(reason)} title={reason ?? undefined}>
|
|
318
|
+
{m.email}
|
|
319
|
+
{reason ? ` (${reason})` : ""}
|
|
320
|
+
</option>
|
|
321
|
+
);
|
|
322
|
+
})}
|
|
206
323
|
</select>
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
324
|
+
<ListSelect
|
|
325
|
+
label="Audience list"
|
|
326
|
+
emptyOption="Audience list (required)"
|
|
327
|
+
optionLabel={(l) => `${l.title}${l.contact_count != null ? ` (${l.contact_count})` : ""}`}
|
|
328
|
+
pageSize={100}
|
|
210
329
|
value={listId}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
330
|
+
valueLabel={defaultListId ? `List ${defaultListId}` : undefined}
|
|
331
|
+
selectClassName={select}
|
|
332
|
+
onChange={(l) => setListId(l ? String(l.id) : "")}
|
|
333
|
+
/>
|
|
334
|
+
<FirstNotice className="sm:col-span-3" shown={listOf<MailboxOption>(mailboxes.data).length} limit={MAILBOX_LIMIT} noun="mailboxes" />
|
|
335
|
+
</div>
|
|
336
|
+
) : null}
|
|
337
|
+
{need.aiPhone || need.dialerPhone || need.linkedin ? (
|
|
338
|
+
<div className="grid gap-2 sm:grid-cols-3">
|
|
339
|
+
{need.aiPhone ? (
|
|
340
|
+
<select aria-label="AI call number" className={select} value={aiPhoneId} onChange={(e) => setAiPhoneId(e.target.value)}>
|
|
341
|
+
<option value="">{phones.loading ? "Loading numbers..." : "Voice AI number for AI calls..."}</option>
|
|
342
|
+
{phoneOptions.filter((p) => phoneFits(p, "ai")).map((p) => (
|
|
343
|
+
<option key={p.id} value={String(p.id)}>
|
|
344
|
+
{p.phone_number}
|
|
345
|
+
</option>
|
|
346
|
+
))}
|
|
347
|
+
</select>
|
|
348
|
+
) : null}
|
|
349
|
+
{need.dialerPhone ? (
|
|
350
|
+
<select aria-label="Dialer number" className={select} value={dialerPhoneId} onChange={(e) => setDialerPhoneId(e.target.value)}>
|
|
351
|
+
<option value="">{phones.loading ? "Loading numbers..." : "Dialer number for call tasks..."}</option>
|
|
352
|
+
{phoneOptions.filter((p) => phoneFits(p, "dialer")).map((p) => (
|
|
353
|
+
<option key={p.id} value={String(p.id)}>
|
|
354
|
+
{p.phone_number}
|
|
355
|
+
</option>
|
|
356
|
+
))}
|
|
357
|
+
</select>
|
|
358
|
+
) : null}
|
|
359
|
+
{need.linkedin ? (
|
|
360
|
+
<select aria-label="LinkedIn account" className={select} value={linkedinId} onChange={(e) => setLinkedinId(e.target.value)}>
|
|
361
|
+
<option value="">{linkedin.loading ? "Loading accounts..." : "LinkedIn account to send from..."}</option>
|
|
362
|
+
{linkedinOptions.map((a) => (
|
|
363
|
+
<option key={a.id} value={String(a.id)}>
|
|
364
|
+
{linkedinLabel(a)}
|
|
365
|
+
</option>
|
|
366
|
+
))}
|
|
367
|
+
</select>
|
|
368
|
+
) : null}
|
|
221
369
|
</div>
|
|
222
370
|
) : null}
|
|
223
|
-
{
|
|
371
|
+
{need.linkedin && linkedin.data && linkedinOptions.length === 0 ? (
|
|
372
|
+
<p className="text-sm text-muted-foreground">
|
|
373
|
+
{String(objectOf(linkedin.data).message ?? "No LinkedIn account is connected.")} Connect one in the {brand.linkLabel} app.
|
|
374
|
+
</p>
|
|
375
|
+
) : null}
|
|
376
|
+
{mailboxes.error || phones.error || linkedin.error ? (
|
|
224
377
|
<ApiError
|
|
225
|
-
error={mailboxes.error ??
|
|
378
|
+
error={mailboxes.error ?? phones.error ?? linkedin.error}
|
|
226
379
|
onRetry={() => {
|
|
227
380
|
void mailboxes.refetch();
|
|
228
|
-
void
|
|
381
|
+
if (need.aiPhone || need.dialerPhone) void phones.refetch();
|
|
382
|
+
if (need.linkedin) void linkedin.refetch();
|
|
229
383
|
}}
|
|
230
384
|
/>
|
|
231
385
|
) : null}
|
|
@@ -258,13 +412,13 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
258
412
|
</div>
|
|
259
413
|
</div>
|
|
260
414
|
))}
|
|
261
|
-
{steps.length === 0 ? <p className="text-sm text-
|
|
415
|
+
{steps.length === 0 ? <p className="text-sm text-muted-foreground">No steps yet. Add the first touch below.</p> : null}
|
|
262
416
|
</div>
|
|
263
417
|
|
|
264
418
|
<div className="flex flex-wrap items-center gap-2">
|
|
265
419
|
{KINDS.map((k) => (
|
|
266
420
|
<Button key={k} size="sm" variant="outline" onClick={() => add(k)}>
|
|
267
|
-
Add {KIND_LABEL[k].toLowerCase()}
|
|
421
|
+
Add {KIND_LABEL[k].split(" ").map((w) => (/^[A-Z]{2,}$/.test(w) ? w : w.toLowerCase())).join(" ")}
|
|
268
422
|
</Button>
|
|
269
423
|
))}
|
|
270
424
|
</div>
|
|
@@ -274,17 +428,27 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
274
428
|
disabled={fresh.length === 0 || fresh.some((s) => stepIssues(s).length > 0) || append.loading}
|
|
275
429
|
onClick={() => void append.mutate(undefined)}
|
|
276
430
|
>
|
|
277
|
-
Add {fresh.length} new
|
|
431
|
+
Add {fresh.length} new {pluralWord(fresh.length, "step")}
|
|
278
432
|
</Button>
|
|
279
433
|
) : (
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
434
|
+
<div className="space-y-1">
|
|
435
|
+
<Button disabled={!canCreate} onClick={() => setPending({ kind: "create" })}>
|
|
436
|
+
Create draft sequence
|
|
437
|
+
</Button>
|
|
438
|
+
{missing.length > 0 && (name.trim() || steps.length > 0) ? (
|
|
439
|
+
<p className="text-xs text-muted-foreground">Still needed: {missing.join(", ")}.</p>
|
|
440
|
+
) : null}
|
|
441
|
+
</div>
|
|
283
442
|
)}
|
|
443
|
+
{editing && (need.aiPhone || need.dialerPhone || need.linkedin) ? (
|
|
444
|
+
<p className="text-xs text-muted-foreground">
|
|
445
|
+
This sequence has no sender for the new steps yet. Pick one above, or it cannot launch.
|
|
446
|
+
</p>
|
|
447
|
+
) : null}
|
|
284
448
|
{run.error ? <ApiError error={run.error} onRetry={run.reset} /> : null}
|
|
285
449
|
{append.error ? <ApiError error={append.error} onRetry={append.reset} /> : null}
|
|
286
450
|
{saved ? (
|
|
287
|
-
<p role="status" className="text-sm text-
|
|
451
|
+
<p role="status" className="text-sm text-positive-foreground">
|
|
288
452
|
{saved}
|
|
289
453
|
</p>
|
|
290
454
|
) : null}
|