@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
|
@@ -81,29 +81,29 @@ function Fleet() {
|
|
|
81
81
|
{safe === false ? "Not safe to send right now" : safe === true ? "Safe to send" : "Send status unknown"}
|
|
82
82
|
</p>
|
|
83
83
|
{verdict.error ? (
|
|
84
|
-
<p className="text-sm text-
|
|
84
|
+
<p className="text-sm text-warning-foreground">
|
|
85
85
|
The deliverability check failed, so sending is not confirmed safe. Retry before you send.
|
|
86
86
|
</p>
|
|
87
87
|
) : null}
|
|
88
|
-
{typeof v.reason === "string" ? <p className="text-sm text-
|
|
88
|
+
{typeof v.reason === "string" ? <p className="text-sm text-foreground">{v.reason}</p> : null}
|
|
89
89
|
</div>
|
|
90
90
|
<dl className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
|
91
91
|
{scalars(objectOf(summary.data))
|
|
92
92
|
.filter(([k]) => k !== "safe to send")
|
|
93
93
|
.map(([k, val]) => (
|
|
94
|
-
<div key={k} className="rounded border border-
|
|
95
|
-
<dt className="text-xs capitalize text-
|
|
96
|
-
<dd className="text-lg font-
|
|
94
|
+
<div key={k} className="rounded border border-border p-2">
|
|
95
|
+
<dt className="text-xs capitalize text-muted-foreground">{k}</dt>
|
|
96
|
+
<dd className="text-lg font-medium">{val}</dd>
|
|
97
97
|
</div>
|
|
98
98
|
))}
|
|
99
99
|
</dl>
|
|
100
100
|
{rows.length > 0 ? (
|
|
101
|
-
<ul className="divide-y rounded border border-
|
|
101
|
+
<ul className="divide-y rounded border border-border" aria-label="Degraded mailboxes">
|
|
102
102
|
{rows.map((r, i) => (
|
|
103
103
|
<li key={String(r.id ?? i)} className="flex items-center justify-between px-3 py-2 text-sm">
|
|
104
104
|
<span>
|
|
105
105
|
{r.email ?? r.mailbox_email ?? `Mailbox ${r.id}`}
|
|
106
|
-
<span className="ml-2 text-xs text-
|
|
106
|
+
<span className="ml-2 text-xs text-muted-foreground">{r.reason ?? r.reasons?.join(", ")}</span>
|
|
107
107
|
</span>
|
|
108
108
|
<span className={`rounded px-2 py-0.5 text-xs ${statusClass(r.severity ?? r.status)}`}>
|
|
109
109
|
{r.severity ?? r.status ?? "watch"}
|
|
@@ -134,7 +134,7 @@ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
|
|
|
134
134
|
const w = warm.data?.data;
|
|
135
135
|
const a = w?.analytics;
|
|
136
136
|
return (
|
|
137
|
-
<section className="space-y-3 rounded border border-
|
|
137
|
+
<section className="space-y-3 rounded border border-border p-3" aria-label="Warmup">
|
|
138
138
|
<div className="flex items-center justify-between">
|
|
139
139
|
<h3 className="font-medium">
|
|
140
140
|
Warmup {w?.warmup_enabled ? "is on" : "is off"}
|
|
@@ -155,7 +155,7 @@ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
|
|
|
155
155
|
</div>
|
|
156
156
|
</div>
|
|
157
157
|
{w?.error ? (
|
|
158
|
-
<p role="alert" className="text-sm text-
|
|
158
|
+
<p role="alert" className="text-sm text-warning-foreground">
|
|
159
159
|
The warmup provider did not answer: {w.error}
|
|
160
160
|
</p>
|
|
161
161
|
) : null}
|
|
@@ -163,22 +163,22 @@ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
|
|
|
163
163
|
<>
|
|
164
164
|
<dl className="grid grid-cols-2 gap-2 sm:grid-cols-4 text-sm">
|
|
165
165
|
<div>
|
|
166
|
-
<dt className="text-xs text-
|
|
167
|
-
<dd className="font-
|
|
166
|
+
<dt className="text-xs text-muted-foreground">Sent</dt>
|
|
167
|
+
<dd className="font-medium">{a.total_sent ?? 0}</dd>
|
|
168
168
|
</div>
|
|
169
169
|
<div>
|
|
170
|
-
<dt className="text-xs text-
|
|
171
|
-
<dd className="font-
|
|
170
|
+
<dt className="text-xs text-muted-foreground">Inbox rate</dt>
|
|
171
|
+
<dd className="font-medium">
|
|
172
172
|
{a.inbox_rate != null ? `${Math.round(a.inbox_rate <= 1 ? a.inbox_rate * 100 : a.inbox_rate)}%` : "-"}
|
|
173
173
|
</dd>
|
|
174
174
|
</div>
|
|
175
175
|
<div>
|
|
176
|
-
<dt className="text-xs text-
|
|
177
|
-
<dd className="font-
|
|
176
|
+
<dt className="text-xs text-muted-foreground">Spam</dt>
|
|
177
|
+
<dd className="font-medium">{a.total_spam ?? 0}</dd>
|
|
178
178
|
</div>
|
|
179
179
|
<div>
|
|
180
|
-
<dt className="text-xs text-
|
|
181
|
-
<dd className="font-
|
|
180
|
+
<dt className="text-xs text-muted-foreground">Reputation</dt>
|
|
181
|
+
<dd className="font-medium">{a.reputation ?? "-"}</dd>
|
|
182
182
|
</div>
|
|
183
183
|
</dl>
|
|
184
184
|
{(a.daily_stats ?? []).length > 0 ? (
|
|
@@ -187,7 +187,7 @@ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
|
|
|
187
187
|
<span
|
|
188
188
|
key={d.date}
|
|
189
189
|
title={`${d.date}: ${d.sent} sent, ${d.inbox} inbox`}
|
|
190
|
-
className="w-2 rounded-t bg-
|
|
190
|
+
className="w-2 rounded-t bg-foreground/70"
|
|
191
191
|
style={{ height: `${Math.max(4, Math.min(100, d.sent * 4))}%` }}
|
|
192
192
|
/>
|
|
193
193
|
))}
|
|
@@ -195,7 +195,7 @@ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
|
|
|
195
195
|
) : null}
|
|
196
196
|
</>
|
|
197
197
|
) : (
|
|
198
|
-
<p className="text-sm text-
|
|
198
|
+
<p className="text-sm text-muted-foreground">No warmup analytics yet.</p>
|
|
199
199
|
)}
|
|
200
200
|
<Button size="sm" variant="outline" onClick={() => setConfirm(true)}>
|
|
201
201
|
Run placement test
|
|
@@ -203,7 +203,7 @@ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
|
|
|
203
203
|
{toggle.error ? <ApiError error={toggle.error} onRetry={toggle.reset} /> : null}
|
|
204
204
|
{placement.error ? <ApiError error={placement.error} onRetry={placement.reset} /> : null}
|
|
205
205
|
{note ? (
|
|
206
|
-
<p role="status" className="text-sm text-
|
|
206
|
+
<p role="status" className="text-sm text-positive-foreground">
|
|
207
207
|
{note}
|
|
208
208
|
</p>
|
|
209
209
|
) : null}
|
|
@@ -53,10 +53,10 @@ export function objectOf(envelope: unknown): Record<string, unknown> {
|
|
|
53
53
|
/** Tailwind classes for a connection or health state. */
|
|
54
54
|
export function statusClass(status: string | null | undefined): string {
|
|
55
55
|
const s = (status ?? "").toLowerCase();
|
|
56
|
-
if (["active", "connected", "healthy", "completed", "receiving"].includes(s)) return "bg-
|
|
57
|
-
if (["paused", "watch", "warming", "pending", "connecting", "provisioning"].includes(s)) return "bg-
|
|
58
|
-
if (s) return "bg-
|
|
59
|
-
return "bg-
|
|
56
|
+
if (["active", "connected", "healthy", "completed", "receiving"].includes(s)) return "bg-positive text-positive-foreground";
|
|
57
|
+
if (["paused", "watch", "warming", "pending", "connecting", "provisioning"].includes(s)) return "bg-warning text-warning-foreground";
|
|
58
|
+
if (s) return "bg-error text-error-foreground";
|
|
59
|
+
return "bg-muted text-foreground";
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/** Scalar fields of an open-shaped object, for a summary grid. */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useProviderDomains: every sending domain of the workspace. The domains API pages (`page`, `limit`), so this reads page
|
|
5
|
+
* after page (100 per page, up to 20 pages) instead of one capped call; `truncated` says the page cap was reached.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations: GET /provider-domains?page&limit (providerDomains.listProviderDomains) read
|
|
8
|
+
*/
|
|
9
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
10
|
+
import { useAllPages } from "@/g8/hooks/use-all-pages";
|
|
11
|
+
import { hasNextOf } from "@/g8/lib/paging";
|
|
12
|
+
import { listOf } from "./mailboxes-utils";
|
|
13
|
+
import type { DomainRow } from "./mailboxes-utils";
|
|
14
|
+
|
|
15
|
+
export const DOMAIN_PAGE = 100;
|
|
16
|
+
|
|
17
|
+
export function useProviderDomains(deps: readonly unknown[] = [], enabled = true) {
|
|
18
|
+
return useAllPages<DomainRow>(async (page, signal) => {
|
|
19
|
+
const env = await g8Get("/provider-domains", { page, limit: DOMAIN_PAGE }, { signal });
|
|
20
|
+
const rows = listOf<DomainRow>(env);
|
|
21
|
+
return { rows, hasNext: hasNextOf(env, DOMAIN_PAGE, rows.length) };
|
|
22
|
+
}, deps, { enabled });
|
|
23
|
+
}
|
|
@@ -133,3 +133,15 @@ Tiers: `read` and `write` need no confirmation; `billable`, `external` and `dest
|
|
|
133
133
|
| `newsletter.unsubscribeSubscriber` | `PATCH /api/v1/newsletters/{newsletter_id}/subscribers/{subscriber_id}/unsubscribe` | external | `newsletter:run` | SubscriberTable |
|
|
134
134
|
| `newsletter.deleteSubscriber` | `DELETE /api/v1/newsletters/{newsletter_id}/subscribers/{subscriber_id}` | destructive | `newsletter:delete` | SubscriberTable |
|
|
135
135
|
| `newsletter.getIssueAnalyticsKpis` | `GET /api/v1/newsletters/{newsletter_id}/issues/{issue_id}/analytics/kpis` | read | `newsletter:read` | NewsletterStats |
|
|
136
|
+
|
|
137
|
+
## Depth added (C7c)
|
|
138
|
+
|
|
139
|
+
New tabs on `NewsletterScreen`: **Templates**, **Lists**, **Settings**; issues gain a **History** tab; a subscriber shows **Activity on an issue**.
|
|
140
|
+
|
|
141
|
+
- `NewsletterSettings`: rename and goal, sender and opt-in policy (only changed fields are sent), sending domain (start verification is an external confirm, DNS records are listed, status check), resume a paused newsletter (external confirm), mark completed.
|
|
142
|
+
- `NewsletterListSyncs`: turn a list sync on (the consent statement is shown and sent), run it now, stop it (destructive). Lists page with Load more.
|
|
143
|
+
- `NewsletterTemplates`: use a template for a draft issue, approve or archive, rename, delete (destructive), write one with AI (billable confirm).
|
|
144
|
+
- `IssueVersions`: version history with restore (drafts only, second click) and Duplicate (external confirm).
|
|
145
|
+
- `SubscriberTimeline`: delivered, opened and clicked for one subscriber on one sent issue.
|
|
146
|
+
|
|
147
|
+
Not covered (and why): `rewriteIssueText`, `setIssueText`, `setIssueMediaEdit`, `setIssueButtonAction` and the template equivalents need element ids the HTML editor does not expose; `linkNewsletterCampaign` needs a campaign picker (owned by the pickers PR); `cloneTemplate`, `createTemplateFromLayout` and `batchTemplateGenerationStatus` are template-gallery flows; `getNewsletterSubscriberVerification` status is not shown (verify still runs and reports through the existing flow); `getNewsletterIssueConversation` is the hosted editor's chat. The subscriber and list pickers keep their existing paging.
|
|
@@ -18,28 +18,51 @@
|
|
|
18
18
|
"activate_newsletter_newsletters__newsletter_id__activate_post",
|
|
19
19
|
"add_subscriber_newsletters__newsletter_id__subscribers_post",
|
|
20
20
|
"cancel_send_newsletters__newsletter_id__issues__issue_id__send_cancel_post",
|
|
21
|
+
"complete_newsletter_newsletters__newsletter_id__complete_post",
|
|
21
22
|
"create_issue_newsletters__newsletter_id__issues_post",
|
|
22
23
|
"create_newsletter_newsletters_post",
|
|
24
|
+
"create_template_from_prompt_newsletters_templates_custom_post",
|
|
23
25
|
"delete_issue_newsletters__newsletter_id__issues__issue_id__delete",
|
|
26
|
+
"delete_newsletter_template_newsletters_templates__template_id__delete",
|
|
24
27
|
"delete_subscriber_newsletters__newsletter_id__subscribers__subscriber_id__delete",
|
|
28
|
+
"disable_newsletter_list_sync_newsletters__newsletter_id__list_syncs__audience_list_id__delete",
|
|
29
|
+
"duplicate_issue_newsletters__newsletter_id__issues__issue_id__duplicate_post",
|
|
30
|
+
"enable_newsletter_list_sync_newsletters__newsletter_id__list_syncs_post",
|
|
25
31
|
"generate_newsletter_issue_newsletters__newsletter_id__issues__issue_id__generate_post",
|
|
26
32
|
"get_issue_analytics_kpis_newsletters__newsletter_id__issues__issue_id__analytics_kpis_get",
|
|
27
33
|
"get_issue_generation_status_newsletters__newsletter_id__issues__issue_id__generation_status_get",
|
|
28
34
|
"get_issue_newsletters__newsletter_id__issues__issue_id__get",
|
|
35
|
+
"get_newsletter_newsletters__newsletter_id__get",
|
|
36
|
+
"get_newsletter_settings_newsletters__newsletter_id__settings_get",
|
|
29
37
|
"get_send_status_newsletters__newsletter_id__issues__issue_id__send_get",
|
|
38
|
+
"get_subscriber_timeline_newsletters__newsletter_id__issues__issue_id__analytics_subscribers__subscriber_id__timeline_get",
|
|
30
39
|
"import_subscribers_from_audience_newsletters__newsletter_id__subscribers_import_audience_post",
|
|
31
40
|
"list_issues_newsletters__newsletter_id__issues_get",
|
|
32
41
|
"list_lists_lists_get",
|
|
42
|
+
"list_newsletter_issue_versions_newsletters__newsletter_id__issues__issue_id__versions_get",
|
|
43
|
+
"list_newsletter_list_syncs_newsletters__newsletter_id__list_syncs_get",
|
|
33
44
|
"list_newsletters_newsletters_get",
|
|
34
45
|
"list_subscribers_newsletters__newsletter_id__subscribers_get",
|
|
46
|
+
"list_templates_newsletters_templates_get",
|
|
47
|
+
"newsletter_domain_status_newsletters__newsletter_id__settings_domain_status_get",
|
|
35
48
|
"pause_newsletter_newsletters__newsletter_id__pause_post",
|
|
36
49
|
"pause_send_newsletters__newsletter_id__issues__issue_id__send_pause_post",
|
|
37
50
|
"preview_newsletter_subscriber_verification_newsletters__newsletter_id__subscribers_verify_preview_post",
|
|
51
|
+
"restore_newsletter_issue_version_newsletters__newsletter_id__issues__issue_id__versions__version_num__restore_post",
|
|
52
|
+
"resume_newsletter_newsletters__newsletter_id__resume_post",
|
|
38
53
|
"resume_send_newsletters__newsletter_id__issues__issue_id__send_resume_post",
|
|
54
|
+
"run_newsletter_list_sync_newsletters__newsletter_id__list_syncs__audience_list_id__run_post",
|
|
39
55
|
"send_issue_newsletters__newsletter_id__issues__issue_id__send_post",
|
|
40
56
|
"send_test_email_newsletters__newsletter_id__issues__issue_id__send_test_post",
|
|
57
|
+
"set_template_approval_newsletters_templates__template_id__approval_post",
|
|
58
|
+
"template_generation_status_newsletters_templates__template_id__generation_status_get",
|
|
41
59
|
"unsubscribe_subscriber_newsletters__newsletter_id__subscribers__subscriber_id__unsubscribe_patch",
|
|
42
60
|
"update_issue_newsletters__newsletter_id__issues__issue_id__put",
|
|
61
|
+
"update_newsletter_newsletters__newsletter_id__patch",
|
|
62
|
+
"update_newsletter_settings_newsletters__newsletter_id__settings_put",
|
|
63
|
+
"update_newsletter_template_newsletters_templates__template_id__patch",
|
|
64
|
+
"use_template_newsletters_templates__template_id__use_post",
|
|
65
|
+
"verify_newsletter_domain_newsletters__newsletter_id__settings_verify_domain_post",
|
|
43
66
|
"verify_newsletter_subscribers_newsletters__newsletter_id__subscribers_verify_post"
|
|
44
67
|
],
|
|
45
68
|
"components": [
|
|
@@ -155,6 +178,96 @@
|
|
|
155
178
|
"tiers": [
|
|
156
179
|
"read"
|
|
157
180
|
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "IssueVersions",
|
|
184
|
+
"file": "components/issue-versions.tsx",
|
|
185
|
+
"purpose": "Version history of one issue with restore, and duplicate into a new draft.",
|
|
186
|
+
"operations": [
|
|
187
|
+
"list_newsletter_issue_versions_newsletters__newsletter_id__issues__issue_id__versions_get",
|
|
188
|
+
"restore_newsletter_issue_version_newsletters__newsletter_id__issues__issue_id__versions__version_num__restore_post",
|
|
189
|
+
"duplicate_issue_newsletters__newsletter_id__issues__issue_id__duplicate_post"
|
|
190
|
+
],
|
|
191
|
+
"tiers": [
|
|
192
|
+
"read",
|
|
193
|
+
"write",
|
|
194
|
+
"external"
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "MoreRows",
|
|
199
|
+
"file": "components/more-rows.tsx",
|
|
200
|
+
"purpose": "Footer for paged lists: how many rows are shown and a Load more button.",
|
|
201
|
+
"operations": [],
|
|
202
|
+
"tiers": []
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "NewsletterListSyncs",
|
|
206
|
+
"file": "components/newsletter-list-syncs.tsx",
|
|
207
|
+
"purpose": "Keep subscribers in step with lists: turn a sync on (consent confirmed), run it, turn it off.",
|
|
208
|
+
"operations": [
|
|
209
|
+
"list_newsletter_list_syncs_newsletters__newsletter_id__list_syncs_get",
|
|
210
|
+
"list_lists_lists_get",
|
|
211
|
+
"enable_newsletter_list_sync_newsletters__newsletter_id__list_syncs_post",
|
|
212
|
+
"run_newsletter_list_sync_newsletters__newsletter_id__list_syncs__audience_list_id__run_post",
|
|
213
|
+
"disable_newsletter_list_sync_newsletters__newsletter_id__list_syncs__audience_list_id__delete"
|
|
214
|
+
],
|
|
215
|
+
"tiers": [
|
|
216
|
+
"read",
|
|
217
|
+
"write",
|
|
218
|
+
"destructive"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "NewsletterSettings",
|
|
223
|
+
"file": "components/newsletter-settings.tsx",
|
|
224
|
+
"purpose": "The newsletter itself: name and goal, sender and opt-in, sending domain verification, resume and complete.",
|
|
225
|
+
"operations": [
|
|
226
|
+
"update_newsletter_newsletters__newsletter_id__patch",
|
|
227
|
+
"update_newsletter_settings_newsletters__newsletter_id__settings_put",
|
|
228
|
+
"newsletter_domain_status_newsletters__newsletter_id__settings_domain_status_get",
|
|
229
|
+
"verify_newsletter_domain_newsletters__newsletter_id__settings_verify_domain_post",
|
|
230
|
+
"complete_newsletter_newsletters__newsletter_id__complete_post",
|
|
231
|
+
"resume_newsletter_newsletters__newsletter_id__resume_post",
|
|
232
|
+
"get_newsletter_settings_newsletters__newsletter_id__settings_get",
|
|
233
|
+
"get_newsletter_newsletters__newsletter_id__get"
|
|
234
|
+
],
|
|
235
|
+
"tiers": [
|
|
236
|
+
"read",
|
|
237
|
+
"write",
|
|
238
|
+
"external"
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"name": "NewsletterTemplates",
|
|
243
|
+
"file": "components/newsletter-templates.tsx",
|
|
244
|
+
"purpose": "Saved issue templates: start a draft from one, approve, rename, delete, or write one with AI.",
|
|
245
|
+
"operations": [
|
|
246
|
+
"update_newsletter_template_newsletters_templates__template_id__patch",
|
|
247
|
+
"set_template_approval_newsletters_templates__template_id__approval_post",
|
|
248
|
+
"use_template_newsletters_templates__template_id__use_post",
|
|
249
|
+
"template_generation_status_newsletters_templates__template_id__generation_status_get",
|
|
250
|
+
"delete_newsletter_template_newsletters_templates__template_id__delete",
|
|
251
|
+
"list_templates_newsletters_templates_get",
|
|
252
|
+
"create_template_from_prompt_newsletters_templates_custom_post"
|
|
253
|
+
],
|
|
254
|
+
"tiers": [
|
|
255
|
+
"read",
|
|
256
|
+
"write",
|
|
257
|
+
"billable",
|
|
258
|
+
"destructive"
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"name": "SubscriberTimeline",
|
|
263
|
+
"file": "components/subscriber-timeline.tsx",
|
|
264
|
+
"purpose": "What one subscriber did with one sent issue.",
|
|
265
|
+
"operations": [
|
|
266
|
+
"get_subscriber_timeline_newsletters__newsletter_id__issues__issue_id__analytics_subscribers__subscriber_id__timeline_get"
|
|
267
|
+
],
|
|
268
|
+
"tiers": [
|
|
269
|
+
"read"
|
|
270
|
+
]
|
|
158
271
|
}
|
|
159
272
|
],
|
|
160
273
|
"hooks": [],
|
|
@@ -162,6 +275,10 @@
|
|
|
162
275
|
{
|
|
163
276
|
"file": "lib/newsletter-types.ts",
|
|
164
277
|
"purpose": "Newsletter/issue/subscriber types and helpers: draft check, percentages, scheduled-time conversion, generation-in-progress guard."
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"file": "lib/newsletter-extra.ts",
|
|
281
|
+
"purpose": "Helpers for the newsletter management components: open-dict readers, changed-fields diff, domain and DNS helpers."
|
|
165
282
|
}
|
|
166
283
|
],
|
|
167
284
|
"recipes": [
|
|
@@ -109,7 +109,7 @@ export function IssueScheduler({ newsletterId, issueId, onSent }: IssueScheduler
|
|
|
109
109
|
<Card>
|
|
110
110
|
<CardHeader><CardTitle>{issueTitle(issue.data)} <span className="text-muted-foreground text-sm font-normal">{issue.data.status}</span></CardTitle></CardHeader>
|
|
111
111
|
<CardContent className="grid gap-4">
|
|
112
|
-
{partial ? <p role="alert" className="text-sm text-
|
|
112
|
+
{partial ? <p role="alert" className="text-sm text-warning-foreground">This issue is still being written by AI. Wait for it to finish: sending now would mail the partial version.</p> : null}
|
|
113
113
|
{draft ? (
|
|
114
114
|
<div className="grid gap-3">
|
|
115
115
|
<p className="text-sm">Audience: <strong>{audience.loading ? "..." : subscribers.toLocaleString()}</strong> subscribed {pluralWord(subscribers, "person", "people")}.</p>
|
|
@@ -118,7 +118,7 @@ export function IssueScheduler({ newsletterId, issueId, onSent }: IssueScheduler
|
|
|
118
118
|
<Button size="sm" variant={when === "later" ? "secondary" : "ghost"} onClick={() => setWhen("later")}>Schedule</Button>
|
|
119
119
|
</div>
|
|
120
120
|
{when === "later" ? <div className="max-w-xs"><Label htmlFor="g8-send-at" className="mb-1">Send at</Label><Input id="g8-send-at" type="datetime-local" value={at} onChange={(e) => setAt(e.target.value)} /></div> : null}
|
|
121
|
-
{inPast ? <p role="alert" className="text-sm text-
|
|
121
|
+
{inPast ? <p role="alert" className="text-sm text-error-foreground">Pick a time in the future.</p> : null}
|
|
122
122
|
<div><Button disabled={blocked} onClick={() => void openSend()}>{when === "later" ? "Schedule issue" : "Send issue"}</Button></div>
|
|
123
123
|
{subscribers === 0 && !audience.loading ? <p className="text-muted-foreground text-sm">Add subscribers first (SubscriberTable).</p> : null}
|
|
124
124
|
</div>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IssueVersions: the saved versions of one issue, newest first, with restore, and a Duplicate action that copies the
|
|
5
|
+
* issue into a new draft.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in the order the screen uses them:
|
|
8
|
+
* 1. listNewsletterIssueVersions GET /newsletters/{id}/issues/{issue}/versions (read)
|
|
9
|
+
* 2. restoreNewsletterIssueVersion POST /newsletters/{id}/issues/{issue}/versions/{n}/restore (write; replaces the draft's content)
|
|
10
|
+
* 3. duplicateIssue POST /newsletters/{id}/issues/{issue}/duplicate (EXTERNAL, confirmed; creates a draft, sends nothing)
|
|
11
|
+
*
|
|
12
|
+
* Only a draft can be restored (a sent issue is a record of what people received). Restoring replaces the draft's
|
|
13
|
+
* current content, so the button says so and asks for a second click before it writes.
|
|
14
|
+
*/
|
|
15
|
+
import { useState } from "react";
|
|
16
|
+
import { Copy } from "lucide-react";
|
|
17
|
+
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
18
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
19
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
20
|
+
import { useConfirmAction } from "@/g8/components/confirmed-action";
|
|
21
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
23
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
24
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
25
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
26
|
+
import { isDraft } from "../lib/newsletter-types";
|
|
27
|
+
import type { Issue } from "../lib/newsletter-types";
|
|
28
|
+
import { asRec, num, rowsOf, str, versionNumber } from "../lib/newsletter-extra";
|
|
29
|
+
import type { Rec } from "../lib/newsletter-extra";
|
|
30
|
+
|
|
31
|
+
export interface IssueVersionsProps {
|
|
32
|
+
newsletterId: string;
|
|
33
|
+
issue: Pick<Issue, "id" | "status" | "subject">;
|
|
34
|
+
/** Called after a version was restored, so the editor reloads the draft. */
|
|
35
|
+
onRestored?: () => void;
|
|
36
|
+
/** Called with the new draft's id after Duplicate. */
|
|
37
|
+
onDuplicated?: (issueId: number) => void;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const when = (v: unknown): string => {
|
|
42
|
+
const d = new Date(str(v));
|
|
43
|
+
return Number.isNaN(d.getTime()) ? "" : d.toLocaleString();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function IssueVersions({ newsletterId, issue, onRestored, onDuplicated, className }: IssueVersionsProps) {
|
|
47
|
+
const base = `/newsletters/${encodeURIComponent(newsletterId)}/issues/${issue.id}`;
|
|
48
|
+
const { ask, dialog, failure } = useConfirmAction();
|
|
49
|
+
const versions = useG8Query((signal) => g8Get(`${base}/versions`, undefined, { signal }), [base]);
|
|
50
|
+
const [sure, setSure] = useState<number | null>(null);
|
|
51
|
+
const restore = useG8Mutation((n: number) => g8Post(`${base}/versions/${n}/restore`), { onSuccess: () => { setSure(null); void versions.refetch(); onRestored?.(); } });
|
|
52
|
+
const rows = rowsOf<Rec>(versions.data?.data, "versions");
|
|
53
|
+
const draft = isDraft(issue);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={className}>
|
|
57
|
+
<div className="grid gap-4">
|
|
58
|
+
<SectionCard title="Duplicate" description="Copy this issue into a new draft you can change and send separately."
|
|
59
|
+
actions={<Button size="sm" variant="outline" onClick={() => ask({
|
|
60
|
+
tier: "external", title: `Duplicate "${issue.subject || `issue ${issue.id}`}"`, confirmLabel: "Duplicate",
|
|
61
|
+
description: "Creates a new draft with the same content. Nothing is sent to anyone.",
|
|
62
|
+
run: async () => { const id = num(asRec((await g8Post(`${base}/duplicate`)).data).id); if (id !== undefined) onDuplicated?.(id); },
|
|
63
|
+
})}><Copy className="size-4" />Duplicate issue</Button>}>
|
|
64
|
+
<p className="text-muted-foreground text-sm">The copy starts as a draft in this newsletter.</p>
|
|
65
|
+
</SectionCard>
|
|
66
|
+
<SectionCard title="Version history" description={draft ? "Restoring replaces the draft's current content." : "A sent issue cannot be changed; its versions are shown for reference."}>
|
|
67
|
+
{versions.error ? <ApiError error={versions.error} title="Could not load the versions" onRetry={() => void versions.refetch()} /> : versions.loading && !versions.data ? <Skeleton className="h-20 w-full" aria-busy="true" /> : rows.length === 0 ? (
|
|
68
|
+
<EmptyState compact title="No saved versions yet" description="A version is saved each time the issue is saved or rewritten." />
|
|
69
|
+
) : (
|
|
70
|
+
<ul className="grid gap-2">
|
|
71
|
+
{rows.map((v, i) => {
|
|
72
|
+
const n = versionNumber(v);
|
|
73
|
+
return (
|
|
74
|
+
<li key={n ?? i} className="flex items-center justify-between gap-3 rounded-md border p-3">
|
|
75
|
+
<div className="min-w-0 text-sm"><span className="font-medium">Version {n ?? i + 1}</span>{str(v.source) ? <span className="text-muted-foreground"> from {str(v.source).replace(/_/g, " ")}</span> : null}<span className="text-muted-foreground block text-xs">{when(v.created_at)}{str(v.subject) ? ` · ${str(v.subject)}` : ""}</span></div>
|
|
76
|
+
{draft && n !== undefined ? (
|
|
77
|
+
sure === n ? (
|
|
78
|
+
<div className="flex gap-1"><Button size="sm" loading={restore.loading} onClick={() => void restore.mutate(n)}>Replace the draft</Button><Button size="sm" variant="ghost" onClick={() => setSure(null)}>Cancel</Button></div>
|
|
79
|
+
) : <Button size="sm" variant="outline" aria-label={`Restore version ${n}`} onClick={() => setSure(n)}>Restore</Button>
|
|
80
|
+
) : null}
|
|
81
|
+
</li>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</ul>
|
|
85
|
+
)}
|
|
86
|
+
{restore.error ? <ApiError error={restore.error} title="Could not restore that version" /> : null}
|
|
87
|
+
</SectionCard>
|
|
88
|
+
{failure ? <ApiError error={failure} title="That did not work" /> : null}
|
|
89
|
+
</div>
|
|
90
|
+
<ConfirmSpendDialog {...dialog} />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MoreRows: the honest end of a list that the API pages. Shows how many rows are on screen and, while the API has
|
|
5
|
+
* another page, a "Load more" button. A list never ends silently: either every row is reachable or this says there are more.
|
|
6
|
+
* (Same contract as the foundation's LoadMore; kept local so the newsletter block does not wait on it.)
|
|
7
|
+
*
|
|
8
|
+
* SDK operations: none.
|
|
9
|
+
*/
|
|
10
|
+
import { Button } from "@graph8/sdk/ui";
|
|
11
|
+
|
|
12
|
+
export interface MoreRowsProps {
|
|
13
|
+
/** Rows on screen. */
|
|
14
|
+
shown: number;
|
|
15
|
+
/** What the rows are, plural: "people". */
|
|
16
|
+
noun: string;
|
|
17
|
+
hasMore: boolean;
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
onMore: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MoreRows({ shown, noun, hasMore, loading, onMore }: MoreRowsProps) {
|
|
23
|
+
if (!hasMore) return null;
|
|
24
|
+
return (
|
|
25
|
+
<div className="text-muted-foreground flex items-center justify-between gap-3 py-2 text-sm">
|
|
26
|
+
<span role="status">Showing {shown.toLocaleString()} {noun}</span>
|
|
27
|
+
<Button type="button" variant="outline" size="sm" loading={loading} onClick={onMore}>Load more</Button>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -81,7 +81,7 @@ export function NewsletterEditor({ initialNewsletterId, onSelectIssue }: Newslet
|
|
|
81
81
|
{!current ? <EmptyState title="Pick a newsletter" description="Choose a newsletter to see its issues, or create one." /> : (
|
|
82
82
|
<>
|
|
83
83
|
<div className="flex items-center justify-between">
|
|
84
|
-
<h2 className="text-lg font-
|
|
84
|
+
<h2 className="text-lg font-medium">{current.name} <span className="text-muted-foreground text-sm font-normal">{current.status}</span></h2>
|
|
85
85
|
{live ? <Button variant="outline" size="sm" loading={pause.loading} onClick={() => void pause.mutate(undefined)}>Pause newsletter</Button> : <Button size="sm" onClick={() => setPending({ kind: "activate" })}>Activate newsletter</Button>}
|
|
86
86
|
</div>
|
|
87
87
|
{pause.error ? <ApiError error={pause.error} title="Could not pause" /> : null}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NewsletterListSyncs: keep subscribers in step with your lists. A synced list adds its contacts as subscribers; run a
|
|
5
|
+
* sync now to pull in the latest, or turn it off.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in the order the screen uses them:
|
|
8
|
+
* 1. listNewsletterListSyncs GET /newsletters/{id}/list-syncs (read)
|
|
9
|
+
* 2. listLists GET /lists?page&limit&search (paged, "Load more") (read; the lists you can turn on)
|
|
10
|
+
* 3. enableNewsletterListSync POST /newsletters/{id}/list-syncs { audience_list_id, attested_text } (write, confirmed: people are subscribed to a newsletter)
|
|
11
|
+
* 4. runNewsletterListSync POST /newsletters/{id}/list-syncs/{list}/run (write, confirmed: adds the list's people now)
|
|
12
|
+
* 5. disableNewsletterListSync DELETE /newsletters/{id}/list-syncs/{list} (destructive, confirmed: stops syncing; subscribers stay)
|
|
13
|
+
*
|
|
14
|
+
* Turning a sync on subscribes real people, so the person confirms the consent statement first: the exact text is shown
|
|
15
|
+
* and sent as `attested_text`. The consent claim is theirs; the dialog does not pretend otherwise.
|
|
16
|
+
*/
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { Button, Input } from "@graph8/sdk/ui";
|
|
19
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
20
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
21
|
+
import { useConfirmAction } from "@/g8/components/confirmed-action";
|
|
22
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
23
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
24
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
25
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
26
|
+
import { usePaginated } from "@/g8/hooks/use-paginated";
|
|
27
|
+
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
28
|
+
import { asRec, num, pageOf, rowsOf, str } from "../lib/newsletter-extra";
|
|
29
|
+
import type { Rec } from "../lib/newsletter-extra";
|
|
30
|
+
import { MoreRows } from "./more-rows";
|
|
31
|
+
|
|
32
|
+
export interface NewsletterListSyncsProps {
|
|
33
|
+
newsletterId: string;
|
|
34
|
+
/** Called after a sync changed who is subscribed. */
|
|
35
|
+
onChanged?: () => void;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const CONSENT_TEXT = "I confirm the people in this list agreed to receive this newsletter.";
|
|
40
|
+
const PAGE = 50;
|
|
41
|
+
|
|
42
|
+
export function NewsletterListSyncs({ newsletterId, onChanged, className }: NewsletterListSyncsProps) {
|
|
43
|
+
const base = `/newsletters/${encodeURIComponent(newsletterId)}/list-syncs`;
|
|
44
|
+
const { ask, dialog, failure } = useConfirmAction();
|
|
45
|
+
const [search, setSearch] = useState("");
|
|
46
|
+
const [note, setNote] = useState("");
|
|
47
|
+
const syncs = useG8Query((signal) => g8Get(base, undefined, { signal }), [newsletterId]);
|
|
48
|
+
const rows = rowsOf<Rec>(syncs.data?.data, "list_syncs");
|
|
49
|
+
// Disabled rows stay in the API list (so they can be turned back on): only enabled ones count as synced.
|
|
50
|
+
const synced = new Set(rows.filter((r) => r.enabled !== false).map((r) => String(r.audience_list_id)));
|
|
51
|
+
const lists = usePaginated<Rec>(async (cursor, signal) => {
|
|
52
|
+
const page = cursor ? Number(cursor) : 1;
|
|
53
|
+
const res = await g8Get("/lists", { limit: PAGE, page, search: search.trim() || undefined }, { signal });
|
|
54
|
+
const got = rowsOf<Rec>(res.data, "lists");
|
|
55
|
+
const more = (res.pagination as { has_next?: boolean } | undefined)?.has_next ?? got.length >= PAGE;
|
|
56
|
+
return pageOf(got, more && got.length > 0 ? page + 1 : null);
|
|
57
|
+
}, [search]);
|
|
58
|
+
const changed = (): void => { void syncs.refetch(); onChanged?.(); };
|
|
59
|
+
|
|
60
|
+
const enable = (l: { id?: unknown; title?: unknown; name?: unknown }): void => ask({
|
|
61
|
+
tier: "external", title: `Subscribe "${str(l.title) || str(l.name)}" to this newsletter`, confirmLabel: "Turn on sync",
|
|
62
|
+
description: <><p>People in this list become subscribers and will receive issues. You are confirming:</p><p className="mt-2 font-medium">{CONSENT_TEXT}</p></>,
|
|
63
|
+
run: () => g8Post(base, { audience_list_id: Number(l.id), attested_text: CONSENT_TEXT }), onDone: changed,
|
|
64
|
+
});
|
|
65
|
+
const run = (s: Rec): void => ask({
|
|
66
|
+
tier: "external", title: `Sync "${str(s.name)}" now`, confirmLabel: "Sync now",
|
|
67
|
+
recipients: num(s.contact_count),
|
|
68
|
+
description: "Adds the list's people who are not subscribed yet. People who unsubscribed stay unsubscribed.",
|
|
69
|
+
run: async () => { await g8Post(`${base}/${encodeURIComponent(String(s.audience_list_id))}/run`); setNote(`Synced "${str(s.name)}".`); }, onDone: changed,
|
|
70
|
+
});
|
|
71
|
+
const disable = (s: Rec): void => ask({
|
|
72
|
+
tier: "destructive", title: `Stop syncing "${str(s.name)}"`, confirmLabel: "Stop syncing",
|
|
73
|
+
description: "New contacts added to this list are no longer subscribed. People already subscribed stay subscribed.",
|
|
74
|
+
tolerateStatus: [404], run: () => g8Delete(`${base}/${encodeURIComponent(String(s.audience_list_id))}`), onDone: changed,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className={className}>
|
|
79
|
+
<div className="grid gap-4">
|
|
80
|
+
<SectionCard title="Synced lists" description="Subscribers are kept in step with these lists.">
|
|
81
|
+
{syncs.error ? <ApiError error={syncs.error} title="Could not load the synced lists" onRetry={() => void syncs.refetch()} /> : rows.length === 0 ? (
|
|
82
|
+
<EmptyState compact title="No lists are synced" description="Choose a list below to subscribe its people." />
|
|
83
|
+
) : (
|
|
84
|
+
<ul className="grid gap-2">
|
|
85
|
+
{rows.map((s) => (
|
|
86
|
+
<li key={String(s.audience_list_id)} className="flex items-center justify-between gap-3 rounded-md border p-3">
|
|
87
|
+
<div className="min-w-0"><span className="block truncate text-sm font-medium">{str(s.name) || `List ${String(s.audience_list_id)}`}</span><span className="text-muted-foreground text-xs">{num(s.contact_count) == null ? "" : `${(s.contact_count as number).toLocaleString()} contacts`}</span></div>
|
|
88
|
+
{s.enabled === false ? <StatusPill tone="neutral">Off</StatusPill> : <StatusPill tone="positive">On</StatusPill>}
|
|
89
|
+
{s.enabled === false ? (
|
|
90
|
+
<Button size="sm" variant="outline" aria-label={`Turn ${str(s.name)} back on`} onClick={() => enable({ id: s.audience_list_id, name: s.name })}>Turn on sync</Button>
|
|
91
|
+
) : <div className="flex gap-1"><Button size="sm" variant="outline" aria-label={`Sync ${str(s.name)} now`} onClick={() => run(s)}>Sync now</Button><Button size="sm" variant="ghost" aria-label={`Stop syncing ${str(s.name)}`} onClick={() => disable(s)}>Stop</Button></div>}
|
|
92
|
+
</li>
|
|
93
|
+
))}
|
|
94
|
+
</ul>
|
|
95
|
+
)}
|
|
96
|
+
{note ? <p role="status" className="mt-2 text-sm">{note}</p> : null}
|
|
97
|
+
</SectionCard>
|
|
98
|
+
<SectionCard title="Add a list" description="Find the list to subscribe.">
|
|
99
|
+
<Input aria-label="Search lists" placeholder="Search lists" value={search} onChange={(e) => setSearch(e.target.value)} className="mb-3 max-w-sm" />
|
|
100
|
+
{lists.error ? <ApiError error={lists.error} title="Could not load your lists" onRetry={() => void lists.reload()} /> : lists.loading ? <p className="text-muted-foreground text-sm" aria-busy="true">Loading lists...</p> : lists.items.length === 0 ? (
|
|
101
|
+
<EmptyState compact title={search ? "No list matches" : "You have no lists"} description={search ? "Try another name." : "Create a list in Lists first."} />
|
|
102
|
+
) : (
|
|
103
|
+
<>
|
|
104
|
+
<ul className="grid gap-2">
|
|
105
|
+
{lists.items.map((l) => (
|
|
106
|
+
<li key={String(l.id)} className="flex items-center justify-between gap-3 rounded-md border p-3">
|
|
107
|
+
<span className="truncate text-sm">{str(l.title)}</span>
|
|
108
|
+
{synced.has(String(l.id)) ? <StatusPill tone="positive">Synced</StatusPill> : <Button size="sm" variant="outline" aria-label={`Sync ${str(l.title)}`} onClick={() => enable(l)}>Turn on sync</Button>}
|
|
109
|
+
</li>
|
|
110
|
+
))}
|
|
111
|
+
</ul>
|
|
112
|
+
<MoreRows shown={lists.items.length} noun="lists" hasMore={lists.hasMore} loading={lists.loadingMore} onMore={() => void lists.loadMore()} />
|
|
113
|
+
</>
|
|
114
|
+
)}
|
|
115
|
+
</SectionCard>
|
|
116
|
+
{failure ? <ApiError error={failure} title="That did not work" /> : null}
|
|
117
|
+
</div>
|
|
118
|
+
<ConfirmSpendDialog {...dialog} />
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|