@graph8/sdk 0.308.1 → 0.357.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 +81 -0
- package/blocks/README.md +32 -0
- package/blocks/_foundation/README.md +78 -0
- package/blocks/_foundation/block.json +79 -0
- package/blocks/_foundation/components/api-error.tsx +62 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
- package/blocks/_foundation/components/empty-state.tsx +24 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
- package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
- package/blocks/_foundation/hooks/use-paginated.ts +105 -0
- package/blocks/_foundation/lib/g8-client.ts +128 -0
- package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
- package/blocks/_foundation/server/proxy.ts +233 -0
- package/blocks/_foundation/server/route.ts +30 -0
- package/blocks/agents/README.md +133 -0
- package/blocks/agents/block.json +223 -0
- package/blocks/agents/components/agent-builder.tsx +199 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
- package/blocks/agents/components/agent-runs.tsx +175 -0
- package/blocks/agents/components/agent-skills-panel.tsx +126 -0
- package/blocks/agents/components/agent-test-console.tsx +175 -0
- package/blocks/agents/lib/agent-types.ts +114 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
- package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
- package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
- package/blocks/analytics/README.md +73 -0
- package/blocks/analytics/block.json +178 -0
- package/blocks/analytics/components/funnel-chart.tsx +79 -0
- package/blocks/analytics/components/kpi-tiles.tsx +74 -0
- package/blocks/analytics/components/leaderboard.tsx +86 -0
- package/blocks/analytics/components/report-builder.tsx +200 -0
- package/blocks/analytics/hooks/use-report.ts +26 -0
- package/blocks/analytics/lib/analytics-types.ts +108 -0
- package/blocks/analytics/lib/use-debounced.ts +16 -0
- package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
- package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
- package/blocks/analytics/recipes/03-saved-report.ts +30 -0
- package/blocks/appointments/README.md +80 -0
- package/blocks/appointments/block.json +218 -0
- package/blocks/appointments/components/availability-editor.tsx +192 -0
- package/blocks/appointments/components/booking-page.tsx +154 -0
- package/blocks/appointments/components/event-type-editor.tsx +214 -0
- package/blocks/appointments/components/meeting-detail.tsx +147 -0
- package/blocks/appointments/components/meeting-list.tsx +148 -0
- package/blocks/appointments/hooks/use-available-slots.ts +29 -0
- package/blocks/appointments/hooks/use-schedules.ts +18 -0
- package/blocks/appointments/lib/appointments-types.ts +159 -0
- package/blocks/appointments/lib/idempotency.ts +30 -0
- package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
- package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
- package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
- package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
- package/blocks/block.schema.json +130 -0
- package/blocks/block.types.ts +109 -0
- package/blocks/campaigns/README.md +118 -0
- package/blocks/campaigns/block.json +361 -0
- package/blocks/campaigns/components/campaign-assets.tsx +180 -0
- package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
- package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
- package/blocks/campaigns/components/campaign-list.tsx +120 -0
- package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
- package/blocks/campaigns/components/campaign-select.tsx +52 -0
- package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
- package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
- package/blocks/campaigns/components/form-field.tsx +29 -0
- package/blocks/campaigns/components/icp-editor.tsx +230 -0
- package/blocks/campaigns/components/persona-editor.tsx +210 -0
- package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
- package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
- package/blocks/campaigns/recipes/campaign-report.ts +22 -0
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
- package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
- package/blocks/coach/README.md +93 -0
- package/blocks/coach/block.json +125 -0
- package/blocks/coach/components/call-scorecard.tsx +126 -0
- package/blocks/coach/components/coaching-feed.tsx +140 -0
- package/blocks/coach/lib/coach-types.ts +116 -0
- package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
- package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
- package/blocks/copilot/README.md +99 -0
- package/blocks/copilot/block.json +151 -0
- package/blocks/copilot/components/copilot-memory.tsx +87 -0
- package/blocks/copilot/components/copilot-panel.tsx +189 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
- package/blocks/copilot/lib/copilot-types.ts +111 -0
- package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
- package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
- package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
- package/blocks/crm/README.md +122 -0
- package/blocks/crm/block.json +378 -0
- package/blocks/crm/components/activity-timeline.tsx +99 -0
- package/blocks/crm/components/company-detail.tsx +118 -0
- package/blocks/crm/components/company-table.tsx +173 -0
- package/blocks/crm/components/contact-detail.tsx +135 -0
- package/blocks/crm/components/contact-table.tsx +217 -0
- package/blocks/crm/components/dedupe-review.tsx +164 -0
- package/blocks/crm/components/import-wizard.tsx +193 -0
- package/blocks/crm/components/notes-panel.tsx +112 -0
- package/blocks/crm/components/record-form.tsx +191 -0
- package/blocks/crm/components/task-list.tsx +107 -0
- package/blocks/crm/hooks/use-custom-fields.ts +18 -0
- package/blocks/crm/hooks/use-debounced.ts +13 -0
- package/blocks/crm/hooks/use-page-query.ts +52 -0
- package/blocks/crm/lib/crm-types.ts +136 -0
- package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
- package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
- package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
- package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
- package/blocks/deals/README.md +78 -0
- package/blocks/deals/block.json +198 -0
- package/blocks/deals/components/deal-detail.tsx +134 -0
- package/blocks/deals/components/forecast-view.tsx +112 -0
- package/blocks/deals/components/pipeline-board.tsx +142 -0
- package/blocks/deals/components/stage-editor.tsx +96 -0
- package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
- package/blocks/deals/hooks/use-pipelines.ts +19 -0
- package/blocks/deals/lib/deals-types.ts +128 -0
- package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
- package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
- package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
- package/blocks/dialer/README.md +114 -0
- package/blocks/dialer/block.json +260 -0
- package/blocks/dialer/components/call-log.tsx +78 -0
- package/blocks/dialer/components/call-queue.tsx +188 -0
- package/blocks/dialer/components/dialer-workspace.tsx +47 -0
- package/blocks/dialer/components/disposition-panel.tsx +75 -0
- package/blocks/dialer/components/live-calls-table.tsx +107 -0
- package/blocks/dialer/components/session-start-form.tsx +111 -0
- package/blocks/dialer/components/softphone.tsx +122 -0
- package/blocks/dialer/components/transcript-viewer.tsx +82 -0
- package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
- package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
- package/blocks/dialer/hooks/use-dispositions.ts +29 -0
- package/blocks/dialer/lib/dialer-types.ts +158 -0
- package/blocks/dialer/lib/softphone-transport.ts +67 -0
- package/blocks/dialer/lib/twilio-transport.ts +74 -0
- package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
- package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
- package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
- package/blocks/enrichment/README.md +105 -0
- package/blocks/enrichment/block.json +204 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
- package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
- package/blocks/enrichment/components/enrich-button.tsx +136 -0
- package/blocks/enrichment/components/waterfall-config.tsx +127 -0
- package/blocks/enrichment/hooks/use-providers.ts +18 -0
- package/blocks/enrichment/lib/enrichment-types.ts +104 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
- package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
- package/blocks/forms/README.md +112 -0
- package/blocks/forms/block.json +200 -0
- package/blocks/forms/components/form-builder.tsx +150 -0
- package/blocks/forms/components/form-embed.tsx +103 -0
- package/blocks/forms/components/form-mapping-editor.tsx +125 -0
- package/blocks/forms/components/site-audit-panel.tsx +73 -0
- package/blocks/forms/components/submissions-table.tsx +158 -0
- package/blocks/forms/lib/form-types.ts +127 -0
- package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
- package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
- package/blocks/forms/recipes/route-form-to-list.ts +44 -0
- package/blocks/inbox/README.md +72 -0
- package/blocks/inbox/block.json +194 -0
- package/blocks/inbox/components/inbox-list.tsx +156 -0
- package/blocks/inbox/components/inbox-screen.tsx +49 -0
- package/blocks/inbox/components/reply-composer.tsx +183 -0
- package/blocks/inbox/components/tag-and-assign.tsx +189 -0
- package/blocks/inbox/components/thread-badge.tsx +17 -0
- package/blocks/inbox/components/thread-view.tsx +108 -0
- package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
- package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
- package/blocks/inbox/lib/idempotency.ts +30 -0
- package/blocks/inbox/lib/inbox-types.ts +100 -0
- package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
- package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
- package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
- package/blocks/integrations/README.md +75 -0
- package/blocks/integrations/block.json +172 -0
- package/blocks/integrations/components/integration-list.tsx +165 -0
- package/blocks/integrations/components/sync-status.tsx +223 -0
- package/blocks/integrations/components/webhook-manager.tsx +300 -0
- package/blocks/integrations/lib/integrations-utils.ts +62 -0
- package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
- package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
- package/blocks/integrations/recipes/register-webhook.ts +18 -0
- package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
- package/blocks/intent/README.md +57 -0
- package/blocks/intent/block.json +138 -0
- package/blocks/intent/components/account-intent-score.tsx +91 -0
- package/blocks/intent/components/intent-feed.tsx +215 -0
- package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
- package/blocks/intent/hooks/use-account-intent.ts +46 -0
- package/blocks/intent/lib/intent-utils.ts +95 -0
- package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
- package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
- package/blocks/intent/recipes/who-visited-url.ts +18 -0
- package/blocks/lifecycle/README.md +79 -0
- package/blocks/lifecycle/block.json +198 -0
- package/blocks/lifecycle/components/churn-list.tsx +92 -0
- package/blocks/lifecycle/components/customer-table.tsx +119 -0
- package/blocks/lifecycle/components/lead-queue.tsx +127 -0
- package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
- package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
- package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
- package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
- package/blocks/lifecycle/lib/lifecycle.ts +99 -0
- package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
- package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
- package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
- package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
- package/blocks/lists/README.md +96 -0
- package/blocks/lists/block.json +166 -0
- package/blocks/lists/components/add-to-list-button.tsx +111 -0
- package/blocks/lists/components/list-picker.tsx +94 -0
- package/blocks/lists/components/list-table.tsx +160 -0
- package/blocks/lists/components/suppression-manager.tsx +153 -0
- package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
- package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
- package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
- package/blocks/mailboxes/README.md +71 -0
- package/blocks/mailboxes/block.json +187 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
- package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
- package/blocks/mailboxes/components/warmup-health.tsx +232 -0
- package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
- package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
- package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
- package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
- package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
- package/blocks/newsletter/README.md +122 -0
- package/blocks/newsletter/block.json +224 -0
- package/blocks/newsletter/components/issue-form.tsx +124 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
- package/blocks/newsletter/components/subscriber-table.tsx +173 -0
- package/blocks/newsletter/lib/newsletter-types.ts +97 -0
- package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
- package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
- package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
- package/blocks/nurture/README.md +110 -0
- package/blocks/nurture/block.json +191 -0
- package/blocks/nurture/components/nurture-builder.tsx +114 -0
- package/blocks/nurture/components/nurture-enroll.tsx +107 -0
- package/blocks/nurture/components/nurture-form.tsx +120 -0
- package/blocks/nurture/components/nurture-stats.tsx +81 -0
- package/blocks/nurture/lib/nurture-types.ts +109 -0
- package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
- package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
- package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
- package/blocks/objects/README.md +120 -0
- package/blocks/objects/block.json +243 -0
- package/blocks/objects/components/attribute-field-input.tsx +82 -0
- package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
- package/blocks/objects/components/object-not-found.tsx +34 -0
- package/blocks/objects/components/object-record-detail.tsx +143 -0
- package/blocks/objects/components/object-schema-editor.tsx +197 -0
- package/blocks/objects/components/object-table.tsx +130 -0
- package/blocks/objects/components/record-form-dialog.tsx +112 -0
- package/blocks/objects/components/record-roles-panel.tsx +106 -0
- package/blocks/objects/hooks/use-object-schema.ts +36 -0
- package/blocks/objects/lib/object-types.ts +214 -0
- package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
- package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
- package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
- package/blocks/pages/README.md +104 -0
- package/blocks/pages/block.json +223 -0
- package/blocks/pages/components/brand-kit-editor.tsx +152 -0
- package/blocks/pages/components/content-library.tsx +163 -0
- package/blocks/pages/components/page-editor.tsx +253 -0
- package/blocks/pages/components/page-list.tsx +159 -0
- package/blocks/pages/components/seo-report.tsx +159 -0
- package/blocks/pages/lib/studio.ts +77 -0
- package/blocks/pages/recipes/audit-site-seo.ts +28 -0
- package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
- package/blocks/pages/recipes/edit-page-section.ts +25 -0
- package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
- package/blocks/quotes/README.md +79 -0
- package/blocks/quotes/block.json +183 -0
- package/blocks/quotes/components/product-picker.tsx +80 -0
- package/blocks/quotes/components/quote-builder.tsx +195 -0
- package/blocks/quotes/components/quote-list.tsx +79 -0
- package/blocks/quotes/components/quote-viewer.tsx +135 -0
- package/blocks/quotes/components/signature-status.tsx +77 -0
- package/blocks/quotes/hooks/use-debounced.ts +13 -0
- package/blocks/quotes/lib/quotes.ts +142 -0
- package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
- package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
- package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
- package/blocks/radar/README.md +106 -0
- package/blocks/radar/block.json +189 -0
- package/blocks/radar/components/competitor-battle-card.tsx +53 -0
- package/blocks/radar/components/competitor-tracker.tsx +158 -0
- package/blocks/radar/components/radar-overview.tsx +67 -0
- package/blocks/radar/components/radar-report.tsx +86 -0
- package/blocks/radar/hooks/use-competitors.ts +23 -0
- package/blocks/radar/lib/radar-types.ts +124 -0
- package/blocks/radar/recipes/battle-card.ts +28 -0
- package/blocks/radar/recipes/deep-dive-report.ts +32 -0
- package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
- package/blocks/radar/recipes/track-and-scan.ts +39 -0
- package/blocks/search/README.md +129 -0
- package/blocks/search/block.json +185 -0
- package/blocks/search/components/filter-builder.tsx +149 -0
- package/blocks/search/components/prospect-results.tsx +175 -0
- package/blocks/search/components/save-to-list-dialog.tsx +135 -0
- package/blocks/search/components/saved-searches.tsx +95 -0
- package/blocks/search/hooks/use-filter-options.ts +41 -0
- package/blocks/search/lib/search-types.ts +149 -0
- package/blocks/search/recipes/explore-filter-values.ts +27 -0
- package/blocks/search/recipes/saved-searches.ts +34 -0
- package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
- package/blocks/sequencer/README.md +87 -0
- package/blocks/sequencer/block.json +214 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
- package/blocks/sequencer/components/sequence-builder.tsx +306 -0
- package/blocks/sequencer/components/sequence-list.tsx +200 -0
- package/blocks/sequencer/components/sequence-stats.tsx +256 -0
- package/blocks/sequencer/components/step-editor.tsx +213 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
- package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
- package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
- package/blocks/sequencer/recipes/sequence-report.ts +22 -0
- package/blocks/social-ads/README.md +128 -0
- package/blocks/social-ads/block.json +256 -0
- package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
- package/blocks/social-ads/components/audience-sync.tsx +150 -0
- package/blocks/social-ads/components/creative-gallery.tsx +121 -0
- package/blocks/social-ads/components/post-composer.tsx +122 -0
- package/blocks/social-ads/components/social-post-list.tsx +116 -0
- package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
- package/blocks/social-ads/lib/social-ads-types.ts +167 -0
- package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
- package/blocks/social-ads/recipes/schedule-post.ts +51 -0
- package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
- package/blocks/visitors/README.md +56 -0
- package/blocks/visitors/block.json +139 -0
- package/blocks/visitors/components/snippet-install.tsx +169 -0
- package/blocks/visitors/components/traffic-chart.tsx +156 -0
- package/blocks/visitors/components/visitor-company-card.tsx +92 -0
- package/blocks/visitors/components/visitor-feed.tsx +157 -0
- package/blocks/visitors/lib/visitors-utils.ts +78 -0
- package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
- package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
- package/blocks/visitors/recipes/traffic-summary.ts +24 -0
- package/blocks/webchat/README.md +87 -0
- package/blocks/webchat/block.json +134 -0
- package/blocks/webchat/components/chat-widget-config.tsx +160 -0
- package/blocks/webchat/components/conversation-list.tsx +74 -0
- package/blocks/webchat/lib/webchat-types.ts +83 -0
- package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
- package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
- package/blocks/workbench/README.md +135 -0
- package/blocks/workbench/block.json +259 -0
- package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
- package/blocks/workbench/components/run-progress.tsx +132 -0
- package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
- package/blocks/workbench/components/workbench-grid.tsx +159 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
- package/blocks/workbench/components/workbench-table-list.tsx +105 -0
- package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
- package/blocks/workbench/lib/waterfall-presets.ts +119 -0
- package/blocks/workbench/lib/workbench-types.ts +191 -0
- package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
- package/blocks/workbench/recipes/import-from-search.ts +42 -0
- package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
- package/blocks/workbench/recipes/waterfall-column.ts +55 -0
- package/blocks/workflows/README.md +100 -0
- package/blocks/workflows/block.json +188 -0
- package/blocks/workflows/components/run-history.tsx +136 -0
- package/blocks/workflows/components/trigger-config.tsx +134 -0
- package/blocks/workflows/components/workflow-editor.tsx +142 -0
- package/blocks/workflows/components/workflow-list.tsx +153 -0
- package/blocks/workflows/hooks/use-workflow.ts +51 -0
- package/blocks/workflows/lib/graph.ts +187 -0
- package/blocks/workflows/recipes/create-workflow.ts +35 -0
- package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
- package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
- package/blocks/workflows/recipes/set-trigger.ts +22 -0
- package/dist/cli.js +5330 -29
- package/dist/index.browser.js +7740 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.mjs +7717 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +1440 -21
- package/dist/index.d.ts +1440 -21
- package/dist/index.js +42 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +7598 -0
- package/dist/react.browser.js.map +1 -0
- package/dist/react.browser.mjs +7568 -0
- package/dist/react.browser.mjs.map +1 -0
- package/dist/react.d.mts +1340 -18
- package/dist/react.d.ts +1340 -18
- package/dist/react.js +41 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +41 -2
- package/dist/react.mjs.map +1 -1
- package/dist/types/agentSetup.d.ts +6 -6
- package/dist/types/blocks/add.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +46 -0
- package/dist/types/blocks/doctor.d.ts +26 -0
- package/dist/types/blocks/registry.d.ts +134 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/types/crypto-browser.d.ts +2 -0
- package/dist/types/crypto-node.d.ts +6 -0
- package/dist/types/react.d.ts +1 -1
- package/dist/types/version.d.ts +2 -2
- package/dist/types/webhooks.d.ts +2 -2
- package/dist/ui/index.d.mts +54 -0
- package/dist/ui/index.d.ts +54 -0
- package/dist/ui/index.js +105 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +44 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/ui/lib/compose-refs.d.mts +19 -0
- package/dist/ui/lib/compose-refs.d.ts +19 -0
- package/dist/ui/lib/compose-refs.js +76 -0
- package/dist/ui/lib/compose-refs.js.map +1 -0
- package/dist/ui/lib/compose-refs.mjs +41 -0
- package/dist/ui/lib/compose-refs.mjs.map +1 -0
- package/dist/ui/lib/use-mobile.d.mts +4 -0
- package/dist/ui/lib/use-mobile.d.ts +4 -0
- package/dist/ui/lib/use-mobile.js +57 -0
- package/dist/ui/lib/use-mobile.js.map +1 -0
- package/dist/ui/lib/use-mobile.mjs +22 -0
- package/dist/ui/lib/use-mobile.mjs.map +1 -0
- package/dist/ui/lib/utils.d.mts +10 -0
- package/dist/ui/lib/utils.d.ts +10 -0
- package/dist/ui/lib/utils.js +33 -0
- package/dist/ui/lib/utils.js.map +1 -0
- package/dist/ui/lib/utils.mjs +9 -0
- package/dist/ui/lib/utils.mjs.map +1 -0
- package/dist/ui/primitives/alert-dialog.d.mts +22 -0
- package/dist/ui/primitives/alert-dialog.d.ts +22 -0
- package/dist/ui/primitives/alert-dialog.js +163 -0
- package/dist/ui/primitives/alert-dialog.js.map +1 -0
- package/dist/ui/primitives/alert-dialog.mjs +119 -0
- package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
- package/dist/ui/primitives/avatar.d.mts +8 -0
- package/dist/ui/primitives/avatar.d.ts +8 -0
- package/dist/ui/primitives/avatar.js +91 -0
- package/dist/ui/primitives/avatar.js.map +1 -0
- package/dist/ui/primitives/avatar.mjs +55 -0
- package/dist/ui/primitives/avatar.mjs.map +1 -0
- package/dist/ui/primitives/breadcrumb.d.mts +13 -0
- package/dist/ui/primitives/breadcrumb.d.ts +13 -0
- package/dist/ui/primitives/breadcrumb.js +127 -0
- package/dist/ui/primitives/breadcrumb.js.map +1 -0
- package/dist/ui/primitives/breadcrumb.mjs +97 -0
- package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
- package/dist/ui/primitives/button.d.mts +15 -0
- package/dist/ui/primitives/button.d.ts +15 -0
- package/dist/ui/primitives/button.js +91 -0
- package/dist/ui/primitives/button.js.map +1 -0
- package/dist/ui/primitives/button.mjs +56 -0
- package/dist/ui/primitives/button.mjs.map +1 -0
- package/dist/ui/primitives/card.d.mts +10 -0
- package/dist/ui/primitives/card.d.ts +10 -0
- package/dist/ui/primitives/card.js +101 -0
- package/dist/ui/primitives/card.js.map +1 -0
- package/dist/ui/primitives/card.mjs +62 -0
- package/dist/ui/primitives/card.mjs.map +1 -0
- package/dist/ui/primitives/carousel.d.mts +45 -0
- package/dist/ui/primitives/carousel.d.ts +45 -0
- package/dist/ui/primitives/carousel.js +300 -0
- package/dist/ui/primitives/carousel.js.map +1 -0
- package/dist/ui/primitives/carousel.mjs +261 -0
- package/dist/ui/primitives/carousel.mjs.map +1 -0
- package/dist/ui/primitives/chart.d.mts +68 -0
- package/dist/ui/primitives/chart.d.ts +68 -0
- package/dist/ui/primitives/chart.js +280 -0
- package/dist/ui/primitives/chart.js.map +1 -0
- package/dist/ui/primitives/chart.mjs +241 -0
- package/dist/ui/primitives/chart.mjs.map +1 -0
- package/dist/ui/primitives/collapsible.d.mts +8 -0
- package/dist/ui/primitives/collapsible.d.ts +8 -0
- package/dist/ui/primitives/collapsible.js +72 -0
- package/dist/ui/primitives/collapsible.js.map +1 -0
- package/dist/ui/primitives/collapsible.mjs +36 -0
- package/dist/ui/primitives/collapsible.mjs.map +1 -0
- package/dist/ui/primitives/context-menu.d.mts +29 -0
- package/dist/ui/primitives/context-menu.d.ts +29 -0
- package/dist/ui/primitives/context-menu.js +203 -0
- package/dist/ui/primitives/context-menu.js.map +1 -0
- package/dist/ui/primitives/context-menu.mjs +155 -0
- package/dist/ui/primitives/context-menu.mjs.map +1 -0
- package/dist/ui/primitives/dialog.d.mts +26 -0
- package/dist/ui/primitives/dialog.d.ts +26 -0
- package/dist/ui/primitives/dialog.js +192 -0
- package/dist/ui/primitives/dialog.js.map +1 -0
- package/dist/ui/primitives/dialog.mjs +148 -0
- package/dist/ui/primitives/dialog.mjs.map +1 -0
- package/dist/ui/primitives/drawer.d.mts +26 -0
- package/dist/ui/primitives/drawer.d.ts +26 -0
- package/dist/ui/primitives/drawer.js +146 -0
- package/dist/ui/primitives/drawer.js.map +1 -0
- package/dist/ui/primitives/drawer.mjs +103 -0
- package/dist/ui/primitives/drawer.mjs.map +1 -0
- package/dist/ui/primitives/input.d.mts +5 -0
- package/dist/ui/primitives/input.d.ts +5 -0
- package/dist/ui/primitives/input.js +46 -0
- package/dist/ui/primitives/input.js.map +1 -0
- package/dist/ui/primitives/input.mjs +22 -0
- package/dist/ui/primitives/input.mjs.map +1 -0
- package/dist/ui/primitives/item.d.mts +27 -0
- package/dist/ui/primitives/item.d.ts +27 -0
- package/dist/ui/primitives/item.js +221 -0
- package/dist/ui/primitives/item.js.map +1 -0
- package/dist/ui/primitives/item.mjs +188 -0
- package/dist/ui/primitives/item.mjs.map +1 -0
- package/dist/ui/primitives/kbd.d.mts +15 -0
- package/dist/ui/primitives/kbd.d.ts +15 -0
- package/dist/ui/primitives/kbd.js +45 -0
- package/dist/ui/primitives/kbd.js.map +1 -0
- package/dist/ui/primitives/kbd.mjs +21 -0
- package/dist/ui/primitives/kbd.mjs.map +1 -0
- package/dist/ui/primitives/label.d.mts +6 -0
- package/dist/ui/primitives/label.d.ts +6 -0
- package/dist/ui/primitives/label.js +55 -0
- package/dist/ui/primitives/label.js.map +1 -0
- package/dist/ui/primitives/label.mjs +21 -0
- package/dist/ui/primitives/label.mjs.map +1 -0
- package/dist/ui/primitives/resizable.d.mts +10 -0
- package/dist/ui/primitives/resizable.d.ts +10 -0
- package/dist/ui/primitives/resizable.js +68 -0
- package/dist/ui/primitives/resizable.js.map +1 -0
- package/dist/ui/primitives/resizable.mjs +42 -0
- package/dist/ui/primitives/resizable.mjs.map +1 -0
- package/dist/ui/primitives/save-status.d.mts +31 -0
- package/dist/ui/primitives/save-status.d.ts +31 -0
- package/dist/ui/primitives/save-status.js +150 -0
- package/dist/ui/primitives/save-status.js.map +1 -0
- package/dist/ui/primitives/save-status.mjs +126 -0
- package/dist/ui/primitives/save-status.mjs.map +1 -0
- package/dist/ui/primitives/separator.d.mts +6 -0
- package/dist/ui/primitives/separator.d.ts +6 -0
- package/dist/ui/primitives/separator.js +62 -0
- package/dist/ui/primitives/separator.js.map +1 -0
- package/dist/ui/primitives/separator.mjs +28 -0
- package/dist/ui/primitives/separator.mjs.map +1 -0
- package/dist/ui/primitives/sheet.d.mts +16 -0
- package/dist/ui/primitives/sheet.d.ts +16 -0
- package/dist/ui/primitives/sheet.js +170 -0
- package/dist/ui/primitives/sheet.js.map +1 -0
- package/dist/ui/primitives/sheet.mjs +129 -0
- package/dist/ui/primitives/sheet.mjs.map +1 -0
- package/dist/ui/primitives/sidebar.d.mts +74 -0
- package/dist/ui/primitives/sidebar.d.ts +74 -0
- package/dist/ui/primitives/sidebar.js +697 -0
- package/dist/ui/primitives/sidebar.js.map +1 -0
- package/dist/ui/primitives/sidebar.mjs +651 -0
- package/dist/ui/primitives/sidebar.mjs.map +1 -0
- package/dist/ui/primitives/skeleton.d.mts +5 -0
- package/dist/ui/primitives/skeleton.d.ts +5 -0
- package/dist/ui/primitives/skeleton.js +40 -0
- package/dist/ui/primitives/skeleton.js.map +1 -0
- package/dist/ui/primitives/skeleton.mjs +16 -0
- package/dist/ui/primitives/skeleton.mjs.map +1 -0
- package/dist/ui/primitives/slider.d.mts +20 -0
- package/dist/ui/primitives/slider.d.ts +20 -0
- package/dist/ui/primitives/slider.js +112 -0
- package/dist/ui/primitives/slider.js.map +1 -0
- package/dist/ui/primitives/slider.mjs +78 -0
- package/dist/ui/primitives/slider.mjs.map +1 -0
- package/dist/ui/primitives/sortable.d.mts +50 -0
- package/dist/ui/primitives/sortable.d.ts +50 -0
- package/dist/ui/primitives/sortable.js +456 -0
- package/dist/ui/primitives/sortable.js.map +1 -0
- package/dist/ui/primitives/sortable.mjs +435 -0
- package/dist/ui/primitives/sortable.mjs.map +1 -0
- package/dist/ui/primitives/switch.d.mts +6 -0
- package/dist/ui/primitives/switch.d.ts +6 -0
- package/dist/ui/primitives/switch.js +67 -0
- package/dist/ui/primitives/switch.js.map +1 -0
- package/dist/ui/primitives/switch.mjs +33 -0
- package/dist/ui/primitives/switch.mjs.map +1 -0
- package/dist/ui/primitives/table.d.mts +12 -0
- package/dist/ui/primitives/table.d.ts +12 -0
- package/dist/ui/primitives/table.js +130 -0
- package/dist/ui/primitives/table.js.map +1 -0
- package/dist/ui/primitives/table.mjs +89 -0
- package/dist/ui/primitives/table.mjs.map +1 -0
- package/dist/ui/primitives/tabs.d.mts +9 -0
- package/dist/ui/primitives/tabs.d.ts +9 -0
- package/dist/ui/primitives/tabs.js +86 -0
- package/dist/ui/primitives/tabs.js.map +1 -0
- package/dist/ui/primitives/tabs.mjs +49 -0
- package/dist/ui/primitives/tabs.mjs.map +1 -0
- package/dist/ui/primitives/textarea.d.mts +5 -0
- package/dist/ui/primitives/textarea.d.ts +5 -0
- package/dist/ui/primitives/textarea.js +43 -0
- package/dist/ui/primitives/textarea.js.map +1 -0
- package/dist/ui/primitives/textarea.mjs +19 -0
- package/dist/ui/primitives/textarea.mjs.map +1 -0
- package/dist/ui/primitives/tooltip.d.mts +9 -0
- package/dist/ui/primitives/tooltip.d.ts +9 -0
- package/dist/ui/primitives/tooltip.js +94 -0
- package/dist/ui/primitives/tooltip.js.map +1 -0
- package/dist/ui/primitives/tooltip.mjs +57 -0
- package/dist/ui/primitives/tooltip.mjs.map +1 -0
- package/docs/agents.md +72 -29
- package/docs/api/README.md +6 -3
- package/docs/api/appObjects.d.ts +1 -1
- package/docs/api/appObjects.md +1 -1
- package/docs/api/by-path.md +29 -0
- package/docs/api/objects.d.ts +72 -5
- package/docs/api/objects.md +84 -7
- package/docs/api/processLists.d.ts +247 -0
- package/docs/api/processLists.md +409 -0
- package/docs/api/reports.d.ts +476 -0
- package/docs/api/reports.md +539 -0
- package/docs/api/scheduleBuy.d.ts +12 -8
- package/docs/api/scheduleBuy.md +13 -10
- package/docs/api/seo.d.ts +406 -3
- package/docs/api/seo.md +505 -1
- package/docs/api/workspaceBackup.d.ts +324 -0
- package/docs/api/workspaceBackup.md +427 -0
- package/package.json +174 -5
- package/stacks/6sense.json +46 -0
- package/stacks/README.md +20 -0
- package/stacks/apollo.json +128 -0
- package/stacks/clay.json +59 -0
- package/stacks/gong.json +72 -0
- package/stacks/hubspot.json +162 -0
- package/stacks/intercom.json +53 -0
- package/stacks/mailchimp.json +71 -0
- package/stacks/outreach.json +86 -0
- package/stacks/salesforce.json +94 -0
- package/stacks/stack.schema.json +32 -0
- package/stacks/starter.json +22 -0
- package/styles/styles.css +2 -0
- package/styles/theme.css +181 -0
- package/styles/tokens.css +284 -0
- package/templates/next-app/README.md.tmpl +35 -0
- package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
- package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
- package/templates/next-app/app/globals.css.tmpl +8 -0
- package/templates/next-app/app/layout.tsx.tmpl +19 -0
- package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
- package/templates/next-app/dot-env.local.example.tmpl +6 -0
- package/templates/next-app/dot-gitignore.tmpl +7 -0
- package/templates/next-app/lib/nav.ts.tmpl +2 -0
- package/templates/next-app/next.config.mjs.tmpl +4 -0
- package/templates/next-app/package.json.tmpl +25 -0
- package/templates/next-app/postcss.config.mjs.tmpl +4 -0
- package/templates/next-app/tsconfig.json.tmpl +21 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NurtureStats: where a nurture stands and how it is performing.
|
|
5
|
+
*
|
|
6
|
+
* SDK operations, in order:
|
|
7
|
+
* 1. getNurture (GET /nurtures/{id}) read
|
|
8
|
+
* 2. listLists (GET /lists) read (audience size)
|
|
9
|
+
* 3. getSequencePerformanceById (GET /analytics/outbound/sequence-performance/{id}?days) read (contacts, emails sent, replies, meetings)
|
|
10
|
+
* There is no nurture-specific analytics endpoint. A nurture is a sequence row, so the outbound sequence report is
|
|
11
|
+
* asked for it; if the report has nothing for it (cached for ~5 minutes, or not yet sending) the panel says so
|
|
12
|
+
* instead of showing zeros. Per-step funnels are not exposed by the API.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useState } from "react";
|
|
16
|
+
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
|
|
17
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
18
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
19
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
20
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
21
|
+
import { isNurtureMissing, isNurtureOff, percentOf, statusOf } from "../lib/nurture-types";
|
|
22
|
+
import type { ListOption, Nurture, NurturePerformance } from "../lib/nurture-types";
|
|
23
|
+
|
|
24
|
+
export interface NurtureStatsProps {
|
|
25
|
+
nurtureId: string;
|
|
26
|
+
/** Window of the report in days. Default 30. */
|
|
27
|
+
days?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Tile({ label, value }: { label: string; value: string }) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="rounded-md border p-3">
|
|
33
|
+
<p className="text-muted-foreground text-xs">{label}</p>
|
|
34
|
+
<p className="text-xl font-semibold">{value}</p>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function NurtureStats({ nurtureId, days = 30 }: NurtureStatsProps) {
|
|
40
|
+
const [windowDays, setWindowDays] = useState(days);
|
|
41
|
+
const nurture = useG8Query((signal) => g8Get<Nurture>(`/nurtures/${encodeURIComponent(nurtureId)}`, {}, { signal }).then((r) => r.data), [nurtureId]);
|
|
42
|
+
const lists = useG8Query((signal) => g8Get<ListOption[]>("/lists", { limit: 200 }, { signal }).then((r) => r.data ?? []), []);
|
|
43
|
+
const perf = useG8Query(
|
|
44
|
+
(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
|
+
[nurtureId, windowDays],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (isNurtureOff(nurture.error)) return <EmptyState title="Nurture is not enabled" description="Nurture is behind a feature flag for this workspace." />;
|
|
49
|
+
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
|
+
if (nurture.error) return <ApiError error={nurture.error} title="Could not load the nurture" onRetry={() => void nurture.refetch()} />;
|
|
51
|
+
if (!nurture.data) return <Skeleton className="h-32 w-full" aria-busy="true" />;
|
|
52
|
+
const audience = (lists.data ?? []).find((l) => l.id === nurture.data?.associated_list_id);
|
|
53
|
+
const p = perf.data;
|
|
54
|
+
const hasNumbers = Boolean(p && (p.contacts_total || p.emails_sent));
|
|
55
|
+
return (
|
|
56
|
+
<Card>
|
|
57
|
+
<CardHeader><CardTitle>{nurture.data.name} <span className="text-muted-foreground text-sm font-normal">{statusOf(nurture.data)}</span></CardTitle></CardHeader>
|
|
58
|
+
<CardContent className="grid gap-4">
|
|
59
|
+
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
|
60
|
+
<Tile label="Audience" value={audience ? (audience.contact_count ?? 0).toLocaleString() : "No list"} />
|
|
61
|
+
<Tile label="Emails" value={String(nurture.data.steps?.length ?? "n/a")} />
|
|
62
|
+
<Tile label="Stops on reply" value={nurture.data.finish_on_reply === false ? "No" : "Yes"} />
|
|
63
|
+
<Tile label="Opt-out line" value={nurture.data.include_unsubscribe_cta === false ? "No" : "Yes"} />
|
|
64
|
+
</div>
|
|
65
|
+
<div className="flex items-center gap-2" role="group" aria-label="Report window">
|
|
66
|
+
{[7, 30, 90].map((d) => <Button key={d} size="sm" variant={windowDays === d ? "secondary" : "ghost"} onClick={() => setWindowDays(d)}>{d} days</Button>)}
|
|
67
|
+
</div>
|
|
68
|
+
{perf.error ? <p className="text-muted-foreground text-sm">Performance numbers are not available for this nurture yet.</p> : perf.loading && !perf.data ? <Skeleton className="h-20 w-full" /> : !hasNumbers ? (
|
|
69
|
+
<p className="text-muted-foreground text-sm">No sends in the last {windowDays} days. Numbers appear after the first emails go out (the report refreshes every few minutes).</p>
|
|
70
|
+
) : (
|
|
71
|
+
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
|
72
|
+
<Tile label="Contacts" value={(p?.contacts_total ?? 0).toLocaleString()} />
|
|
73
|
+
<Tile label="Emails sent" value={(p?.emails_sent ?? 0).toLocaleString()} />
|
|
74
|
+
<Tile label="Replies" value={`${(p?.emails_replied ?? 0).toLocaleString()} (${percentOf(p?.reply_rate)})`} />
|
|
75
|
+
<Tile label="Meetings" value={(p?.meetings_booked ?? 0).toLocaleString()} />
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
</CardContent>
|
|
79
|
+
</Card>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Types and helpers for the nurture block.
|
|
5
|
+
*
|
|
6
|
+
* A nurture is a sequence of kind `nurture`: a series of emails to a CONSENTED audience (a list), sent from ONE
|
|
7
|
+
* pinned mailbox, stopping per contact when they reply. It is not cold outreach. There is no nurture-specific stats
|
|
8
|
+
* endpoint; status, audience size and the sequence-performance report are what the API gives.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { G8Error } from "@graph8/sdk";
|
|
12
|
+
|
|
13
|
+
export interface Nurture {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
status?: string;
|
|
17
|
+
is_shared?: boolean;
|
|
18
|
+
description?: string | null;
|
|
19
|
+
associated_list_id?: number | null;
|
|
20
|
+
pinned_mailbox_id?: number | null;
|
|
21
|
+
finish_on_reply?: boolean;
|
|
22
|
+
send_in_same_thread?: boolean;
|
|
23
|
+
include_unsubscribe_cta?: boolean;
|
|
24
|
+
steps?: unknown[];
|
|
25
|
+
created_at?: string;
|
|
26
|
+
updated_at?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MailboxOption {
|
|
30
|
+
id: number;
|
|
31
|
+
email: string;
|
|
32
|
+
channel_type: string;
|
|
33
|
+
status?: string;
|
|
34
|
+
is_archived?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ListOption {
|
|
38
|
+
id: number;
|
|
39
|
+
title: string;
|
|
40
|
+
contact_count?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface StepDraft {
|
|
44
|
+
waitDays: number;
|
|
45
|
+
subject: string;
|
|
46
|
+
body: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface NurturePerformance {
|
|
50
|
+
contacts_total?: number;
|
|
51
|
+
emails_sent?: number;
|
|
52
|
+
emails_replied?: number;
|
|
53
|
+
meetings_booked?: number;
|
|
54
|
+
reply_rate?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The nurture feature is behind a flag: the API answers 404 "Nurture is not enabled" until it is on. */
|
|
58
|
+
export function isNurtureOff(error: unknown): boolean {
|
|
59
|
+
if (!(error instanceof G8Error) || error.status !== 404) return false;
|
|
60
|
+
const text = `${typeof error.detail === "string" ? error.detail : JSON.stringify(error.detail ?? "")} ${error.message}`;
|
|
61
|
+
return /not enabled/i.test(text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A 404 that is NOT the flag: the nurture does not exist, or is not yours to see. */
|
|
65
|
+
export function isNurtureMissing(error: unknown): boolean {
|
|
66
|
+
return error instanceof G8Error && error.status === 404 && !isNurtureOff(error);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function statusOf(n: Pick<Nurture, "status">): string {
|
|
70
|
+
return (n.status ?? "drafted").toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isDrafted(n: Pick<Nurture, "status">): boolean {
|
|
74
|
+
return /^draft/.test(statusOf(n));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isLiveLike(n: Pick<Nurture, "status">): boolean {
|
|
78
|
+
return /live|running|active|waiting|scheduling/.test(statusOf(n));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isPaused(n: Pick<Nurture, "status">): boolean {
|
|
82
|
+
return /paus/.test(statusOf(n));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isFinished(n: Pick<Nurture, "status">): boolean {
|
|
86
|
+
return /complet|terminat|archiv/.test(statusOf(n));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Email steps for the create call. Step 1 has no wait; later steps wait `waitDays` (the API takes seconds). */
|
|
90
|
+
export function stepsPayload(drafts: readonly StepDraft[]) {
|
|
91
|
+
return drafts.map((d, i) => ({
|
|
92
|
+
step_order: i + 1,
|
|
93
|
+
step_type: "EMAIL",
|
|
94
|
+
input_type: "MANUAL_TEMPLATE",
|
|
95
|
+
time_interval: i === 0 ? 0 : Math.max(0, Math.round(d.waitDays * 86400)),
|
|
96
|
+
step_data: { subject: d.subject.trim(), body: d.body, email_type: "plain" },
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A nurture sends from exactly one mailbox; the same mailbox is attached as its email channel. */
|
|
101
|
+
export function channelPayload(mailbox: MailboxOption) {
|
|
102
|
+
return [{ channel_id: mailbox.id, channel_value: mailbox.email, channel_type: mailbox.channel_type }];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function percentOf(rate: number | undefined): string {
|
|
106
|
+
if (rate == null) return "n/a";
|
|
107
|
+
const pct = rate <= 1 ? rate * 100 : rate;
|
|
108
|
+
return `${Math.round(pct * 10) / 10}%`;
|
|
109
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Create a drafted nurture with its emails, pinned to one mailbox
|
|
3
|
+
* @when Setting up a lifecycle series (welcome, onboarding, re-engagement) for CONSENTED contacts. A nurture is not
|
|
4
|
+
* cold outreach: it is pinned to ONE mailbox, which must already be connected in the graph8 app (Google and Microsoft
|
|
5
|
+
* sign-in cannot be done through the API). Creating it only drafts it; nothing is sent until launch.
|
|
6
|
+
*/
|
|
7
|
+
import { g8 } from "@graph8/sdk";
|
|
8
|
+
|
|
9
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
10
|
+
|
|
11
|
+
// 1. The sender. Its id is the nurture's pinned_mailbox_id AND the id of its email channel.
|
|
12
|
+
const mine = await g8.api.mailboxes.listMyMailboxes({});
|
|
13
|
+
const mailbox = mine.data.mailboxes?.find((m) => !m.is_archived && m.id != null);
|
|
14
|
+
if (!mailbox || mailbox.id == null) throw new Error("No mailbox connected. Connect one in the graph8 app first.");
|
|
15
|
+
|
|
16
|
+
// 2. The audience: a list of people who agreed to hear from you.
|
|
17
|
+
const audience = await g8.lists.create("Newsletter signups - welcome series");
|
|
18
|
+
|
|
19
|
+
// 3. Create. time_interval is SECONDS before the step; step 1 sends at launch.
|
|
20
|
+
const nurture = await g8.api.sequences.createNurture({
|
|
21
|
+
body: {
|
|
22
|
+
name: "Welcome series",
|
|
23
|
+
pinned_mailbox_id: mailbox.id,
|
|
24
|
+
associated_list_id: Number(audience.id),
|
|
25
|
+
finish_on_reply: true,
|
|
26
|
+
include_unsubscribe_cta: true,
|
|
27
|
+
steps: [
|
|
28
|
+
{ step_order: 1, step_type: "EMAIL", input_type: "MANUAL_TEMPLATE", time_interval: 0, step_data: { subject: "Welcome", body: "Glad you are here...", email_type: "plain" } },
|
|
29
|
+
{ step_order: 2, step_type: "EMAIL", input_type: "MANUAL_TEMPLATE", time_interval: 3 * 86400, step_data: { subject: "Getting started", body: "Three things to try first...", email_type: "plain" } },
|
|
30
|
+
],
|
|
31
|
+
channels: [{ channel_id: mailbox.id, channel_value: mailbox.email, channel_type: mailbox.channel_type }],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
console.log("drafted nurture", nurture.data.id);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Add consented contacts to a nurture's audience and (with approval) launch it
|
|
3
|
+
* @when A contact opted in (form, signup, purchase) and should start the series. "Enrolling" is adding them to the
|
|
4
|
+
* nurture's audience list. Launching SENDS REAL EMAIL: the last call is guarded and must be approved by a person.
|
|
5
|
+
*/
|
|
6
|
+
import { g8 } from "@graph8/sdk";
|
|
7
|
+
|
|
8
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
9
|
+
|
|
10
|
+
export async function enroll(nurtureId: string, contactIds: number[], approvedToLaunch = false): Promise<void> {
|
|
11
|
+
const nurture = await g8.api.sequences.getNurture({ path: { nurture_id: nurtureId } });
|
|
12
|
+
const listId = (nurture.data as { associated_list_id?: number | null }).associated_list_id;
|
|
13
|
+
if (!listId) throw new Error("Point the nurture at a list first (updateNurture associated_list_id).");
|
|
14
|
+
|
|
15
|
+
// 1. Put the contacts in the audience (write). They must have consented.
|
|
16
|
+
await g8.api.lists.addContactsToList({ path: { list_id: listId }, body: { contact_ids: contactIds } });
|
|
17
|
+
|
|
18
|
+
// 2. Launch only on an explicit yes, and only for a nurture that is still a draft.
|
|
19
|
+
if (!approvedToLaunch) {
|
|
20
|
+
console.log(`Added ${contactIds.length} contacts. Not launched: ask the owner to approve going live.`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await g8.api.sequences.launchNurture({ path: { nurture_id: nurtureId } });
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Check every nurture's status and recent results
|
|
3
|
+
* @when A daily check or a dashboard backend. The performance report is cached for about five minutes and is the
|
|
4
|
+
* outbound sequence report (a nurture is a sequence row); a nurture with no sends yet has no numbers, which is not an error.
|
|
5
|
+
*/
|
|
6
|
+
import { g8 } from "@graph8/sdk";
|
|
7
|
+
|
|
8
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
9
|
+
|
|
10
|
+
export async function healthCheck(days = 30): Promise<Array<{ id: string; name: string; status: string; sent: number; replies: number }>> {
|
|
11
|
+
const page = await g8.api.sequences.listNurtures({ query: { limit: 100 } });
|
|
12
|
+
const out: Array<{ id: string; name: string; status: string; sent: number; replies: number }> = [];
|
|
13
|
+
for (const n of page.data.nurtures ?? []) {
|
|
14
|
+
if (!n.id) continue;
|
|
15
|
+
let sent = 0;
|
|
16
|
+
let replies = 0;
|
|
17
|
+
try {
|
|
18
|
+
const report = await g8.api.analytics.getSequencePerformanceById({ path: { sequence_id: n.id }, query: { days } });
|
|
19
|
+
const row = (report.data as { data?: { emails_sent?: number; emails_replied?: number } }).data;
|
|
20
|
+
sent = row?.emails_sent ?? 0;
|
|
21
|
+
replies = row?.emails_replied ?? 0;
|
|
22
|
+
} catch {
|
|
23
|
+
// no report for this nurture yet
|
|
24
|
+
}
|
|
25
|
+
out.push({ id: n.id, name: n.name ?? "", status: n.status ?? "", sent, replies });
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# objects: custom objects
|
|
2
|
+
|
|
3
|
+
Define your own record types (an invoice, a shipment, a subscription) with typed fields, then list, create,
|
|
4
|
+
edit and archive their records. `ObjectTable` and `ObjectRecordDetail` are generic: point them at ANY object
|
|
5
|
+
slug, including the standard `contacts`, `companies` and `deals`. This is the HubSpot "custom objects" / Salesforce
|
|
6
|
+
"custom object" screen set.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
npx @graph8/sdk add objects
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Installs `foundation` first if it is missing. Everything lands under `g8/objects/`.
|
|
15
|
+
|
|
16
|
+
## Prerequisites (the human must do these)
|
|
17
|
+
|
|
18
|
+
| Where | What | How to verify |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| graph8 workspace | Creating objects and fields needs `objects:manage` **and an Admin** behind the key. Records need `objects:read`, `objects:write`, `objects:delete`. | `g8.api.me.describeCurrentKey()` |
|
|
21
|
+
| Your app | Add `"objects"` and `"records"` to `allowedPrefixes` in `app/api/g8/[...path]/route.ts`. | The route file lists both prefixes. |
|
|
22
|
+
|
|
23
|
+
## The flow, in order
|
|
24
|
+
|
|
25
|
+
1. **Define the schema** (once, an admin): render `<ObjectSchemaEditor />`.
|
|
26
|
+
`createObject` returns `{ slug, singular_noun, plural_noun }`. The `slug` is immutable and is the id every other call uses
|
|
27
|
+
(`/objects/{slug}/...`). Then `createAttribute` for each field; its `slug` is the key inside `record.values`.
|
|
28
|
+
Select and status fields need `config.options` (a list of strings); the editor takes one option per line.
|
|
29
|
+
2. **List records**: `<ObjectTable objectSlug="invoices" onSelectRecord={...} />`. Columns come from `listObjectAttributes`
|
|
30
|
+
(display order), rows from `listObjectRecords`, paged with `pagination.next_cursor` ("Load more").
|
|
31
|
+
3. **Create or edit a record**: `RecordFormDialog` builds one input per field. Create sends only the fields the person filled in.
|
|
32
|
+
Edit sends only the fields that **changed**, plus `expected_revision` from the record, so a concurrent edit is a `409` instead of a silent overwrite.
|
|
33
|
+
4. **Open one record**: `<ObjectRecordDetail objectSlug="invoices" recordId={record.id} />` loads `getObjectRecord`, then
|
|
34
|
+
`getObjectRecordChanges` (history) and `getObjectRelationships` (links to other records).
|
|
35
|
+
5. **Archive**: the only delete. The record leaves lists but stays readable by id with its history; `restoreObjectRecord` brings it back.
|
|
36
|
+
Archiving a record, a field or a whole object always goes through `ConfirmSpendDialog`.
|
|
37
|
+
|
|
38
|
+
For `contacts`, `companies` and `deals`, `ObjectRecordDetail` also renders `RecordRolesPanel` (SDR / AE / CSM owner slots).
|
|
39
|
+
Custom objects have no role slots.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// app/invoices/page.tsx
|
|
43
|
+
"use client";
|
|
44
|
+
import { useState } from "react";
|
|
45
|
+
import { ObjectTable } from "@/g8/objects/components/object-table";
|
|
46
|
+
import { ObjectRecordDetail } from "@/g8/objects/components/object-record-detail";
|
|
47
|
+
|
|
48
|
+
export default function Invoices() {
|
|
49
|
+
const [open, setOpen] = useState<string | null>(null);
|
|
50
|
+
return open ? (
|
|
51
|
+
<ObjectRecordDetail objectSlug="invoices" recordId={open} onBack={() => setOpen(null)} onArchived={() => setOpen(null)} />
|
|
52
|
+
) : (
|
|
53
|
+
<ObjectTable objectSlug="invoices" onSelectRecord={(r) => setOpen(r.id)} />
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Default decisions (do not ask the human unless marked)
|
|
59
|
+
|
|
60
|
+
| Decision | Default | Ask? |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Who can open `ObjectSchemaEditor` | Only signed-in admins (put it behind an admin route) | **yes** |
|
|
63
|
+
| How records are deleted | Archive (hidden, readable by id, history kept). The API has no hard delete for records | no |
|
|
64
|
+
| Which field titles a record | `display_attribute_slug`, else the first text field | no |
|
|
65
|
+
| Page size | 50 rows per request with "Load more" | no |
|
|
66
|
+
| Field types in the editor | All except `formula` (needs an expression tree: use a recipe or `validateFormula`) | no |
|
|
67
|
+
|
|
68
|
+
## Things that surprise people
|
|
69
|
+
|
|
70
|
+
- **Unknown fields are a 422, not ignored.** The response lists every problem at once; the dialogs show them inline.
|
|
71
|
+
- **`null` clears a value** on update. Omitted fields are left alone. Required fields cannot be cleared.
|
|
72
|
+
- **No server-side search** on `listObjectRecords`. `ObjectTable` filters the rows already loaded; for big objects use `upsert` recipes and your own index,.
|
|
73
|
+
- **Values are plain scalars** keyed by field slug (`{ number: "INV-1", amount: 1200 }`). A multiselect field takes a list; a reference field takes a record id.
|
|
74
|
+
|
|
75
|
+
## Recipes (server-side, `recipes/`)
|
|
76
|
+
|
|
77
|
+
- `define-object-and-add-records.ts`: create the object, its fields and the first records.
|
|
78
|
+
- `upsert-records-from-your-system.ts`: idempotent sync keyed on a unique field.
|
|
79
|
+
- `update-audit-and-archive-records.ts`: cursor paging, conditional update, change history, archive and restore.
|
|
80
|
+
|
|
81
|
+
## What is in the block
|
|
82
|
+
|
|
83
|
+
| Component | File | What it is |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `ObjectSchemaEditor` | `components/object-schema-editor.tsx` | Create objects, edit their labels, add/edit/archive fields (text, number, select, reference...). |
|
|
86
|
+
| `AttributeFormDialog` | `components/attribute-form-dialog.tsx` | Dialog to add or edit one field of an object. |
|
|
87
|
+
| `ObjectTable` | `components/object-table.tsx` | Records of any object as a table with search over loaded rows, load-more, create, edit and archive. |
|
|
88
|
+
| `RecordFormDialog` | `components/record-form-dialog.tsx` | Dialog to create or edit one record; builds inputs from the object's fields and shows 422 field errors. |
|
|
89
|
+
| `AttributeFieldInput` | `components/attribute-field-input.tsx` | One form control chosen by attribute type (text, number, date, select, checkbox...). |
|
|
90
|
+
| `ObjectNotFound` | `components/object-not-found.tsx` | Tells an unreachable custom-objects API (the list call itself 404s) apart from a deleted, mistyped or inaccessible object/record. |
|
|
91
|
+
| `ObjectRecordDetail` | `components/object-record-detail.tsx` | One record: values, related records, change history, edit, archive and restore. |
|
|
92
|
+
| `RecordRolesPanel` | `components/record-roles-panel.tsx` | Owner slots (SDR, AE, CSM) of a contact, company or deal: view, assign, clear. |
|
|
93
|
+
| `useObjectSchema` (hook) | `hooks/use-object-schema.ts` | Load one object's definition and its active fields in display order. |
|
|
94
|
+
|
|
95
|
+
## Operations
|
|
96
|
+
|
|
97
|
+
Tiers: `read` and `write` need no confirmation; `destructive` always opens `ConfirmSpendDialog` first.
|
|
98
|
+
|
|
99
|
+
| Operation | Method and path | Tier | Scope | Used by |
|
|
100
|
+
|---|---|---|---|---|
|
|
101
|
+
| `objects.listObjects` | `GET /api/v1/objects` | read | `objects:read` | ObjectSchemaEditor |
|
|
102
|
+
| `objects.createObject` | `POST /api/v1/objects` | write | `objects:manage` | ObjectSchemaEditor |
|
|
103
|
+
| `objects.getObject` | `GET /api/v1/objects/{object_slug}` | read | `objects:read` | ObjectSchemaEditor, ObjectTable, ObjectRecordDetail, useObjectSchema |
|
|
104
|
+
| `objects.listObjectAttributes` | `GET /api/v1/objects/{object_slug}/attributes` | read | `objects:read` | ObjectSchemaEditor, ObjectTable, ObjectRecordDetail, useObjectSchema |
|
|
105
|
+
| `objects.updateObject` | `PATCH /api/v1/objects/{object_slug}` | write | `objects:manage` | ObjectSchemaEditor |
|
|
106
|
+
| `objects.createAttribute` | `POST /api/v1/objects/{object_slug}/attributes` | write | `objects:manage` | ObjectSchemaEditor, AttributeFormDialog |
|
|
107
|
+
| `objects.updateAttribute` | `PATCH /api/v1/objects/{object_slug}/attributes/{attr_slug}` | write | `objects:manage` | ObjectSchemaEditor, AttributeFormDialog |
|
|
108
|
+
| `objects.archiveAttribute` | `DELETE /api/v1/objects/{object_slug}/attributes/{attr_slug}` | destructive | `objects:manage` | ObjectSchemaEditor |
|
|
109
|
+
| `objects.archiveObject` | `DELETE /api/v1/objects/{object_slug}` | destructive | `objects:manage` | ObjectSchemaEditor |
|
|
110
|
+
| `objects.listObjectRecords` | `GET /api/v1/objects/{object_slug}/records` | read | `objects:read` | ObjectTable |
|
|
111
|
+
| `objects.createObjectRecord` | `POST /api/v1/objects/{object_slug}/records` | write | `objects:write` | ObjectTable, RecordFormDialog |
|
|
112
|
+
| `objects.updateObjectRecord` | `PATCH /api/v1/objects/{object_slug}/records/{record_id}` | write | `objects:write` | ObjectTable, RecordFormDialog, ObjectRecordDetail |
|
|
113
|
+
| `objects.archiveObjectRecord` | `DELETE /api/v1/objects/{object_slug}/records/{record_id}` | destructive | `objects:delete` | ObjectTable, ObjectRecordDetail |
|
|
114
|
+
| `objects.getObjectRecord` | `GET /api/v1/objects/{object_slug}/records/{record_id}` | read | `objects:read` | ObjectRecordDetail |
|
|
115
|
+
| `objects.getObjectRecordChanges` | `GET /api/v1/objects/{object_slug}/records/{record_id}/changes` | read | `objects:read` | ObjectRecordDetail |
|
|
116
|
+
| `objects.getObjectRelationships` | `GET /api/v1/objects/{object_slug}/records/{record_id}/relationships` | read | `objects:read` | ObjectRecordDetail |
|
|
117
|
+
| `objects.restoreObjectRecord` | `POST /api/v1/objects/{object_slug}/records/{record_id}/restore` | write | `objects:write` | ObjectRecordDetail |
|
|
118
|
+
| `recordRoles.getRecordRoleSlots` | `GET /api/v1/records/{entity_type}/{entity_id}/role-slots` | read | `contacts:read` | RecordRolesPanel |
|
|
119
|
+
| `recordRoles.assignRecordRole` | `POST /api/v1/records/{entity_type}/{entity_id}/role-assignments` | write | `contacts:write` | RecordRolesPanel |
|
|
120
|
+
| `recordRoles.clearRecordRole` | `DELETE /api/v1/records/{entity_type}/{entity_id}/role-assignments/{role_slot}` | destructive | `contacts:delete` | RecordRolesPanel |
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "objects",
|
|
4
|
+
"title": "Custom objects",
|
|
5
|
+
"summary": "Define your own record types (invoices, shipments, subscriptions) with typed fields, then list, create, edit and archive their records.",
|
|
6
|
+
"features": [
|
|
7
|
+
"object and field schema editor",
|
|
8
|
+
"generic records table for any object",
|
|
9
|
+
"record detail with owners, relationships and change history",
|
|
10
|
+
"optimistic concurrency on edits",
|
|
11
|
+
"archive and restore with history kept"
|
|
12
|
+
],
|
|
13
|
+
"dependsOn": [
|
|
14
|
+
"foundation"
|
|
15
|
+
],
|
|
16
|
+
"operations": [
|
|
17
|
+
"list_objects_objects_get",
|
|
18
|
+
"create_object_objects_post",
|
|
19
|
+
"get_object_objects__object_slug__get",
|
|
20
|
+
"list_object_attributes_objects__object_slug__attributes_get",
|
|
21
|
+
"update_object_objects__object_slug__patch",
|
|
22
|
+
"create_attribute_objects__object_slug__attributes_post",
|
|
23
|
+
"update_attribute_objects__object_slug__attributes__attr_slug__patch",
|
|
24
|
+
"archive_attribute_objects__object_slug__attributes__attr_slug__delete",
|
|
25
|
+
"archive_object_objects__object_slug__delete",
|
|
26
|
+
"list_object_records_objects__object_slug__records_get",
|
|
27
|
+
"create_object_record_objects__object_slug__records_post",
|
|
28
|
+
"update_object_record_objects__object_slug__records__record_id__patch",
|
|
29
|
+
"archive_object_record_objects__object_slug__records__record_id__delete",
|
|
30
|
+
"get_object_record_objects__object_slug__records__record_id__get",
|
|
31
|
+
"get_object_record_changes_objects__object_slug__records__record_id__changes_get",
|
|
32
|
+
"get_object_relationships_objects__object_slug__records__record_id__relationships_get",
|
|
33
|
+
"restore_object_record_objects__object_slug__records__record_id__restore_post",
|
|
34
|
+
"get_record_role_slots_records__entity_type___entity_id__role_slots_get",
|
|
35
|
+
"assign_record_role_records__entity_type___entity_id__role_assignments_post",
|
|
36
|
+
"clear_record_role_records__entity_type___entity_id__role_assignments__role_slot__delete"
|
|
37
|
+
],
|
|
38
|
+
"components": [
|
|
39
|
+
{
|
|
40
|
+
"name": "ObjectSchemaEditor",
|
|
41
|
+
"file": "components/object-schema-editor.tsx",
|
|
42
|
+
"purpose": "Create objects, edit their labels, add/edit/archive fields (text, number, select, reference...).",
|
|
43
|
+
"operations": [
|
|
44
|
+
"list_objects_objects_get",
|
|
45
|
+
"create_object_objects_post",
|
|
46
|
+
"get_object_objects__object_slug__get",
|
|
47
|
+
"list_object_attributes_objects__object_slug__attributes_get",
|
|
48
|
+
"update_object_objects__object_slug__patch",
|
|
49
|
+
"create_attribute_objects__object_slug__attributes_post",
|
|
50
|
+
"update_attribute_objects__object_slug__attributes__attr_slug__patch",
|
|
51
|
+
"archive_attribute_objects__object_slug__attributes__attr_slug__delete",
|
|
52
|
+
"archive_object_objects__object_slug__delete"
|
|
53
|
+
],
|
|
54
|
+
"tiers": [
|
|
55
|
+
"read",
|
|
56
|
+
"write",
|
|
57
|
+
"destructive"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "AttributeFormDialog",
|
|
62
|
+
"file": "components/attribute-form-dialog.tsx",
|
|
63
|
+
"purpose": "Dialog to add or edit one field of an object.",
|
|
64
|
+
"operations": [
|
|
65
|
+
"create_attribute_objects__object_slug__attributes_post",
|
|
66
|
+
"update_attribute_objects__object_slug__attributes__attr_slug__patch"
|
|
67
|
+
],
|
|
68
|
+
"tiers": [
|
|
69
|
+
"write"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "ObjectTable",
|
|
74
|
+
"file": "components/object-table.tsx",
|
|
75
|
+
"purpose": "Records of any object as a table with search over loaded rows, load-more, create, edit and archive.",
|
|
76
|
+
"operations": [
|
|
77
|
+
"get_object_objects__object_slug__get",
|
|
78
|
+
"list_object_attributes_objects__object_slug__attributes_get",
|
|
79
|
+
"list_object_records_objects__object_slug__records_get",
|
|
80
|
+
"create_object_record_objects__object_slug__records_post",
|
|
81
|
+
"update_object_record_objects__object_slug__records__record_id__patch",
|
|
82
|
+
"archive_object_record_objects__object_slug__records__record_id__delete"
|
|
83
|
+
],
|
|
84
|
+
"tiers": [
|
|
85
|
+
"read",
|
|
86
|
+
"write",
|
|
87
|
+
"destructive"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "RecordFormDialog",
|
|
92
|
+
"file": "components/record-form-dialog.tsx",
|
|
93
|
+
"purpose": "Dialog to create or edit one record; builds inputs from the object's fields and shows 422 field errors.",
|
|
94
|
+
"operations": [
|
|
95
|
+
"create_object_record_objects__object_slug__records_post",
|
|
96
|
+
"update_object_record_objects__object_slug__records__record_id__patch"
|
|
97
|
+
],
|
|
98
|
+
"tiers": [
|
|
99
|
+
"write"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "AttributeFieldInput",
|
|
104
|
+
"file": "components/attribute-field-input.tsx",
|
|
105
|
+
"purpose": "One form control chosen by attribute type (text, number, date, select, checkbox...).",
|
|
106
|
+
"operations": [],
|
|
107
|
+
"tiers": []
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "ObjectNotFound",
|
|
111
|
+
"file": "components/object-not-found.tsx",
|
|
112
|
+
"purpose": "Tells an unreachable custom-objects API (the list call itself 404s) apart from a deleted, mistyped or inaccessible object/record.",
|
|
113
|
+
"operations": [
|
|
114
|
+
"list_objects_objects_get"
|
|
115
|
+
],
|
|
116
|
+
"tiers": [
|
|
117
|
+
"read"
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "ObjectRecordDetail",
|
|
122
|
+
"file": "components/object-record-detail.tsx",
|
|
123
|
+
"purpose": "One record: values, related records, change history, edit, archive and restore.",
|
|
124
|
+
"operations": [
|
|
125
|
+
"get_object_objects__object_slug__get",
|
|
126
|
+
"list_object_attributes_objects__object_slug__attributes_get",
|
|
127
|
+
"get_object_record_objects__object_slug__records__record_id__get",
|
|
128
|
+
"get_object_record_changes_objects__object_slug__records__record_id__changes_get",
|
|
129
|
+
"get_object_relationships_objects__object_slug__records__record_id__relationships_get",
|
|
130
|
+
"update_object_record_objects__object_slug__records__record_id__patch",
|
|
131
|
+
"archive_object_record_objects__object_slug__records__record_id__delete",
|
|
132
|
+
"restore_object_record_objects__object_slug__records__record_id__restore_post"
|
|
133
|
+
],
|
|
134
|
+
"tiers": [
|
|
135
|
+
"read",
|
|
136
|
+
"write",
|
|
137
|
+
"destructive"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "RecordRolesPanel",
|
|
142
|
+
"file": "components/record-roles-panel.tsx",
|
|
143
|
+
"purpose": "Owner slots (SDR, AE, CSM) of a contact, company or deal: view, assign, clear.",
|
|
144
|
+
"operations": [
|
|
145
|
+
"get_record_role_slots_records__entity_type___entity_id__role_slots_get",
|
|
146
|
+
"assign_record_role_records__entity_type___entity_id__role_assignments_post",
|
|
147
|
+
"clear_record_role_records__entity_type___entity_id__role_assignments__role_slot__delete"
|
|
148
|
+
],
|
|
149
|
+
"tiers": [
|
|
150
|
+
"read",
|
|
151
|
+
"write",
|
|
152
|
+
"destructive"
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"hooks": [
|
|
157
|
+
{
|
|
158
|
+
"name": "useObjectSchema",
|
|
159
|
+
"file": "hooks/use-object-schema.ts",
|
|
160
|
+
"purpose": "Load one object's definition and its active fields in display order.",
|
|
161
|
+
"operations": [
|
|
162
|
+
"get_object_objects__object_slug__get",
|
|
163
|
+
"list_object_attributes_objects__object_slug__attributes_get"
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"lib": [
|
|
168
|
+
{
|
|
169
|
+
"file": "lib/object-types.ts",
|
|
170
|
+
"purpose": "Object/field/record types and pure helpers: slugify, value formatting, input to API value conversion, missing-API detection."
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
"recipes": [
|
|
174
|
+
{
|
|
175
|
+
"file": "recipes/define-object-and-add-records.ts",
|
|
176
|
+
"title": "Define a custom object, add its fields, create the first records",
|
|
177
|
+
"when": "Modelling something graph8 has no table for, before ObjectTable has anything to show."
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"file": "recipes/upsert-records-from-your-system.ts",
|
|
181
|
+
"title": "Sync records from your own system (idempotent upsert)",
|
|
182
|
+
"when": "Nightly or webhook-driven sync where the same external row can arrive twice."
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"file": "recipes/update-audit-and-archive-records.ts",
|
|
186
|
+
"title": "Page through records, update safely, read history, archive and restore",
|
|
187
|
+
"when": "Admin tooling or an agent that edits records; uses expected revision to avoid overwrites."
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"prerequisites": [
|
|
191
|
+
{
|
|
192
|
+
"id": "admin-schema-key",
|
|
193
|
+
"description": "Creating objects and fields needs the objects:manage scope and an Admin user behind the key; records need objects:read / objects:write / objects:delete.",
|
|
194
|
+
"where": "api",
|
|
195
|
+
"verify": "g8.api.me.describeCurrentKey() lists the objects:* scopes and the key owner is an Admin."
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": "proxy-allowlist",
|
|
199
|
+
"description": "Add \"objects\" and \"records\" to allowedPrefixes in app/api/g8/[...path]/route.ts so the browser can reach these areas through the proxy.",
|
|
200
|
+
"where": "app",
|
|
201
|
+
"verify": "The route file's allowedPrefixes contains \"objects\" and \"records\"."
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"scopes": [
|
|
205
|
+
"contacts:delete",
|
|
206
|
+
"contacts:read",
|
|
207
|
+
"contacts:write",
|
|
208
|
+
"objects:delete",
|
|
209
|
+
"objects:manage",
|
|
210
|
+
"objects:read",
|
|
211
|
+
"objects:write"
|
|
212
|
+
],
|
|
213
|
+
"defaults": [
|
|
214
|
+
{
|
|
215
|
+
"decision": "Who can open ObjectSchemaEditor in the app",
|
|
216
|
+
"default": "Only signed-in admins (put it behind an admin route); ObjectTable and ObjectRecordDetail are for everyone with access",
|
|
217
|
+
"askHuman": true
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"decision": "How records are deleted",
|
|
221
|
+
"default": "Archive: hidden from lists, still readable by id, history kept (the API has no hard delete for records)",
|
|
222
|
+
"askHuman": false
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"decision": "Which field titles a record",
|
|
226
|
+
"default": "The object's display_attribute_slug, else the first text field",
|
|
227
|
+
"askHuman": false
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"decision": "Page size of ObjectTable",
|
|
231
|
+
"default": "50 rows per request with Load more",
|
|
232
|
+
"askHuman": false
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"decision": "Field types offered in the editor",
|
|
236
|
+
"default": "All types except formula (formulas need an expression tree: use a recipe)",
|
|
237
|
+
"askHuman": false
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
"peerDependencies": {
|
|
241
|
+
"react": ">=18"
|
|
242
|
+
}
|
|
243
|
+
}
|