@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,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ThreadActions: the small things you do to one conversation besides replying: mark it read (or, on LinkedIn,
|
|
5
|
+
* unread), take a tag off, take a teammate off, and block the sender.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order (the thread's own id and channel decide the route):
|
|
8
|
+
* Read state POST /inbox/emails/{id}/mark-read (aiInbox.markInboxEmailRead) write
|
|
9
|
+
* POST /inbox/sms/{id}/mark-read (aiInbox.markInboxSmsRead) write
|
|
10
|
+
* POST /inbox/linkedin/accounts/{account}/conversations/{id}/read|unread (aiInbox.markInboxLinkedinConversationRead|Unread) write
|
|
11
|
+
* There is no "mark unread" for email or SMS in the API, so those offer only "Mark as read".
|
|
12
|
+
* Remove tag DELETE /inbox/channels/{id}/tags?tag_id&channel_type (aiInbox.removeChannelTag) DESTRUCTIVE -> ConfirmSpendDialog
|
|
13
|
+
* Unassign DELETE /inbox/channels/{id}/assign?assignee_email&channel_type (aiInbox.unassignChannel) DESTRUCTIVE -> ConfirmSpendDialog
|
|
14
|
+
* Block sender POST /inbox/blocked-senders { mailbox, sender_email } (aiInbox.blockInboxSender) write, email threads only;
|
|
15
|
+
* asked inline first because future mail from them stops arriving (BlockedSenders can undo it)
|
|
16
|
+
* Applying a tag and assigning are TagAndAssign. Nothing here sends a message.
|
|
17
|
+
*/
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
import { Ban, CheckCheck, MailOpen, X } from "lucide-react";
|
|
20
|
+
import { Button } from "@graph8/sdk/ui";
|
|
21
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
22
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
23
|
+
import { g8Delete, g8Post } from "@/g8/lib/g8-client";
|
|
24
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
25
|
+
import { canMarkRead, canMarkUnread, channelTypeOf } from "../lib/inbox-manage";
|
|
26
|
+
import type { InboxThread } from "../lib/inbox-types";
|
|
27
|
+
|
|
28
|
+
type Pending = { kind: "tag"; id: string; name: string } | { kind: "assignee"; email: string; name: string } | null;
|
|
29
|
+
|
|
30
|
+
export interface ThreadActionsProps {
|
|
31
|
+
thread: InboxThread;
|
|
32
|
+
/** Called after any change; reload the thread here. */
|
|
33
|
+
onChanged?: () => void;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readPath(thread: InboxThread, read: boolean): string | null {
|
|
38
|
+
const id = encodeURIComponent(thread.id);
|
|
39
|
+
if (thread.channel === "sms") return read ? `/inbox/sms/${id}/mark-read` : null;
|
|
40
|
+
if (thread.channel === "linkedin") {
|
|
41
|
+
return thread.linkedin_account_id == null ? null : `/inbox/linkedin/accounts/${thread.linkedin_account_id}/conversations/${id}/${read ? "read" : "unread"}`;
|
|
42
|
+
}
|
|
43
|
+
return read ? `/inbox/emails/${id}/mark-read` : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function ThreadActions({ thread, onChanged, className }: ThreadActionsProps) {
|
|
47
|
+
const [pending, setPending] = useState<Pending>(null);
|
|
48
|
+
const [blocking, setBlocking] = useState(false);
|
|
49
|
+
const [notice, setNotice] = useState<string | null>(null);
|
|
50
|
+
const channelType = channelTypeOf(thread.channel);
|
|
51
|
+
const sender = thread.channel === "email" ? (thread.contact?.email ?? "") : "";
|
|
52
|
+
const channelQuery = { channel_type: channelType, linkedin_account_id: thread.channel === "linkedin" ? thread.linkedin_account_id : undefined };
|
|
53
|
+
const done = (message: string) => { setNotice(message); onChanged?.(); };
|
|
54
|
+
|
|
55
|
+
const mark = useG8Mutation(async (read: boolean) => {
|
|
56
|
+
const path = readPath(thread, read);
|
|
57
|
+
if (!path) throw new Error("This conversation cannot be marked that way.");
|
|
58
|
+
await g8Post(path);
|
|
59
|
+
done(read ? "Marked as read." : "Marked as unread.");
|
|
60
|
+
}, { invalidates: ["inbox"] });
|
|
61
|
+
const remove = useG8Mutation(async (p: NonNullable<Pending>) => {
|
|
62
|
+
const base = `/inbox/channels/${encodeURIComponent(thread.id)}`;
|
|
63
|
+
if (p.kind === "tag") await g8Delete(`${base}/tags`, { query: { ...channelQuery, tag_id: p.id } });
|
|
64
|
+
else await g8Delete(`${base}/assign`, { query: { ...channelQuery, assignee_email: p.email } });
|
|
65
|
+
done(p.kind === "tag" ? `Removed the tag "${p.name}".` : `Unassigned ${p.name}.`);
|
|
66
|
+
}, { invalidates: ["inbox"] });
|
|
67
|
+
const block = useG8Mutation(async () => {
|
|
68
|
+
await g8Post("/inbox/blocked-senders", { mailbox: thread.mailbox, sender_email: sender });
|
|
69
|
+
setBlocking(false);
|
|
70
|
+
done(`Blocked ${sender}.`);
|
|
71
|
+
}, { invalidates: ["inbox"] });
|
|
72
|
+
|
|
73
|
+
const confirm = async () => {
|
|
74
|
+
const current = pending;
|
|
75
|
+
if (!current) return;
|
|
76
|
+
try {
|
|
77
|
+
await remove.mutateAsync(current);
|
|
78
|
+
} catch {
|
|
79
|
+
/* surfaced below */
|
|
80
|
+
}
|
|
81
|
+
setPending(null);
|
|
82
|
+
};
|
|
83
|
+
const canBlock = thread.channel === "email" && Boolean(thread.mailbox) && Boolean(sender);
|
|
84
|
+
const label = (n: string | null | undefined, fallback: string) => n || fallback;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<section aria-label="Conversation actions" className={`space-y-3 rounded-lg border p-3 text-sm ${className ?? ""}`}>
|
|
88
|
+
<h3 className="font-medium">Manage conversation</h3>
|
|
89
|
+
<div className="flex flex-wrap gap-2">
|
|
90
|
+
{canMarkRead(thread) ? <Button size="sm" variant="outline" loading={mark.loading} onClick={() => void mark.mutate(true)}><MailOpen className="mr-1 size-4" aria-hidden />Mark as read</Button> : null}
|
|
91
|
+
{canMarkUnread(thread) ? <Button size="sm" variant="outline" disabled={mark.loading} onClick={() => void mark.mutate(false)}>Mark as unread</Button> : null}
|
|
92
|
+
{canBlock && !blocking ? <Button size="sm" variant="outline" onClick={() => setBlocking(true)}><Ban className="mr-1 size-4" aria-hidden />Block sender</Button> : null}
|
|
93
|
+
</div>
|
|
94
|
+
{blocking ? (
|
|
95
|
+
<div role="group" aria-label="Block this sender" className="flex flex-wrap items-center gap-2 rounded-md border border-border p-2">
|
|
96
|
+
<span>Stop receiving mail from {sender} in {thread.mailbox}?</span>
|
|
97
|
+
<Button size="sm" loading={block.loading} onClick={() => void block.mutate(undefined)}>Block {sender}</Button>
|
|
98
|
+
<Button size="sm" variant="ghost" onClick={() => setBlocking(false)}>Cancel</Button>
|
|
99
|
+
</div>
|
|
100
|
+
) : null}
|
|
101
|
+
{thread.tags.length > 0 || thread.assignees.length > 0 ? (
|
|
102
|
+
<ul aria-label="Remove from conversation" className="space-y-1">
|
|
103
|
+
{thread.tags.filter((t) => t.id).map((t) => (
|
|
104
|
+
<li key={`t-${t.id}`} className="flex items-center justify-between gap-2">
|
|
105
|
+
<span>Tag: {label(t.name, String(t.id))}</span>
|
|
106
|
+
<Button size="sm" variant="ghost" aria-label={`Remove tag ${label(t.name, String(t.id))}`} onClick={() => setPending({ kind: "tag", id: String(t.id), name: label(t.name, String(t.id)) })}><X className="size-4" aria-hidden /></Button>
|
|
107
|
+
</li>
|
|
108
|
+
))}
|
|
109
|
+
{thread.assignees.map((a) => (
|
|
110
|
+
<li key={`a-${a.email}`} className="flex items-center justify-between gap-2">
|
|
111
|
+
<span>Assigned to {label(a.name, a.email)}</span>
|
|
112
|
+
<Button size="sm" variant="ghost" aria-label={`Unassign ${label(a.name, a.email)}`} onClick={() => setPending({ kind: "assignee", email: a.email, name: label(a.name, a.email) })}><X className="size-4" aria-hidden /></Button>
|
|
113
|
+
</li>
|
|
114
|
+
))}
|
|
115
|
+
</ul>
|
|
116
|
+
) : null}
|
|
117
|
+
{[mark.error, remove.error, block.error].map((e, i) => (e ? <ApiError key={i} error={e} title="Could not update the conversation" /> : null))}
|
|
118
|
+
{notice ? <p role="status" className="text-xs text-muted-foreground"><CheckCheck className="mr-1 inline size-3" aria-hidden />{notice}</p> : null}
|
|
119
|
+
|
|
120
|
+
<ConfirmSpendDialog
|
|
121
|
+
open={pending != null}
|
|
122
|
+
tier="destructive"
|
|
123
|
+
title={pending?.kind === "tag" ? `Remove the tag "${pending.name}"?` : `Unassign ${pending?.name ?? ""}?`}
|
|
124
|
+
description={pending?.kind === "tag" ? "The tag comes off this conversation only. Other conversations keep it." : "They no longer own this conversation. You can assign them again later."}
|
|
125
|
+
confirmLabel={pending?.kind === "tag" ? "Remove tag" : "Unassign"}
|
|
126
|
+
affected={1}
|
|
127
|
+
confirming={remove.loading}
|
|
128
|
+
onConfirm={() => void confirm()}
|
|
129
|
+
onCancel={() => setPending(null)}
|
|
130
|
+
/>
|
|
131
|
+
</section>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -8,8 +8,8 @@ import type { ReactNode } from "react";
|
|
|
8
8
|
|
|
9
9
|
const TONES = {
|
|
10
10
|
neutral: "bg-muted text-foreground",
|
|
11
|
-
info: "bg-
|
|
12
|
-
warn: "bg-
|
|
11
|
+
info: "bg-info text-info-foreground",
|
|
12
|
+
warn: "bg-warning text-warning-foreground",
|
|
13
13
|
} as const;
|
|
14
14
|
|
|
15
15
|
export function ThreadBadge({ children, tone = "neutral" }: { children: ReactNode; tone?: keyof typeof TONES }) {
|
|
@@ -18,6 +18,7 @@ import { ApiError } from "@/g8/components/api-error";
|
|
|
18
18
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
19
19
|
import { useInboxThread } from "../hooks/use-inbox-thread";
|
|
20
20
|
import { contactLabel, formatWhen, stripHtml, type InboxMessage, type InboxThread } from "../lib/inbox-types";
|
|
21
|
+
import { isNotFound } from "../lib/inbox-manage";
|
|
21
22
|
|
|
22
23
|
export interface ThreadViewProps {
|
|
23
24
|
/** A row from InboxList. Only `id` and `channel` are required; the rest is loaded fresh. */
|
|
@@ -50,7 +51,7 @@ function MessageBubble({ message, outbound }: { message: InboxMessage; outbound:
|
|
|
50
51
|
function ThreadHeader({ thread }: { thread: InboxThread }) {
|
|
51
52
|
return (
|
|
52
53
|
<header className="space-y-1 border-b pb-3">
|
|
53
|
-
<h2 className="text-base font-
|
|
54
|
+
<h2 className="text-base font-medium">{thread.subject || contactLabel(thread)}</h2>
|
|
54
55
|
<p className="text-sm text-muted-foreground">
|
|
55
56
|
{contactLabel(thread)}
|
|
56
57
|
{thread.contact?.company ? ` at ${thread.contact.company}` : ""} · <span className="capitalize">{thread.channel}</span>
|
|
@@ -73,6 +74,7 @@ function ThreadHeader({ thread }: { thread: InboxThread }) {
|
|
|
73
74
|
export function ThreadView({ thread: row, children, className }: ThreadViewProps) {
|
|
74
75
|
const { data, loading, error, refetch } = useInboxThread(row.id, row.channel);
|
|
75
76
|
|
|
77
|
+
if (isNotFound(error)) return <EmptyState title="Conversation not found" description="It may have been removed, or it is not shared with this API key. Hidden conversations answer exactly like missing ones." />;
|
|
76
78
|
if (error) return <ApiError error={error} title="Could not load this conversation" onRetry={() => void refetch()} />;
|
|
77
79
|
if (loading && !data) {
|
|
78
80
|
return (
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useInboxMailboxes: EVERY mailbox the caller can see, with whether they may send as it. One page of 100 is read, then the
|
|
5
|
+
* next, until the API says there is no more (at most 20 pages); `truncated` is true when that cap was hit, and the picker
|
|
6
|
+
* must say so (FirstNotice). A mailbox beyond the first page can therefore never be silently missing from a sender picker.
|
|
7
|
+
*
|
|
8
|
+
* SDK operations: GET /inbox/mailboxes/paginated?page&page_size (aiInbox.listInboxMailboxesPaginated), read tier.
|
|
9
|
+
*/
|
|
10
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
11
|
+
import { useAllPages } from "@/g8/hooks/use-all-pages";
|
|
12
|
+
import { hasNextOf } from "@/g8/lib/paging";
|
|
13
|
+
import { mailboxChoices } from "../lib/inbox-manage";
|
|
14
|
+
import type { MailboxChoice } from "../lib/inbox-manage";
|
|
15
|
+
|
|
16
|
+
const PAGE = 100;
|
|
17
|
+
|
|
18
|
+
export function useInboxMailboxes(options: { enabled?: boolean } = {}) {
|
|
19
|
+
const all = useAllPages<MailboxChoice>(
|
|
20
|
+
async (page, signal) => {
|
|
21
|
+
const env = await g8Get("/inbox/mailboxes/paginated", { page, page_size: PAGE }, { signal });
|
|
22
|
+
const rows = mailboxChoices(env);
|
|
23
|
+
return { rows, hasNext: hasNextOf(env, PAGE, rows.length) };
|
|
24
|
+
},
|
|
25
|
+
[],
|
|
26
|
+
{ enabled: options.enabled, pageSize: PAGE },
|
|
27
|
+
);
|
|
28
|
+
const seen = new Set<string>();
|
|
29
|
+
const items = all.items.filter((m) => !seen.has(m.address.toLowerCase()) && seen.add(m.address.toLowerCase()));
|
|
30
|
+
return { ...all, items, data: all.loaded ? items : undefined };
|
|
31
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure helpers for the inbox management parts: composing a new message, managing tags and blocked senders,
|
|
5
|
+
* server-side search, and the per-thread actions (read state, unassign, remove a tag). No SDK calls here.
|
|
6
|
+
*
|
|
7
|
+
* Several of these endpoints answer with an open `{ items: [...] }` payload (no response schema in the
|
|
8
|
+
* contract), so every reader below is defensive: it accepts a bare list, `{ items }`, or the named list the
|
|
9
|
+
* route actually uses, and skips rows it cannot make sense of.
|
|
10
|
+
*/
|
|
11
|
+
import type { InboxChannel, InboxTag, InboxThread } from "./inbox-types";
|
|
12
|
+
import { stripHtml } from "./inbox-types";
|
|
13
|
+
|
|
14
|
+
/** Rows from a bare list, `{ data: [...] }`, `{ items: [...] }` or a named list such as `{ tags: [...] }`. */
|
|
15
|
+
export function rowsFrom<T = Record<string, unknown>>(payload: unknown, ...names: string[]): T[] {
|
|
16
|
+
const list = (v: unknown): T[] | null => (Array.isArray(v) ? (v as T[]) : null);
|
|
17
|
+
const direct = list(payload);
|
|
18
|
+
if (direct) return direct;
|
|
19
|
+
const obj = (payload ?? {}) as Record<string, unknown>;
|
|
20
|
+
const inner = (obj.data && typeof obj.data === "object" ? (obj.data as Record<string, unknown>) : obj) as Record<string, unknown>;
|
|
21
|
+
return list(inner) ?? list(inner.items) ?? names.map((n) => list(inner[n])).find(Boolean) ?? list(obj.items) ?? [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --- Compose ----------------------------------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export interface MailboxChoice {
|
|
27
|
+
/** The address the message is sent from (`from_mailbox`). */
|
|
28
|
+
address: string;
|
|
29
|
+
label: string;
|
|
30
|
+
/** False when the caller may see the mailbox but not send as it; `reason` says why. */
|
|
31
|
+
canSend: boolean;
|
|
32
|
+
reason?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Sender choices from `GET /inbox/mailboxes/paginated` (rows carry `can_send` / `send_blocked_reason`). */
|
|
36
|
+
export function mailboxChoices(payload: unknown): MailboxChoice[] {
|
|
37
|
+
const out: MailboxChoice[] = [];
|
|
38
|
+
const seen = new Set<string>();
|
|
39
|
+
for (const row of rowsFrom<Record<string, unknown>>(payload, "mailboxes")) {
|
|
40
|
+
const address = String(row?.mailbox ?? row?.email ?? "").trim();
|
|
41
|
+
if (!address || seen.has(address.toLowerCase())) continue;
|
|
42
|
+
seen.add(address.toLowerCase());
|
|
43
|
+
const canSend = row.can_send !== false;
|
|
44
|
+
const reason = typeof row.send_blocked_reason === "string" ? row.send_blocked_reason : undefined;
|
|
45
|
+
out.push({ address, label: address, canSend, reason: canSend ? undefined : (reason ?? "not allowed to send as this mailbox") });
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const EMAIL = /^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/;
|
|
51
|
+
export const isEmail = (value: string): boolean => EMAIL.test(value.trim());
|
|
52
|
+
|
|
53
|
+
/** Split "a@x.com, b@y.com; c@z.com" into trimmed, de-duplicated addresses (case-insensitive). */
|
|
54
|
+
export function splitAddresses(value: string): string[] {
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
const out: string[] = [];
|
|
57
|
+
for (const part of value.split(/[,;\n]/)) {
|
|
58
|
+
const v = part.trim();
|
|
59
|
+
if (v && !seen.has(v.toLowerCase())) {
|
|
60
|
+
seen.add(v.toLowerCase());
|
|
61
|
+
out.push(v);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A phone number the SMS route will accept: E.164 (`+` then 8 to 15 digits). */
|
|
68
|
+
export const isPhone = (value: string): boolean => /^\+[1-9]\d{7,14}$/.test(value.replace(/[\s().-]/g, ""));
|
|
69
|
+
export const normalizePhone = (value: string): string => value.replace(/[\s().-]/g, "");
|
|
70
|
+
|
|
71
|
+
const escapeHtml = (s: string) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
72
|
+
|
|
73
|
+
/** The compose API takes an HTML body; what people type is plain text. Paragraphs become `<p>`, newlines `<br>`. */
|
|
74
|
+
export function textToHtml(text: string): string {
|
|
75
|
+
return text
|
|
76
|
+
.trim()
|
|
77
|
+
.split(/\n{2,}/)
|
|
78
|
+
.map((p) => `<p>${escapeHtml(p).replace(/\n/g, "<br>")}</p>`)
|
|
79
|
+
.join("");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface EmailComposeInput {
|
|
83
|
+
from: string;
|
|
84
|
+
to: string;
|
|
85
|
+
cc?: string;
|
|
86
|
+
subject: string;
|
|
87
|
+
body: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** What is still missing before an email may be sent, in the order the form asks for it. Empty means ready. */
|
|
91
|
+
export function emailComposeProblems(input: EmailComposeInput): string[] {
|
|
92
|
+
const problems: string[] = [];
|
|
93
|
+
if (!input.from.trim()) problems.push("Choose the mailbox to send from.");
|
|
94
|
+
const to = splitAddresses(input.to);
|
|
95
|
+
if (to.length === 0) problems.push("Add at least one recipient.");
|
|
96
|
+
else if (to.some((a) => !isEmail(a))) problems.push("One of the recipient addresses is not valid.");
|
|
97
|
+
if (splitAddresses(input.cc ?? "").some((a) => !isEmail(a))) problems.push("One of the Cc addresses is not valid.");
|
|
98
|
+
if (!input.subject.trim()) problems.push("Add a subject.");
|
|
99
|
+
if (!input.body.trim()) problems.push("Write the message.");
|
|
100
|
+
return problems;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SmsComposeInput {
|
|
104
|
+
from: string;
|
|
105
|
+
to: string;
|
|
106
|
+
body: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function smsComposeProblems(input: SmsComposeInput): string[] {
|
|
110
|
+
const problems: string[] = [];
|
|
111
|
+
if (!input.from.trim()) problems.push("Choose the number to text from.");
|
|
112
|
+
if (!input.to.trim()) problems.push("Add the phone number to text.");
|
|
113
|
+
else if (!isPhone(input.to)) problems.push("Use the full number with country code, for example +14155550100.");
|
|
114
|
+
if (!input.body.trim()) problems.push("Write the message.");
|
|
115
|
+
return problems;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- Tags -------------------------------------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
export interface ManagedTag {
|
|
121
|
+
id: string;
|
|
122
|
+
name: string;
|
|
123
|
+
description: string;
|
|
124
|
+
color: string;
|
|
125
|
+
/** Built-in tags: "system" cannot be edited or deleted, "sdr_ai" cannot be renamed or deleted. */
|
|
126
|
+
policy: "system" | "sdr_ai" | "user";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Tags from `GET /workflows/inbox-tags` (`{ tags: [...], total }`, with or without an envelope). */
|
|
130
|
+
export function managedTags(payload: unknown): ManagedTag[] {
|
|
131
|
+
const out: ManagedTag[] = [];
|
|
132
|
+
for (const row of rowsFrom<Record<string, unknown>>(payload, "tags")) {
|
|
133
|
+
const id = row?.id == null ? "" : String(row.id);
|
|
134
|
+
if (!id) continue;
|
|
135
|
+
const policy = row.policy === "system" || row.policy === "sdr_ai" || row.is_system === true ? (row.policy === "sdr_ai" ? "sdr_ai" : "system") : "user";
|
|
136
|
+
out.push({ id, name: String(row.name ?? id), description: String(row.description ?? ""), color: String(row.color ?? ""), policy });
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const asInboxTags = (tags: ManagedTag[]): InboxTag[] => tags.map((t) => ({ id: t.id, name: t.name }));
|
|
142
|
+
|
|
143
|
+
export const canEditTag = (tag: ManagedTag): boolean => tag.policy !== "system";
|
|
144
|
+
export const canRenameTag = (tag: ManagedTag): boolean => tag.policy === "user";
|
|
145
|
+
export const canDeleteTag = (tag: ManagedTag): boolean => tag.policy === "user";
|
|
146
|
+
|
|
147
|
+
/** Only the fields that changed, for `PUT /inbox/tags/{id}` (which writes exactly what it is sent). */
|
|
148
|
+
export function tagChanges(
|
|
149
|
+
loaded: Pick<ManagedTag, "name" | "description" | "color">,
|
|
150
|
+
draft: { name: string; description: string; color: string },
|
|
151
|
+
): Record<string, string> {
|
|
152
|
+
const out: Record<string, string> = {};
|
|
153
|
+
if (draft.name.trim() !== loaded.name) out.name = draft.name.trim();
|
|
154
|
+
if (draft.description.trim() !== loaded.description) out.description = draft.description.trim();
|
|
155
|
+
if (draft.color.trim() !== loaded.color) out.color = draft.color.trim();
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const isHexColor = (v: string): boolean => v === "" || /^#[0-9a-f]{6}$/i.test(v.trim());
|
|
160
|
+
|
|
161
|
+
// --- Blocked senders --------------------------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
export interface BlockedSender {
|
|
164
|
+
id: string;
|
|
165
|
+
email: string;
|
|
166
|
+
mailbox: string;
|
|
167
|
+
reason: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function blockedSenders(payload: unknown): BlockedSender[] {
|
|
171
|
+
const out: BlockedSender[] = [];
|
|
172
|
+
for (const row of rowsFrom<Record<string, unknown>>(payload, "blocked_senders", "senders")) {
|
|
173
|
+
const id = row?.id == null ? "" : String(row.id);
|
|
174
|
+
const email = String(row?.sender_email ?? row?.email ?? "");
|
|
175
|
+
if (id && email) out.push({ id, email, mailbox: String(row.mailbox ?? ""), reason: String(row.reason ?? "") });
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- Per-thread actions -----------------------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
/** The `channel_type` the `/inbox/channels/{id}/...` routes use for a thread of this channel. */
|
|
183
|
+
export function channelTypeOf(channel: InboxChannel | string): "email" | "sms" | "heyreach" {
|
|
184
|
+
return channel === "sms" ? "sms" : channel === "linkedin" ? "heyreach" : "email";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Marking a thread unread exists only for LinkedIn; email and SMS can only be marked read. */
|
|
188
|
+
export const canMarkUnread = (thread: Pick<InboxThread, "channel" | "linkedin_account_id">): boolean =>
|
|
189
|
+
thread.channel === "linkedin" && thread.linkedin_account_id != null;
|
|
190
|
+
|
|
191
|
+
/** Can this thread be marked read at all? (LinkedIn needs its account id for the route.) */
|
|
192
|
+
export const canMarkRead = (thread: Pick<InboxThread, "channel" | "linkedin_account_id">): boolean =>
|
|
193
|
+
thread.channel !== "linkedin" || thread.linkedin_account_id != null;
|
|
194
|
+
|
|
195
|
+
// --- Server-side search -----------------------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
export interface SearchHit {
|
|
198
|
+
id: string;
|
|
199
|
+
channel: "email" | "linkedin";
|
|
200
|
+
title: string;
|
|
201
|
+
subtitle: string;
|
|
202
|
+
preview: string;
|
|
203
|
+
when?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const str = (v: unknown): string => (typeof v === "string" ? v : "");
|
|
207
|
+
|
|
208
|
+
/** A search result row (email thread or LinkedIn conversation) as a list entry. Null when it has no usable id. */
|
|
209
|
+
export function searchHit(channel: "email" | "linkedin", row: Record<string, unknown>): SearchHit | null {
|
|
210
|
+
const id = row.id ?? row.email_id ?? row.thread_id ?? row.conversation_id ?? row.chat_id;
|
|
211
|
+
if (id == null || id === "") return null;
|
|
212
|
+
const contact = (row.contact ?? {}) as Record<string, unknown>;
|
|
213
|
+
const first = Array.isArray(row.messages) ? ((row.messages as Array<Record<string, unknown>>)[0] ?? {}) : {};
|
|
214
|
+
const who = str(contact.name) || str(row.contact_name) || str(row.from_name) || str(row.from_email) || str(row.sender) || str(contact.email);
|
|
215
|
+
const title = str(row.subject) || who || (channel === "linkedin" ? "LinkedIn conversation" : "Email conversation");
|
|
216
|
+
const preview = stripHtml(str(row.snippet) || str(row.preview) || str(row.last_message) || str(first.content) || str(row.body)).replace(/\s+/g, " ").slice(0, 140);
|
|
217
|
+
const when = str(row.updated_at) || str(row.last_message_at) || str(row.created_at) || undefined;
|
|
218
|
+
return { id: String(id), channel, title, subtitle: who && who !== title ? who : str(row.mailbox), preview, when };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// --- Hidden or missing threads ------------------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The dev API answers a uniform 404 for a thread that does not exist AND for one this key may not see (an org key sees
|
|
225
|
+
* shared threads only), so the two cannot be told apart. Views say so instead of showing a generic failure.
|
|
226
|
+
*/
|
|
227
|
+
export const isNotFound = (error: unknown): boolean => (error as { status?: number } | null)?.status === 404;
|
|
@@ -11,7 +11,11 @@ Depends on: `foundation`. Install: `npx @graph8/sdk add integrations`.
|
|
|
11
11
|
|
|
12
12
|
- **Connections tab**: the five CRMs and the workspace tools. "Manage sync" opens a side panel with sync switches, job progress and a manual
|
|
13
13
|
push (deep link `?id=hubspot`; a webhook is `?tab=webhooks&id=<webhook id>`). Connecting is a link out to the graph8 app, because OAuth consent only runs there.
|
|
14
|
+
- A connected CRM's panel also has: **Health** (live status, connection record, CRM account, last historic import), **Sync now** (push or pull contacts, deals and leads as background jobs you can follow, with a lead push preview; each sync asks first), **CRM records** (browse contacts, companies, deals, leads and the activity log inside the CRM, read only), **Fields** (the CRM's fields beside graph8's) and **Historic import** (scan, review the counts, import, then sync later changes).
|
|
15
|
+
- **SugarCRM** is the one CRM the API can sign in to (instance address, user, password), so its panel has a **Connection** tab instead of a link-out; the other four link out to graph8.
|
|
14
16
|
- **Webhooks tab**: create, pause, rotate and delete webhooks, read deliveries. The secret is shown once.
|
|
17
|
+
- **Tools tab**: Slack, Google Workspace and Roam with live status and a "Connect in graph8" link for any that is not connected; Google also has a connection test and a Drive folder browser.
|
|
18
|
+
- **Ad accounts tab**: the ad platforms graph8 can sync audiences to, their accounts, a warning for any account the platform has paused, and a connect link-out.
|
|
15
19
|
- Props (all optional): `connectUrl`, `defaultWebhookUrl`, `pushRecords`, `toolbarActions`, `renderDetailActions(record)`, `extraDetailTabs` (each `{ id, label, render(record) }`), where `record` is `{ kind: "crm", provider }` or `{ kind: "webhook", id }`.
|
|
16
20
|
|
|
17
21
|
## Components
|
|
@@ -21,6 +25,14 @@ Depends on: `foundation`. Install: `npx @graph8/sdk add integrations`.
|
|
|
21
25
|
| `IntegrationList` | HubSpot, Salesforce, Pipedrive, Zoho and SugarCRM status, plus Slack, Google Workspace and Roam. Connect links to the app; Disconnect is confirmed. | read, destructive |
|
|
22
26
|
| `SyncStatus` | One CRM: health, enable/disable continuous push and pull, job progress, manual contact push. | read, external |
|
|
23
27
|
| `WebhookManager` | Create, pause, rotate and delete webhooks; secret shown once; delivery log; server-side verification note. | read, external, destructive |
|
|
28
|
+
| `ConnectionHealth` | One CRM connection: live status, record, account metadata, last historic import. | read |
|
|
29
|
+
| `SyncTriggers` | Run a sync now (contacts, deals, leads, push or pull), with a limit, a lead preview and job progress. | read, external |
|
|
30
|
+
| `CrmRecords` | Browse the records and activity log inside a connected CRM, read only. | read |
|
|
31
|
+
| `FieldMappings` | The CRM's fields next to the graph8 fields they can map to. | read |
|
|
32
|
+
| `MigrationPanel` | Scan a CRM, review, import its history, sync later changes. | read, external, destructive |
|
|
33
|
+
| `SugarCrmConnect` | Connect, check, update and remove a SugarCRM login. | read, external, destructive |
|
|
34
|
+
| `WorkspaceTools` | Slack, Google Workspace and Roam status, connect link-outs, Google test and Drive browser. | read |
|
|
35
|
+
| `AdAccounts` | Ad platforms, accounts and paused-account warnings. | read |
|
|
24
36
|
|
|
25
37
|
`external` and `destructive` actions open `ConfirmSpendDialog` first (they write to the customer's CRM,
|
|
26
38
|
change where data is sent, or remove a subscription).
|
|
@@ -50,7 +62,8 @@ reach the browser, which is why `WebhookManager` only displays it once at creati
|
|
|
50
62
|
|
|
51
63
|
- CRM connected via the app's OAuth screen (`where: "app"`). Verify: `GET /crm-syncs/{provider}/status` -> `connected: true`.
|
|
52
64
|
- A public https webhook route with `G8_WEBHOOK_SECRET` set (`where: "app"`).
|
|
53
|
-
- The API key's user has integrations/webhooks admin rights (`where: "api"`).
|
|
65
|
+
- The API key's user has integrations/webhooks admin rights (`where: "api"`). Migrations and the SugarCRM sign-in need an organisation admin.
|
|
66
|
+
- The proxy `allowedPrefixes` include `integrations`, `crm-syncs`, `webhooks` and `audience-syncs` (the ad platform reads).
|
|
54
67
|
|
|
55
68
|
## Defaults (do not ask the human)
|
|
56
69
|
|
|
@@ -80,5 +93,45 @@ reach the browser, which is why `WebhookManager` only displays it once at creati
|
|
|
80
93
|
| `rotate_webhook_secret_webhooks__webhook_id__rotate_secret_post` | POST /webhooks/{webhook_id}/rotate-secret | external | WebhookManager |
|
|
81
94
|
| `list_webhook_deliveries_webhooks__webhook_id__deliveries_get` | GET /webhooks/{webhook_id}/deliveries | read | WebhookManager |
|
|
82
95
|
| `delete_webhook_webhooks__webhook_id__delete` | DELETE /webhooks/{webhook_id} | destructive | WebhookManager |
|
|
96
|
+
| `cancel_migration_integrations_migrations__migration_id__cancel_post` | POST /integrations/migrations/{migration_id}/cancel | destructive | MigrationPanel |
|
|
97
|
+
| `confirm_migration_integrations_migrations__migration_id__confirm_post` | POST /integrations/migrations/{migration_id}/confirm | external | MigrationPanel |
|
|
98
|
+
| `disconnect_sugarcrm_integrations_sugarcrm_delete` | DELETE /integrations/sugarcrm | destructive | SugarCrmConnect |
|
|
99
|
+
| `discover_crm_fields_integrations_crm_fields_get` | GET /integrations/crm/fields | read | FieldMappings |
|
|
100
|
+
| `get_crm_company_integrations_crm_connections__connection_id__companies__company_id__get` | GET /integrations/crm/connections/{connection_id}/companies/{company_id} | read | CrmRecords |
|
|
101
|
+
| `get_crm_connection_integrations_crm_connections__connection_id__get` | GET /integrations/crm/connections/{connection_id} | read | ConnectionHealth |
|
|
102
|
+
| `get_crm_connection_metadata_integrations_crm_connections__connection_id__metadata_get` | GET /integrations/crm/connections/{connection_id}/metadata | read | ConnectionHealth |
|
|
103
|
+
| `get_crm_connection_status_integrations_crm_connections__connection_id__status_get` | GET /integrations/crm/connections/{connection_id}/status | read | ConnectionHealth |
|
|
104
|
+
| `get_crm_contact_integrations_crm_connections__connection_id__contacts__contact_id__get` | GET /integrations/crm/connections/{connection_id}/contacts/{contact_id} | read | CrmRecords |
|
|
105
|
+
| `get_crm_deal_integrations_crm_connections__connection_id__deals__deal_id__get` | GET /integrations/crm/connections/{connection_id}/deals/{deal_id} | read | CrmRecords |
|
|
106
|
+
| `get_crm_lead_integrations_crm_connections__connection_id__leads__lead_id__get` | GET /integrations/crm/connections/{connection_id}/leads/{lead_id} | read | CrmRecords |
|
|
107
|
+
| `get_last_completed_migration_integrations_connections__connection_id__last_migration_get` | GET /integrations/connections/{connection_id}/last-migration | read | ConnectionHealth, MigrationPanel |
|
|
108
|
+
| `get_migration_status_integrations_migrations__migration_id__get` | GET /integrations/migrations/{migration_id} | read | MigrationPanel |
|
|
109
|
+
| `get_webhook_webhooks__webhook_id__get` | GET /webhooks/{webhook_id} | read | WebhookDetail |
|
|
110
|
+
| `list_ad_account_health_audience_syncs_account_health_get` | GET /audience-syncs/account-health | read | AdAccounts |
|
|
111
|
+
| `list_ad_accounts_audience_syncs_accounts_get` | GET /audience-syncs/accounts | read | AdAccounts |
|
|
112
|
+
| `list_ad_platforms_audience_syncs_platforms_get` | GET /audience-syncs/platforms | read | AdAccounts |
|
|
113
|
+
| `list_crm_activities_integrations_crm_activities_get` | GET /integrations/crm/activities | read | CrmRecords |
|
|
114
|
+
| `list_crm_companies_integrations_crm_connections__connection_id__companies_get` | GET /integrations/crm/connections/{connection_id}/companies | read | CrmRecords |
|
|
115
|
+
| `list_crm_contact_activities_integrations_crm_connections__connection_id__contacts__contact_id__activities_get` | GET /integrations/crm/connections/{connection_id}/contacts/{contact_id}/activities | read | CrmRecords |
|
|
116
|
+
| `list_crm_contacts_integrations_crm_connections__connection_id__contacts_get` | GET /integrations/crm/connections/{connection_id}/contacts | read | CrmRecords |
|
|
117
|
+
| `list_crm_deals_integrations_crm_connections__connection_id__deals_get` | GET /integrations/crm/connections/{connection_id}/deals | read | CrmRecords |
|
|
118
|
+
| `list_crm_leads_integrations_crm_connections__connection_id__leads_get` | GET /integrations/crm/connections/{connection_id}/leads | read | CrmRecords |
|
|
119
|
+
| `list_google_drive_folder_files_integrations_google_workspace_folders__folder_id__files_get` | GET /integrations/google-workspace/folders/{folder_id}/files | read | WorkspaceTools |
|
|
120
|
+
| `list_google_drive_folders_integrations_google_workspace_folders_get` | GET /integrations/google-workspace/folders | read | WorkspaceTools |
|
|
121
|
+
| `list_lead_statuses_integrations_crm_sync_leads_statuses_get` | GET /integrations/crm-sync/leads/statuses | read | SyncTriggers |
|
|
122
|
+
| `list_mappable_graph8_fields_integrations_crm_fields_mappable_get` | GET /integrations/crm/fields/mappable | read | FieldMappings |
|
|
123
|
+
| `preview_lead_push_integrations_crm_sync_leads_push_preview_get` | GET /integrations/crm-sync/leads/push-preview | read | SyncTriggers |
|
|
124
|
+
| `pull_all_contacts_integrations_crm_sync_contacts_pull_post` | POST /integrations/crm-sync/contacts/pull | external | SyncTriggers |
|
|
125
|
+
| `pull_all_deals_integrations_crm_sync_deals_pull_post` | POST /integrations/crm-sync/deals/pull | external | SyncTriggers |
|
|
126
|
+
| `pull_all_leads_integrations_crm_sync_leads_pull_post` | POST /integrations/crm-sync/leads/pull | external | SyncTriggers |
|
|
127
|
+
| `push_all_contacts_integrations_crm_sync_contacts_push_post` | POST /integrations/crm-sync/contacts/push | external | SyncTriggers |
|
|
128
|
+
| `push_all_deals_integrations_crm_sync_deals_push_post` | POST /integrations/crm-sync/deals/push | external | SyncTriggers |
|
|
129
|
+
| `push_all_leads_integrations_crm_sync_leads_push_post` | POST /integrations/crm-sync/leads/push | external | SyncTriggers |
|
|
130
|
+
| `start_delta_sync_integrations_migrations_delta_sync_post` | POST /integrations/migrations/delta-sync | external | MigrationPanel |
|
|
131
|
+
| `start_migration_discovery_integrations_migrations_discovery_post` | POST /integrations/migrations/discovery | external | MigrationPanel |
|
|
132
|
+
| `test_google_workspace_connection_integrations_google_workspace_test_post` | POST /integrations/google-workspace/test | read | WorkspaceTools |
|
|
133
|
+
| `validate_sugarcrm_integrations_sugarcrm_validate_post` | POST /integrations/sugarcrm/validate | read | SugarCrmConnect |
|
|
134
|
+
|
|
135
|
+
Not offered on purpose: CRM pass-through writes (create, update and delete a contact, company, deal, lead or meeting directly in the CRM, and the batch versions) because they bypass graph8's records and field mapping (they stay in recipes and `g8.api.integrations`); the embedded connection session token and `saveConnection` (the OAuth consent screen only runs in the graph8 app); syncing a graph8 list or audience (needs a list picker and starts from the Lists screen); Google Ads conversions and video assets (the Ads screen owns audience syncs).
|
|
83
136
|
|
|
84
137
|
Recipes (`recipes/`): `verify-webhook-route.ts`, `register-webhook.ts`, `crm-sync-health.ts`, `push-contacts-to-crm.ts`.
|