@graph8/sdk 0.370.0 → 0.373.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 +19 -0
- package/blocks/_foundation/README.md +14 -0
- package/blocks/_foundation/block.json +44 -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/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/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- 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 +161 -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 +6 -0
- package/blocks/crm/block.json +53 -4
- 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-lookup-link.tsx +44 -0
- package/blocks/crm/components/contacts-screen.tsx +31 -13
- 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/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/hooks/use-all-lists.ts +18 -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/deals/README.md +24 -6
- package/blocks/deals/block.json +151 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +2 -2
- 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 +4 -1
- 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/enrichment/README.md +16 -6
- package/blocks/enrichment/block.json +19 -7
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +76 -27
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-screen.tsx +70 -17
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +39 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +8 -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 +51 -17
- 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 +5 -0
- package/blocks/inbox/block.json +15 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +1 -1
- 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 +36 -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 +113 -1
- 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/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/quotes/README.md +13 -0
- package/blocks/quotes/block.json +48 -1
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +5 -3
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-viewer.tsx +7 -2
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -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 +6 -0
- package/blocks/sequencer/block.json +28 -1
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +19 -20
- package/blocks/sequencer/components/sequence-detail.tsx +4 -2
- package/blocks/sequencer/components/sequence-enrollments.tsx +4 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequences-screen.tsx +3 -0
- package/blocks/sequencer/components/step-editor.tsx +11 -11
- package/blocks/sequencer/lib/sequencer-utils.ts +5 -5
- 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 +4 -4
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-grid.tsx +1 -1
- package/blocks/workbench/components/workbench-screen.tsx +15 -5
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +536 -392
- 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/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +7 -3
- 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/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +17 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +29 -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 +12 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +36 -8
- 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 +12 -0
- package/stacks/clay/pages/lists.tsx.tmpl +12 -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 +32 -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 +28 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +9 -4
- package/stacks/hubspot/pages/companies.tsx.tmpl +14 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +29 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +10 -1
- 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 +12 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot.json +21 -5
- 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 +8 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +20 -5
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +27 -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 +12 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +25 -7
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +15 -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 +4 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +14 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +20 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +10 -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 +7 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +16 -3
- package/styles/styles.css +1 -1
- 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
|
@@ -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.
|
package/blocks/pages/block.json
CHANGED
|
@@ -14,27 +14,45 @@
|
|
|
14
14
|
"foundation"
|
|
15
15
|
],
|
|
16
16
|
"operations": [
|
|
17
|
+
"apply_landing_page_review_findings_landing_pages__landing_page_id__ai_review_apply_post",
|
|
18
|
+
"clear_landing_page_consent_override_landing_pages__landing_page_id__tracking_consent_delete",
|
|
19
|
+
"clone_html_to_landing_page_landing_pages_clone_html_post",
|
|
20
|
+
"clone_url_to_landing_page_landing_pages_clone_url_post",
|
|
17
21
|
"create_content_page_content_pages_post",
|
|
18
22
|
"create_landing_page_landing_pages_post",
|
|
19
23
|
"create_landing_page_preview_link_landing_pages__landing_page_id__preview_get",
|
|
20
24
|
"delete_content_page_content_pages__page_id__delete",
|
|
21
25
|
"delete_landing_page_landing_pages__landing_page_id__delete",
|
|
26
|
+
"edit_landing_page_with_chat_landing_pages__landing_page_id__edit_post",
|
|
22
27
|
"enrich_content_page_content_pages__page_id__enrich_seo_post",
|
|
28
|
+
"export_landing_page_landing_pages__landing_page_id__export_get",
|
|
29
|
+
"fix_landing_page_finding_with_ai_landing_pages__landing_page_id__ai_review_fix_with_ai_post",
|
|
30
|
+
"generate_landing_page_seo_metadata_landing_pages__landing_page_id__generate_seo_metadata_post",
|
|
23
31
|
"get_brand_kit_overview_brand_kit_get",
|
|
24
32
|
"get_content_page_content_pages__page_id__get",
|
|
25
33
|
"get_landing_page_landing_pages__landing_page_id__get",
|
|
34
|
+
"get_landing_page_reporting_overview_landing_pages_reporting_overview_get",
|
|
35
|
+
"get_landing_page_status_landing_pages__landing_page_id__status_get",
|
|
36
|
+
"get_landing_page_tracking_landing_pages__landing_page_id__tracking_get",
|
|
26
37
|
"get_layout_selections_brand_kit_layout_selections_get",
|
|
27
38
|
"get_seo_audit_issues_seo_audits__audit_id__issues_get",
|
|
28
39
|
"get_seo_audit_seo_audits__audit_id__get",
|
|
29
40
|
"list_archetypes_brand_kit_archetypes_get",
|
|
30
41
|
"list_content_pages_content_pages_get",
|
|
42
|
+
"list_landing_page_bindings_landing_pages__landing_page_id__bindings_get",
|
|
43
|
+
"list_landing_page_domains_landing_pages_domains_get",
|
|
44
|
+
"list_landing_page_reporting_companies_landing_pages_reporting_companies_get",
|
|
45
|
+
"list_landing_page_reporting_people_landing_pages_reporting_people_get",
|
|
31
46
|
"list_landing_page_versions_landing_pages__landing_page_id__versions_get",
|
|
32
47
|
"list_landing_pages_landing_pages_get",
|
|
33
48
|
"list_seo_audits_seo_audits_get",
|
|
34
49
|
"list_seo_tracked_keywords_seo_rank_tracking_get",
|
|
50
|
+
"provision_landing_page_tracking_landing_pages__landing_page_id__tracking_provision_post",
|
|
35
51
|
"publish_landing_page_landing_pages__landing_page_id__publish_post",
|
|
52
|
+
"remove_landing_page_binding_landing_pages__landing_page_id__bindings_delete",
|
|
36
53
|
"remove_seo_tracked_keywords_seo_rank_tracking_keywords_remove_post",
|
|
37
54
|
"restore_landing_page_version_landing_pages__landing_page_id__versions__version_num__restore_post",
|
|
55
|
+
"review_landing_page_landing_pages__landing_page_id__ai_review_post",
|
|
38
56
|
"save_layout_selections_brand_kit_layout_selections_put",
|
|
39
57
|
"save_seo_tracked_keywords_seo_rank_tracking_keywords_post",
|
|
40
58
|
"start_seo_audit_seo_audits_post",
|
|
@@ -42,7 +60,8 @@
|
|
|
42
60
|
"unpublish_landing_page_landing_pages__landing_page_id__unpublish_post",
|
|
43
61
|
"update_content_page_content_pages__page_id__patch",
|
|
44
62
|
"update_landing_page_landing_pages__landing_page_id__patch",
|
|
45
|
-
"update_landing_page_section_landing_pages__landing_page_id__sections__section_idx__patch"
|
|
63
|
+
"update_landing_page_section_landing_pages__landing_page_id__sections__section_idx__patch",
|
|
64
|
+
"update_landing_page_tracking_landing_pages__landing_page_id__tracking_patch"
|
|
46
65
|
],
|
|
47
66
|
"components": [
|
|
48
67
|
{
|
|
@@ -148,6 +167,99 @@
|
|
|
148
167
|
"write",
|
|
149
168
|
"billable"
|
|
150
169
|
]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "MoreRows",
|
|
173
|
+
"file": "components/more-rows.tsx",
|
|
174
|
+
"purpose": "Footer for paged lists: how many rows are shown and a Load more button.",
|
|
175
|
+
"operations": [],
|
|
176
|
+
"tiers": []
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "PageAiTools",
|
|
180
|
+
"file": "components/page-ai-tools.tsx",
|
|
181
|
+
"purpose": "AI help for a page: edit by instruction, review and apply fixes, write search metadata (each AI pass confirmed).",
|
|
182
|
+
"operations": [
|
|
183
|
+
"edit_landing_page_with_chat_landing_pages__landing_page_id__edit_post",
|
|
184
|
+
"apply_landing_page_review_findings_landing_pages__landing_page_id__ai_review_apply_post",
|
|
185
|
+
"review_landing_page_landing_pages__landing_page_id__ai_review_post",
|
|
186
|
+
"fix_landing_page_finding_with_ai_landing_pages__landing_page_id__ai_review_fix_with_ai_post",
|
|
187
|
+
"update_landing_page_landing_pages__landing_page_id__patch",
|
|
188
|
+
"generate_landing_page_seo_metadata_landing_pages__landing_page_id__generate_seo_metadata_post"
|
|
189
|
+
],
|
|
190
|
+
"tiers": [
|
|
191
|
+
"write",
|
|
192
|
+
"billable"
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "PageAnalytics",
|
|
197
|
+
"file": "components/page-analytics.tsx",
|
|
198
|
+
"purpose": "Landing page performance: visits, identified people and companies, form submissions and sources, for all pages or one.",
|
|
199
|
+
"operations": [
|
|
200
|
+
"list_landing_page_reporting_people_landing_pages_reporting_people_get",
|
|
201
|
+
"list_landing_page_reporting_companies_landing_pages_reporting_companies_get",
|
|
202
|
+
"get_landing_page_reporting_overview_landing_pages_reporting_overview_get"
|
|
203
|
+
],
|
|
204
|
+
"tiers": [
|
|
205
|
+
"read"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "PageClone",
|
|
210
|
+
"file": "components/page-clone.tsx",
|
|
211
|
+
"purpose": "Rebuild a URL or pasted HTML as an editable landing page (confirmed AI spend).",
|
|
212
|
+
"operations": [
|
|
213
|
+
"clone_url_to_landing_page_landing_pages_clone_url_post",
|
|
214
|
+
"clone_html_to_landing_page_landing_pages_clone_html_post"
|
|
215
|
+
],
|
|
216
|
+
"tiers": [
|
|
217
|
+
"billable"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"name": "PageDomains",
|
|
222
|
+
"file": "components/page-domains.tsx",
|
|
223
|
+
"purpose": "Where one page is live: its URLs, publish to a connected domain, take a URL down, download the HTML.",
|
|
224
|
+
"operations": [
|
|
225
|
+
"list_landing_page_bindings_landing_pages__landing_page_id__bindings_get",
|
|
226
|
+
"list_landing_page_domains_landing_pages_domains_get",
|
|
227
|
+
"export_landing_page_landing_pages__landing_page_id__export_get",
|
|
228
|
+
"publish_landing_page_landing_pages__landing_page_id__publish_post",
|
|
229
|
+
"remove_landing_page_binding_landing_pages__landing_page_id__bindings_delete"
|
|
230
|
+
],
|
|
231
|
+
"tiers": [
|
|
232
|
+
"read",
|
|
233
|
+
"external",
|
|
234
|
+
"destructive"
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"name": "PageGenerationStatus",
|
|
239
|
+
"file": "components/page-generation-status.tsx",
|
|
240
|
+
"purpose": "Progress of an AI-built page while it is generating.",
|
|
241
|
+
"operations": [
|
|
242
|
+
"get_landing_page_status_landing_pages__landing_page_id__status_get"
|
|
243
|
+
],
|
|
244
|
+
"tiers": [
|
|
245
|
+
"read"
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"name": "PageTracking",
|
|
250
|
+
"file": "components/page-tracking.tsx",
|
|
251
|
+
"purpose": "How one page is measured: tracking mode, form label, chat widget and conversion goal; own stream and consent reset.",
|
|
252
|
+
"operations": [
|
|
253
|
+
"update_landing_page_tracking_landing_pages__landing_page_id__tracking_patch",
|
|
254
|
+
"provision_landing_page_tracking_landing_pages__landing_page_id__tracking_provision_post",
|
|
255
|
+
"clear_landing_page_consent_override_landing_pages__landing_page_id__tracking_consent_delete",
|
|
256
|
+
"get_landing_page_tracking_landing_pages__landing_page_id__tracking_get"
|
|
257
|
+
],
|
|
258
|
+
"tiers": [
|
|
259
|
+
"read",
|
|
260
|
+
"write",
|
|
261
|
+
"destructive"
|
|
262
|
+
]
|
|
151
263
|
}
|
|
152
264
|
],
|
|
153
265
|
"hooks": [],
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MoreRows: the honest end of a list that the API pages. Shows how many rows are on screen and, while the API has
|
|
5
|
+
* another page, a "Load more" button. A list never ends silently: either every row is reachable or this says there are more.
|
|
6
|
+
* (Same contract as the foundation's LoadMore; kept local so the pages block does not wait on it.)
|
|
7
|
+
*
|
|
8
|
+
* SDK operations: none.
|
|
9
|
+
*/
|
|
10
|
+
import { Button } from "@graph8/sdk/ui";
|
|
11
|
+
|
|
12
|
+
export interface MoreRowsProps {
|
|
13
|
+
/** Rows on screen. */
|
|
14
|
+
shown: number;
|
|
15
|
+
/** What the rows are, plural: "people". */
|
|
16
|
+
noun: string;
|
|
17
|
+
hasMore: boolean;
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
onMore: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MoreRows({ shown, noun, hasMore, loading, onMore }: MoreRowsProps) {
|
|
23
|
+
if (!hasMore) return null;
|
|
24
|
+
return (
|
|
25
|
+
<div className="text-muted-foreground flex items-center justify-between gap-3 py-2 text-sm">
|
|
26
|
+
<span role="status">Showing {shown.toLocaleString()} {noun}</span>
|
|
27
|
+
<Button type="button" variant="outline" size="sm" loading={loading} onClick={onMore}>Load more</Button>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|