@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# surveys
|
|
2
|
+
|
|
3
|
+
NPS and CSAT surveys: ask customers **one scored question**, read the score, the answers and the trend. A survey is one question
|
|
4
|
+
with a numeric answer (NPS 0 to 10, CSAT 1 to 5) and an optional comment. It is sent to an audience **list** by email or chat
|
|
5
|
+
message, shown **in-app** to your own team, or sent automatically (on a schedule, when someone joins a list, when a record reaches
|
|
6
|
+
a stage). A send reaches real people, so it is tier `external` and is confirmed with the audience size.
|
|
7
|
+
|
|
8
|
+
## Screens
|
|
9
|
+
|
|
10
|
+
`SurveysScreen` (`components/surveys-screen.tsx`) is the finished page: list, create, results, answers, send, automatic sends, settings, org overview, sending domains and account lookups.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { SurveysScreen } from "@/g8/surveys/components/surveys-screen";
|
|
14
|
+
export default function Page() { return <SurveysScreen />; }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Tabs (`?tab=`): **Surveys**, **Overview**, **Sending domains**, **Accounts and people**. Open a survey (`?id=`) for Results, Responses, Send, Automatic sends, Settings and Details; `&view=full` shows it as a full page.
|
|
18
|
+
|
|
19
|
+
Flows: see every survey with its type, state and channel (search; filter by type, open or closed, channel; the list is complete: the API does not page it), create one, open it to read the score, response rate, split and trend (by day, week or month), read and search its answers (filter by promoter, passive, detractor), open one answer, export the answers on screen as CSV, send it to a list now or later (confirmed with the list's size), add or remove automatic sends (both confirmed), edit the question, thank-you line and channel, **close** it (stops new sends, keeps every answer) and reopen it, delete it (confirmed), verify a sending domain (confirmed, returns the DNS records), read org-wide NPS and CSAT (all contacts or one list), look up one company's NPS and one person's answers. Optional props: `renderRowActions`, `renderDetailActions`, `renderContact`, `extraDetailTabs`, `toolbarActions`.
|
|
20
|
+
|
|
21
|
+
Parts you can mount alone: `SurveyForm`, `SurveySend`, `SurveySendHistory`, `SurveyTriggers`, `SurveyResults`, `SurveyResponses`, `SendingDomains`, `OrgSummary`, `AccountNps` (pass `companyId` on a company page), `ContactSurveyHistory` (pass `contactId` on a contact page).
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
npx @graph8/sdk add surveys
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Installs `foundation`, `crm`, `lists` and `pickers` first if missing. Everything lands under `g8/surveys/`.
|
|
30
|
+
|
|
31
|
+
## Prerequisites (the human must do these)
|
|
32
|
+
|
|
33
|
+
| Where | What | How to verify |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| graph8 | A **verified sending domain** for email surveys. Verify one in the Sending domains tab (it returns DNS records), publish them at your DNS provider, then press Check status. | `GET /api/v1/feedback/sending-domains` lists it as `verified`. |
|
|
36
|
+
| The business | An **audience list** of people you may ask. A person picks it and confirms the size; unsubscribed and suppressed contacts are skipped by the API. | The owner can say who is on the list and why. |
|
|
37
|
+
| graph8 key | `surveys:read`, `surveys:write`, `surveys:delete`; `surveys:run` to send or verify a domain (separate from `surveys:write`); `lists:read`. | `g8.api.me.describeCurrentKey()` |
|
|
38
|
+
| Your app | `allowedPrefixes` must include `feedback`, `lists`, `companies`, `contacts`. The survey endpoints live under `/feedback`, not `/surveys`. | The route file lists them. |
|
|
39
|
+
|
|
40
|
+
## The flow, in order (IDs passed between steps)
|
|
41
|
+
|
|
42
|
+
1. **Create**: `createSurvey({ name, type: "nps" | "csat", question_text, delivery_channel, comment_mode, thank_you_copy? })` -> **`survey.id`** (a UUID string). `type`, the scale (`scale_min`/`scale_max`) and `comment_mode` are fixed now; `updateSurvey` takes none of them.
|
|
43
|
+
2. **Sender**: `listSurveySendingDomains` -> pick a `verified` domain; the sender address must be on it. A new domain: `verifySurveyDomain({ domain })` then `surveyDomainStatus({ domain })` until verified.
|
|
44
|
+
3. **Audience**: a list id (`listLists`, or build one with the `lists` block) -> **`list.id`**, sent as the string `audience_ref`.
|
|
45
|
+
4. **Send**: `sendSurvey(survey.id, { audience_ref, channel, sender_email, subject, scheduled_at? })` behind `ConfirmSpendDialog` showing the list's size -> **`send_id`**. It answers 202; poll `getSurveySend(send_id)` until `sent`, `failed` or `scheduled`.
|
|
46
|
+
5. **Automate** (optional): `createSurveyTrigger(survey.id, { trigger_kind, sender_email, subject, audience_ref | stage_value, cadence_days })`. `scheduled` needs `audience_ref` + `cadence_days`, `list_entry` needs `audience_ref`, `stage_entry` needs `stage_value`. There is no update: delete and recreate.
|
|
47
|
+
6. **Read**: `getSurveySummary` (score, response rate, split), `getSurveyTrends`, `getSurveyResponses` (every answer), `getSurveyAppUserResponses` (your team, in-app), `getSurveySends` (history), `getSurveyOrgSummary`, `getCompanySurveyNps`, `getContactSurveyHistory`.
|
|
48
|
+
7. **Stop**: `updateSurvey({ is_active: false })` closes it and keeps every answer. `deleteSurvey` removes the survey **and its answers** (confirmed).
|
|
49
|
+
|
|
50
|
+
## Default decisions (do not ask the human unless marked)
|
|
51
|
+
|
|
52
|
+
| Decision | Default | Ask? |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Survey type | NPS (0 to 10) for relationship feedback, CSAT (1 to 5) for one interaction | no |
|
|
55
|
+
| Question | The standard question for the type, with the brand name | no |
|
|
56
|
+
| Comment capture | Score only (`none`) | no |
|
|
57
|
+
| Delivery channel | Email | no |
|
|
58
|
+
| Which list is asked | None: a person always picks the audience | **yes** |
|
|
59
|
+
| Sending | Never without the audience size and sender domain confirmed | no |
|
|
60
|
+
| Stopping | Close, do not delete | no |
|
|
61
|
+
| Trend bucket | Weekly for one survey, monthly for the org overview | no |
|
|
62
|
+
|
|
63
|
+
## Operation coverage (21 surveys operations, all used)
|
|
64
|
+
|
|
65
|
+
Every operation in the `surveys` namespace is reachable from `SurveysScreen`:
|
|
66
|
+
`listSurveys`, `getSurvey`, `createSurvey`, `updateSurvey`, `deleteSurvey`, `sendSurvey`, `getSurveySend`, `getSurveySends`, `createSurveyTrigger`, `listSurveyTriggers`, `deleteSurveyTrigger`, `getSurveySummary`, `getSurveyTrends`, `getSurveyResponses`, `getSurveyAppUserResponses`, `getSurveyOrgSummary`, `getCompanySurveyNps`, `getContactSurveyHistory`, `listSurveySendingDomains`, `verifySurveyDomain`, `surveyDomainStatus`.
|
|
67
|
+
The block also reads `listLists`, `listCompanies` and `listContacts` for its pickers.
|
|
68
|
+
|
|
69
|
+
## API gaps (what the request asked for that the API does not have)
|
|
70
|
+
|
|
71
|
+
| Asked for | Reality | What the block does |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Several questions, question types, reordering | A survey is **one** question; "type" is only `nps` or `csat`. There is no question list or question endpoint. | Create takes the one question; the type is chosen at creation. |
|
|
74
|
+
| Publish / close | No publish endpoint; `updateSurvey({ is_active })` is the switch. | Close / Reopen buttons. |
|
|
75
|
+
| Edit type, scale, comment mode, template | `UpdateSurveyRequest` takes `name`, `question_text`, `thank_you_copy`, `delivery_channel`, `template_id`, `is_active` only. | Shown read-only with "create a new survey to change it". Templates have no list endpoint, so `template_id` is not offered. |
|
|
76
|
+
| Clear the thank-you line | The API ignores `null` on update; only `""` clears it. | A cleared line is sent as `""`. |
|
|
77
|
+
| Share link and embed code | None. The respondent link is signed per recipient inside the emailed survey; the in-app widget has no embed endpoint. | In-app surveys explain they appear inside the product; email is the way out. |
|
|
78
|
+
| Export responses | No export endpoint. | CSV built in the browser from the answers on screen (formula-safe). |
|
|
79
|
+
| AI generation (questions, summaries) | None for surveys. | Nothing to confirm or bill; no button. |
|
|
80
|
+
| Paging and server filters | `listSurveys`, `getSurveyResponses` and `getSurveySends` take no paging or filter parameters and return everything. | The UI says "All N" and filters client-side; a survey with a very large number of answers is one large read. |
|
|
81
|
+
| Pause or edit an automatic send | Triggers can only be created, listed and deleted. | Delete and recreate. |
|
|
82
|
+
| Respond as a contact (`/surveys/{id}/respond`, `/in-app/pending`) | Browser-session and signed-link surfaces, deliberately not in the developer API. | Out of scope. |
|
|
83
|
+
|
|
84
|
+
## Things that surprise people
|
|
85
|
+
|
|
86
|
+
- The endpoints are under **`/feedback`**, not `/surveys` (the in-app router owns `/surveys`). Add `feedback` to the proxy allowlist.
|
|
87
|
+
- Scores are lowercase types (`nps`, `csat`); sending channels are `email` and `bridge` only (`in_app` is a delivery channel for showing, not a send channel).
|
|
88
|
+
- A score is `null` when there are too few answers (the API suppresses it). The UI shows a dash, not 0.
|
|
89
|
+
- `response_rate.rate` is a fraction (0 to 1).
|
|
90
|
+
- A send is dispatched asynchronously: `202` with a `send_id`, then poll. A send scheduled for later stays `scheduled`.
|
|
91
|
+
- `contact_id` on an answer and in the history endpoint is the contact's primary key (the mashup contact id), not the business `CONTACT_ID` key.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "surveys",
|
|
4
|
+
"title": "Surveys (NPS and CSAT)",
|
|
5
|
+
"summary": "Ask customers one scored question (NPS or CSAT): create and edit surveys, close, send to a list with confirmation, automate sends, read scores and answers, export, verify a sending domain.",
|
|
6
|
+
"features": [
|
|
7
|
+
"create NPS and CSAT surveys with a question, channel, comment capture and thank-you line",
|
|
8
|
+
"close a survey (keeps answers) and reopen it, or delete it with confirmation",
|
|
9
|
+
"send to an audience list now or later, confirmed with the audience size",
|
|
10
|
+
"automatic sends on a schedule, on list entry or on stage entry",
|
|
11
|
+
"score, response rate, answer split and trend by day, week or month",
|
|
12
|
+
"every answer with search, band filter, detail and CSV export",
|
|
13
|
+
"org-wide NPS and CSAT, account NPS and a person's survey history",
|
|
14
|
+
"sending domain verification with the DNS records to publish"
|
|
15
|
+
],
|
|
16
|
+
"dependsOn": [
|
|
17
|
+
"lists",
|
|
18
|
+
"pickers"
|
|
19
|
+
],
|
|
20
|
+
"operations": [
|
|
21
|
+
"create_survey_feedback_surveys_post",
|
|
22
|
+
"create_survey_trigger_feedback_surveys__survey_id__triggers_post",
|
|
23
|
+
"delete_survey_feedback_surveys__survey_id__delete",
|
|
24
|
+
"delete_survey_trigger_feedback_surveys__survey_id__triggers__trigger_id__delete",
|
|
25
|
+
"get_company_survey_nps_feedback_companies__company_id__nps_get",
|
|
26
|
+
"get_contact_survey_history_feedback_contacts__contact_id__history_get",
|
|
27
|
+
"get_survey_app_user_responses_feedback_surveys__survey_id__app_user_responses_get",
|
|
28
|
+
"get_survey_feedback_surveys__survey_id__get",
|
|
29
|
+
"get_survey_org_summary_feedback_org_summary_get",
|
|
30
|
+
"get_survey_responses_feedback_surveys__survey_id__responses_get",
|
|
31
|
+
"get_survey_send_feedback_sends__send_id__get",
|
|
32
|
+
"get_survey_sends_feedback_surveys__survey_id__sends_get",
|
|
33
|
+
"get_survey_summary_feedback_surveys__survey_id__summary_get",
|
|
34
|
+
"get_survey_trends_feedback_surveys__survey_id__trends_get",
|
|
35
|
+
"list_companies_companies_get",
|
|
36
|
+
"list_contacts_contacts_get",
|
|
37
|
+
"list_lists_lists_get",
|
|
38
|
+
"list_survey_sending_domains_feedback_sending_domains_get",
|
|
39
|
+
"list_survey_triggers_feedback_surveys__survey_id__triggers_get",
|
|
40
|
+
"list_surveys_feedback_surveys_get",
|
|
41
|
+
"send_survey_feedback_surveys__survey_id__send_post",
|
|
42
|
+
"survey_domain_status_feedback_sending_domains_status_get",
|
|
43
|
+
"update_survey_feedback_surveys__survey_id__patch",
|
|
44
|
+
"verify_survey_domain_feedback_sending_domains_verify_post"
|
|
45
|
+
],
|
|
46
|
+
"components": [
|
|
47
|
+
{
|
|
48
|
+
"name": "SurveysScreen",
|
|
49
|
+
"file": "components/surveys-screen.tsx",
|
|
50
|
+
"purpose": "Finished NPS and CSAT workspace: survey list with filters, create, close and reopen, delete, and per-survey results, answers, send, automatic sends and settings; org overview, sending domains and account lookups.",
|
|
51
|
+
"operations": [
|
|
52
|
+
"delete_survey_feedback_surveys__survey_id__delete",
|
|
53
|
+
"list_surveys_feedback_surveys_get",
|
|
54
|
+
"update_survey_feedback_surveys__survey_id__patch"
|
|
55
|
+
],
|
|
56
|
+
"tiers": [
|
|
57
|
+
"read",
|
|
58
|
+
"write",
|
|
59
|
+
"destructive"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "SurveyForm",
|
|
64
|
+
"file": "components/survey-form.tsx",
|
|
65
|
+
"purpose": "Create a survey (name, NPS or CSAT, question, channel, comments, thank-you line, scale) or edit one; edit reads the full record first and sends only changed fields.",
|
|
66
|
+
"operations": [
|
|
67
|
+
"create_survey_feedback_surveys_post",
|
|
68
|
+
"get_survey_feedback_surveys__survey_id__get",
|
|
69
|
+
"update_survey_feedback_surveys__survey_id__patch"
|
|
70
|
+
],
|
|
71
|
+
"tiers": [
|
|
72
|
+
"read",
|
|
73
|
+
"write"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "SurveySend",
|
|
78
|
+
"file": "components/survey-send.tsx",
|
|
79
|
+
"purpose": "Send a survey to an audience list now or later, sized from the list and confirmed; follows the send until it settles.",
|
|
80
|
+
"operations": [
|
|
81
|
+
"get_survey_send_feedback_sends__send_id__get",
|
|
82
|
+
"list_lists_lists_get",
|
|
83
|
+
"list_survey_sending_domains_feedback_sending_domains_get",
|
|
84
|
+
"send_survey_feedback_surveys__survey_id__send_post"
|
|
85
|
+
],
|
|
86
|
+
"tiers": [
|
|
87
|
+
"read",
|
|
88
|
+
"external"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "SurveySendHistory",
|
|
93
|
+
"file": "components/survey-send-history.tsx",
|
|
94
|
+
"purpose": "Every send of one survey with status, audience, reach and answers.",
|
|
95
|
+
"operations": [
|
|
96
|
+
"get_survey_sends_feedback_surveys__survey_id__sends_get"
|
|
97
|
+
],
|
|
98
|
+
"tiers": [
|
|
99
|
+
"read"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "SurveyTriggers",
|
|
104
|
+
"file": "components/survey-triggers.tsx",
|
|
105
|
+
"purpose": "Automatic sends for a survey: on a schedule, when someone joins a list or when a record reaches a stage; add (confirmed) and remove (confirmed).",
|
|
106
|
+
"operations": [
|
|
107
|
+
"create_survey_trigger_feedback_surveys__survey_id__triggers_post",
|
|
108
|
+
"delete_survey_trigger_feedback_surveys__survey_id__triggers__trigger_id__delete",
|
|
109
|
+
"list_lists_lists_get",
|
|
110
|
+
"list_survey_sending_domains_feedback_sending_domains_get",
|
|
111
|
+
"list_survey_triggers_feedback_surveys__survey_id__triggers_get"
|
|
112
|
+
],
|
|
113
|
+
"tiers": [
|
|
114
|
+
"read",
|
|
115
|
+
"write",
|
|
116
|
+
"destructive"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "SurveyResults",
|
|
121
|
+
"file": "components/survey-results.tsx",
|
|
122
|
+
"purpose": "Headline NPS or CSAT, response rate, answer split and trend by day, week or month.",
|
|
123
|
+
"operations": [
|
|
124
|
+
"get_survey_summary_feedback_surveys__survey_id__summary_get",
|
|
125
|
+
"get_survey_trends_feedback_surveys__survey_id__trends_get"
|
|
126
|
+
],
|
|
127
|
+
"tiers": [
|
|
128
|
+
"read"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "SurveyResponses",
|
|
133
|
+
"file": "components/survey-responses.tsx",
|
|
134
|
+
"purpose": "Every answer with search, band filter, per-answer detail and CSV export, plus the team's in-app answers.",
|
|
135
|
+
"operations": [
|
|
136
|
+
"get_survey_app_user_responses_feedback_surveys__survey_id__app_user_responses_get",
|
|
137
|
+
"get_survey_responses_feedback_surveys__survey_id__responses_get"
|
|
138
|
+
],
|
|
139
|
+
"tiers": [
|
|
140
|
+
"read"
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "SendingDomains",
|
|
145
|
+
"file": "components/sending-domains.tsx",
|
|
146
|
+
"purpose": "Domains surveys may be emailed from, their verification state, and verify a new domain (confirmed) to get its DNS records.",
|
|
147
|
+
"operations": [
|
|
148
|
+
"list_survey_sending_domains_feedback_sending_domains_get",
|
|
149
|
+
"survey_domain_status_feedback_sending_domains_status_get",
|
|
150
|
+
"verify_survey_domain_feedback_sending_domains_verify_post"
|
|
151
|
+
],
|
|
152
|
+
"tiers": [
|
|
153
|
+
"read",
|
|
154
|
+
"external"
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "OrgSummary",
|
|
159
|
+
"file": "components/org-summary.tsx",
|
|
160
|
+
"purpose": "Org-wide NPS and CSAT for all contacts or one audience list, with a rollup per survey.",
|
|
161
|
+
"operations": [
|
|
162
|
+
"get_survey_org_summary_feedback_org_summary_get",
|
|
163
|
+
"list_lists_lists_get"
|
|
164
|
+
],
|
|
165
|
+
"tiers": [
|
|
166
|
+
"read"
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"name": "AccountNps",
|
|
171
|
+
"file": "components/account-nps.tsx",
|
|
172
|
+
"purpose": "One company's NPS, coverage and each contact's latest score; takes a companyId or finds a company by name.",
|
|
173
|
+
"operations": [
|
|
174
|
+
"get_company_survey_nps_feedback_companies__company_id__nps_get",
|
|
175
|
+
"list_companies_companies_get"
|
|
176
|
+
],
|
|
177
|
+
"tiers": [
|
|
178
|
+
"read"
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "ContactSurveyHistory",
|
|
183
|
+
"file": "components/contact-survey-history.tsx",
|
|
184
|
+
"purpose": "Every survey answer one person has given; takes a contactId or finds a person by name.",
|
|
185
|
+
"operations": [
|
|
186
|
+
"get_contact_survey_history_feedback_contacts__contact_id__history_get",
|
|
187
|
+
"list_contacts_contacts_get"
|
|
188
|
+
],
|
|
189
|
+
"tiers": [
|
|
190
|
+
"read"
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"hooks": [],
|
|
195
|
+
"lib": [
|
|
196
|
+
{
|
|
197
|
+
"file": "lib/surveys-types.ts",
|
|
198
|
+
"purpose": "Survey, summary, response, send and trigger types; the API's exact vocabularies (types, channels, comment modes, trigger kinds); score and band helpers; the changed-fields diff; CSV building with formula-injection guard."
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"recipes": [
|
|
202
|
+
{
|
|
203
|
+
"file": "recipes/create-an-nps-survey.ts",
|
|
204
|
+
"title": "Create an NPS or CSAT survey and read it back",
|
|
205
|
+
"when": "Setting up the question you will ask; nothing is sent."
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"file": "recipes/send-a-survey-and-follow-it.ts",
|
|
209
|
+
"title": "Send a survey to a list and follow the send",
|
|
210
|
+
"when": "Asking an audience now or later; emails real people, so a person confirms the audience size first."
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"file": "recipes/survey-score-report.ts",
|
|
214
|
+
"title": "Report every survey's score, response rate and recent comments",
|
|
215
|
+
"when": "A weekly digest or a dashboard backend."
|
|
216
|
+
}
|
|
217
|
+
],
|
|
218
|
+
"prerequisites": [
|
|
219
|
+
{
|
|
220
|
+
"id": "sending-domain",
|
|
221
|
+
"description": "Email surveys need a VERIFIED sending domain: the sender address must be on one. Verify a domain in the Sending domains tab (it returns the DNS records), publish those records at your DNS provider, then check its status.",
|
|
222
|
+
"where": "api",
|
|
223
|
+
"verify": "GET /api/v1/feedback/sending-domains lists the domain with status verified."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"id": "consented-audience",
|
|
227
|
+
"description": "A list of people who may be asked for feedback. A send reaches real people and cannot be recalled; a person confirms the audience size first. Unsubscribed and suppressed contacts are skipped by the API.",
|
|
228
|
+
"where": "app",
|
|
229
|
+
"verify": "The owner confirmed the list is people you may email."
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "survey-scopes",
|
|
233
|
+
"description": "The key needs surveys:read, surveys:write, surveys:delete and, to send or verify a domain, surveys:run (an external operation separate from surveys:write). Listing audiences needs lists:read.",
|
|
234
|
+
"where": "api",
|
|
235
|
+
"verify": "g8.api.me.describeCurrentKey() lists the scopes."
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"id": "proxy-allowlist",
|
|
239
|
+
"description": "Add \"feedback\", \"lists\", \"companies\" and \"contacts\" to allowedPrefixes in app/api/g8/[...path]/route.ts. The survey endpoints live under /feedback, not /surveys.",
|
|
240
|
+
"where": "app",
|
|
241
|
+
"verify": "The route file's allowedPrefixes contains them."
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"scopes": [
|
|
245
|
+
"companies:read",
|
|
246
|
+
"contacts:read",
|
|
247
|
+
"lists:read",
|
|
248
|
+
"surveys:delete",
|
|
249
|
+
"surveys:read",
|
|
250
|
+
"surveys:run",
|
|
251
|
+
"surveys:write"
|
|
252
|
+
],
|
|
253
|
+
"defaults": [
|
|
254
|
+
{
|
|
255
|
+
"decision": "Survey type",
|
|
256
|
+
"default": "NPS (0 to 10) for relationship feedback, CSAT (1 to 5) for a specific interaction",
|
|
257
|
+
"askHuman": false
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"decision": "Question wording",
|
|
261
|
+
"default": "The standard question for the type, with the brand name; the person may rewrite it",
|
|
262
|
+
"askHuman": false
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"decision": "Comment capture",
|
|
266
|
+
"default": "Score only (none); choose link to ask for a comment after the score",
|
|
267
|
+
"askHuman": false
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"decision": "Delivery channel",
|
|
271
|
+
"default": "Email",
|
|
272
|
+
"askHuman": false
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"decision": "Which list is asked",
|
|
276
|
+
"default": "None: a person always picks the audience",
|
|
277
|
+
"askHuman": true
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"decision": "Sending",
|
|
281
|
+
"default": "Never without the person confirming the audience size and the sender domain",
|
|
282
|
+
"askHuman": false
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"decision": "Stopping a survey",
|
|
286
|
+
"default": "Close it (keeps every answer); delete only when asked, after a confirmation",
|
|
287
|
+
"askHuman": false
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"decision": "Trend bucket",
|
|
291
|
+
"default": "Weekly for one survey, monthly for the org overview",
|
|
292
|
+
"askHuman": false
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
"peerDependencies": {
|
|
296
|
+
"react": ">=18",
|
|
297
|
+
"lucide-react": ">=0.400.0"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AccountNps: how the people at one company scored you.
|
|
5
|
+
*
|
|
6
|
+
* SDK operations, in order:
|
|
7
|
+
* 1. listCompanies, through CompanyPicker read find the company by name (skipped when you pass `companyId`)
|
|
8
|
+
* 2. getCompanySurveyNps (GET /feedback/companies/{company_id}/nps) read account NPS, split, coverage and each contact's latest score
|
|
9
|
+
* Pass `companyId` to show a company you already have open (for example in a company page). The account score is hidden by the API when there are too few answers.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Label, Skeleton } from "@graph8/sdk/ui";
|
|
14
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
15
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
16
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
17
|
+
import { StatStrip } from "@/g8/components/screen/stat-strip";
|
|
18
|
+
import { CompanyPicker } from "@/g8/pickers/components/company-picker";
|
|
19
|
+
import type { CompanyOption } from "@/g8/pickers/components/company-picker";
|
|
20
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
21
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
22
|
+
import { plural } from "@/g8/lib/plural";
|
|
23
|
+
import { bandOf, fullName, percent, scoreText, when } from "../lib/surveys-types";
|
|
24
|
+
|
|
25
|
+
interface CompanyNps {
|
|
26
|
+
company_id: number;
|
|
27
|
+
nps_score?: number | null;
|
|
28
|
+
suppressed: boolean;
|
|
29
|
+
promoters: number;
|
|
30
|
+
passives: number;
|
|
31
|
+
detractors: number;
|
|
32
|
+
total_respondents: number;
|
|
33
|
+
coverage: { rate: number; responded: number; total_contacts: number };
|
|
34
|
+
contacts: { contact_id: number; first_name?: string | null; last_name?: string | null; score?: number | null; classification?: string | null; responded?: boolean; last_responded_at?: string | null }[];
|
|
35
|
+
}
|
|
36
|
+
export interface AccountNpsProps { companyId?: number; className?: string }
|
|
37
|
+
|
|
38
|
+
export function AccountNps({ companyId, className }: AccountNpsProps) {
|
|
39
|
+
const [picked, setPicked] = useState<CompanyOption | null>(null);
|
|
40
|
+
const id = companyId ?? picked?.id;
|
|
41
|
+
const nps = useG8Query((signal) => g8Get<CompanyNps>(`/feedback/companies/${id}/nps`, {}, { signal }).then((r) => r.data), [id], { enabled: id != null, key: ["surveys", "company-nps", id ?? ""] });
|
|
42
|
+
const d = nps.data;
|
|
43
|
+
return (
|
|
44
|
+
<SectionCard title="Account NPS" description="How the people at one company scored you." className={className}>
|
|
45
|
+
<div className="grid gap-3">
|
|
46
|
+
{companyId == null ? <div><Label htmlFor="g8-sva-company" className="mb-1">Company</Label><CompanyPicker id="g8-sva-company" value={picked} onChange={setPicked} /></div> : null}
|
|
47
|
+
{id == null ? <EmptyState compact title="Choose a company" description="Its NPS, coverage and each contact's latest score appear here." /> : nps.error ? <ApiError error={nps.error} title="Could not load the account NPS" onRetry={() => void nps.refetch()} /> : !d ? <Skeleton className="h-24 w-full" aria-busy="true" /> : (
|
|
48
|
+
<>
|
|
49
|
+
<StatStrip items={[
|
|
50
|
+
{ label: "Account NPS", value: d.suppressed ? "-" : scoreText(d.nps_score, 0), hint: d.suppressed ? "Hidden: too few answers to publish a score." : `${plural(d.total_respondents, "person", "people")} answered.` },
|
|
51
|
+
{ label: "Coverage", value: percent(d.coverage.responded, d.coverage.total_contacts), hint: `${d.coverage.responded} of ${d.coverage.total_contacts} contacts answered.` },
|
|
52
|
+
{ label: "Split", value: `${d.promoters} / ${d.passives} / ${d.detractors}`, hint: "Promoters, passives, detractors." },
|
|
53
|
+
]} />
|
|
54
|
+
{d.contacts.length === 0 ? <p className="text-muted-foreground text-sm">No contact at this company has answered.</p> : (
|
|
55
|
+
<ul className="grid gap-1 text-sm">
|
|
56
|
+
{d.contacts.map((c) => (
|
|
57
|
+
<li key={c.contact_id} className="flex items-center justify-between gap-2 rounded-md border p-2">
|
|
58
|
+
<span>{fullName(c)}</span>
|
|
59
|
+
<span className="text-muted-foreground text-xs">{c.responded && c.score != null ? `${c.score} · ${bandOf("nps", c.score, c.classification)} · ${when(c.last_responded_at)}` : "No answer yet"}</span>
|
|
60
|
+
</li>
|
|
61
|
+
))}
|
|
62
|
+
</ul>
|
|
63
|
+
)}
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
</SectionCard>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ContactSurveyHistory: every survey answer one person has given, newest first.
|
|
5
|
+
*
|
|
6
|
+
* SDK operations, in order:
|
|
7
|
+
* 1. listContacts, through ContactPicker read find the person by name (skipped when you pass `contactId`)
|
|
8
|
+
* 2. getContactSurveyHistory (GET /feedback/contacts/{contact_id}/history) read their answers across all surveys
|
|
9
|
+
* Pass `contactId` to show a person you already have open (for example in a contact page).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Label, Skeleton } from "@graph8/sdk/ui";
|
|
14
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
15
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
16
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
17
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
18
|
+
import { ContactPicker } from "@/g8/pickers/components/contact-picker";
|
|
19
|
+
import type { ContactOption } from "@/g8/pickers/components/contact-picker";
|
|
20
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
21
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
22
|
+
import { bandOf, when } from "../lib/surveys-types";
|
|
23
|
+
|
|
24
|
+
interface ContactHistory { contact_id: number; responses: { survey_id: string; survey_name?: string | null; type: string; score: number; classification?: string | null; channel?: string | null; responded_at?: string | null }[] }
|
|
25
|
+
|
|
26
|
+
export interface ContactSurveyHistoryProps { contactId?: number; className?: string }
|
|
27
|
+
|
|
28
|
+
export function ContactSurveyHistory({ contactId, className }: ContactSurveyHistoryProps) {
|
|
29
|
+
const [picked, setPicked] = useState<ContactOption | null>(null);
|
|
30
|
+
const id = contactId ?? picked?.id;
|
|
31
|
+
const history = useG8Query((signal) => g8Get<ContactHistory>(`/feedback/contacts/${id}/history`, {}, { signal }).then((r) => r.data), [id], { enabled: id != null, key: ["surveys", "contact-history", id ?? ""] });
|
|
32
|
+
const rows = history.data?.responses ?? [];
|
|
33
|
+
return (
|
|
34
|
+
<SectionCard title="A person's survey history" description="Every survey this person has answered, newest first." className={className}>
|
|
35
|
+
<div className="grid gap-3">
|
|
36
|
+
{contactId == null ? <div><Label htmlFor="g8-sva-contact" className="mb-1">Contact</Label><ContactPicker id="g8-sva-contact" value={picked} onChange={setPicked} /></div> : null}
|
|
37
|
+
{id == null ? <EmptyState compact title="Choose a contact" description="Their answers across all surveys appear here." /> : history.error ? <ApiError error={history.error} title="Could not load the history" onRetry={() => void history.refetch()} /> : !history.data ? <Skeleton className="h-16 w-full" aria-busy="true" /> : rows.length === 0 ? (
|
|
38
|
+
<p className="text-muted-foreground text-sm">This person has not answered a survey.</p>
|
|
39
|
+
) : (
|
|
40
|
+
<ul className="grid gap-1 text-sm">
|
|
41
|
+
{rows.map((r, i) => (
|
|
42
|
+
<li key={`${r.survey_id}:${i}`} className="flex items-center justify-between gap-2 rounded-md border p-2">
|
|
43
|
+
<span className="min-w-0 truncate">{r.survey_name || r.type.toUpperCase()} <span className="text-muted-foreground text-xs">{when(r.responded_at)}</span></span>
|
|
44
|
+
<span className="flex items-center gap-2">Score {r.score}<StatusPill tone={r.classification === "promoter" || r.classification === "satisfied" ? "positive" : r.classification === "detractor" || r.classification === "dissatisfied" ? "negative" : "neutral"}>{bandOf(r.type, r.score, r.classification)}</StatusPill></span>
|
|
45
|
+
</li>
|
|
46
|
+
))}
|
|
47
|
+
</ul>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</SectionCard>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OrgSummary: org-wide NPS and CSAT across every survey, optionally for one audience list, with a rollup per survey.
|
|
5
|
+
*
|
|
6
|
+
* SDK operations, in order:
|
|
7
|
+
* 1. getSurveyOrgSummary (GET /feedback/org-summary?segment&bucket) read headline scores, split and per-survey rollup
|
|
8
|
+
* 2. listLists (GET /lists), through ListSelect read the optional audience filter
|
|
9
|
+
* A dash is "not enough answers", never a zero score.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Skeleton } from "@graph8/sdk/ui";
|
|
14
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
15
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
16
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
17
|
+
import { StatStrip } from "@/g8/components/screen/stat-strip";
|
|
18
|
+
import { ListSelect } from "@/g8/crm/components/list-select";
|
|
19
|
+
import type { ListSelectOption } from "@/g8/crm/components/list-select";
|
|
20
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
21
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
22
|
+
import { plural } from "@/g8/lib/plural";
|
|
23
|
+
import { BUCKETS, scoreText } from "../lib/surveys-types";
|
|
24
|
+
import type { Bucket, OrgSummary as OrgSummaryData } from "../lib/surveys-types";
|
|
25
|
+
|
|
26
|
+
const SELECT = "border-input bg-background h-9 rounded-md border px-2 text-sm";
|
|
27
|
+
|
|
28
|
+
export interface OrgSummaryProps {
|
|
29
|
+
/** Called with a survey id when a row of the rollup is chosen. */
|
|
30
|
+
onOpenSurvey?: (surveyId: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function OrgSummary({ onOpenSurvey }: OrgSummaryProps) {
|
|
34
|
+
const [bucket, setBucket] = useState<Bucket>("month");
|
|
35
|
+
const [segment, setSegment] = useState<ListSelectOption | null>(null);
|
|
36
|
+
const org = useG8Query(
|
|
37
|
+
(signal) => g8Get<OrgSummaryData>("/feedback/org-summary", { bucket, segment: segment ? String(segment.id) : undefined }, { signal }).then((r) => r.data),
|
|
38
|
+
[bucket, segment?.id],
|
|
39
|
+
{ key: ["surveys", "summary", "org", bucket, segment?.id ?? ""] },
|
|
40
|
+
);
|
|
41
|
+
const d = org.data;
|
|
42
|
+
return (
|
|
43
|
+
<div className="grid gap-4">
|
|
44
|
+
<div className="flex flex-wrap items-end gap-3">
|
|
45
|
+
<div className="min-w-56">
|
|
46
|
+
<label htmlFor="g8-svo-segment" className="text-muted-foreground mb-1 block text-sm">Audience</label>
|
|
47
|
+
<ListSelect id="g8-svo-segment" label="Audience" emptyOption="All contacts" value={segment?.id ?? ""} valueLabel={segment?.title} selectClassName={SELECT} onChange={setSegment} />
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<label htmlFor="g8-svo-bucket" className="text-muted-foreground mb-1 block text-sm">Trend by</label>
|
|
51
|
+
<select id="g8-svo-bucket" className={SELECT} value={bucket} onChange={(e) => setBucket(e.target.value as Bucket)}>{BUCKETS.map((b) => <option key={b.value} value={b.value}>{b.label}</option>)}</select>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
{org.error ? <ApiError error={org.error} title="Could not load the overview" onRetry={() => void org.refetch()} /> : !d ? <Skeleton className="h-32 w-full" aria-busy="true" /> : (
|
|
55
|
+
<>
|
|
56
|
+
<StatStrip
|
|
57
|
+
items={[
|
|
58
|
+
{ label: "Org NPS", value: scoreText(d.org_nps.nps_score, 0), hint: `${plural(d.org_nps.total_respondents, "answer")}. A dash means too few answers.` },
|
|
59
|
+
{ label: "Promoters / passives / detractors", value: `${d.org_nps.promoters} / ${d.org_nps.passives} / ${d.org_nps.detractors}` },
|
|
60
|
+
{ label: "Org CSAT", value: scoreText(d.org_csat.csat_avg, 1), hint: `${plural(d.org_csat.total_respondents, "answer")}, scored 1 to 5.` },
|
|
61
|
+
{ label: "In-app CSAT", value: scoreText(d.in_app_csat_avg, 1), hint: `${plural(d.in_app_respondents ?? 0, "team answer")}.` },
|
|
62
|
+
]}
|
|
63
|
+
/>
|
|
64
|
+
<SectionCard title="By survey" description={plural(d.per_survey.length, "survey")} flush>
|
|
65
|
+
{d.per_survey.length === 0 ? <EmptyState title="No surveys yet" description="Create a survey to start collecting scores." compact /> : (
|
|
66
|
+
<ul className="grid">
|
|
67
|
+
{d.per_survey.map((p) => (
|
|
68
|
+
<li key={p.survey_id} className="flex flex-wrap items-center justify-between gap-2 border-t p-3 text-sm first:border-t-0">
|
|
69
|
+
{onOpenSurvey ? <button type="button" className="min-w-0 text-left font-medium underline-offset-2 hover:underline" aria-label={`Open ${p.name ?? p.type}`} onClick={() => onOpenSurvey(p.survey_id)}>{p.name || p.type.toUpperCase()}</button> : <span className="font-medium">{p.name || p.type.toUpperCase()}</span>}
|
|
70
|
+
<span className="text-muted-foreground text-xs">
|
|
71
|
+
{p.type === "nps" ? `NPS ${scoreText(p.nps_score, 0)}` : `CSAT ${scoreText(p.csat_avg, 1)}`} · {plural(p.total_respondents, "answer")}{p.sent_count != null ? ` of ${p.sent_count.toLocaleString()} asked` : ""}
|
|
72
|
+
</span>
|
|
73
|
+
</li>
|
|
74
|
+
))}
|
|
75
|
+
</ul>
|
|
76
|
+
)}
|
|
77
|
+
</SectionCard>
|
|
78
|
+
</>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|