@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,235 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ProspectSearchScreen: the finished prospecting page. People / Companies tabs, a filter builder with saved searches
|
|
5
|
+
* on the left, the results with paging and the locked-field banner on the right, and, once results are saved, a panel
|
|
6
|
+
* with the NEW list's real (unlocked) contacts.
|
|
7
|
+
*
|
|
8
|
+
* Composes: ScreenLayout, SplitView, DetailPanel, SectionCard (screen kit), FilterBuilder, SavedSearches,
|
|
9
|
+
* ProspectResults (results + SaveToListDialog), ContactTable (crm), AddToListButton (lists).
|
|
10
|
+
*
|
|
11
|
+
* User flows:
|
|
12
|
+
* - choose People or Companies (`?tab=`), add filters (real value suggestions), Search, page through matches
|
|
13
|
+
* - Clear filters; load, name and delete saved searches
|
|
14
|
+
* - Save results to a new list (billable, confirmed, polled until built), then "Open list" shows the saved members:
|
|
15
|
+
* emails and phones are unlocked now, and they can be added to another list from the same panel
|
|
16
|
+
* - hand the saved list to your own next step (enrich, enroll) through `renderSavedListActions`
|
|
17
|
+
* Masked results never appear as values: locked cells read "Locked" and have no contact id until saved.
|
|
18
|
+
*
|
|
19
|
+
* SDK operations (all inside the parts it composes): getFilterOptions, getAutocompleteSuggestions (read),
|
|
20
|
+
* searchContacts / searchCompanies (read in practice), saveContactSearch / saveCompanySearch (billable, ConfirmSpendDialog),
|
|
21
|
+
* listLists (polling), listSavedSearches / saveSearch / deleteSavedSearch (destructive, ConfirmSpendDialog),
|
|
22
|
+
* listContacts + addContactsToList for the saved list's members.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useState } from "react";
|
|
26
|
+
import type { ReactNode } from "react";
|
|
27
|
+
import { Bookmark, ExternalLink, ListChecks, Play, Search, Trash2 } from "lucide-react";
|
|
28
|
+
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
29
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
30
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
31
|
+
import { KeyValueList } from "@/g8/components/screen/key-value-list";
|
|
32
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
33
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
34
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
35
|
+
import { g8Delete, g8Get } from "@/g8/lib/g8-client";
|
|
36
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
37
|
+
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
38
|
+
import { EntityAvatar } from "@/g8/components/screen/entity-avatar";
|
|
39
|
+
import { ScreenLayout } from "@/g8/components/screen/screen-layout";
|
|
40
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
41
|
+
import { SplitView } from "@/g8/components/screen/split-view";
|
|
42
|
+
import { useUrlState } from "@/g8/hooks/use-url-state";
|
|
43
|
+
import { ContactTable } from "@/g8/crm/components/contact-table";
|
|
44
|
+
import { AddToListButton } from "@/g8/lists/components/add-to-list-button";
|
|
45
|
+
import { OPERATOR_LABEL, completeFilters, fieldsFor, isLocked } from "../lib/search-types";
|
|
46
|
+
import type { ProspectCompany, ProspectContact, SearchCondition, SearchResource } from "../lib/search-types";
|
|
47
|
+
import { FilterBuilder } from "./filter-builder";
|
|
48
|
+
import { ProspectResults } from "./prospect-results";
|
|
49
|
+
import { SAVED_FORMAT, SavedSearches } from "./saved-searches";
|
|
50
|
+
import type { SavedListInfo } from "./save-to-list-dialog";
|
|
51
|
+
import { useBrand } from "@/g8/hooks/use-brand";
|
|
52
|
+
|
|
53
|
+
export interface ProspectSearchScreenProps {
|
|
54
|
+
/** Which tab opens first when `?tab=` is absent. Default people. */
|
|
55
|
+
defaultResource?: SearchResource;
|
|
56
|
+
pageSize?: number;
|
|
57
|
+
confirmSearch?: boolean;
|
|
58
|
+
creditsPerRecord?: number;
|
|
59
|
+
onSaved?: (list: SavedListInfo) => void;
|
|
60
|
+
toolbarActions?: ReactNode;
|
|
61
|
+
/** Buttons for the saved list panel, for example enrich or enroll: `(list) => <BulkEnrichPanel listId={list.id} />`. */
|
|
62
|
+
renderSavedListActions?: (list: SavedListInfo) => ReactNode;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
type Row = ProspectContact & ProspectCompany;
|
|
68
|
+
|
|
69
|
+
interface SavedRow { id: number; title: string; scope?: string | null; icp_query?: { format?: string; filters?: SearchCondition[] } & Record<string, unknown>; created_at?: string | null }
|
|
70
|
+
|
|
71
|
+
const CONTACT_FACTS: Array<[string, keyof ProspectContact]> = [
|
|
72
|
+
["Job title", "job_title"], ["Seniority", "seniority_level"], ["Department", "job_department"], ["Work email", "work_email"], ["Direct phone", "direct_phone"],
|
|
73
|
+
["Mobile phone", "mobile_phone"], ["Personal email", "personal_emails"], ["LinkedIn", "linkedin_url"], ["City", "city"], ["State", "state"], ["Country", "country"],
|
|
74
|
+
["Company", "company_name"], ["Company domain", "company_domain"], ["Industry", "company_industry"],
|
|
75
|
+
];
|
|
76
|
+
const COMPANY_FACTS: Array<[string, keyof ProspectCompany]> = [
|
|
77
|
+
["Domain", "domain"], ["Industry", "industry"], ["Employees", "employee_count"], ["Revenue", "revenue"], ["City", "city"], ["State", "state"], ["Country", "country"], ["LinkedIn", "linkedin_url"],
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/** Every field of a search result. Locked values read "Locked" and are never shown; there is no contact id until it is saved to a list. */
|
|
81
|
+
function ProspectDetail({ row, resource, onClose }: { row: Row; resource: SearchResource; onClose: () => void }) {
|
|
82
|
+
const person = resource === "contacts";
|
|
83
|
+
const name = person ? [row.first_name, row.last_name].filter(Boolean).join(" ") || "Contact" : row.name ?? "Company";
|
|
84
|
+
const facts = (person ? CONTACT_FACTS : COMPANY_FACTS).map(([label, key]) => {
|
|
85
|
+
const locked = person && isLocked(row, key as keyof ProspectContact);
|
|
86
|
+
const raw = (row as Record<string, unknown>)[key as string];
|
|
87
|
+
const text = typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
88
|
+
const link = text && key === "linkedin_url" && !locked ? <a className="inline-flex items-center gap-1 underline" href={text.startsWith("http") ? text : `https://${text}`} target="_blank" rel="noreferrer">{text}<ExternalLink className="size-3" aria-hidden /></a> : null;
|
|
89
|
+
return { label, value: locked ? <StatusPill tone="neutral">Locked</StatusPill> : (link ?? text) };
|
|
90
|
+
});
|
|
91
|
+
const hasLocked = person && (row.locked_fields?.length ?? 0) > 0;
|
|
92
|
+
return (
|
|
93
|
+
<DetailPanel title={name} subtitle={person ? [row.job_title, row.company_name].filter(Boolean).join(" at ") : row.industry} onClose={onClose}
|
|
94
|
+
avatar={<EntityAvatar name={name} kind={person ? "person" : "company"} size="lg" />}
|
|
95
|
+
badges={hasLocked ? <StatusPill tone="warning" dot>Details locked</StatusPill> : undefined}>
|
|
96
|
+
<div className="flex flex-col gap-4 p-1">
|
|
97
|
+
<KeyValueList columns={2} items={facts} />
|
|
98
|
+
{!person && row.description ? <p className="text-sm">{String(row.description)}</p> : null}
|
|
99
|
+
<p className="text-xs text-muted-foreground">{hasLocked ? "Locked fields unlock when you save these results to a list. " : ""}A search result has no contact id: save the results to a list to work with the real record.</p>
|
|
100
|
+
</div>
|
|
101
|
+
</DetailPanel>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Saved searches of both kinds in one place, each openable (`?id=`) with its filters, Run and Delete. */
|
|
106
|
+
function SavedSearchesTab({ onRun, toolbarId, setToolbarId }: { onRun: (resource: SearchResource, filters: SearchCondition[]) => void; toolbarId: string; setToolbarId: (v: string | null) => void }) {
|
|
107
|
+
const brand = useBrand();
|
|
108
|
+
const [deleting, setDeleting] = useState<SavedRow | null>(null);
|
|
109
|
+
const [view, setView] = useUrlState("view");
|
|
110
|
+
const full = view === "full";
|
|
111
|
+
const all = useG8Query(async (signal) => {
|
|
112
|
+
const [c, p] = await Promise.all((["contacts", "companies"] as const).map((scope) => g8Get<SavedRow[]>("/search/saved-searches", { scope }, { signal })));
|
|
113
|
+
return [...((c.data ?? []) as SavedRow[]), ...((p.data ?? []) as SavedRow[])];
|
|
114
|
+
}, []);
|
|
115
|
+
const remove = useG8Mutation((id: number) => g8Delete(`/search/saved-searches/${id}`), { onSuccess: (_r, id) => { setDeleting(null); if (String(id) === toolbarId) setToolbarId(null); void all.refetch(); } });
|
|
116
|
+
const rows = all.data ?? [];
|
|
117
|
+
const open = rows.find((r) => String(r.id) === toolbarId);
|
|
118
|
+
const loadable = (r: SavedRow) => r.icp_query?.format === SAVED_FORMAT && Array.isArray(r.icp_query.filters) && r.icp_query.filters.length > 0;
|
|
119
|
+
const resourceOf = (r: SavedRow): SearchResource => (r.scope === "companies" ? "companies" : "contacts");
|
|
120
|
+
const list = (
|
|
121
|
+
<div className="flex flex-col gap-3 p-1">
|
|
122
|
+
{all.error ? <ApiError error={all.error} title="Could not load saved searches" onRetry={() => void all.refetch()} /> : null}
|
|
123
|
+
{remove.error ? <ApiError error={remove.error} title="Could not delete the search" /> : null}
|
|
124
|
+
{all.loading && !all.data ? <Skeleton className="h-24 w-full" /> : null}
|
|
125
|
+
{!all.loading && !all.error && rows.length === 0 ? <EmptyState icon={<Bookmark className="size-6" aria-hidden />} title="No saved searches" description="Build filters on the People or Companies tab and name them to keep them here." /> : null}
|
|
126
|
+
<ul className="divide-y rounded-md border">
|
|
127
|
+
{rows.map((r) => (
|
|
128
|
+
<li key={r.id} className={r.id === open?.id ? "bg-muted/50" : ""}>
|
|
129
|
+
<button type="button" className="flex w-full items-center gap-3 p-3 text-left" onClick={() => setToolbarId(String(r.id))}>
|
|
130
|
+
<Bookmark className="size-4 text-muted-foreground" aria-hidden />
|
|
131
|
+
<span className="min-w-0 flex-1 truncate text-sm font-medium">{r.title}</span>
|
|
132
|
+
<StatusPill tone="neutral">{resourceOf(r) === "companies" ? "Companies" : "People"}</StatusPill>
|
|
133
|
+
</button>
|
|
134
|
+
</li>
|
|
135
|
+
))}
|
|
136
|
+
</ul>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
const detail = open ? (
|
|
140
|
+
<DetailPanel key={open.id} onExpand={full ? undefined : () => setView("full")} onCollapse={full ? () => setView(null) : undefined} title={open.title} subtitle={`${resourceOf(open) === "companies" ? "Companies" : "People"} search`} onClose={() => setToolbarId(null)}
|
|
141
|
+
actions={(<>
|
|
142
|
+
<Button size="sm" disabled={!loadable(open)} onClick={() => onRun(resourceOf(open), open.icp_query!.filters!)}><Play className="mr-1 size-4" aria-hidden />Run search</Button>
|
|
143
|
+
<Button size="sm" variant="outline" onClick={() => setDeleting(open)}><Trash2 className="mr-1 size-4" aria-hidden />Delete</Button>
|
|
144
|
+
</>)}>
|
|
145
|
+
<div className="flex flex-col gap-4 p-1">
|
|
146
|
+
{loadable(open) ? (
|
|
147
|
+
<ul className="flex flex-col gap-1" aria-label="Saved filters">
|
|
148
|
+
{open.icp_query!.filters!.map((f, i) => {
|
|
149
|
+
const meta = fieldsFor(resourceOf(open)).find((m) => m.key === f.field);
|
|
150
|
+
return <li key={i} className="text-sm"><strong>{meta?.label ?? f.field}</strong> {OPERATOR_LABEL[f.operator] ?? f.operator} {(f.value ?? []).map(String).join(", ")}</li>;
|
|
151
|
+
})}
|
|
152
|
+
</ul>
|
|
153
|
+
) : <p className="text-sm text-muted-foreground">Saved in the {brand.linkLabel} app. It uses the app's own format, so it can be seen here but not run.</p>}
|
|
154
|
+
<KeyValueList items={[{ label: "Saved", value: open.created_at ? new Date(open.created_at).toLocaleDateString() : "" }, { label: "Search id", value: String(open.id), mono: true }]} columns={2} />
|
|
155
|
+
</div>
|
|
156
|
+
</DetailPanel>
|
|
157
|
+
) : null;
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<SplitView expanded={full} onExpandedChange={(e) => setView(e ? "full" : null)} list={list} detail={detail} detailOpen={open != null} onCloseDetail={() => setToolbarId(null)} detailTitle="Saved search" />
|
|
161
|
+
<ConfirmSpendDialog open={deleting != null} tier="destructive" title={`Delete "${deleting?.title ?? ""}"`} description="Lists already built from this search are not affected."
|
|
162
|
+
affected={1} confirming={remove.loading} onConfirm={() => deleting && void remove.mutate(deleting.id)} onCancel={() => setDeleting(null)} />
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function ProspectSearchScreen({ defaultResource = "contacts", pageSize = 25, confirmSearch, creditsPerRecord, onSaved, toolbarActions, renderSavedListActions, className }: ProspectSearchScreenProps) {
|
|
168
|
+
const brand = useBrand();
|
|
169
|
+
const [tab, setTab] = useUrlState("tab", defaultResource === "companies" ? "companies" : "people");
|
|
170
|
+
const resource: SearchResource = tab === "companies" ? "companies" : "contacts";
|
|
171
|
+
const [savedId, setSavedId] = useUrlState("id");
|
|
172
|
+
const [draft, setDraft] = useState<SearchCondition[]>([]);
|
|
173
|
+
const [applied, setApplied] = useState<SearchCondition[]>([]);
|
|
174
|
+
const [saved, setSaved] = useState<SavedListInfo | null>(null);
|
|
175
|
+
const [prospect, setProspect] = useState<{ row: Row; resource: SearchResource } | null>(null);
|
|
176
|
+
const [membersKey, setMembersKey] = useState(0);
|
|
177
|
+
|
|
178
|
+
const switchTab = (id: string) => { setTab(id); setDraft([]); setApplied([]); setSaved(null); setProspect(null); setSavedId(null); };
|
|
179
|
+
const clear = () => { setDraft([]); setApplied([]); setProspect(null); };
|
|
180
|
+
const count = (l: SavedListInfo) => (l.contact_count ?? l.company_count ?? l.total ?? 0).toLocaleString();
|
|
181
|
+
const runSaved = (r: SearchResource, filters: SearchCondition[]) => { setTab(r === "companies" ? "companies" : "people"); setSavedId(null); setDraft(filters); setApplied(completeFilters(filters)); };
|
|
182
|
+
|
|
183
|
+
const left = (
|
|
184
|
+
<div className="flex min-w-0 flex-col gap-4 xl:grid xl:grid-cols-[20rem_minmax(0,1fr)]">
|
|
185
|
+
<div className="flex flex-col gap-4">
|
|
186
|
+
<SectionCard title="Filters" description="All filters must match. Pick values from the suggestions."
|
|
187
|
+
actions={<Button size="sm" variant="ghost" disabled={draft.length === 0 && applied.length === 0} onClick={clear}>Reset search</Button>}>
|
|
188
|
+
<FilterBuilder key={resource} resource={resource} value={draft} onChange={setDraft} onSearch={(f) => { setApplied(completeFilters(f)); setProspect(null); }} />
|
|
189
|
+
</SectionCard>
|
|
190
|
+
<SectionCard title="Saved searches">
|
|
191
|
+
<SavedSearches key={resource} resource={resource} current={draft} onLoad={(f) => { setDraft(f); setApplied(completeFilters(f)); setProspect(null); }} />
|
|
192
|
+
</SectionCard>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="min-w-0">
|
|
195
|
+
<ProspectResults resource={resource} filters={applied} pageSize={pageSize} confirmSearch={confirmSearch} creditsPerRecord={creditsPerRecord}
|
|
196
|
+
onOpenRow={(row, r) => { setSaved(null); setProspect({ row, resource: r }); }}
|
|
197
|
+
onSaved={(list) => { setProspect(null); setSaved(list); setMembersKey((k) => k + 1); onSaved?.(list); }} />
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const detail = saved ? (
|
|
203
|
+
<DetailPanel
|
|
204
|
+
key={saved.id}
|
|
205
|
+
title={saved.title}
|
|
206
|
+
subtitle={`${count(saved)} ${resource === "companies" ? "companies" : "contacts"} saved to your workspace`}
|
|
207
|
+
onClose={() => setSaved(null)}
|
|
208
|
+
actions={renderSavedListActions?.(saved)}
|
|
209
|
+
>
|
|
210
|
+
{resource === "companies"
|
|
211
|
+
? <EmptyState icon={<ListChecks className="size-6" aria-hidden />} title="Companies saved" description={`The API has no read operation for company-list members, so review them in the ${brand.linkLabel} app.`} />
|
|
212
|
+
: (
|
|
213
|
+
<ContactTable listId={saved.id} selectable allowDelete={false} pageSize={10} refreshKey={membersKey}
|
|
214
|
+
renderBulkActions={({ ids, clear: done }) => <AddToListButton contactIds={ids} label="Add to another list" onAdded={() => done()} />} />
|
|
215
|
+
)}
|
|
216
|
+
</DetailPanel>
|
|
217
|
+
) : prospect ? <ProspectDetail row={prospect.row} resource={prospect.resource} onClose={() => setProspect(null)} /> : null;
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<ScreenLayout
|
|
221
|
+
className={className}
|
|
222
|
+
title="Prospect search"
|
|
223
|
+
description="Find new people and companies, preview them, and save the ones you want to a list. Saving unlocks their details."
|
|
224
|
+
icon={<Search className="size-5" aria-hidden />}
|
|
225
|
+
actions={toolbarActions}
|
|
226
|
+
tabs={[{ id: "people", label: "People" }, { id: "companies", label: "Companies" }, { id: "saved", label: "Saved searches" }]}
|
|
227
|
+
activeTab={tab === "saved" ? "saved" : resource === "companies" ? "companies" : "people"}
|
|
228
|
+
onTabChange={switchTab}
|
|
229
|
+
>
|
|
230
|
+
{tab === "saved"
|
|
231
|
+
? <SavedSearchesTab onRun={runSaved} toolbarId={savedId} setToolbarId={setSavedId} />
|
|
232
|
+
: <SplitView list={left} detail={detail} detailOpen={saved != null || prospect != null} onCloseDetail={() => { setSaved(null); setProspect(null); }} detailWidth="xl" detailTitle="Details" />}
|
|
233
|
+
</ScreenLayout>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
@@ -22,8 +22,10 @@ import { EmptyState } from "@/g8/components/empty-state";
|
|
|
22
22
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
23
23
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
24
24
|
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
25
|
+
import { plural } from "@/g8/lib/plural";
|
|
25
26
|
import { completeFilters } from "../lib/search-types";
|
|
26
27
|
import type { SearchCondition, SearchResource } from "../lib/search-types";
|
|
28
|
+
import { useBrand } from "@/g8/hooks/use-brand";
|
|
27
29
|
|
|
28
30
|
export const SAVED_FORMAT = "g8-sdk-v1";
|
|
29
31
|
|
|
@@ -49,6 +51,7 @@ function loadable(s: SavedSearch): boolean {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export function SavedSearches({ resource = "contacts", current, onLoad, className }: SavedSearchesProps) {
|
|
54
|
+
const brand = useBrand();
|
|
52
55
|
const [title, setTitle] = useState("");
|
|
53
56
|
const [deleting, setDeleting] = useState<SavedSearch | null>(null);
|
|
54
57
|
const ready = completeFilters(current);
|
|
@@ -78,7 +81,7 @@ export function SavedSearches({ resource = "contacts", current, onLoad, classNam
|
|
|
78
81
|
<li key={s.id} className="flex items-center justify-between gap-2 rounded-md border p-3" data-testid="saved-search">
|
|
79
82
|
<div className="min-w-0">
|
|
80
83
|
<p className="truncate text-sm font-medium">{s.title}</p>
|
|
81
|
-
<p className="text-xs text-muted-foreground">{loadable(s) ?
|
|
84
|
+
<p className="text-xs text-muted-foreground">{loadable(s) ? plural(s.icp_query.filters!.length, "filter") : `Saved in the ${brand.linkLabel} app`}</p>
|
|
82
85
|
</div>
|
|
83
86
|
<div className="flex gap-1">
|
|
84
87
|
<Button size="sm" variant="outline" disabled={!loadable(s)} onClick={() => onLoad(s.icp_query.filters!, { id: s.id, title: s.title })}>Load</Button>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
4
|
-
import { g8Get } from "@/g8/lib/g8-client";
|
|
5
|
-
import
|
|
4
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
5
|
+
import { completeFilters } from "../lib/search-types";
|
|
6
|
+
import type { SearchCondition, SearchResource } from "../lib/search-types";
|
|
6
7
|
|
|
7
8
|
export interface FilterOption {
|
|
8
9
|
/** Normalised value: send this back as the filter value. */
|
|
@@ -12,21 +13,29 @@ export interface FilterOption {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const cache = new Map<string, FilterOption[]>();
|
|
16
|
+
/** Each filter combination is its own key, so the cache is capped; the least recently used entry goes first. */
|
|
17
|
+
const CACHE_LIMIT = 50;
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* The distinct values a field holds (`GET /search/filter-options`), so a filter can offer real
|
|
18
21
|
* choices instead of guessed strings (a guessed industry silently returns zero rows). Cached per
|
|
19
22
|
* resource and field for the life of the page. `enabled: false` makes no request.
|
|
20
23
|
*/
|
|
21
|
-
export function useFilterOptions(resource: SearchResource, field: string, enabled = true) {
|
|
22
|
-
|
|
24
|
+
export function useFilterOptions(resource: SearchResource, field: string, enabled = true, within: SearchCondition[] = []) {
|
|
25
|
+
// With other filters already set, ask what exists WITHIN them (POST /search/filter-options): industries among
|
|
26
|
+
// companies in Spain, not every industry. An empty context falls back to the cached global list.
|
|
27
|
+
const context = completeFilters(within);
|
|
28
|
+
const key = `${resource}:${field}${context.length ? `:${JSON.stringify(context)}` : ""}`;
|
|
23
29
|
const query = useG8Query(
|
|
24
30
|
async (signal) => {
|
|
25
31
|
const hit = cache.get(key);
|
|
26
|
-
if (hit) return hit;
|
|
27
|
-
const res =
|
|
32
|
+
if (hit) { cache.delete(key); cache.set(key, hit); return hit; }
|
|
33
|
+
const res = context.length
|
|
34
|
+
? await g8Post<Record<string, FilterOption[]>>("/search/filter-options", { resource, fields: [field], filters: context }, { signal })
|
|
35
|
+
: await g8Get<Record<string, FilterOption[]>>("/search/filter-options", { resource, fields: [field] }, { signal });
|
|
28
36
|
const options = res.data?.[field] ?? [];
|
|
29
37
|
cache.set(key, options);
|
|
38
|
+
while (cache.size > CACHE_LIMIT) cache.delete(cache.keys().next().value as string);
|
|
30
39
|
return options;
|
|
31
40
|
},
|
|
32
41
|
[key],
|
|
@@ -39,3 +48,8 @@ export function useFilterOptions(resource: SearchResource, field: string, enable
|
|
|
39
48
|
export function clearFilterOptionsCache(): void {
|
|
40
49
|
cache.clear();
|
|
41
50
|
}
|
|
51
|
+
|
|
52
|
+
/** Test hook: how many option lists are cached. */
|
|
53
|
+
export function filterOptionsCacheSize(): number {
|
|
54
|
+
return cache.size;
|
|
55
|
+
}
|
|
@@ -111,6 +111,7 @@ export interface ProspectContact {
|
|
|
111
111
|
direct_phone?: string | null;
|
|
112
112
|
mobile_phone?: string | null;
|
|
113
113
|
job_title?: string | null;
|
|
114
|
+
job_department?: string | null;
|
|
114
115
|
seniority_level?: string | null;
|
|
115
116
|
linkedin_url?: string | null;
|
|
116
117
|
city?: string | null;
|
|
@@ -132,6 +133,7 @@ export interface ProspectCompany {
|
|
|
132
133
|
state?: string | null;
|
|
133
134
|
country?: string | null;
|
|
134
135
|
linkedin_url?: string | null;
|
|
136
|
+
description?: string | null;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
/** True when a value exists but is locked ("***", or named in `locked_fields`) until the contact is saved to a list. */
|
|
@@ -5,12 +5,35 @@ Apollo / Outreach "sequences" screen.
|
|
|
5
5
|
|
|
6
6
|
Depends on: `lists` (the audience) and `mailboxes` (the sender). Install: `npx @graph8/sdk add sequencer`.
|
|
7
7
|
|
|
8
|
+
## Screens
|
|
9
|
+
|
|
10
|
+
`SequencesScreen` is the finished page: mount it as the route. Every record type has a complete detail view,
|
|
11
|
+
deep-linkable (`?id=` the sequence, `?dtab=` the tab, `?step=` a step, `?enrollment=` a contact's enrollment).
|
|
12
|
+
|
|
13
|
+
- **List** with status filter and a stat strip (live, paused, drafts, contacts in live sequences, from the first
|
|
14
|
+
100). Launch (after a copy and sender check), pause, resume, duplicate, archive, all confirmed.
|
|
15
|
+
- **New sequence** (header button): the builder in a sheet (audience list, email/LinkedIn/AI call/call task steps
|
|
16
|
+
and a sender for each step type); the new draft opens right away. Nobody is contacted until you launch it.
|
|
17
|
+
- **`SequenceDetail`**: Details (every field, audience), Steps, Contacts, Results, plus the same actions as the list
|
|
18
|
+
and **Enroll contacts** (pick a list, tick contacts or enroll the whole list, confirm the recipient count).
|
|
19
|
+
- **`SequenceStepsPanel`**: each step is a record (channel, wait, every field, per-state counts) and opens the editor.
|
|
20
|
+
- **`SequenceEnrollments`**: each enrolled contact is a record (state, step, the full contact); pause directly,
|
|
21
|
+
resume confirmed.
|
|
22
|
+
|
|
23
|
+
Slot props: `toolbarActions`, `renderDetailActions(sequence)`, `extraDetailTabs`, `ownerEmail`. Needs a connected
|
|
24
|
+
sender mailbox and a contact list (see Prerequisites). `SequenceList` gained an optional `onChanged`; `EnrollDialog`
|
|
25
|
+
an optional `note`.
|
|
26
|
+
|
|
27
|
+
Known limits: "Enroll the whole list" reads at most 5,000 contacts (the enroll endpoint needs explicit
|
|
28
|
+
`contact_ids`); a bigger list is cut there and the confirm says so. `?enrollment=` opens a contact only when it is on
|
|
29
|
+
the enrollments page being shown (the API has no single-enrollment read), so a link to a later page opens nothing.
|
|
30
|
+
|
|
8
31
|
## Components
|
|
9
32
|
|
|
10
33
|
| Component | What it does | Tiers |
|
|
11
34
|
|---|---|---|
|
|
12
|
-
| `SequenceList` | Sequences with status. Launch (after a copy
|
|
13
|
-
| `SequenceBuilder` | Build a draft (email, LinkedIn, call, call task
|
|
35
|
+
| `SequenceList` | Sequences with status. Launch (after a copy and sender check), pause, resume, duplicate, archive. | read, write, external, destructive |
|
|
36
|
+
| `SequenceBuilder` | Build a draft (email, LinkedIn, AI call, call task, a required list and a sender per step type), or edit an existing sequence's steps. | read, write, external, destructive |
|
|
14
37
|
| `StepEditor` | One step's form, with the rules the API only enforces at send time. | read |
|
|
15
38
|
| `EnrollDialog` | Confirm and enroll contacts, with the recipient count and a deliverability warning. | read, external |
|
|
16
39
|
| `SequenceStats` | Totals, per-step progress, skip reasons, per-contact state with pause and resume. | read, write, external |
|
|
@@ -21,12 +44,23 @@ un-archive.
|
|
|
21
44
|
|
|
22
45
|
## The flow, in order
|
|
23
46
|
|
|
24
|
-
1. **Pick
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
1. **Pick the senders and an audience.** Every step type needs its own sender, or `runSequence` answers 400:
|
|
48
|
+
- email steps: a mailbox from `listMailboxes`. The channel type comes from the mailbox `source` first:
|
|
49
|
+
`inboxkit` -> `INBOXKIT`, Google -> `GMAIL`, SMTP -> `SMTP`. A directly connected Microsoft mailbox
|
|
50
|
+
(`manual_microsoft`) cannot send from sequences yet, so the builder shows it disabled with the reason.
|
|
51
|
+
- AI call steps (`PHONE`, an AI voice agent calls): a Voice AI number (`inbound_type: "ai"`) from
|
|
52
|
+
`listSequencerPhoneNumbers` (GET /sequencer/channels/phone_numbers), channel type `PHONE`.
|
|
53
|
+
- call tasks (`MANUAL_DIALER`, a rep calls from the dialer): a dialer number (`inbound_type: "dialer"`), `PHONE`.
|
|
54
|
+
- LinkedIn steps: an account from `listLinkedinAccounts` (GET /sequencer/channels/linkedin_accounts), channel type
|
|
55
|
+
`LINKEDIN` (HeyReach), or `LINKEDIN_NETRION` with `NETRION` steps when the response says `provider: "NETRION"`.
|
|
56
|
+
- the audience: `listLists`; the list `id` becomes `associated_list_id`. **Required**: the API cannot set it later.
|
|
57
|
+
2. **Create the draft.** `createSequence` with all steps and channels in one call (confirmed; nobody is contacted).
|
|
58
|
+
It returns the sequence `id`. Editing later: steps that need a sender the sequence lacks offer a picker and attach
|
|
59
|
+
it with `addSequenceChannels` (POST /sequences/{id}/channels) before the steps are appended.
|
|
60
|
+
3. **Check before launch.** `SequenceList` / `SequenceDetail` -> `previewSequence`. The copy verdict is per step:
|
|
61
|
+
`steps[].rendered.will_send_as_shown` and `steps[].rendered.content_issues`. An `error` issue, a step type with no
|
|
62
|
+
sender channel (`channels[]`) or a missing audience list would make `/run` answer 400, so the launch is refused
|
|
63
|
+
with the reasons; other issues are warnings in the confirm dialog.
|
|
30
64
|
4. **Launch.** `runSequence` (confirmed with the contact count). Draft -> live.
|
|
31
65
|
5. **Enroll more contacts.** `EnrollDialog` -> `addContactsToSequence` with `contact_ids` AND the `list_id` they
|
|
32
66
|
came from (both required). Already-enrolled contacts are skipped, so a retry never duplicates.
|
|
@@ -39,7 +73,8 @@ un-archive.
|
|
|
39
73
|
writes each email from `instructions`).
|
|
40
74
|
- **LinkedIn** is the `HEYREACH` step type: `linkedin_action_type` is required, and the copy goes in that action's
|
|
41
75
|
own key (`linkedin_connection_message`, `linkedin_message_text`, `linkedin_inmail_body`), never `body`.
|
|
42
|
-
- **
|
|
76
|
+
- **AI call** is `PHONE`: an AI voice agent places the call from a Voice AI number. **Call task** is `MANUAL_DIALER`
|
|
77
|
+
(a person works it in the dialer, from a dialer number). Do-not-call numbers are never dialed by default.
|
|
43
78
|
- `time_interval` is in seconds (the UI works in days). A step's `step_data` is replaced wholesale on update, so the
|
|
44
79
|
editor always sends the complete object.
|
|
45
80
|
- Search results are masked until saved to a list: enroll contacts that already live in a list.
|
|
@@ -49,6 +84,7 @@ un-archive.
|
|
|
49
84
|
- A connected sending mailbox (`where: "api"`). Google and Microsoft are connected in the graph8 app.
|
|
50
85
|
- An audience list with the contacts (`where: "api"`).
|
|
51
86
|
- A LinkedIn sender account for LinkedIn steps (`where: "app"`): a sequence with LinkedIn steps and no sender cannot launch.
|
|
87
|
+
- A Voice AI number for AI call steps and a dialer number for call tasks (`where: "app"`).
|
|
52
88
|
|
|
53
89
|
## Defaults (do not ask the human)
|
|
54
90
|
|
|
@@ -75,6 +111,10 @@ un-archive.
|
|
|
75
111
|
| `add_sequence_steps_sequences__sequence_id__steps_post` | POST /sequences/{sequence_id}/steps | write | SequenceBuilder |
|
|
76
112
|
| `update_sequence_step_sequences__sequence_id__steps__step_id__patch` | PATCH /sequences/{sequence_id}/steps/{step_id} | external | SequenceBuilder |
|
|
77
113
|
| `delete_sequence_step_sequences__sequence_id__steps__step_id__delete` | DELETE /sequences/{sequence_id}/steps/{step_id} | destructive | SequenceBuilder |
|
|
114
|
+
| `list_sequencer_phone_numbers_sequencer_channels_phone_numbers_get` | GET /sequencer/channels/phone_numbers | read | SequenceBuilder |
|
|
115
|
+
| `list_linkedin_accounts_sequencer_channels_linkedin_accounts_get` | GET /sequencer/channels/linkedin_accounts | read | SequenceBuilder |
|
|
116
|
+
| `list_sequence_channels_sequences__sequence_id__channels_get` | GET /sequences/{sequence_id}/channels | read | SequenceBuilder |
|
|
117
|
+
| `add_sequence_channels_sequences__sequence_id__channels_post` | POST /sequences/{sequence_id}/channels | write | SequenceBuilder |
|
|
78
118
|
| `get_outreach_connection_integrations_linkedin_outreach_connection_get` | GET /integrations/linkedin-outreach/connection | read | StepEditor |
|
|
79
119
|
| `get_safe_to_send_deliverability_safe_to_send_get` | GET /deliverability/safe-to-send | read | EnrollDialog |
|
|
80
120
|
| `add_contacts_to_sequence_sequences__sequence_id__contacts_post` | POST /sequences/{sequence_id}/contacts | external | EnrollDialog |
|
|
@@ -85,3 +125,9 @@ un-archive.
|
|
|
85
125
|
| `resume_sequence_contact_sequences__sequence_id__contacts__contact_id__resume_post` | POST /sequences/{sequence_id}/contacts/{contact_id}/resume | external | SequenceStats |
|
|
86
126
|
|
|
87
127
|
Recipes (`recipes/`): `create-multichannel-sequence.ts`, `launch-and-enroll.ts`, `sequence-report.ts`, `pause-when-unsafe.ts`.
|
|
128
|
+
|
|
129
|
+
## Cross-block glue
|
|
130
|
+
|
|
131
|
+
- `AddToSequenceButton` (`components/add-to-sequence-button.tsx`): "Add to sequence" for any selection of contacts (live sequences only, then the enroll dialog with its preflight and confirmation). `SequencesScreen` gains optional `onOpenContact`: an enrolled person gets an "Open in contacts" button.
|
|
132
|
+
|
|
133
|
+
- `RecordSequencesTab` (`components/record-sequences-tab.tsx`): a contact's enrollments with state and step (`getContactSequences`, the API's 50 most recent, with a notice when full), `AddToSequenceButton`, and a confirmed Withdraw from one sequence (`withdrawContactsFromSequences`, needs `contacts:write`). `extraDetailTabs` entry for ContactsScreen.
|