@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,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Small helpers for the newsletter management components (settings, versions, list syncs, templates).
|
|
5
|
+
* Responses for these endpoints are open dicts in the contract, so every reader accepts what is present.
|
|
6
|
+
*/
|
|
7
|
+
import type { PaginatedResponse } from "@graph8/sdk";
|
|
8
|
+
|
|
9
|
+
export type Rec = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
export function asRec(value: unknown): Rec {
|
|
12
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Rec) : {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function str(value: unknown, fallback = ""): string {
|
|
16
|
+
return typeof value === "string" ? value : value == null ? fallback : String(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function num(value: unknown): number | undefined {
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The rows of a list payload: a bare array, or the first of `keys` (then `items`) that holds one. */
|
|
24
|
+
export function rowsOf<T = Rec>(data: unknown, ...keys: string[]): T[] {
|
|
25
|
+
if (Array.isArray(data)) return data as T[];
|
|
26
|
+
const rec = asRec(data);
|
|
27
|
+
for (const key of [...keys, "items"]) if (Array.isArray(rec[key])) return rec[key] as T[];
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The fields of `draft` that differ from `saved`: the body of a partial update. Strings are compared trimmed. */
|
|
32
|
+
export function changedFields<T extends Record<string, string | boolean>>(saved: T, draft: T): Partial<T> {
|
|
33
|
+
const out: Partial<T> = {};
|
|
34
|
+
for (const key of Object.keys(draft) as Array<keyof T>) {
|
|
35
|
+
const a = saved[key];
|
|
36
|
+
const b = draft[key];
|
|
37
|
+
if (typeof b === "string" ? b.trim() !== String(a).trim() : b !== a) out[key] = (typeof b === "string" ? b.trim() : b) as T[keyof T];
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True for a bare hostname such as updates.acme.com (no scheme, path or spaces). */
|
|
43
|
+
export function isDomain(raw: string): boolean {
|
|
44
|
+
return /^(?=.{4,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test(raw.trim());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The DNS records a verify-domain answer lists, under whichever key it used. */
|
|
48
|
+
export function dnsRecordsOf(data: unknown): Rec[] {
|
|
49
|
+
return rowsOf<Rec>(data, "dns_records", "records");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A version row's number, under whichever key it used. */
|
|
53
|
+
export function versionNumber(v: Rec): number | undefined {
|
|
54
|
+
return num(v.version_num) ?? num(v.version) ?? num(v.number) ?? num(v.id);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Wrap rows and the API's "next" marker (a cursor or an offset; null or absent when there is no more) for `usePaginated`. */
|
|
58
|
+
export function pageOf<T>(rows: T[], next: unknown): PaginatedResponse<T> {
|
|
59
|
+
return { data: rows, pagination: { next_cursor: next == null || next === "" ? null : String(next) } } as unknown as PaginatedResponse<T>;
|
|
60
|
+
}
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
import { useState } from "react";
|
|
18
18
|
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
|
|
19
19
|
import { ApiError } from "@/g8/components/api-error";
|
|
20
|
+
import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
|
|
21
|
+
import { byPage, hasNextOf } from "@/g8/lib/paging";
|
|
22
|
+
import { LoadMore } from "@/g8/components/paging-notices";
|
|
20
23
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
21
24
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
25
|
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
@@ -40,7 +43,11 @@ export function NurtureBuilder({ onSelectNurture, pageSize = 50 }: NurtureBuilde
|
|
|
40
43
|
const [form, setForm] = useState<{ nurture?: Nurture } | null>(null);
|
|
41
44
|
const [toArchive, setToArchive] = useState<Nurture | null>(null);
|
|
42
45
|
const [toResume, setToResume] = useState<Nurture | null>(null);
|
|
43
|
-
const list =
|
|
46
|
+
const list = useLoadMoreList(byPage<Nurture>(pageSize, async (page, signal) => {
|
|
47
|
+
const r = await g8Get<{ nurtures?: Nurture[] }>("/nurtures", { page, limit: pageSize }, { signal });
|
|
48
|
+
const rows = r.data?.nurtures ?? [];
|
|
49
|
+
return { rows, hasNext: hasNextOf(r, pageSize, rows.length) };
|
|
50
|
+
}), [pageSize]);
|
|
44
51
|
const [busy, setBusy] = useState<string | null>(null);
|
|
45
52
|
const act = useG8Mutation((a: Action) => g8Post(`/nurtures/${encodeURIComponent(a.nurture.id)}/${a.kind}`, a.kind === "duplicate" ? {} : undefined), { onSuccess: () => void list.refetch() });
|
|
46
53
|
const run = async (a: Action): Promise<void> => {
|
|
@@ -65,7 +72,7 @@ export function NurtureBuilder({ onSelectNurture, pageSize = 50 }: NurtureBuilde
|
|
|
65
72
|
return (
|
|
66
73
|
<div className="grid gap-3">
|
|
67
74
|
<div className="flex items-center justify-between">
|
|
68
|
-
<h2 className="text-lg font-
|
|
75
|
+
<h2 className="text-lg font-medium">Nurtures</h2>
|
|
69
76
|
<Button onClick={() => setForm({})}>New nurture</Button>
|
|
70
77
|
</div>
|
|
71
78
|
{act.error ? <ApiError error={act.error} title="That did not work" /> : null}
|
|
@@ -91,6 +98,7 @@ export function NurtureBuilder({ onSelectNurture, pageSize = 50 }: NurtureBuilde
|
|
|
91
98
|
))}
|
|
92
99
|
</ul>
|
|
93
100
|
)}
|
|
101
|
+
<LoadMore shown={nurtures.length} noun="nurtures" hasMore={list.hasMore} loading={list.loadingMore} onLoadMore={list.loadMore} />
|
|
94
102
|
<ConfirmSpendDialog
|
|
95
103
|
open={toResume !== null}
|
|
96
104
|
tier="external"
|
|
@@ -17,13 +17,15 @@
|
|
|
17
17
|
import { useEffect, useState } from "react";
|
|
18
18
|
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
|
|
19
19
|
import { ApiError } from "@/g8/components/api-error";
|
|
20
|
+
import { useAllLists } from "@/g8/crm/hooks/use-all-lists";
|
|
21
|
+
import { ListSelect } from "@/g8/crm/components/list-select";
|
|
20
22
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
21
23
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
24
|
import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
|
|
23
25
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
24
26
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
25
27
|
import { isDrafted, isNurtureMissing, isNurtureOff, statusOf } from "../lib/nurture-types";
|
|
26
|
-
import type {
|
|
28
|
+
import type { Nurture } from "../lib/nurture-types";
|
|
27
29
|
import { pluralWord } from "@/g8/lib/plural";
|
|
28
30
|
import { useBrand } from "@/g8/hooks/use-brand";
|
|
29
31
|
|
|
@@ -38,7 +40,7 @@ export function NurtureEnroll({ nurtureId, contactIds = [], onLaunched }: Nurtur
|
|
|
38
40
|
const brand = useBrand();
|
|
39
41
|
const base = `/nurtures/${encodeURIComponent(nurtureId)}`;
|
|
40
42
|
const nurture = useG8Query((signal) => g8Get<Nurture>(base, {}, { signal }).then((r) => r.data), [base]);
|
|
41
|
-
const lists =
|
|
43
|
+
const lists = useAllLists([]);
|
|
42
44
|
const [listId, setListId] = useState("");
|
|
43
45
|
const [consent, setConsent] = useState(false);
|
|
44
46
|
const [confirming, setConfirming] = useState(false);
|
|
@@ -63,7 +65,7 @@ export function NurtureEnroll({ nurtureId, contactIds = [], onLaunched }: Nurtur
|
|
|
63
65
|
if (nurture.error) return <ApiError error={nurture.error} title="Could not load the nurture" onRetry={() => void nurture.refetch()} />;
|
|
64
66
|
if (!nurture.data) return <Skeleton className="h-40 w-full" aria-busy="true" />;
|
|
65
67
|
|
|
66
|
-
const list =
|
|
68
|
+
const list = lists.items.find((l) => String(l.id) === listId);
|
|
67
69
|
const attached = nurture.data.associated_list_id ? String(nurture.data.associated_list_id) === listId : false;
|
|
68
70
|
const audience = list?.contact_count ?? 0;
|
|
69
71
|
const canLaunch = isDrafted(nurture.data) && attached && audience > 0 && consent;
|
|
@@ -73,10 +75,7 @@ export function NurtureEnroll({ nurtureId, contactIds = [], onLaunched }: Nurtur
|
|
|
73
75
|
<CardContent className="grid gap-4">
|
|
74
76
|
<div className="grid max-w-md gap-1">
|
|
75
77
|
<label htmlFor="g8-nurture-audience" className="text-sm font-medium">Audience list</label>
|
|
76
|
-
<
|
|
77
|
-
<option value="">Choose a list</option>
|
|
78
|
-
{(lists.data ?? []).map((l) => <option key={l.id} value={l.id}>{l.title} ({(l.contact_count ?? 0).toLocaleString()})</option>)}
|
|
79
|
-
</select>
|
|
78
|
+
<ListSelect id="g8-nurture-audience" label="Audience list" emptyOption="Choose a list" pageSize={200} optionLabel={(l) => `${l.title} (${(l.contact_count ?? 0).toLocaleString()})`} value={listId} valueLabel={list?.title} disabled={!isDrafted(nurture.data)} onChange={(l) => setListId(l ? String(l.id) : "")} />
|
|
80
79
|
<div className="flex gap-2">
|
|
81
80
|
<Button size="sm" variant="outline" loading={setAudience.loading} disabled={!listId || attached || !isDrafted(nurture.data)} onClick={() => void setAudience.mutate(Number(listId))}>Use this list</Button>
|
|
82
81
|
{contactIds.length > 0 ? <Button size="sm" variant="outline" loading={addContacts.loading} disabled={!attached} onClick={() => void addContacts.mutate(Number(listId))}>Add {contactIds.length} selected {pluralWord(contactIds.length, "contact")}</Button> : null}
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
import { useState } from "react";
|
|
16
16
|
import { Button, Input, Label, Skeleton, Textarea } from "@graph8/sdk/ui";
|
|
17
17
|
import { ApiError } from "@/g8/components/api-error";
|
|
18
|
+
import { ListSelect } from "@/g8/crm/components/list-select";
|
|
18
19
|
import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
|
|
19
20
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
20
21
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
21
22
|
import { channelPayload, stepsPayload } from "../lib/nurture-types";
|
|
22
|
-
import type {
|
|
23
|
+
import type { MailboxOption, Nurture, StepDraft } from "../lib/nurture-types";
|
|
23
24
|
import { useBrand } from "@/g8/hooks/use-brand";
|
|
24
25
|
|
|
25
26
|
export interface NurtureFormProps {
|
|
@@ -57,7 +58,6 @@ function NurtureFormFields({ nurture, onSaved, onCancel }: NurtureFormProps) {
|
|
|
57
58
|
const [unsubscribe, setUnsubscribe] = useState(nurture?.include_unsubscribe_cta ?? true);
|
|
58
59
|
const [steps, setSteps] = useState<StepDraft[]>([{ waitDays: 0, subject: "", body: "" }]);
|
|
59
60
|
const mailboxes = useG8Query((signal) => g8Get<{ mailboxes?: MailboxOption[] }>("/mailboxes/my", {}, { signal }).then((r) => (r.data?.mailboxes ?? []).filter((m) => !m.is_archived)), []);
|
|
60
|
-
const lists = useG8Query((signal) => g8Get<ListOption[]>("/lists", { limit: 200 }, { signal }).then((r) => r.data ?? []), []);
|
|
61
61
|
|
|
62
62
|
const mailbox = (mailboxes.data ?? []).find((m) => String(m.id) === mailboxId);
|
|
63
63
|
const stepsOk = steps.length > 0 && steps.every((s) => s.subject.trim() && s.body.trim());
|
|
@@ -95,14 +95,11 @@ function NurtureFormFields({ nurture, onSaved, onCancel }: NurtureFormProps) {
|
|
|
95
95
|
<option value="">{mailboxes.loading ? "Loading..." : "Choose a mailbox"}</option>
|
|
96
96
|
{(mailboxes.data ?? []).map((m) => <option key={m.id} value={m.id}>{m.email}</option>)}
|
|
97
97
|
</select>
|
|
98
|
-
{!editing && !mailboxes.loading && (mailboxes.data ?? []).length === 0 ? <p className="mt-1 text-xs text-
|
|
98
|
+
{!editing && !mailboxes.loading && (mailboxes.data ?? []).length === 0 ? <p className="mt-1 text-xs text-warning-foreground">No mailbox is connected. Connect one in {brand.linkLabel} first (Google and Microsoft sign-in happens in the app).</p> : null}
|
|
99
99
|
</div>
|
|
100
100
|
<div>
|
|
101
101
|
<Label htmlFor="g8-nurture-list" className="mb-1">Audience list (consented contacts)</Label>
|
|
102
|
-
<
|
|
103
|
-
<option value="">Choose later</option>
|
|
104
|
-
{(lists.data ?? []).map((l) => <option key={l.id} value={l.id}>{l.title}</option>)}
|
|
105
|
-
</select>
|
|
102
|
+
<ListSelect id="g8-nurture-list" label="Audience list (consented contacts)" emptyOption="Choose later" pageSize={200} value={listId} valueLabel={nurture?.associated_list_id ? `List ${nurture.associated_list_id}` : undefined} selectClassName={SELECT} onChange={(l) => setListId(l ? String(l.id) : "")} />
|
|
106
103
|
</div>
|
|
107
104
|
</div>
|
|
108
105
|
<fieldset className="flex flex-wrap gap-4 text-sm">
|
|
@@ -113,7 +110,7 @@ function NurtureFormFields({ nurture, onSaved, onCancel }: NurtureFormProps) {
|
|
|
113
110
|
</fieldset>
|
|
114
111
|
{editing ? <p className="text-muted-foreground text-sm">The emails and the sender are fixed once created. To change them, duplicate this nurture and build the copy.</p> : (
|
|
115
112
|
<div className="grid gap-3">
|
|
116
|
-
<h3 className="text-sm font-
|
|
113
|
+
<h3 className="text-sm font-medium">Emails</h3>
|
|
117
114
|
{steps.map((s, i) => (
|
|
118
115
|
<div key={i} className="grid gap-2 rounded-md border p-3">
|
|
119
116
|
<div className="flex items-end gap-2">
|
|
@@ -25,6 +25,10 @@ import type { ReactNode } from "react";
|
|
|
25
25
|
import { Copy, ExternalLink, HeartHandshake, Pause, Play, Plus, Trash2 } from "lucide-react";
|
|
26
26
|
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
27
27
|
import { ApiError } from "@/g8/components/api-error";
|
|
28
|
+
import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
|
|
29
|
+
import { byPage, hasNextOf } from "@/g8/lib/paging";
|
|
30
|
+
import { LoadMore } from "@/g8/components/paging-notices";
|
|
31
|
+
import { useAllLists } from "@/g8/crm/hooks/use-all-lists";
|
|
28
32
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
29
33
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
30
34
|
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
@@ -41,7 +45,7 @@ import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
|
41
45
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
42
46
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
43
47
|
import { isDrafted, isFinished, isLiveLike, isNurtureOff, isPaused, statusOf } from "../lib/nurture-types";
|
|
44
|
-
import type {
|
|
48
|
+
import type { MailboxOption, Nurture } from "../lib/nurture-types";
|
|
45
49
|
import { NurtureEnroll } from "./nurture-enroll";
|
|
46
50
|
import { NurtureForm } from "./nurture-form";
|
|
47
51
|
import { NurtureStats } from "./nurture-stats";
|
|
@@ -79,6 +83,9 @@ function matchesFilter(n: Nurture, f: string): boolean {
|
|
|
79
83
|
return isFinished(n);
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
/** Nurtures per read; "Load more" appends the next page. */
|
|
87
|
+
const NURTURE_PAGE = 100;
|
|
88
|
+
|
|
82
89
|
interface StepRow { step_order?: number; time_interval?: number; step_data?: { subject?: string; body?: string } }
|
|
83
90
|
|
|
84
91
|
const when = (v: string | null | undefined): string => (v ? new Date(v).toLocaleString() : "");
|
|
@@ -87,12 +94,12 @@ const yesNo = (v: boolean | undefined, fallback: boolean): string => ((v ?? fall
|
|
|
87
94
|
/** Every field of one nurture, its audience and sender by name, and each email of the series. */
|
|
88
95
|
function NurtureDetails({ nurtureId }: { nurtureId: string }) {
|
|
89
96
|
const one = useG8Query((signal) => g8Get<Nurture>(`/nurtures/${encodeURIComponent(nurtureId)}`, {}, { signal }).then((r) => r.data), [nurtureId]);
|
|
90
|
-
const lists =
|
|
97
|
+
const lists = useAllLists([]);
|
|
91
98
|
const boxes = useG8Query((signal) => g8Get<{ mailboxes?: MailboxOption[] }>("/mailboxes/my", {}, { signal }).then((r) => r.data?.mailboxes ?? []), []);
|
|
92
99
|
if (one.error) return <ApiError error={one.error} title="Could not load the nurture" onRetry={() => void one.refetch()} />;
|
|
93
100
|
if (!one.data) return <Skeleton className="h-40 w-full" aria-busy="true" />;
|
|
94
101
|
const n = one.data;
|
|
95
|
-
const list =
|
|
102
|
+
const list = lists.items.find((l) => l.id === n.associated_list_id);
|
|
96
103
|
const box = (boxes.data ?? []).find((m) => m.id === n.pinned_mailbox_id);
|
|
97
104
|
const steps = Array.isArray(n.steps) ? (n.steps as StepRow[]) : [];
|
|
98
105
|
return (
|
|
@@ -147,7 +154,11 @@ export function NurtureScreen({ connectMailboxUrl: connectMailboxUrlProp, contac
|
|
|
147
154
|
const [toArchive, setToArchive] = useState<Nurture | null>(null);
|
|
148
155
|
const [busy, setBusy] = useState<string | null>(null);
|
|
149
156
|
|
|
150
|
-
const list =
|
|
157
|
+
const list = useLoadMoreList(byPage<Nurture>(NURTURE_PAGE, async (page, signal) => {
|
|
158
|
+
const r = await g8Get<{ nurtures?: Nurture[] }>("/nurtures", { page, limit: NURTURE_PAGE }, { signal });
|
|
159
|
+
const rows = r.data?.nurtures ?? [];
|
|
160
|
+
return { rows, hasNext: hasNextOf(r, NURTURE_PAGE, rows.length) };
|
|
161
|
+
}), []);
|
|
151
162
|
const mailboxes = useG8Query((signal) => g8Get<{ mailboxes?: MailboxOption[] }>("/mailboxes/my", {}, { signal }).then((r) => (r.data?.mailboxes ?? []).filter((m) => !m.is_archived)), [], { enabled: creating });
|
|
152
163
|
|
|
153
164
|
const act = useG8Mutation((a: { kind: "pause" | "duplicate"; nurture: Nurture }) => g8Post(`/nurtures/${encodeURIComponent(a.nurture.id)}/${a.kind}`, a.kind === "duplicate" ? {} : undefined), { onSuccess: () => void list.refetch() });
|
|
@@ -247,7 +258,7 @@ export function NurtureScreen({ connectMailboxUrl: connectMailboxUrlProp, contac
|
|
|
247
258
|
)}
|
|
248
259
|
>
|
|
249
260
|
{act.error ? <ApiError error={act.error} title="That did not work" /> : null}
|
|
250
|
-
<SplitView list={rows} detail={detail} detailOpen={Boolean(selected)} onCloseDetail={() => { setId(null); setView(null); }} expanded={view === "full"} onExpandedChange={(e) => setView(e ? "full" : null)} detailWidth="xl" />
|
|
261
|
+
<SplitView list={<>{rows}<LoadMore shown={all.length} noun="nurtures" hasMore={list.hasMore} loading={list.loadingMore} onLoadMore={list.loadMore} /></>} detail={detail} detailOpen={Boolean(selected)} onCloseDetail={() => { setId(null); setView(null); }} expanded={view === "full"} onExpandedChange={(e) => setView(e ? "full" : null)} detailWidth="xl" />
|
|
251
262
|
<FormSheet open={creating} onOpenChange={setCreating} title="New nurture" description="Pick the sender and the audience, then write the emails. Nothing is sent until you go live.">
|
|
252
263
|
<div className="grid gap-4">
|
|
253
264
|
{noMailbox ? (
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
import { useState } from "react";
|
|
16
16
|
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
|
|
17
17
|
import { ApiError } from "@/g8/components/api-error";
|
|
18
|
+
import { useAllLists } from "@/g8/crm/hooks/use-all-lists";
|
|
18
19
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
19
20
|
import { g8Get } from "@/g8/lib/g8-client";
|
|
20
21
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
21
22
|
import { isNurtureMissing, isNurtureOff, percentOf, statusOf } from "../lib/nurture-types";
|
|
22
|
-
import type {
|
|
23
|
+
import type { Nurture, NurturePerformance } from "../lib/nurture-types";
|
|
23
24
|
|
|
24
25
|
export interface NurtureStatsProps {
|
|
25
26
|
nurtureId: string;
|
|
@@ -31,7 +32,7 @@ function Tile({ label, value }: { label: string; value: string }) {
|
|
|
31
32
|
return (
|
|
32
33
|
<div className="rounded-md border p-3">
|
|
33
34
|
<p className="text-muted-foreground text-xs">{label}</p>
|
|
34
|
-
<p className="text-xl font-
|
|
35
|
+
<p className="text-xl font-medium">{value}</p>
|
|
35
36
|
</div>
|
|
36
37
|
);
|
|
37
38
|
}
|
|
@@ -39,7 +40,7 @@ function Tile({ label, value }: { label: string; value: string }) {
|
|
|
39
40
|
export function NurtureStats({ nurtureId, days = 30 }: NurtureStatsProps) {
|
|
40
41
|
const [windowDays, setWindowDays] = useState(days);
|
|
41
42
|
const nurture = useG8Query((signal) => g8Get<Nurture>(`/nurtures/${encodeURIComponent(nurtureId)}`, {}, { signal }).then((r) => r.data), [nurtureId]);
|
|
42
|
-
const lists =
|
|
43
|
+
const lists = useAllLists([]);
|
|
43
44
|
const perf = useG8Query(
|
|
44
45
|
(signal) => g8Get<{ data?: NurturePerformance }>(`/analytics/outbound/sequence-performance/${encodeURIComponent(nurtureId)}`, { days: windowDays }, { signal }).then((r) => (r.data?.data ?? (r.data as NurturePerformance | undefined)) ?? null),
|
|
45
46
|
[nurtureId, windowDays],
|
|
@@ -49,7 +50,7 @@ export function NurtureStats({ nurtureId, days = 30 }: NurtureStatsProps) {
|
|
|
49
50
|
if (isNurtureMissing(nurture.error)) return <EmptyState title="This nurture no longer exists" description="It may have been deleted, or it is not yours to see. Pick another nurture." />;
|
|
50
51
|
if (nurture.error) return <ApiError error={nurture.error} title="Could not load the nurture" onRetry={() => void nurture.refetch()} />;
|
|
51
52
|
if (!nurture.data) return <Skeleton className="h-32 w-full" aria-busy="true" />;
|
|
52
|
-
const audience =
|
|
53
|
+
const audience = lists.items.find((l) => l.id === nurture.data?.associated_list_id);
|
|
53
54
|
const p = perf.data;
|
|
54
55
|
const hasNumbers = Boolean(p && (p.contacts_total || p.emails_sent));
|
|
55
56
|
return (
|
package/blocks/objects/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Installs `foundation` first if it is missing. Everything lands under `g8/objects
|
|
|
51
51
|
5. **Archive**: the only delete. The record leaves lists but stays readable by id with its history; `restoreObjectRecord` brings it back.
|
|
52
52
|
Archiving a record, a field or a whole object always goes through `ConfirmSpendDialog`.
|
|
53
53
|
|
|
54
|
-
For `contacts`, `companies` and `deals`, `ObjectRecordDetail` also renders `RecordRolesPanel` (SDR / AE / CSM owner slots).
|
|
54
|
+
For `contacts`, `companies` and `deals`, `ObjectRecordDetail` also renders `RecordRolesPanel` (SDR / AE / CSM owner slots; each slot is a teammate picker, so nobody types a user id; `userOptions` still gives a fixed list). A person attribute (`actor_reference`) is a teammate picker too.
|
|
55
55
|
Custom objects have no role slots.
|
|
56
56
|
|
|
57
57
|
```tsx
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"finished ObjectsScreen composing the picker, table, record detail and schema editor"
|
|
13
13
|
],
|
|
14
14
|
"dependsOn": [
|
|
15
|
-
"foundation"
|
|
15
|
+
"foundation",
|
|
16
|
+
"pickers"
|
|
16
17
|
],
|
|
17
18
|
"operations": [
|
|
18
19
|
"archive_attribute_objects__object_slug__attributes__attr_slug__delete",
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
"list_object_attributes_objects__object_slug__attributes_get",
|
|
32
33
|
"list_object_records_objects__object_slug__records_get",
|
|
33
34
|
"list_objects_objects_get",
|
|
35
|
+
"list_team_members_team_members_get",
|
|
34
36
|
"restore_object_record_objects__object_slug__records__record_id__restore_post",
|
|
35
37
|
"update_attribute_objects__object_slug__attributes__attr_slug__patch",
|
|
36
38
|
"update_object_objects__object_slug__patch",
|
|
@@ -104,8 +106,12 @@
|
|
|
104
106
|
"name": "AttributeFieldInput",
|
|
105
107
|
"file": "components/attribute-field-input.tsx",
|
|
106
108
|
"purpose": "One form control chosen by attribute type (text, number, date, select, checkbox...).",
|
|
107
|
-
"operations": [
|
|
108
|
-
|
|
109
|
+
"operations": [
|
|
110
|
+
"list_team_members_team_members_get"
|
|
111
|
+
],
|
|
112
|
+
"tiers": [
|
|
113
|
+
"read"
|
|
114
|
+
]
|
|
109
115
|
},
|
|
110
116
|
{
|
|
111
117
|
"name": "ObjectNotFound",
|
|
@@ -145,7 +151,8 @@
|
|
|
145
151
|
"operations": [
|
|
146
152
|
"get_record_role_slots_records__entity_type___entity_id__role_slots_get",
|
|
147
153
|
"assign_record_role_records__entity_type___entity_id__role_assignments_post",
|
|
148
|
-
"clear_record_role_records__entity_type___entity_id__role_assignments__role_slot__delete"
|
|
154
|
+
"clear_record_role_records__entity_type___entity_id__role_assignments__role_slot__delete",
|
|
155
|
+
"list_team_members_team_members_get"
|
|
149
156
|
],
|
|
150
157
|
"tiers": [
|
|
151
158
|
"read",
|
|
@@ -220,7 +227,8 @@
|
|
|
220
227
|
"objects:delete",
|
|
221
228
|
"objects:manage",
|
|
222
229
|
"objects:read",
|
|
223
|
-
"objects:write"
|
|
230
|
+
"objects:write",
|
|
231
|
+
"team:read"
|
|
224
232
|
],
|
|
225
233
|
"defaults": [
|
|
226
234
|
{
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Purpose: render the right input for an attribute's type (text, number, date, select, checkbox...)
|
|
7
7
|
* and report the raw draft value. Convert it with `fromInputValue` from ../lib/object-types.
|
|
8
|
-
* SDK operations: none (
|
|
8
|
+
* SDK operations: none of its own. A person attribute (`actor_reference`) uses the pickers block MemberPicker (GET /team-members,
|
|
9
|
+
* read) so nobody types a user id; the raw id stays under "Use an id instead". Used by RecordFormDialog and ObjectRecordDetail.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { Input } from "@graph8/sdk/ui";
|
|
13
|
+
import { MemberPicker } from "@/g8/pickers/components/member-picker";
|
|
12
14
|
import { selectOptions } from "../lib/object-types";
|
|
13
15
|
import type { ObjectAttribute } from "../lib/object-types";
|
|
14
16
|
|
|
@@ -58,6 +60,17 @@ export function AttributeFieldInput({ attribute, value, onChange, disabled, id }
|
|
|
58
60
|
</select>
|
|
59
61
|
);
|
|
60
62
|
}
|
|
63
|
+
if (attribute.attribute_type === "actor_reference" && !attribute.is_multiselect) {
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex flex-col gap-1">
|
|
66
|
+
<MemberPicker id={fieldId} label={attribute.title} value={String(value) || null} disabled={disabled} placeholder="Choose a person" onChange={(m) => onChange(m.id)} />
|
|
67
|
+
<details className="text-xs">
|
|
68
|
+
<summary className="text-muted-foreground cursor-pointer">Use an id instead</summary>
|
|
69
|
+
<Input aria-label={`${attribute.title} user id`} placeholder="User id" className="mt-1 h-8 w-40" value={String(value)} disabled={disabled} onChange={(e) => onChange(e.target.value)} />
|
|
70
|
+
</details>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
61
74
|
const hint = attribute.is_multiselect
|
|
62
75
|
? "Separate several values with commas"
|
|
63
76
|
: attribute.attribute_type === "record_reference"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { useEffect, useState } from "react";
|
|
18
18
|
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
|
|
19
19
|
import { ApiError } from "@/g8/components/api-error";
|
|
20
|
+
import { FirstNotice } from "@/g8/components/paging-notices";
|
|
20
21
|
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
21
22
|
import { EmptyState } from "@/g8/components/empty-state";
|
|
22
23
|
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
@@ -37,6 +38,10 @@ export interface ObjectRecordDetailProps {
|
|
|
37
38
|
onBack?: () => void;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** The relationships and history APIs have no paging: a full read is announced under the list. */
|
|
42
|
+
const CHANGE_LIMIT = 20;
|
|
43
|
+
const RELATION_LIMIT = 25;
|
|
44
|
+
|
|
40
45
|
interface ChangeEntry { id: string; action: string; occurred_at: string; actor_type?: string; changes?: Record<string, unknown> }
|
|
41
46
|
interface Relationship { id: string; relationship_label?: string; source_record_id: string; target_record_id: string; source_label?: string; target_label?: string }
|
|
42
47
|
|
|
@@ -70,7 +75,7 @@ export function ObjectRecordDetail({ objectSlug, recordId, onArchived, onBack }:
|
|
|
70
75
|
<div className="flex items-center justify-between gap-2">
|
|
71
76
|
<div>
|
|
72
77
|
{onBack ? <Button variant="link" size="sm" onClick={onBack}>Back</Button> : null}
|
|
73
|
-
<h2 className="text-lg font-
|
|
78
|
+
<h2 className="text-lg font-medium">{recordTitle(schema.definition, schema.attributes, current)}</h2>
|
|
74
79
|
{current.is_archived ? <p className="text-muted-foreground text-sm">This record is archived.</p> : null}
|
|
75
80
|
</div>
|
|
76
81
|
<div className="flex gap-2">
|
|
@@ -108,6 +113,7 @@ export function ObjectRecordDetail({ objectSlug, recordId, onArchived, onBack }:
|
|
|
108
113
|
))}
|
|
109
114
|
</ul>
|
|
110
115
|
)}
|
|
116
|
+
<FirstNotice shown={(relationships.data ?? []).length} limit={RELATION_LIMIT} noun="related records" />
|
|
111
117
|
</CardContent>
|
|
112
118
|
</Card>
|
|
113
119
|
<Card>
|
|
@@ -126,6 +132,7 @@ export function ObjectRecordDetail({ objectSlug, recordId, onArchived, onBack }:
|
|
|
126
132
|
))}
|
|
127
133
|
</ul>
|
|
128
134
|
)}
|
|
135
|
+
<FirstNotice shown={(changes.data ?? []).length} limit={CHANGE_LIMIT} noun="changes" />
|
|
129
136
|
</CardContent>
|
|
130
137
|
</Card>
|
|
131
138
|
<RecordFormDialog open={editing} objectSlug={objectSlug} singularNoun={noun} attributes={schema.attributes} record={current} onClose={() => setEditing(false)} onSaved={() => { setEditing(false); refreshAll(); }} />
|
|
@@ -148,7 +148,7 @@ export function ObjectSchemaEditor({ initialObjectSlug, onSelectObject }: Object
|
|
|
148
148
|
{schema.error ? <ApiError error={schema.error} title="Could not load the fields" onRetry={() => void schema.refetch()} /> : null}
|
|
149
149
|
{!current.is_system ? <ObjectDetailsForm key={current.slug} object={current} attributes={schema.attributes} onSaved={() => void objects.refetch()} /> : <p className="text-muted-foreground text-sm">Standard objects can gain fields but cannot be renamed or archived.</p>}
|
|
150
150
|
<div className="flex items-center justify-between">
|
|
151
|
-
<h3 className="text-sm font-
|
|
151
|
+
<h3 className="text-sm font-medium">Fields</h3>
|
|
152
152
|
<Button size="sm" onClick={() => setEditing("new")}>Add field</Button>
|
|
153
153
|
</div>
|
|
154
154
|
{schema.loading ? <Skeleton className="h-24 w-full" /> : schema.attributes.length === 0 ? (
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* 2. assignRecordRole (POST /records/{entity_type}/{entity_id}/role-assignments) write
|
|
9
9
|
* 3. clearRecordRole (DELETE /records/{entity_type}/{entity_id}/role-assignments/{role_slot}) destructive -> ConfirmSpendDialog
|
|
10
10
|
* Only the standard objects (contacts, companies, deals) have role slots; custom objects do not.
|
|
11
|
-
* The API takes a user id, not a name
|
|
11
|
+
* The API takes a user id, not a name, so nobody types one: each slot has a MemberPicker (search teammates by name;
|
|
12
|
+
* GET /team-members, read). Pass `userOptions` for your own fixed list; a raw id still works under "Use an id instead".
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { useEffect, useState } from "react";
|
|
@@ -18,6 +19,7 @@ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
|
18
19
|
import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
19
20
|
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
20
21
|
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
22
|
+
import { MemberPicker } from "@/g8/pickers/components/member-picker";
|
|
21
23
|
|
|
22
24
|
type Slot = "sdr" | "ae" | "csm";
|
|
23
25
|
const SLOTS: ReadonlyArray<{ slot: Slot; label: string }> = [
|
|
@@ -32,7 +34,7 @@ interface RoleSlots { sdr?: SlotInfo | null; ae?: SlotInfo | null; csm?: SlotInf
|
|
|
32
34
|
export interface RecordRolesPanelProps {
|
|
33
35
|
entityType: "contact" | "company" | "deal";
|
|
34
36
|
entityId: string;
|
|
35
|
-
/** Optional people to pick from. Without it
|
|
37
|
+
/** Optional people to pick from (your own list). Without it a MemberPicker searches the team. */
|
|
36
38
|
userOptions?: ReadonlyArray<{ id: string; name: string }>;
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -40,10 +42,11 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
|
|
|
40
42
|
const base = `/records/${entityType}/${encodeURIComponent(entityId)}`;
|
|
41
43
|
const slots = useG8Query((signal) => g8Get<RoleSlots>(`${base}/role-slots`, {}, { signal }).then((r) => r.data), [base]);
|
|
42
44
|
const [draft, setDraft] = useState<Record<string, string>>({});
|
|
45
|
+
const [draftNames, setDraftNames] = useState<Record<string, string>>({});
|
|
43
46
|
const [clearing, setClearing] = useState<Slot | null>(null);
|
|
44
47
|
const [busySlot, setBusySlot] = useState<Slot | null>(null);
|
|
45
48
|
const assign = useG8Mutation((vars: { slot: Slot; userId: string }) => g8Post(`${base}/role-assignments`, { role_slot: vars.slot, user_id: vars.userId }), {
|
|
46
|
-
onSuccess: (_r, vars) => { setDraft((d) => ({ ...d, [vars.slot]: "" })); void slots.refetch(); },
|
|
49
|
+
onSuccess: (_r, vars) => { setDraft((d) => ({ ...d, [vars.slot]: "" })); setDraftNames((n) => ({ ...n, [vars.slot]: "" })); void slots.refetch(); },
|
|
47
50
|
});
|
|
48
51
|
const assignSlot = async (slot: Slot, userId: string): Promise<void> => {
|
|
49
52
|
setBusySlot(slot);
|
|
@@ -53,6 +56,7 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
|
|
|
53
56
|
// Typed ids and open dialogs belong to ONE record.
|
|
54
57
|
useEffect(() => {
|
|
55
58
|
setDraft({});
|
|
59
|
+
setDraftNames({});
|
|
56
60
|
setClearing(null);
|
|
57
61
|
assign.reset();
|
|
58
62
|
clear.reset();
|
|
@@ -65,7 +69,7 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
|
|
|
65
69
|
const canModify = slots.data != null && slots.data.can_modify !== false;
|
|
66
70
|
return (
|
|
67
71
|
<section aria-label="Owners" className="grid gap-2">
|
|
68
|
-
<h3 className="text-sm font-
|
|
72
|
+
<h3 className="text-sm font-medium">Owners</h3>
|
|
69
73
|
{slots.loading && !slots.data ? <p className="text-muted-foreground text-sm">Loading owners...</p> : null}
|
|
70
74
|
{SLOTS.map(({ slot, label }) => {
|
|
71
75
|
const holder = slots.data?.[slot];
|
|
@@ -81,7 +85,13 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
|
|
|
81
85
|
{userOptions.map((u) => <option key={u.id} value={u.id}>{u.name}</option>)}
|
|
82
86
|
</select>
|
|
83
87
|
) : (
|
|
84
|
-
|
|
88
|
+
<>
|
|
89
|
+
<MemberPicker label={`${label} person`} value={draft[slot] || null} selectedName={draftNames[slot]} placeholder="Choose a person" onChange={(m) => { setDraft((d) => ({ ...d, [slot]: m.id })); setDraftNames((n) => ({ ...n, [slot]: m.name })); }} />
|
|
90
|
+
<details className="text-xs">
|
|
91
|
+
<summary className="text-muted-foreground cursor-pointer">Use an id instead</summary>
|
|
92
|
+
<Input aria-label={`${label} user id`} placeholder="User id" className="mt-1 h-8 w-40" value={draft[slot] ?? ""} onChange={(e) => { setDraft((d) => ({ ...d, [slot]: e.target.value })); setDraftNames((n) => ({ ...n, [slot]: "" })); }} />
|
|
93
|
+
</details>
|
|
94
|
+
</>
|
|
85
95
|
)}
|
|
86
96
|
<Button size="sm" variant="outline" loading={busySlot === slot} disabled={!draft[slot] || busySlot !== null} onClick={() => void assignSlot(slot, draft[slot])}>Assign</Button>
|
|
87
97
|
{holder ? <Button size="sm" variant="ghost" onClick={() => { clear.reset(); setClearing(slot); }}>Clear</Button> : null}
|
package/blocks/pages/README.md
CHANGED
|
@@ -114,3 +114,17 @@ export default function Pages() { const router = useRouter(); return <PageList o
|
|
|
114
114
|
- `recipes/edit-page-section.ts`: Change the text of one section of a landing page
|
|
115
115
|
- `recipes/audit-site-seo.ts`: Run an SEO site audit and list what to fix first
|
|
116
116
|
- `recipes/refresh-brand-kit.ts`: Re-scan the brand kit from the website
|
|
117
|
+
|
|
118
|
+
## Depth added (C7c)
|
|
119
|
+
|
|
120
|
+
Landing pages now cover the whole life of a page, not only create and publish. Each open page has these tabs beside Edit and Details:
|
|
121
|
+
|
|
122
|
+
- **Publishing** (`PageDomains`): the URLs the page is live on, publish to a connected domain (domain, subdomain and path are sent together, behind a confirm), take one URL down (destructive confirm), download the HTML.
|
|
123
|
+
- **Tracking** (`PageTracking`): tracking mode, form label, chat widget, conversion goal (only changed fields are sent), a dedicated stream through Provision, reset consent (destructive confirm).
|
|
124
|
+
- **AI tools** (`PageAiTools`): edit by instruction, review (quick check is 0 credits), apply exact fixes for free, fix one finding with AI, write search metadata. Every AI pass is a billable confirm; the API quotes no price in advance, so the dialog says what runs.
|
|
125
|
+
- **Analytics** (`PageAnalytics`): visits, people, companies, sources and forms for 7/30/90 days and a traffic source; the top-level Analytics tab is portfolio-wide. People and companies page with Load more.
|
|
126
|
+
- "Clone a page" (`PageClone`, confirmed spend) rebuilds a URL or pasted HTML as a draft; `PageGenerationStatus` shows progress of an AI-built page.
|
|
127
|
+
|
|
128
|
+
The headline numbers read the API totals, and a page past the first 100 still opens with its real title.
|
|
129
|
+
|
|
130
|
+
Not covered (and why): HTML-level edits (`importLandingPageHtml`, `replaceLandingPageHtml`, `updateLandingPageHtml`, `setLandingPageText/Media`, element edits, anchors) need element ids a form cannot show; templates and components (`duplicateLandingPageTemplate`, `promoteLandingPageTemplate`, `ensureLandingPageBaseTemplate`, `extract components`) are template admin; `agent-sessions` and `getLandingPageConversation` are the hosted editor's chat; consent default (`/landing-page-consent/*`) is workspace admin; clone-homepage duplicates clone-url. Content calendar, programmatic pages and brand-kit template ops belong to later blocks.
|