@graph8/sdk 0.370.0 → 0.374.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 +22 -3
- package/blocks/_foundation/README.md +61 -0
- package/blocks/_foundation/block.json +57 -0
- package/blocks/_foundation/components/auth-gate.tsx +363 -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/_foundation/server/auth.ts +815 -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/README.md +1 -1
- package/blocks/campaigns/block.json +34 -1
- package/blocks/campaigns/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- package/blocks/campaigns/components/campaign-links.tsx +180 -0
- 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 +165 -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 +25 -0
- package/blocks/crm/block.json +257 -9
- package/blocks/crm/components/bulk-update.tsx +119 -0
- 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-documents-tab.tsx +144 -0
- package/blocks/crm/components/contact-insights-tab.tsx +118 -0
- package/blocks/crm/components/contact-lookup-link.tsx +85 -0
- package/blocks/crm/components/contact-row-actions.tsx +43 -0
- package/blocks/crm/components/contact-tag-chips.tsx +113 -0
- package/blocks/crm/components/contacts-screen.tsx +77 -18
- 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/owner-assign.tsx +132 -0
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/record-open-links.tsx +79 -0
- package/blocks/crm/components/tag-assign.tsx +112 -0
- package/blocks/crm/components/tag-manager.tsx +178 -0
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/components/unlock-info-button.tsx +82 -0
- package/blocks/crm/components/verify-email-button.tsx +93 -0
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-contact-tags.ts +29 -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/crm/lib/record-actions.ts +102 -0
- package/blocks/deals/README.md +25 -6
- package/blocks/deals/block.json +168 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/create-deal-button.tsx +109 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +7 -5
- 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 +10 -2
- 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/documents/README.md +86 -0
- package/blocks/documents/block.json +378 -0
- package/blocks/documents/components/account-bar.tsx +48 -0
- package/blocks/documents/components/correspondence-form.tsx +95 -0
- package/blocks/documents/components/correspondence-import.tsx +106 -0
- package/blocks/documents/components/correspondence-panel.tsx +191 -0
- package/blocks/documents/components/documents-screen.tsx +76 -0
- package/blocks/documents/components/library-document-detail.tsx +175 -0
- package/blocks/documents/components/library-document-form.tsx +105 -0
- package/blocks/documents/components/library-folder-form.tsx +74 -0
- package/blocks/documents/components/library-panel.tsx +235 -0
- package/blocks/documents/components/library-upload.tsx +160 -0
- package/blocks/documents/components/question-row.tsx +130 -0
- package/blocks/documents/components/questionnaire-detail.tsx +197 -0
- package/blocks/documents/components/questionnaires-panel.tsx +173 -0
- package/blocks/documents/components/record-documents-tab.tsx +109 -0
- package/blocks/documents/lib/documents-types.ts +252 -0
- package/blocks/documents/lib/download.ts +64 -0
- package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
- package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
- package/blocks/documents/recipes/organize-the-library.ts +25 -0
- package/blocks/email-signatures/README.md +123 -0
- package/blocks/email-signatures/block.json +341 -0
- package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
- package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
- package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
- package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
- package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
- package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
- package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
- package/blocks/email-signatures/components/signature-policy.tsx +130 -0
- package/blocks/email-signatures/components/signature-preview.tsx +119 -0
- package/blocks/email-signatures/components/signature-report.tsx +77 -0
- package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
- package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
- package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
- package/blocks/email-signatures/lib/signatures-types.ts +392 -0
- package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
- package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
- package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
- package/blocks/enrichment/README.md +38 -10
- package/blocks/enrichment/block.json +164 -12
- package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
- package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
- package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
- package/blocks/enrichment/components/list-pipelines.tsx +97 -0
- package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
- package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
- package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +51 -0
- package/blocks/enrichment/lib/pipeline-types.ts +234 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +10 -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 +57 -20
- 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 +45 -4
- package/blocks/inbox/block.json +145 -8
- package/blocks/inbox/components/blocked-senders.tsx +103 -0
- package/blocks/inbox/components/compose-message.tsx +193 -0
- package/blocks/inbox/components/inbox-screen.tsx +54 -6
- package/blocks/inbox/components/inbox-search.tsx +106 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/tag-manager.tsx +136 -0
- package/blocks/inbox/components/thread-actions.tsx +133 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +3 -1
- package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
- package/blocks/inbox/lib/inbox-manage.ts +227 -0
- 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 +38 -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 +123 -2
- 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/hooks/use-landing-page-paths.ts +60 -0
- 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/programmatic-pages/README.md +82 -0
- package/blocks/programmatic-pages/block.json +292 -0
- package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
- package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
- package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
- package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
- package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
- package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
- package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
- package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
- package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
- package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
- package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
- package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
- package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
- package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
- package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
- package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
- package/blocks/quotes/README.md +38 -1
- package/blocks/quotes/block.json +170 -6
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/manual-products.tsx +144 -0
- package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-documents.tsx +94 -0
- package/blocks/quotes/components/quote-evidence.tsx +95 -0
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +28 -8
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-settings.tsx +184 -0
- package/blocks/quotes/components/quote-templates.tsx +189 -0
- package/blocks/quotes/components/quote-viewer.tsx +23 -5
- package/blocks/quotes/components/quotes-screen.tsx +15 -5
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/quotes/lib/quote-admin.ts +264 -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 +61 -0
- package/blocks/sequencer/block.json +200 -6
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +32 -23
- package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
- package/blocks/sequencer/components/sequence-detail.tsx +10 -2
- package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
- package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequence-templates.tsx +160 -0
- package/blocks/sequencer/components/sequences-screen.tsx +39 -6
- package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
- package/blocks/sequencer/components/step-editor.tsx +21 -12
- package/blocks/sequencer/components/step-templates.tsx +224 -0
- package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
- package/blocks/signals/README.md +39 -0
- package/blocks/signals/block.json +287 -0
- package/blocks/signals/components/auto-seed-panel.tsx +111 -0
- package/blocks/signals/components/listener-form.tsx +184 -0
- package/blocks/signals/components/matched-posts.tsx +195 -0
- package/blocks/signals/components/post-engagements.tsx +114 -0
- package/blocks/signals/components/record-social-tab.tsx +49 -0
- package/blocks/signals/components/social-listeners-screen.tsx +250 -0
- package/blocks/signals/components/social-settings.tsx +128 -0
- package/blocks/signals/components/source-lists.tsx +83 -0
- package/blocks/signals/components/top-engagers.tsx +68 -0
- package/blocks/signals/components/x-integration.tsx +129 -0
- package/blocks/signals/lib/signals-types.ts +228 -0
- package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
- package/blocks/signals/recipes/spend-against-caps.ts +14 -0
- package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
- 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 +7 -6
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/web-analytics/README.md +102 -0
- package/blocks/web-analytics/block.json +292 -0
- package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
- package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
- package/blocks/web-analytics/components/audience-tab.tsx +135 -0
- package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
- package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
- package/blocks/web-analytics/components/overview-tab.tsx +102 -0
- package/blocks/web-analytics/components/pages-tab.tsx +148 -0
- package/blocks/web-analytics/components/ranked-table.tsx +103 -0
- package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
- package/blocks/web-analytics/components/report-card.tsx +71 -0
- package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
- package/blocks/web-analytics/hooks/use-report.ts +48 -0
- package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
- package/blocks/web-analytics/lib/page-path.ts +14 -0
- package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
- package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
- package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
- package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/README.md +54 -2
- package/blocks/workbench/block.json +234 -5
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
- package/blocks/workbench/components/workbench-automation.tsx +36 -0
- package/blocks/workbench/components/workbench-connections.tsx +136 -0
- package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
- package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
- package/blocks/workbench/components/workbench-grid.tsx +71 -8
- package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
- package/blocks/workbench/components/workbench-schedule.tsx +191 -0
- package/blocks/workbench/components/workbench-screen.tsx +65 -22
- package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
- package/blocks/workbench/components/workbench-triggers.tsx +178 -0
- package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
- package/blocks/workbench/lib/api-status.ts +8 -0
- package/blocks/workbench/lib/automation-types.ts +257 -0
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +744 -394
- 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/auth.d.ts +45 -0
- package/dist/types/blocks/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +11 -4
- 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/docs/api/forms.md +1 -1
- package/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +37 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +35 -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 +24 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +68 -9
- 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 +14 -0
- package/stacks/clay/pages/lists.tsx.tmpl +24 -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 +34 -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 +30 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +19 -4
- package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
- package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
- 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 +18 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
- package/stacks/hubspot.json +66 -7
- 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 +18 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
- package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +22 -6
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +29 -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 +18 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +38 -9
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -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 +14 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +14 -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 +17 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +36 -3
- package/styles/styles.css +1 -1
- package/templates/auth/auth-route.ts.tmpl +16 -0
- package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
- package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
- package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
- package/templates/auth/g8.auth.password.ts.tmpl +18 -0
- package/templates/auth/g8.auth.session.ts.tmpl +23 -0
- package/templates/auth/middleware.ts.tmpl +14 -0
- package/templates/next-app/README.md.tmpl +5 -0
- 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
|
@@ -98,7 +98,7 @@ export function BookingPage({ eventTypeId, daysAhead = 14, timeZone, defaultAtte
|
|
|
98
98
|
if (book.data) {
|
|
99
99
|
return (
|
|
100
100
|
<div className={`space-y-2 rounded-lg border p-4 ${className ?? ""}`} role="status">
|
|
101
|
-
<h2 className="text-base font-
|
|
101
|
+
<h2 className="text-base font-medium">You are booked</h2>
|
|
102
102
|
<p className="text-sm">
|
|
103
103
|
{eventType.data?.title ?? "Meeting"} on {formatDateTime(bookedAt ?? slot?.time, zone)}. A calendar invite was sent to {email}.
|
|
104
104
|
</p>
|
|
@@ -116,7 +116,7 @@ export function BookingPage({ eventTypeId, daysAhead = 14, timeZone, defaultAtte
|
|
|
116
116
|
return (
|
|
117
117
|
<div className={`space-y-4 ${className ?? ""}`}>
|
|
118
118
|
<header>
|
|
119
|
-
<h2 className="text-base font-
|
|
119
|
+
<h2 className="text-base font-medium">{eventType.data?.title ?? "Book a meeting"}</h2>
|
|
120
120
|
<p className="text-sm text-muted-foreground">
|
|
121
121
|
{eventType.data ? `${eventType.data.length} minutes · ` : ""}times shown in {zone}
|
|
122
122
|
</p>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MeetingContactLinks: "Open <attendee>" buttons for a booked or recorded meeting, one per attendee (at most three)
|
|
5
|
+
* who is a contact in your CRM. Use it in MeetingsScreen's `renderDetailActions`:
|
|
6
|
+
* renderDetailActions={(selection) => <MeetingContactLinks selection={selection} onOpenContact={(id) => router.push(`/contacts?id=${id}`)} />}
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in order:
|
|
9
|
+
* 1. getBooking GET /appointments/bookings/{uid} (read) for a booking
|
|
10
|
+
* getMeeting GET /inbox/meetings/{id} (read) for a recorded meeting
|
|
11
|
+
* 2. listContacts GET /contacts?email= (read) once per attendee, via the crm ContactLookupLink
|
|
12
|
+
* Nothing here is billable, external or destructive.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
16
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
17
|
+
import { ContactLookupLink } from "@/g8/crm/components/contact-lookup-link";
|
|
18
|
+
import { unwrap } from "../lib/appointments-types";
|
|
19
|
+
import type { MeetingSelection } from "./meeting-list";
|
|
20
|
+
|
|
21
|
+
interface Attendee { email?: string | null; name?: string | null }
|
|
22
|
+
|
|
23
|
+
export interface MeetingContactLinksProps {
|
|
24
|
+
selection: MeetingSelection;
|
|
25
|
+
/** Route to the contact in your contacts screen. */
|
|
26
|
+
onOpenContact: (contactId: number) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function MeetingContactLinks({ selection, onOpenContact }: MeetingContactLinksProps) {
|
|
30
|
+
const { kind, id } = selection;
|
|
31
|
+
const q = useG8Query(async (signal) => {
|
|
32
|
+
const path = kind === "booking" ? `/appointments/bookings/${encodeURIComponent(id)}` : `/inbox/meetings/${encodeURIComponent(id)}`;
|
|
33
|
+
return unwrap<{ attendees?: Attendee[] | null }>(await g8Get(path, undefined, { signal })).attendees ?? [];
|
|
34
|
+
}, [kind, id]);
|
|
35
|
+
const seen = new Set<string>();
|
|
36
|
+
const people = (q.data ?? []).filter((a) => {
|
|
37
|
+
const email = a.email?.trim().toLowerCase();
|
|
38
|
+
if (!email || seen.has(email)) return false;
|
|
39
|
+
seen.add(email);
|
|
40
|
+
return true;
|
|
41
|
+
}).slice(0, 3);
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
{people.map((a) => (
|
|
45
|
+
<ContactLookupLink key={a.email} email={a.email} label={`Open ${a.name || a.email}`} onOpen={onOpenContact} />
|
|
46
|
+
))}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -48,7 +48,7 @@ function RecordedDetail({ id, className }: { id: string; className?: string }) {
|
|
|
48
48
|
const m = q.data;
|
|
49
49
|
return (
|
|
50
50
|
<section className={`space-y-3 ${className ?? ""}`}>
|
|
51
|
-
<h2 className="text-base font-
|
|
51
|
+
<h2 className="text-base font-medium">{m.subject || "Untitled meeting"}</h2>
|
|
52
52
|
<dl className="grid gap-3 sm:grid-cols-2">
|
|
53
53
|
<Field label="When">{formatDateTime(m.start_time)}</Field>
|
|
54
54
|
<Field label="Attendees">{(m.attendees ?? []).map((a) => a.name || a.email).join(", ") || "Unknown"}</Field>
|
|
@@ -94,7 +94,7 @@ function BookingDetail({ uid, onChanged, className }: { uid: string; onChanged?:
|
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
96
|
<section className={`space-y-4 ${className ?? ""}`}>
|
|
97
|
-
<h2 className="text-base font-
|
|
97
|
+
<h2 className="text-base font-medium">{b.title || b.event_type_title || "Meeting"}</h2>
|
|
98
98
|
<dl className="grid gap-3 sm:grid-cols-2">
|
|
99
99
|
<Field label="When">{formatDateTime(b.start_time)}</Field>
|
|
100
100
|
<Field label="Status"><span className="capitalize">{b.status}</span></Field>
|
|
@@ -18,6 +18,8 @@ import { ApiError } from "@/g8/components/api-error";
|
|
|
18
18
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
19
19
|
import { g8Get } from "@/g8/lib/g8-client";
|
|
20
20
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
21
|
+
import { useUrlFilters } from "@/g8/hooks/use-url-filters";
|
|
22
|
+
import { SelectFilter, TextFilter } from "@/g8/components/screen/filter-controls";
|
|
21
23
|
import { formatDateTime, listOf, type Booking, type RecordedMeeting } from "../lib/appointments-types";
|
|
22
24
|
import { rowActivationProps } from "@/g8/hooks/use-row-activation";
|
|
23
25
|
import { RecordLink } from "@/g8/components/screen/record-link";
|
|
@@ -45,8 +47,8 @@ const VIEWS: ReadonlyArray<{ value: MeetingView; label: string }> = [
|
|
|
45
47
|
{ value: "recorded", label: "Recorded" },
|
|
46
48
|
];
|
|
47
49
|
|
|
48
|
-
function bookingQuery(view: Exclude<MeetingView, "recorded">, now: string, page: number, take: number) {
|
|
49
|
-
const base = { skip: page * take, take };
|
|
50
|
+
function bookingQuery(view: Exclude<MeetingView, "recorded">, now: string, page: number, take: number, attendeeEmail?: string) {
|
|
51
|
+
const base = { skip: page * take, take, attendee_email: attendeeEmail || undefined };
|
|
50
52
|
if (view === "upcoming") return { ...base, after_start: now, sort_by: "start_time", sort_order: "asc" };
|
|
51
53
|
if (view === "past") return { ...base, before_end: now, sort_by: "start_time", sort_order: "desc" };
|
|
52
54
|
return { ...base, status: "cancelled", sort_by: "start_time", sort_order: "desc" };
|
|
@@ -56,36 +58,38 @@ function attendeeNames(b: Booking): string {
|
|
|
56
58
|
return b.attendees.map((a) => a.name || a.email).join(", ") || "No attendees";
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
useEffect(() => { const t = setTimeout(() => setV(value.trim()), ms); return () => clearTimeout(t); }, [value, ms]);
|
|
62
|
-
return v;
|
|
63
|
-
}
|
|
61
|
+
/** Every filter the meetings (/inbox/meetings) and bookings APIs take besides the view tab. */
|
|
62
|
+
const MEETING_FILTERS = ["search", "participant_email", "timeframe", "has_transcript", "attendee_email"] as const;
|
|
64
63
|
|
|
65
64
|
export function MeetingList({ linkParam, onSelect, initialView = "upcoming", pageSize = 20, refreshKey, selectedId, className }: MeetingListProps) {
|
|
66
65
|
const [view, setView] = useState<MeetingView>(initialView);
|
|
67
66
|
const [page, setPage] = useState(0);
|
|
68
|
-
|
|
69
|
-
const
|
|
67
|
+
// Every filter the meetings and bookings APIs take lives in the URL (`m_participant_email=...`).
|
|
68
|
+
const uf = useUrlFilters(MEETING_FILTERS, { prefix: "m_" });
|
|
69
|
+
const search = uf.draft.search;
|
|
70
|
+
const query = uf.filters.search.trim();
|
|
71
|
+
const { participant_email, timeframe, has_transcript, attendee_email } = uf.filters;
|
|
70
72
|
const now = useMemo(() => new Date().toISOString(), [view]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
73
|
|
|
72
74
|
const rows = useG8Query(
|
|
73
75
|
async (signal) => {
|
|
74
76
|
if (view === "recorded") {
|
|
75
|
-
const env = await g8Get("/inbox/meetings", { search: query || undefined, page: page + 1, page_size: pageSize }, { signal });
|
|
77
|
+
const env = await g8Get("/inbox/meetings", { search: query || undefined, participant_email: participant_email ? participant_email.split(",").map((e) => e.trim()).filter(Boolean) : undefined, timeframe: timeframe || undefined, has_transcript: has_transcript ? has_transcript === "yes" : undefined, page: page + 1, page_size: pageSize }, { signal });
|
|
76
78
|
return { kind: "meeting" as const, items: listOf<RecordedMeeting>(env), more: Boolean((env as { pagination?: { has_next?: boolean } }).pagination?.has_next) };
|
|
77
79
|
}
|
|
78
|
-
const env = await g8Get("/appointments/bookings", bookingQuery(view, now, page, pageSize), { signal });
|
|
80
|
+
const env = await g8Get("/appointments/bookings", bookingQuery(view, now, page, pageSize, attendee_email.trim()), { signal });
|
|
79
81
|
const items = listOf<Booking>(env, "bookings");
|
|
80
82
|
return { kind: "booking" as const, items, more: items.length === pageSize };
|
|
81
83
|
},
|
|
82
|
-
[view, page, query, pageSize, now, refreshKey],
|
|
84
|
+
[view, page, query, participant_email, timeframe, has_transcript, attendee_email, pageSize, now, refreshKey],
|
|
85
|
+
{ enabled: uf.ready },
|
|
83
86
|
);
|
|
84
87
|
|
|
85
88
|
const switchView = (next: MeetingView) => {
|
|
86
89
|
setView(next);
|
|
87
90
|
setPage(0);
|
|
88
91
|
};
|
|
92
|
+
useEffect(() => { setPage(0); }, [query, participant_email, timeframe, has_transcript, attendee_email]);
|
|
89
93
|
const result = rows.data;
|
|
90
94
|
|
|
91
95
|
return (
|
|
@@ -98,7 +102,19 @@ export function MeetingList({ linkParam, onSelect, initialView = "upcoming", pag
|
|
|
98
102
|
</Button>
|
|
99
103
|
))}
|
|
100
104
|
</div>
|
|
101
|
-
{view === "recorded" ? <Input aria-label="Search meetings" className="w-56" placeholder="Search recorded meetings" value={search} onChange={(e) =>
|
|
105
|
+
{view === "recorded" ? <Input aria-label="Search meetings" className="w-56" placeholder="Search recorded meetings" value={search} onChange={(e) => uf.set("search", e.target.value)} /> : null}
|
|
106
|
+
</div>
|
|
107
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
108
|
+
{view === "recorded" ? (
|
|
109
|
+
<>
|
|
110
|
+
<TextFilter label="participant email" placeholder="Participant email" type="email" value={uf.draft.participant_email} onChange={(v) => uf.set("participant_email", v)} className="w-52" />
|
|
111
|
+
<SelectFilter label="timeframe" any="Past (default)" value={uf.draft.timeframe} onChange={(v) => uf.set("timeframe", v)} options={[{ value: "upcoming", label: "Upcoming" }, { value: "all", label: "All" }]} />
|
|
112
|
+
<SelectFilter label="transcript" any="With or without transcript" value={uf.draft.has_transcript} onChange={(v) => uf.set("has_transcript", v)} options={[{ value: "yes", label: "Has a transcript" }, { value: "no", label: "No transcript" }]} />
|
|
113
|
+
</>
|
|
114
|
+
) : (
|
|
115
|
+
<TextFilter label="attendee email" placeholder="Attendee email" type="email" value={uf.draft.attendee_email} onChange={(v) => uf.set("attendee_email", v)} className="w-52" />
|
|
116
|
+
)}
|
|
117
|
+
{uf.active ? <Button size="sm" variant="ghost" onClick={uf.clear}>Clear filters</Button> : null}
|
|
102
118
|
</div>
|
|
103
119
|
|
|
104
120
|
{rows.error ? (
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RecordMeetingsTab: the meetings booked with one contact, past and upcoming, newest first, each opening in the meetings
|
|
5
|
+
* screen. Use it as an `extraDetailTabs` entry of ContactsScreen:
|
|
6
|
+
* { id: "meetings", label: "Meetings", render: (c) => <RecordMeetingsTab contactId={c.id} onOpenMeeting={(sel) => router.push(`/meetings?sel=${sel}`)} /> }
|
|
7
|
+
*
|
|
8
|
+
* SDK operations, in order:
|
|
9
|
+
* 1. getContactMeetings GET /contacts/{id}/meetings (read; booking events, the API answers the 50 most recent)
|
|
10
|
+
* Nothing here is billable, external or destructive. Book a new meeting from the scheduling screen.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Skeleton } from "@graph8/sdk/ui";
|
|
14
|
+
import { CalendarDays } from "lucide-react";
|
|
15
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
16
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
17
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
18
|
+
import type { Tone } from "@/g8/components/screen/status-pill";
|
|
19
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
20
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
21
|
+
import { listOf } from "../lib/appointments-types";
|
|
22
|
+
|
|
23
|
+
/** One row of GET /contacts/{id}/meetings: a booking event. */
|
|
24
|
+
interface BookingEvent { event?: string | null; timestamp?: string | null; booking_id?: string | null; title?: string | null; start_time?: string | null; end_time?: string | null; attendees?: unknown }
|
|
25
|
+
|
|
26
|
+
/** The server returns at most this many events, newest first. */
|
|
27
|
+
export const MEETINGS_TAB_API_LIMIT = 50;
|
|
28
|
+
|
|
29
|
+
const EVENT: Record<string, { label: string; tone: Tone }> = {
|
|
30
|
+
"appointments.booking_created": { label: "Booked", tone: "info" },
|
|
31
|
+
"appointments.booking_rescheduled": { label: "Rescheduled", tone: "warning" },
|
|
32
|
+
"appointments.booking_cancelled": { label: "Cancelled", tone: "negative" },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function when(iso: string | null | undefined): string {
|
|
36
|
+
const d = iso ? new Date(iso) : null;
|
|
37
|
+
return d && !Number.isNaN(d.getTime()) ? d.toLocaleString(undefined, { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" }) : "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function attendeeNames(a: unknown): string {
|
|
41
|
+
if (!Array.isArray(a)) return "";
|
|
42
|
+
return a.map((x) => (typeof x === "string" ? x : ((x as { name?: string; email?: string } | null)?.name || (x as { email?: string } | null)?.email || ""))).filter(Boolean).join(", ");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RecordMeetingsTabProps {
|
|
46
|
+
contactId: number;
|
|
47
|
+
/** Route to the booking in your meetings screen; receives the `sel` value ("booking:<id>"). Without it the rows link to `${meetingsPath}?sel=booking:<id>`. */
|
|
48
|
+
onOpenMeeting?: (selection: string) => void;
|
|
49
|
+
/** Path of your meetings screen for the plain links. Default "/meetings". */
|
|
50
|
+
meetingsPath?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function RecordMeetingsTab({ contactId, onOpenMeeting, meetingsPath = "/meetings" }: RecordMeetingsTabProps) {
|
|
54
|
+
const q = useG8Query(async (signal) => listOf<BookingEvent>(await g8Get(`/contacts/${contactId}/meetings`, undefined, { signal }), "items"), [contactId]);
|
|
55
|
+
if (q.error) return <ApiError error={q.error} title="Could not load meetings" onRetry={() => void q.refetch()} />;
|
|
56
|
+
if (!q.data) return <Skeleton className="h-24 w-full" aria-label="Loading meetings" />;
|
|
57
|
+
if (q.data.length === 0) return <EmptyState compact icon={<CalendarDays className="size-8" />} title="No meetings yet" description="Meetings booked with this contact show up here." />;
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-2">
|
|
60
|
+
<ul className="divide-y rounded-md border" aria-label="Meetings">
|
|
61
|
+
{q.data.map((m, i) => {
|
|
62
|
+
const kind = EVENT[m.event ?? ""] ?? { label: "Booking", tone: "neutral" as Tone };
|
|
63
|
+
const selection = m.booking_id ? `booking:${m.booking_id}` : null;
|
|
64
|
+
const title = m.title || "Meeting";
|
|
65
|
+
const detail = [when(m.start_time ?? m.timestamp), attendeeNames(m.attendees)].filter(Boolean).join(" · ");
|
|
66
|
+
return (
|
|
67
|
+
<li key={`${m.booking_id ?? "event"}-${m.event}-${i}`} className="flex items-center justify-between gap-3 p-3 text-sm">
|
|
68
|
+
<div className="min-w-0">
|
|
69
|
+
{selection ? (
|
|
70
|
+
<a
|
|
71
|
+
href={`${meetingsPath}?sel=${encodeURIComponent(selection)}`}
|
|
72
|
+
className="block truncate font-medium hover:underline"
|
|
73
|
+
onClick={(e) => {
|
|
74
|
+
if (!onOpenMeeting || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
onOpenMeeting(selection);
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{title}
|
|
80
|
+
</a>
|
|
81
|
+
) : <span className="font-medium">{title}</span>}
|
|
82
|
+
{detail ? <p className="truncate text-xs text-muted-foreground">{detail}</p> : null}
|
|
83
|
+
</div>
|
|
84
|
+
<StatusPill tone={kind.tone} dot>{kind.label}</StatusPill>
|
|
85
|
+
</li>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
</ul>
|
|
89
|
+
{q.data.length >= MEETINGS_TAB_API_LIMIT ? <p role="note" className="text-xs text-muted-foreground">Showing the {MEETINGS_TAB_API_LIMIT} most recent booking events. Older ones are in the meetings screen.</p> : null}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -18,7 +18,7 @@ deep-linkable (`?tab=campaigns|profile|personas`, `?id=` the record, `?dtab=` th
|
|
|
18
18
|
- **Company profile tab**: the profile plus ICPs; `IcpDetail` shows every field and edits, pins or archives.
|
|
19
19
|
- **Personas tab**: `PersonaDetail` shows every field, the campaigns that target it, and edits, pins or archives.
|
|
20
20
|
|
|
21
|
-
Slot props: `toolbarActions`, `renderDetailActions(campaign)`, `extraDetailTabs`, `websiteUrl`. Prerequisites: a
|
|
21
|
+
Slot props: `toolbarActions`, `renderDetailActions(campaign)`, `extraDetailTabs`, `websiteUrl`. `CampaignLinksTab` (`components/campaign-links.tsx`) is a ready `extraDetailTabs` entry: the campaign's landing pages, workflows and newsletters, each opening in your app (`onOpenLandingPage`, `onOpenWorkflow`, `onOpenNewsletter`), with link and unlink for landing pages and newsletters and unlink only for workflows (the API has no link operation for them; a newsletter that belongs to another campaign is not offered, because linking moves it). Prerequisites: a
|
|
22
22
|
company profile with the real website, an active mailbox, a contact list, and credits for AI generation.
|
|
23
23
|
`IcpEditor` and `PersonaEditor` gained optional `onOpen` and `refreshKey`; `CampaignAssets` optional
|
|
24
24
|
`documentId` and `onDocumentChange`.
|
|
@@ -42,16 +42,25 @@
|
|
|
42
42
|
"get_persona_personas__persona_id__get",
|
|
43
43
|
"get_safe_to_send_deliverability_safe_to_send_get",
|
|
44
44
|
"launch_campaign_campaigns__campaign_id__launch_post",
|
|
45
|
+
"link_landing_page_to_campaign_campaigns__campaign_id__landing_pages_post",
|
|
46
|
+
"link_newsletter_campaign_newsletters__newsletter_id__campaign_put",
|
|
45
47
|
"list_campaign_documents_campaigns__campaign_id__documents_get",
|
|
48
|
+
"list_campaign_landing_pages_campaigns__campaign_id__landing_pages_get",
|
|
49
|
+
"list_campaign_workflows_campaigns__campaign_id__workflows_get",
|
|
46
50
|
"list_campaigns_campaigns_get",
|
|
47
51
|
"list_campaigns_for_picker_campaigns_picker_get",
|
|
48
52
|
"list_icps_icps_get",
|
|
53
|
+
"list_landing_pages_landing_pages_get",
|
|
49
54
|
"list_mailboxes_mailboxes_get",
|
|
55
|
+
"list_newsletters_newsletters_get",
|
|
50
56
|
"list_personas_personas_get",
|
|
51
57
|
"pin_icp_icps__icp_id__pin_post",
|
|
52
58
|
"pin_persona_personas__persona_id__pin_post",
|
|
53
59
|
"recommend_campaign_personas_campaigns_recommend_personas_post",
|
|
54
60
|
"regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post",
|
|
61
|
+
"unlink_campaign_workflow_campaigns__campaign_id__workflows__link_id__delete",
|
|
62
|
+
"unlink_landing_page_from_campaign_campaigns__campaign_id__landing_pages__landing_page_id__delete",
|
|
63
|
+
"unlink_newsletter_campaign_newsletters__newsletter_id__campaign_delete",
|
|
55
64
|
"unpin_icp_icps__icp_id__unpin_post",
|
|
56
65
|
"unpin_persona_personas__persona_id__unpin_post",
|
|
57
66
|
"update_campaign_campaigns__campaign_id__patch",
|
|
@@ -307,6 +316,27 @@
|
|
|
307
316
|
"write",
|
|
308
317
|
"destructive"
|
|
309
318
|
]
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"name": "CampaignLinksTab",
|
|
322
|
+
"file": "components/campaign-links.tsx",
|
|
323
|
+
"purpose": "The landing pages, workflows and newsletters tied to one campaign: each row opens the record in your app, landing pages and newsletters can be linked and unlinked (workflows only unlinked, the API has no link operation). Use as an extraDetailTabs entry of CampaignsScreen.",
|
|
324
|
+
"operations": [
|
|
325
|
+
"list_campaign_landing_pages_campaigns__campaign_id__landing_pages_get",
|
|
326
|
+
"link_landing_page_to_campaign_campaigns__campaign_id__landing_pages_post",
|
|
327
|
+
"unlink_landing_page_from_campaign_campaigns__campaign_id__landing_pages__landing_page_id__delete",
|
|
328
|
+
"list_landing_pages_landing_pages_get",
|
|
329
|
+
"list_campaign_workflows_campaigns__campaign_id__workflows_get",
|
|
330
|
+
"unlink_campaign_workflow_campaigns__campaign_id__workflows__link_id__delete",
|
|
331
|
+
"list_newsletters_newsletters_get",
|
|
332
|
+
"link_newsletter_campaign_newsletters__newsletter_id__campaign_put",
|
|
333
|
+
"unlink_newsletter_campaign_newsletters__newsletter_id__campaign_delete"
|
|
334
|
+
],
|
|
335
|
+
"tiers": [
|
|
336
|
+
"read",
|
|
337
|
+
"write",
|
|
338
|
+
"destructive"
|
|
339
|
+
]
|
|
310
340
|
}
|
|
311
341
|
],
|
|
312
342
|
"hooks": [
|
|
@@ -378,7 +408,10 @@
|
|
|
378
408
|
"campaigns:launch",
|
|
379
409
|
"campaigns:read",
|
|
380
410
|
"campaigns:run",
|
|
381
|
-
"campaigns:write"
|
|
411
|
+
"campaigns:write",
|
|
412
|
+
"newsletter:delete",
|
|
413
|
+
"newsletter:read",
|
|
414
|
+
"newsletter:write"
|
|
382
415
|
],
|
|
383
416
|
"defaults": [
|
|
384
417
|
{
|
|
@@ -86,7 +86,7 @@ function Editor({ campaignId, doc, onChanged, onClosed }: { campaignId: string;
|
|
|
86
86
|
{full.data ? (
|
|
87
87
|
<>
|
|
88
88
|
<Textarea aria-label="Document content" className="min-h-48 font-mono text-sm" value={body} onChange={(e) => setContent(e.target.value)} />
|
|
89
|
-
{tooBig ? <p role="alert" className="text-sm text-
|
|
89
|
+
{tooBig ? <p role="alert" className="text-sm text-error-foreground">This document is over 5 MiB and cannot be saved through the proxy.</p> : null}
|
|
90
90
|
<div className="flex gap-2">
|
|
91
91
|
<Button disabled={content === null || tooBig || poll.polling} onClick={() => setPending({ kind: "save" })}>Save changes</Button>
|
|
92
92
|
<Button variant="outline" disabled={poll.polling} onClick={() => setPending({ kind: "regenerate" })}>Rewrite with AI</Button>
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import { useEffect, useState } from "react";
|
|
35
35
|
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
36
36
|
import { ApiError } from "@/g8/components/api-error";
|
|
37
|
+
import { FirstNotice } from "@/g8/components/paging-notices";
|
|
37
38
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
38
39
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
39
40
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
@@ -55,7 +56,7 @@ interface Mailbox { id: string | number; email?: string | null; connection_statu
|
|
|
55
56
|
|
|
56
57
|
function Check({ ok, children, detail }: { ok: boolean | undefined; children: string; detail?: string }) {
|
|
57
58
|
return (
|
|
58
|
-
<li className={ok ? "text-
|
|
59
|
+
<li className={ok ? "text-positive-foreground" : "text-warning-foreground"}>
|
|
59
60
|
<span aria-hidden>{ok ? "Ready: " : "Missing: "}</span>{children}
|
|
60
61
|
{!ok && detail ? <span className="block pl-4 text-xs text-muted-foreground">{detail}</span> : null}
|
|
61
62
|
</li>
|
|
@@ -64,11 +65,14 @@ function Check({ ok, children, detail }: { ok: boolean | undefined; children: st
|
|
|
64
65
|
|
|
65
66
|
const day = (d: Date) => d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
|
|
66
67
|
|
|
68
|
+
/** The mailboxes API has no paging: a full read is announced under the sender list. */
|
|
69
|
+
const MAILBOX_LIMIT = 200;
|
|
70
|
+
|
|
67
71
|
export function CampaignLaunchStep({ campaignId, audienceSize, onLaunched, className }: CampaignLaunchStepProps) {
|
|
68
72
|
const brand = useBrand();
|
|
69
73
|
const campaign = useG8Query((signal) => g8Get(`/campaigns/${campaignId}/full`, undefined, { signal }), [campaignId]);
|
|
70
74
|
const generation = useG8Query((signal) => g8Get(`/campaigns/${campaignId}/status`, undefined, { signal }), [campaignId]);
|
|
71
|
-
const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit:
|
|
75
|
+
const mailboxes = useG8Query((signal) => g8Get("/mailboxes", { limit: MAILBOX_LIMIT }, { signal }), []);
|
|
72
76
|
const verdict = useG8Query((signal) => g8Get("/deliverability/safe-to-send", undefined, { signal }), []);
|
|
73
77
|
const [chosen, setChosen] = useState<Set<string> | null>(null);
|
|
74
78
|
const [confirm, setConfirm] = useState(false);
|
|
@@ -136,20 +140,20 @@ export function CampaignLaunchStep({ campaignId, audienceSize, onLaunched, class
|
|
|
136
140
|
if (result) {
|
|
137
141
|
const channels = (result.channels_attached as Array<{ email?: string }> | undefined) ?? [];
|
|
138
142
|
return (
|
|
139
|
-
<section aria-label="Launch result" className={`space-y-2 rounded-md border border-
|
|
143
|
+
<section aria-label="Launch result" className={`space-y-2 rounded-md border border-positive-foreground/25 bg-positive p-4 ${className ?? ""}`}>
|
|
140
144
|
<h3 className="font-medium">Campaign {String(result.status ?? "launched")}</h3>
|
|
141
145
|
<p className="text-sm">{String(result.message ?? "")}</p>
|
|
142
146
|
<p className="text-sm text-muted-foreground">
|
|
143
147
|
Sequence {String(result.sequence_id ?? "")} with {String(result.steps_created ?? 0)} steps, sending from {channels.map((m) => m.email).join(", ") || "the chosen mailboxes"}.
|
|
144
148
|
</p>
|
|
145
|
-
{Array.isArray(result.warnings) && result.warnings.length > 0 ? <ul className="list-disc pl-5 text-sm text-
|
|
149
|
+
{Array.isArray(result.warnings) && result.warnings.length > 0 ? <ul className="list-disc pl-5 text-sm text-warning-foreground">{result.warnings.map((w) => <li key={String(w)}>{String(w)}</li>)}</ul> : null}
|
|
146
150
|
</section>
|
|
147
151
|
);
|
|
148
152
|
}
|
|
149
153
|
return (
|
|
150
154
|
<section aria-label="Launch" className={`space-y-3 ${className ?? ""}`}>
|
|
151
155
|
<h3 className="font-medium">Ready to launch {String(c.name ?? "")}</h3>
|
|
152
|
-
{alreadyLaunched ? <p role="status" className="rounded bg-
|
|
156
|
+
{alreadyLaunched ? <p role="status" className="rounded bg-positive p-2 text-sm text-positive-foreground">This campaign has already been launched. Manage it in the sequencer.</p> : null}
|
|
153
157
|
<ul className="space-y-1 text-sm">
|
|
154
158
|
<Check ok={hasAudience}>An audience list is attached</Check>
|
|
155
159
|
<Check ok={hasSteps}>The sequence has at least one step</Check>
|
|
@@ -157,23 +161,23 @@ export function CampaignLaunchStep({ campaignId, audienceSize, onLaunched, class
|
|
|
157
161
|
<Check ok={senders.size > 0}>At least one active sending mailbox is selected</Check>
|
|
158
162
|
</ul>
|
|
159
163
|
{genError && !copy.ready ? (
|
|
160
|
-
<p role="alert" className="rounded bg-
|
|
164
|
+
<p role="alert" className="rounded bg-error p-2 text-sm text-error-foreground">Writing the campaign documents failed: {genError}</p>
|
|
161
165
|
) : null}
|
|
162
166
|
{copy.known && !copy.ready ? (
|
|
163
167
|
<div className="space-y-1">
|
|
164
168
|
{writeCopy.error ? <ApiError error={writeCopy.error} title="Could not write the copy" /> : null}
|
|
165
|
-
{copyPoll.done && !copyStateOf(copyPoll.status).ready && !writeCopy.loading ? <p role="status" className="text-sm text-
|
|
166
|
-
{!copyPoll.polling && !copyPoll.done && copyPoll.attempts > 0 ? <p role="status" className="text-sm text-
|
|
169
|
+
{copyPoll.done && !copyStateOf(copyPoll.status).ready && !writeCopy.loading ? <p role="status" className="text-sm text-warning-foreground">The copy did not finish. Try again, or write it in Campaign assets.</p> : null}
|
|
170
|
+
{!copyPoll.polling && !copyPoll.done && copyPoll.attempts > 0 ? <p role="status" className="text-sm text-warning-foreground">Still writing. Check back in a minute.</p> : null}
|
|
167
171
|
<Button variant="outline" loading={writeCopy.loading} disabled={writingCopy} onClick={() => setConfirmCopy(true)}>Write the email copy with AI</Button>
|
|
168
172
|
</div>
|
|
169
173
|
) : null}
|
|
170
174
|
{unsafe ? (
|
|
171
|
-
<p role="alert" className="rounded bg-
|
|
175
|
+
<p role="alert" className="rounded bg-warning p-2 text-sm text-warning-foreground">
|
|
172
176
|
Deliverability warning: {typeof v.reason === "string" ? v.reason : "sending is not recommended right now."}
|
|
173
177
|
</p>
|
|
174
178
|
) : null}
|
|
175
179
|
{held.length > 0 ? (
|
|
176
|
-
<div role="status" className="rounded bg-
|
|
180
|
+
<div role="status" className="rounded bg-warning p-2 text-sm text-warning-foreground">
|
|
177
181
|
<p>New mailboxes send nothing for their first {WARMUP_HOLD_DAYS} days while they warm up. The campaign goes live, but no email goes out from:</p>
|
|
178
182
|
<ul className="list-disc pl-5">{held.map(({ m, until }) => <li key={m.id}>{m.email ?? `Mailbox ${m.id}`} until {day(until as Date)}</li>)}</ul>
|
|
179
183
|
</div>
|
|
@@ -195,11 +199,12 @@ export function CampaignLaunchStep({ campaignId, audienceSize, onLaunched, class
|
|
|
195
199
|
{m.email ?? `Mailbox ${m.id}`}
|
|
196
200
|
</label>
|
|
197
201
|
))}
|
|
202
|
+
<FirstNotice shown={listOf<Mailbox>(mailboxes.data).length} limit={MAILBOX_LIMIT} noun="mailboxes" />
|
|
198
203
|
</fieldset>
|
|
199
204
|
{launch.error ? (
|
|
200
205
|
<div className="space-y-1">
|
|
201
206
|
<ApiError error={launch.error} title="The launch did not complete" />
|
|
202
|
-
<p className="text-sm text-
|
|
207
|
+
<p className="text-sm text-warning-foreground">Do not retry blindly. A sequence may already exist; check the campaign before launching again.</p>
|
|
203
208
|
</div>
|
|
204
209
|
) : null}
|
|
205
210
|
<Button disabled={!ready} loading={launch.loading} onClick={() => setConfirm(true)}>Launch campaign</Button>
|