@graph8/sdk 0.370.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 +19 -0
- package/blocks/_foundation/README.md +14 -0
- package/blocks/_foundation/block.json +44 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
- 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 +1 -0
- package/blocks/_foundation/components/screen/split-view.tsx +1 -1
- package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
- package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
- package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
- package/blocks/_foundation/lib/paging.ts +57 -0
- package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
- package/blocks/agents/README.md +81 -4
- package/blocks/agents/block.json +311 -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 +17 -3
- package/blocks/agents/components/agent-conversations.tsx +143 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
- package/blocks/agents/components/agent-operators.tsx +164 -0
- package/blocks/agents/components/agent-run-detail.tsx +20 -3
- package/blocks/agents/components/agent-runs.tsx +3 -3
- 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 +53 -11
- 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/components/analytics-screen.tsx +16 -3
- package/blocks/analytics/components/kpi-tiles.tsx +1 -1
- package/blocks/analytics/components/report-builder.tsx +15 -2
- package/blocks/appointments/README.md +6 -0
- package/blocks/appointments/block.json +27 -0
- package/blocks/appointments/components/booking-page.tsx +2 -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 +29 -13
- package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
- package/blocks/campaigns/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
- package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
- package/blocks/campaigns/components/icp-detail.tsx +11 -2
- package/blocks/campaigns/components/icp-editor.tsx +8 -3
- package/blocks/campaigns/components/persona-detail.tsx +11 -2
- package/blocks/campaigns/components/persona-editor.tsx +6 -1
- package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
- package/blocks/coach/components/call-scorecard.tsx +7 -7
- 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/components/copilot-memory.tsx +6 -2
- package/blocks/copilot/components/copilot-panel.tsx +1 -1
- package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
- package/blocks/crm/README.md +6 -0
- package/blocks/crm/block.json +53 -4
- package/blocks/crm/components/companies-screen.tsx +23 -9
- package/blocks/crm/components/company-detail.tsx +2 -2
- package/blocks/crm/components/contact-detail.tsx +4 -4
- package/blocks/crm/components/contact-lookup-link.tsx +44 -0
- package/blocks/crm/components/contacts-screen.tsx +31 -13
- package/blocks/crm/components/dedupe-review.tsx +6 -1
- package/blocks/crm/components/import-wizard.tsx +3 -7
- package/blocks/crm/components/list-select.tsx +20 -0
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-page-query.ts +3 -1
- package/blocks/crm/lib/crm-types.ts +14 -0
- package/blocks/crm/lib/read-lists.ts +16 -0
- package/blocks/deals/README.md +24 -6
- package/blocks/deals/block.json +151 -2
- 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 +2 -2
- 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 +30 -9
- package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
- package/blocks/deals/components/deals-screen.tsx +57 -22
- 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 +1 -1
- package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
- package/blocks/deals/lib/deal-extras.ts +156 -0
- package/blocks/dialer/README.md +14 -5
- package/blocks/dialer/block.json +17 -2
- package/blocks/dialer/components/call-contact-button.tsx +26 -9
- package/blocks/dialer/components/call-log-screen.tsx +4 -1
- package/blocks/dialer/components/call-log.tsx +16 -4
- package/blocks/dialer/components/call-queue.tsx +3 -3
- package/blocks/dialer/components/dialer-screen.tsx +69 -27
- package/blocks/dialer/components/disposition-panel.tsx +1 -1
- package/blocks/dialer/components/record-calls-tab.tsx +101 -0
- package/blocks/dialer/components/session-start-form.tsx +58 -11
- package/blocks/dialer/components/softphone.tsx +1 -1
- package/blocks/enrichment/README.md +16 -6
- package/blocks/enrichment/block.json +19 -7
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +76 -27
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-screen.tsx +70 -17
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +39 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +8 -0
- package/blocks/forms/block.json +107 -0
- 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 +1 -1
- package/blocks/forms/components/form-embed.tsx +1 -1
- 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 +51 -17
- 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 +15 -6
- package/blocks/forms/lib/forms-extra.ts +67 -0
- package/blocks/inbox/README.md +5 -0
- package/blocks/inbox/block.json +15 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +1 -1
- package/blocks/integrations/README.md +54 -1
- package/blocks/integrations/block.json +189 -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 +20 -1
- 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 +4 -4
- package/blocks/integrations/components/integrations-screen.tsx +20 -3
- 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 +6 -6
- package/blocks/integrations/components/sync-triggers.tsx +134 -0
- package/blocks/integrations/components/webhook-detail.tsx +26 -7
- package/blocks/integrations/components/webhook-manager.tsx +30 -17
- 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 +5 -0
- package/blocks/integrations/lib/load-more.tsx +141 -0
- package/blocks/intent/components/account-intent-score.tsx +5 -5
- package/blocks/intent/components/intent-feed.tsx +8 -8
- package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
- package/blocks/intent/lib/intent-utils.ts +4 -4
- 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/lists/README.md +7 -1
- package/blocks/lists/block.json +31 -3
- package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
- package/blocks/lists/components/add-to-list-button.tsx +17 -7
- package/blocks/lists/components/list-picker.tsx +13 -3
- package/blocks/lists/components/list-table.tsx +1 -1
- package/blocks/lists/components/lists-screen.tsx +4 -2
- package/blocks/lists/components/record-lists-tab.tsx +99 -0
- package/blocks/lists/components/suppression-manager.tsx +27 -6
- package/blocks/lists/components/suppressions-screen.tsx +21 -12
- package/blocks/mailboxes/block.json +4 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
- package/blocks/mailboxes/components/domain-detail.tsx +10 -4
- package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
- package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
- package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
- package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
- package/blocks/mailboxes/components/warmup-health.tsx +20 -20
- package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
- package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
- package/blocks/newsletter/README.md +12 -0
- package/blocks/newsletter/block.json +117 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
- 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 +36 -13
- 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/components/nurture-builder.tsx +10 -2
- package/blocks/nurture/components/nurture-enroll.tsx +6 -7
- package/blocks/nurture/components/nurture-form.tsx +5 -8
- package/blocks/nurture/components/nurture-screen.tsx +16 -5
- package/blocks/nurture/components/nurture-stats.tsx +5 -4
- package/blocks/objects/README.md +1 -1
- package/blocks/objects/block.json +13 -5
- package/blocks/objects/components/attribute-field-input.tsx +14 -1
- package/blocks/objects/components/object-record-detail.tsx +8 -1
- package/blocks/objects/components/object-schema-editor.tsx +1 -1
- package/blocks/objects/components/record-roles-panel.tsx +15 -5
- package/blocks/pages/README.md +14 -0
- package/blocks/pages/block.json +113 -1
- 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-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 +48 -1
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- 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 +3 -2
- package/blocks/quotes/components/quote-viewer.tsx +7 -2
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/radar/components/competitor-battle-card.tsx +1 -1
- package/blocks/radar/components/radar-overview.tsx +4 -4
- package/blocks/radar/components/radar-screen.tsx +2 -2
- package/blocks/sequencer/README.md +6 -0
- package/blocks/sequencer/block.json +28 -1
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +19 -20
- package/blocks/sequencer/components/sequence-detail.tsx +4 -2
- package/blocks/sequencer/components/sequence-enrollments.tsx +4 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequences-screen.tsx +3 -0
- package/blocks/sequencer/components/step-editor.tsx +11 -11
- package/blocks/sequencer/lib/sequencer-utils.ts +5 -5
- package/blocks/social-ads/README.md +8 -0
- package/blocks/social-ads/block.json +137 -4
- package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
- package/blocks/social-ads/components/audience-sync.tsx +6 -2
- package/blocks/social-ads/components/campaign-ads.tsx +166 -0
- package/blocks/social-ads/components/campaign-detail.tsx +6 -1
- 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 +6 -1
- package/blocks/social-ads/components/creative-gallery.tsx +20 -2
- 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 +4 -2
- package/blocks/social-ads/components/post-detail.tsx +3 -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-screen.tsx +33 -6
- 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/components/snippet-install.tsx +18 -9
- package/blocks/visitors/components/traffic-chart.tsx +4 -4
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-grid.tsx +1 -1
- package/blocks/workbench/components/workbench-screen.tsx +15 -5
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +536 -392
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +1 -1
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +1 -1
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +7 -3
- package/dist/types/blocks/doctor.d.ts +1 -0
- package/dist/types/blocks/flows.d.ts +7 -1
- package/dist/types/blocks/orgBrand.d.ts +34 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/version.d.ts +2 -2
- package/docs/api/desk.d.ts +2 -2
- package/docs/api/desk.md +3 -3
- package/package.json +1 -1
- 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 +17 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +29 -95
- 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 +36 -8
- 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 +8 -4
- package/stacks/clay/pages/companies.tsx.tmpl +17 -1
- package/stacks/clay/pages/contacts.tsx.tmpl +12 -0
- package/stacks/clay/pages/lists.tsx.tmpl +12 -0
- package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
- package/stacks/clay.json +16 -3
- 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 +23 -6
- 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 +9 -4
- package/stacks/hubspot/pages/companies.tsx.tmpl +14 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +29 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +10 -1
- package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
- 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/sequences.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot.json +21 -5
- 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 +8 -4
- 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 +20 -5
- 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 +27 -66
- 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 +25 -7
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +15 -35
- package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
- package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
- package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
- package/stacks/pipedrive.json +4 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +14 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +20 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +10 -1
- package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
- package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
- package/stacks/salesforce.json +7 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +16 -3
- package/styles/styles.css +1 -1
- package/templates/next-app/app/globals.css.tmpl +349 -64
- package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RecordSequencesTab: every sequence one contact is or was enrolled in, with its state and step, "Add to sequence"
|
|
5
|
+
* (the sending preflight and confirmation live in the enroll dialog) and a confirmed "Withdraw" per active enrollment.
|
|
6
|
+
* Use it as an `extraDetailTabs` entry of ContactsScreen:
|
|
7
|
+
* { id: "sequences", label: "Sequences", render: (c) => <RecordSequencesTab contactId={c.id} onOpenSequence={(id) => router.push(`/sequences?id=${id}`)} /> }
|
|
8
|
+
*
|
|
9
|
+
* SDK operations, in order:
|
|
10
|
+
* 1. getContactSequences GET /contacts/{id}/sequences (read; the API answers the 50 most recent enrollments)
|
|
11
|
+
* 2. AddToSequenceButton: listSequences, getSafeToSend, getSequence, addContactsToSequence (external: confirmed in the dialog)
|
|
12
|
+
* 3. withdrawContactsFromSequences POST /contacts/withdraw-from-sequences (write; stops the contact's steps, confirmed here)
|
|
13
|
+
* Withdraw is contract-tier `write`, but it stops a contact's outreach, so it is confirmed with the destructive dialog anyway.
|
|
14
|
+
* Withdraw names ONE sequence; it never uses the API's remove-from-all switch.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
19
|
+
import { Workflow } from "lucide-react";
|
|
20
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
21
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
22
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
23
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
24
|
+
import type { Tone } from "@/g8/components/screen/status-pill";
|
|
25
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
26
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
27
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
28
|
+
import { AddToSequenceButton } from "./add-to-sequence-button";
|
|
29
|
+
import { listOf } from "../lib/sequencer-utils";
|
|
30
|
+
|
|
31
|
+
/** One row of GET /contacts/{id}/sequences. */
|
|
32
|
+
interface Enrollment { id?: number | null; sequence_id?: string | null; sequence_name?: string | null; sequence_status?: string | null; state?: string | null; current_step_order?: number | null; created_at?: string | null }
|
|
33
|
+
|
|
34
|
+
/** The server returns at most this many enrollments, newest first. */
|
|
35
|
+
export const SEQUENCES_TAB_API_LIMIT = 50;
|
|
36
|
+
|
|
37
|
+
const TONE: Record<string, Tone> = { waiting: "info", paused: "warning", completed: "positive", replied: "positive", bounced: "negative", skipped: "neutral" };
|
|
38
|
+
/** Enrollments that are already over cannot be withdrawn. */
|
|
39
|
+
const OVER = new Set(["completed", "replied", "bounced", "skipped", "withdrawn", "removed"]);
|
|
40
|
+
|
|
41
|
+
function when(iso: string | null | undefined): string {
|
|
42
|
+
const d = iso ? new Date(iso) : null;
|
|
43
|
+
return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }) : "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RecordSequencesTabProps {
|
|
47
|
+
contactId: number;
|
|
48
|
+
/** Route to the sequence in your sequences screen. Without it the rows link to `${sequencesPath}?id=<sequence id>`. */
|
|
49
|
+
onOpenSequence?: (sequenceId: string) => void;
|
|
50
|
+
/** Path of your sequences screen for the plain links. Default "/sequences". */
|
|
51
|
+
sequencesPath?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function RecordSequencesTab({ contactId, onOpenSequence, sequencesPath = "/sequences" }: RecordSequencesTabProps) {
|
|
55
|
+
const [withdrawing, setWithdrawing] = useState<Enrollment | null>(null);
|
|
56
|
+
const q = useG8Query(async (signal) => listOf<Enrollment>(await g8Get(`/contacts/${contactId}/sequences`, undefined, { signal })), [contactId]);
|
|
57
|
+
const withdraw = useG8Mutation(async (e: Enrollment) => {
|
|
58
|
+
await g8Post("/contacts/withdraw-from-sequences", { contact_ids: [contactId], sequence_id: e.sequence_id });
|
|
59
|
+
setWithdrawing(null);
|
|
60
|
+
await q.refetch();
|
|
61
|
+
}, { invalidates: [["sequences"]], onError: () => setWithdrawing(null) });
|
|
62
|
+
|
|
63
|
+
if (q.error) return <ApiError error={q.error} title="Could not load sequences" onRetry={() => void q.refetch()} />;
|
|
64
|
+
if (!q.data) return <Skeleton className="h-24 w-full" aria-label="Loading sequences" />;
|
|
65
|
+
const rows = q.data;
|
|
66
|
+
return (
|
|
67
|
+
<div className="space-y-3">
|
|
68
|
+
<div className="flex items-center justify-between gap-2">
|
|
69
|
+
<p className="text-sm text-muted-foreground">{rows.length === 1 ? "1 enrollment" : `${rows.length.toLocaleString()} enrollments`}</p>
|
|
70
|
+
<AddToSequenceButton contactIds={[contactId]} onEnrolled={() => void q.refetch()} />
|
|
71
|
+
</div>
|
|
72
|
+
{withdraw.error ? <ApiError error={withdraw.error} title="Could not withdraw the contact" /> : null}
|
|
73
|
+
{rows.length === 0 ? (
|
|
74
|
+
<EmptyState compact icon={<Workflow className="size-8" />} title="Not in any sequence" description="Add this contact to a sequence to start its steps." />
|
|
75
|
+
) : (
|
|
76
|
+
<ul className="divide-y rounded-md border" aria-label="Sequences">
|
|
77
|
+
{rows.map((e, i) => {
|
|
78
|
+
const state = (e.state ?? "").toLowerCase();
|
|
79
|
+
return (
|
|
80
|
+
<li key={e.id ?? `${e.sequence_id}-${i}`} className="flex items-center justify-between gap-3 p-3 text-sm">
|
|
81
|
+
<div className="min-w-0">
|
|
82
|
+
{e.sequence_id ? (
|
|
83
|
+
<a
|
|
84
|
+
href={`${sequencesPath}?id=${encodeURIComponent(e.sequence_id)}`}
|
|
85
|
+
className="block truncate font-medium hover:underline"
|
|
86
|
+
onClick={(ev) => {
|
|
87
|
+
if (!onOpenSequence || ev.defaultPrevented || ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return;
|
|
88
|
+
ev.preventDefault();
|
|
89
|
+
onOpenSequence(e.sequence_id as string);
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{e.sequence_name || "Untitled sequence"}
|
|
93
|
+
</a>
|
|
94
|
+
) : <span className="font-medium">{e.sequence_name || "Untitled sequence"}</span>}
|
|
95
|
+
<p className="text-xs text-muted-foreground">
|
|
96
|
+
{[e.current_step_order != null ? `Step ${e.current_step_order}` : null, e.created_at ? `Enrolled ${when(e.created_at)}` : null, e.sequence_status ? `Sequence ${e.sequence_status.toLowerCase()}` : null].filter(Boolean).join(" · ")}
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
100
|
+
<StatusPill tone={TONE[state] ?? "neutral"} dot>{state || "unknown"}</StatusPill>
|
|
101
|
+
{e.sequence_id && !OVER.has(state) ? (
|
|
102
|
+
<Button size="sm" variant="ghost" loading={withdraw.loading && withdrawing?.id === e.id} onClick={() => setWithdrawing(e)}>Withdraw</Button>
|
|
103
|
+
) : null}
|
|
104
|
+
</div>
|
|
105
|
+
</li>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</ul>
|
|
109
|
+
)}
|
|
110
|
+
{rows.length >= SEQUENCES_TAB_API_LIMIT ? (
|
|
111
|
+
<p role="note" className="text-xs text-muted-foreground">Showing the {SEQUENCES_TAB_API_LIMIT} most recent enrollments. Older ones are in the sequence itself.</p>
|
|
112
|
+
) : null}
|
|
113
|
+
<ConfirmSpendDialog
|
|
114
|
+
open={withdrawing !== null}
|
|
115
|
+
tier="destructive"
|
|
116
|
+
title={`Withdraw from ${withdrawing?.sequence_name || "this sequence"}`}
|
|
117
|
+
description="The contact stops receiving this sequence's steps. Their other sequences are not changed."
|
|
118
|
+
affected={1}
|
|
119
|
+
confirmLabel="Withdraw"
|
|
120
|
+
confirming={withdraw.loading}
|
|
121
|
+
onConfirm={() => withdrawing && void withdraw.mutate(withdrawing)}
|
|
122
|
+
onCancel={() => setWithdrawing(null)}
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -34,6 +34,8 @@ import { g8Delete, g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
|
|
|
34
34
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
35
35
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
36
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";
|
|
37
39
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
38
40
|
import type { ConfirmTier } from "@/g8/components/confirm-spend-dialog";
|
|
39
41
|
import { StepEditor } from "./step-editor";
|
|
@@ -102,11 +104,8 @@ const PHONE_KEYS = ["phone_numbers", "active_phone_numbers", "drafted_phone_numb
|
|
|
102
104
|
const LINKEDIN_KEYS = ["linkedin_accounts", "active_linkedin_accounts", "drafted_linkedin_accounts"];
|
|
103
105
|
const phoneFits = (p: PhoneOption, type: "ai" | "dialer") => !p.inbound_type || p.inbound_type === type;
|
|
104
106
|
const linkedinLabel = (a: LinkedinOption) => a.name || a.display_name || a.public_identifier || a.email || `Account ${a.id}`;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
title: string;
|
|
108
|
-
contact_count?: number | null;
|
|
109
|
-
}
|
|
107
|
+
/** The mailboxes API has no paging: a full read is announced under the sender dropdown. */
|
|
108
|
+
const MAILBOX_LIMIT = 200;
|
|
110
109
|
type Pending = { kind: "create" } | { kind: "update" | "delete"; step: DraftStep };
|
|
111
110
|
|
|
112
111
|
function describe(p: Pending, live: boolean): { tier: ConfirmTier; title: string; description: string; label: string } {
|
|
@@ -157,8 +156,7 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
157
156
|
// Senders already attached in edit mode by an earlier (failed) append: never attach them twice.
|
|
158
157
|
const attached = useRef<Set<string>>(new Set());
|
|
159
158
|
|
|
160
|
-
const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit:
|
|
161
|
-
const lists = useG8Query((signal) => g8Get("/lists", { limit: 100 }, { signal }), [], { enabled: !editing });
|
|
159
|
+
const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: MAILBOX_LIMIT }, { signal }), [], { enabled: !editing });
|
|
162
160
|
const existing = useG8Query(
|
|
163
161
|
async (signal) => {
|
|
164
162
|
const [seq, list] = await Promise.all([
|
|
@@ -323,15 +321,17 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
323
321
|
);
|
|
324
322
|
})}
|
|
325
323
|
</select>
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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}
|
|
329
|
+
value={listId}
|
|
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
335
|
</div>
|
|
336
336
|
) : null}
|
|
337
337
|
{need.aiPhone || need.dialerPhone || need.linkedin ? (
|
|
@@ -373,12 +373,11 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
373
373
|
{String(objectOf(linkedin.data).message ?? "No LinkedIn account is connected.")} Connect one in the {brand.linkLabel} app.
|
|
374
374
|
</p>
|
|
375
375
|
) : null}
|
|
376
|
-
{mailboxes.error ||
|
|
376
|
+
{mailboxes.error || phones.error || linkedin.error ? (
|
|
377
377
|
<ApiError
|
|
378
|
-
error={mailboxes.error ??
|
|
378
|
+
error={mailboxes.error ?? phones.error ?? linkedin.error}
|
|
379
379
|
onRetry={() => {
|
|
380
380
|
void mailboxes.refetch();
|
|
381
|
-
void lists.refetch();
|
|
382
381
|
if (need.aiPhone || need.dialerPhone) void phones.refetch();
|
|
383
382
|
if (need.linkedin) void linkedin.refetch();
|
|
384
383
|
}}
|
|
@@ -449,7 +448,7 @@ export function SequenceBuilder({ sequenceId, defaultListId, ownerEmail, onSaved
|
|
|
449
448
|
{run.error ? <ApiError error={run.error} onRetry={run.reset} /> : null}
|
|
450
449
|
{append.error ? <ApiError error={append.error} onRetry={append.reset} /> : null}
|
|
451
450
|
{saved ? (
|
|
452
|
-
<p role="status" className="text-sm text-
|
|
451
|
+
<p role="status" className="text-sm text-positive-foreground">
|
|
453
452
|
{saved}
|
|
454
453
|
</p>
|
|
455
454
|
) : null}
|
|
@@ -143,6 +143,8 @@ export interface SequenceDetailProps {
|
|
|
143
143
|
refreshKey?: number;
|
|
144
144
|
/** Open another sequence (the duplicate). */
|
|
145
145
|
onOpenSequence?: (sequenceId: string) => void;
|
|
146
|
+
/** Open an enrolled person in your contacts screen. */
|
|
147
|
+
onOpenContact?: (contactId: number) => void;
|
|
146
148
|
renderActions?: (sequence: SequenceRow) => ReactNode;
|
|
147
149
|
extraTabs?: { id: string; label: string; render: (sequence: SequenceRow) => ReactNode }[];
|
|
148
150
|
className?: string;
|
|
@@ -211,7 +213,7 @@ export function SequenceDetail(props: SequenceDetailProps) {
|
|
|
211
213
|
description={
|
|
212
214
|
<>
|
|
213
215
|
<p>{COPY[p.action].description}</p>
|
|
214
|
-
{p.warnings.length > 0 ? <ul className="mt-2 list-disc pl-5 text-
|
|
216
|
+
{p.warnings.length > 0 ? <ul className="mt-2 list-disc pl-5 text-warning-foreground">{p.warnings.map((w) => <li key={w}>{w}</li>)}</ul> : null}
|
|
215
217
|
</>
|
|
216
218
|
}
|
|
217
219
|
recipients={p.action === "archive" ? undefined : (row.contact_count ?? undefined)}
|
|
@@ -250,7 +252,7 @@ export function SequenceDetail(props: SequenceDetailProps) {
|
|
|
250
252
|
),
|
|
251
253
|
},
|
|
252
254
|
{ id: "steps", label: "Steps", count: row.step_count ?? undefined, content: <SequenceStepsPanel sequenceId={id} stepId={props.stepId} onStepChange={props.onStepChange} onChanged={changed} /> },
|
|
253
|
-
{ id: "contacts", label: "Contacts", count: row.contact_count ?? undefined, content: <SequenceEnrollments key={statsKey} sequenceId={id} contactId={props.contactId} onContactChange={props.onContactChange} /> },
|
|
255
|
+
{ id: "contacts", label: "Contacts", count: row.contact_count ?? undefined, content: <SequenceEnrollments key={statsKey} sequenceId={id} contactId={props.contactId} onContactChange={props.onContactChange} onOpenContact={props.onOpenContact} /> },
|
|
254
256
|
{ id: "results", label: "Results", content: <SequenceStats key={`${id}:${statsKey}`} sequenceId={id} refreshSeconds={group === "live" ? 60 : undefined} /> },
|
|
255
257
|
...(props.extraTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(row) })),
|
|
256
258
|
]}
|
|
@@ -31,6 +31,8 @@ export interface SequenceEnrollmentsProps {
|
|
|
31
31
|
/** The open enrollment, by contact id. Controlled so a screen can deep-link it. */
|
|
32
32
|
contactId?: string;
|
|
33
33
|
onContactChange?: (contactId: string | null) => void;
|
|
34
|
+
/** Open the enrolled person in your contacts screen (adds an "Open in contacts" button). */
|
|
35
|
+
onOpenContact?: (contactId: number) => void;
|
|
34
36
|
pageSize?: number;
|
|
35
37
|
className?: string;
|
|
36
38
|
}
|
|
@@ -41,7 +43,7 @@ const STATES = ["", "waiting", "paused", "completed", "replied", "bounced", "ski
|
|
|
41
43
|
const TONE: Record<string, Tone> = { waiting: "info", paused: "warning", completed: "positive", replied: "positive", bounced: "negative", skipped: "neutral" };
|
|
42
44
|
const nameOf = (e: Enrollment) => e.name || e.email || (e.contact_id != null ? `Contact ${e.contact_id}` : "Contact");
|
|
43
45
|
|
|
44
|
-
export function SequenceEnrollments({ sequenceId, contactId, onContactChange, pageSize = 25, className }: SequenceEnrollmentsProps) {
|
|
46
|
+
export function SequenceEnrollments({ sequenceId, contactId, onContactChange, onOpenContact, pageSize = 25, className }: SequenceEnrollmentsProps) {
|
|
45
47
|
const [state, setState] = useState("");
|
|
46
48
|
const [page, setPage] = useState(1);
|
|
47
49
|
const [resume, setResume] = useState<Enrollment | null>(null);
|
|
@@ -89,7 +91,7 @@ export function SequenceEnrollments({ sequenceId, contactId, onContactChange, pa
|
|
|
89
91
|
<Button size="sm" variant="outline" disabled={rows.length < pageSize} onClick={() => setPage((p) => p + 1)}>Next</Button>
|
|
90
92
|
</div>
|
|
91
93
|
{open ? (
|
|
92
|
-
<SectionCard title={`${nameOf(open)} in this sequence`} actions={<>{actions(open)}<Button size="sm" variant="ghost" onClick={() => onContactChange?.(null)}>Close</Button></>}>
|
|
94
|
+
<SectionCard title={`${nameOf(open)} in this sequence`} actions={<>{actions(open)}{onOpenContact && open.contact_id != null ? <Button size="sm" variant="outline" onClick={() => onOpenContact(open.contact_id as number)}>Open in contacts</Button> : null}<Button size="sm" variant="ghost" onClick={() => onContactChange?.(null)}>Close</Button></>}>
|
|
93
95
|
<KeyValueList columns={2} items={fieldItems(open as Record<string, unknown>)} />
|
|
94
96
|
{open.contact_id != null ? (
|
|
95
97
|
<SectionCard title="Contact" description="The full contact record.">
|
|
@@ -204,7 +204,7 @@ export function SequenceList({ status, pageSize = 25, onOpen, onCreate, onChange
|
|
|
204
204
|
<>
|
|
205
205
|
<p>{COPY[pending.action].description}</p>
|
|
206
206
|
{pending.warnings.length > 0 ? (
|
|
207
|
-
<ul className="mt-2 list-disc pl-5 text-
|
|
207
|
+
<ul className="mt-2 list-disc pl-5 text-warning-foreground">
|
|
208
208
|
{pending.warnings.map((w) => (
|
|
209
209
|
<li key={w}>{w}</li>
|
|
210
210
|
))}
|
|
@@ -50,9 +50,9 @@ const STATES = ["", "waiting", "paused", "completed", "replied", "bounced", "ski
|
|
|
50
50
|
|
|
51
51
|
function Tile({ label, value }: { label: string; value: string | number }) {
|
|
52
52
|
return (
|
|
53
|
-
<div className="rounded border border-
|
|
54
|
-
<div className="text-xs capitalize text-
|
|
55
|
-
<div className="text-xl font-
|
|
53
|
+
<div className="rounded border border-border p-3">
|
|
54
|
+
<div className="text-xs capitalize text-muted-foreground">{label}</div>
|
|
55
|
+
<div className="text-xl font-medium">{value}</div>
|
|
56
56
|
</div>
|
|
57
57
|
);
|
|
58
58
|
}
|
|
@@ -145,7 +145,7 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
|
|
|
145
145
|
<TableCell>{st.step_order}</TableCell>
|
|
146
146
|
<TableCell>{st.step_type}</TableCell>
|
|
147
147
|
<TableCell>{st.total_contacts ?? 0}</TableCell>
|
|
148
|
-
<TableCell className="text-xs text-
|
|
148
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
149
149
|
{Object.entries(st.counts ?? {})
|
|
150
150
|
.filter(([, n]) => n > 0)
|
|
151
151
|
.map(([k, n]) => `${k}: ${n}`)
|
|
@@ -156,11 +156,11 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
|
|
|
156
156
|
</TableBody>
|
|
157
157
|
</Table>
|
|
158
158
|
) : (
|
|
159
|
-
<p className="text-sm text-
|
|
159
|
+
<p className="text-sm text-muted-foreground">No step activity yet.</p>
|
|
160
160
|
)}
|
|
161
161
|
|
|
162
162
|
{skips.length > 0 ? (
|
|
163
|
-
<p className="text-sm text-
|
|
163
|
+
<p className="text-sm text-muted-foreground" data-testid="skip-reasons">
|
|
164
164
|
Skipped contacts, counted once each: {skips.map(([k, n]) => `${k.replace(/^skipped_/, "").replace(/_/g, " ")} ${n}`).join(", ")}
|
|
165
165
|
</p>
|
|
166
166
|
) : null}
|
|
@@ -171,7 +171,7 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
|
|
|
171
171
|
Contacts in
|
|
172
172
|
<select
|
|
173
173
|
aria-label="Contact state"
|
|
174
|
-
className="ml-2 h-8 rounded border border-
|
|
174
|
+
className="ml-2 h-8 rounded border border-input px-1"
|
|
175
175
|
value={state}
|
|
176
176
|
onChange={(e) => {
|
|
177
177
|
setState(e.target.value);
|
|
@@ -201,7 +201,7 @@ export function SequenceStats({ sequenceId, pageSize = 25, refreshSeconds, class
|
|
|
201
201
|
<TableBody>
|
|
202
202
|
{rows.length === 0 ? (
|
|
203
203
|
<TableRow>
|
|
204
|
-
<TableCell colSpan={4} className="text-sm text-
|
|
204
|
+
<TableCell colSpan={4} className="text-sm text-muted-foreground">
|
|
205
205
|
{contacts.loading ? "Loading..." : "No contacts in this state."}
|
|
206
206
|
</TableCell>
|
|
207
207
|
</TableRow>
|
|
@@ -52,6 +52,8 @@ export interface SequencesScreenProps {
|
|
|
52
52
|
renderDetailActions?: (sequence: SequenceRow) => ReactNode;
|
|
53
53
|
/** Extra tabs in the open sequence's panel. */
|
|
54
54
|
extraDetailTabs?: { id: string; label: string; render: (sequence: SequenceRow) => ReactNode }[];
|
|
55
|
+
/** Open an enrolled person in your contacts screen; without it the Contacts tab has no such button. */
|
|
56
|
+
onOpenContact?: (contactId: number) => void;
|
|
55
57
|
className?: string;
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -135,6 +137,7 @@ export function SequencesScreen(props: SequencesScreenProps) {
|
|
|
135
137
|
onStepChange={(v) => setStepId(v)}
|
|
136
138
|
contactId={contactId || undefined}
|
|
137
139
|
onContactChange={(v) => setContactId(v)}
|
|
140
|
+
onOpenContact={props.onOpenContact}
|
|
138
141
|
onClose={close}
|
|
139
142
|
onChanged={refreshList}
|
|
140
143
|
refreshKey={version}
|
|
@@ -52,9 +52,9 @@ function LinkedinReadiness() {
|
|
|
52
52
|
const c = conn.data?.data;
|
|
53
53
|
if (conn.loading || conn.error || !c) return null;
|
|
54
54
|
if (c.connected && c.is_token_valid !== false && !c.requires_manual_login)
|
|
55
|
-
return <p className="text-xs text-
|
|
55
|
+
return <p className="text-xs text-positive-foreground">LinkedIn sender connected.</p>;
|
|
56
56
|
return (
|
|
57
|
-
<p role="alert" className="text-xs text-
|
|
57
|
+
<p role="alert" className="text-xs text-warning-foreground">
|
|
58
58
|
LinkedIn is not ready to send. Reconnect the LinkedIn account in the {brand.linkLabel} app before launching.
|
|
59
59
|
</p>
|
|
60
60
|
);
|
|
@@ -72,13 +72,13 @@ export function StepEditor({ step, index, onChange, onRemove, disabled, classNam
|
|
|
72
72
|
const phoneOptions = Array.isArray(data.phone_options) ? (data.phone_options as string[]) : [];
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
|
-
<fieldset disabled={disabled} className={`space-y-3 rounded border border-
|
|
75
|
+
<fieldset disabled={disabled} className={`space-y-3 rounded border border-border p-3 ${className ?? ""}`} data-step-kind={step.kind}>
|
|
76
76
|
<div className="flex items-center justify-between">
|
|
77
|
-
<legend className="text-sm font-
|
|
77
|
+
<legend className="text-sm font-medium">
|
|
78
78
|
Step {index}: {KIND_LABEL[step.kind]}
|
|
79
79
|
</legend>
|
|
80
80
|
{onRemove ? (
|
|
81
|
-
<button type="button" className="text-xs text-
|
|
81
|
+
<button type="button" className="text-xs text-error-foreground hover:underline" onClick={onRemove}>
|
|
82
82
|
Remove step
|
|
83
83
|
</button>
|
|
84
84
|
) : null}
|
|
@@ -105,7 +105,7 @@ export function StepEditor({ step, index, onChange, onRemove, disabled, classNam
|
|
|
105
105
|
Copy
|
|
106
106
|
<select
|
|
107
107
|
aria-label="Email copy"
|
|
108
|
-
className="ml-2 h-8 rounded border border-
|
|
108
|
+
className="ml-2 h-8 rounded border border-input px-1 text-sm"
|
|
109
109
|
value={step.input_type}
|
|
110
110
|
onChange={(e) => onChange({ ...step, dirty: true, input_type: e.target.value as InputType })}
|
|
111
111
|
>
|
|
@@ -141,7 +141,7 @@ export function StepEditor({ step, index, onChange, onRemove, disabled, classNam
|
|
|
141
141
|
<>
|
|
142
142
|
<select
|
|
143
143
|
aria-label="LinkedIn action"
|
|
144
|
-
className="h-8 rounded border border-
|
|
144
|
+
className="h-8 rounded border border-input px-1 text-sm"
|
|
145
145
|
value={action}
|
|
146
146
|
onChange={(e) => set({ linkedin_action_type: e.target.value })}
|
|
147
147
|
>
|
|
@@ -160,7 +160,7 @@ export function StepEditor({ step, index, onChange, onRemove, disabled, classNam
|
|
|
160
160
|
onChange={(e) => set({ [copyKey]: e.target.value })}
|
|
161
161
|
/>
|
|
162
162
|
) : (
|
|
163
|
-
<p className="text-xs text-
|
|
163
|
+
<p className="text-xs text-muted-foreground">This action sends no text.</p>
|
|
164
164
|
)}
|
|
165
165
|
{action === "INMAIL" ? (
|
|
166
166
|
<Input
|
|
@@ -176,7 +176,7 @@ export function StepEditor({ step, index, onChange, onRemove, disabled, classNam
|
|
|
176
176
|
|
|
177
177
|
{step.kind === "call" || step.kind === "task" ? (
|
|
178
178
|
<div className="space-y-1 text-sm">
|
|
179
|
-
<p className="text-
|
|
179
|
+
<p className="text-muted-foreground">
|
|
180
180
|
{step.kind === "call" ? "An automatic call attempt." : "Creates a call task for a person to work in the dialer."}
|
|
181
181
|
</p>
|
|
182
182
|
<div className="flex flex-wrap gap-3">
|
|
@@ -199,12 +199,12 @@ export function StepEditor({ step, index, onChange, onRemove, disabled, classNam
|
|
|
199
199
|
) : null}
|
|
200
200
|
|
|
201
201
|
{broken ? (
|
|
202
|
-
<p role="alert" className="text-xs text-
|
|
202
|
+
<p role="alert" className="text-xs text-warning-foreground">
|
|
203
203
|
A {"{{merge token}}"} is not closed. Unclosed tokens are deleted when the email sends.
|
|
204
204
|
</p>
|
|
205
205
|
) : null}
|
|
206
206
|
{issues.length > 0 ? (
|
|
207
|
-
<ul className="text-xs text-
|
|
207
|
+
<ul className="text-xs text-error-foreground" aria-label="Step problems">
|
|
208
208
|
{issues.map((i) => (
|
|
209
209
|
<li key={i}>{i}</li>
|
|
210
210
|
))}
|
|
@@ -142,11 +142,11 @@ export function statusGroup(status: string | null | undefined): "draft" | "live"
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
export const STATUS_CLASS: Record<ReturnType<typeof statusGroup>, string> = {
|
|
145
|
-
draft: "bg-
|
|
146
|
-
live: "bg-
|
|
147
|
-
paused: "bg-
|
|
148
|
-
ended: "bg-
|
|
149
|
-
busy: "bg-
|
|
145
|
+
draft: "bg-muted text-foreground",
|
|
146
|
+
live: "bg-positive text-positive-foreground",
|
|
147
|
+
paused: "bg-warning text-warning-foreground",
|
|
148
|
+
ended: "bg-muted text-muted-foreground",
|
|
149
|
+
busy: "bg-info text-info-foreground",
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
export function listOf<T>(envelope: unknown): T[] {
|
|
@@ -137,3 +137,11 @@ Ask the person: campaign daily budget, dates and destination URL (always typed b
|
|
|
137
137
|
| updateAdCreative | PATCH /ad-creatives/{creative_id} | write | CreativeGallery |
|
|
138
138
|
| updateAudienceSync | PATCH /audience-syncs/{config_id} | external | AudienceSync |
|
|
139
139
|
| updateSocialPost | PATCH /social/posts/{post_id} | write | PostComposer |
|
|
140
|
+
|
|
141
|
+
## Depth added (C7c)
|
|
142
|
+
|
|
143
|
+
- `CampaignDetail` gains **Ads** (`CampaignAds`: list, edit, pause or activate, archive, add; all external or destructive confirms) and **Edit** (`CampaignEditor`: name, daily budget, end date, spend cap, pause or resume; previewed, then an external confirm; fields the platform refuses are disabled with its reason).
|
|
144
|
+
- `CreativeDetail` gains **AI tools** (`CreativeTools`: edit by instruction, rewrite a field with undo, review and apply, other sizes, retry, move to draft, save as template; billable passes are confirmed) and **Revisions** (`CreativeRevisions`). `CreativeGallery` loads past 50 and has a **Concept board** (`CreativeConcepts`, billable confirm).
|
|
145
|
+
- `SocialScreen` gains **Ideas** (`SocialIdeas`, Draft a post opens the composer with the brief), **Performance** (`SocialPerformance`), a per-post Performance tab, and Disconnect on an account (destructive confirm).
|
|
146
|
+
|
|
147
|
+
Not covered (and why): `createAdCreativeSet` and `readAdCreativeThroughPersona` need persona pickers; video clips, image-point edits, reskin, set-image and library ops need pickers or media editing; `uploadAdImage` is multipart; social autopilot (`saveSocialAutopilot`) auto-publishes and engagers (`addSocialEngagersToAudience`, `enrollSocialEngagers`) need list and sequence pickers; `getAudienceSync` is already covered by the list read.
|