@graph8/sdk 0.303.0 → 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 +2220 -23
- package/dist/index.d.ts +2220 -23
- package/dist/index.js +64 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -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 +2082 -20
- package/dist/react.d.ts +2082 -20
- package/dist/react.js +63 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +63 -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 +7 -3
- package/docs/api/appObjects.d.ts +3 -1
- package/docs/api/appObjects.md +2 -1
- package/docs/api/by-path.md +51 -0
- package/docs/api/dataPipelines.d.ts +149 -0
- package/docs/api/dataPipelines.md +232 -0
- package/docs/api/forms.d.ts +16 -0
- package/docs/api/forms.md +8 -0
- package/docs/api/objects.d.ts +705 -5
- package/docs/api/objects.md +1099 -14
- package/docs/api/ops.d.ts +1 -1
- package/docs/api/ops.md +1 -1
- package/docs/api/processLists.d.ts +247 -0
- package/docs/api/processLists.md +409 -0
- package/docs/api/quotes.d.ts +3 -1
- package/docs/api/quotes.md +7 -2
- package/docs/api/radar.d.ts +4 -0
- package/docs/api/radar.md +1 -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,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Sync records from your own system into an object (idempotent upsert)
|
|
3
|
+
* @when A nightly or webhook-driven sync where the same external row can arrive twice. Upsert matches on one
|
|
4
|
+
* attribute (it must be unique), updates that record if it exists and creates it if not, so reruns never duplicate.
|
|
5
|
+
*/
|
|
6
|
+
import { g8, G8Error } from "@graph8/sdk";
|
|
7
|
+
|
|
8
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
9
|
+
|
|
10
|
+
interface ExternalInvoice {
|
|
11
|
+
number: string;
|
|
12
|
+
amount: number;
|
|
13
|
+
status: "draft" | "sent" | "paid" | "overdue";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function syncInvoices(rows: ExternalInvoice[]): Promise<{ ok: number; failed: Array<{ number: string; reason: string }> }> {
|
|
17
|
+
let ok = 0;
|
|
18
|
+
const failed: Array<{ number: string; reason: string }> = [];
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
try {
|
|
21
|
+
// `number` is the matching attribute: it must be marked unique on the object.
|
|
22
|
+
await g8.objects.upsertRecord("invoices", "number", { number: row.number, amount: row.amount, status: row.status });
|
|
23
|
+
ok += 1;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
// A 422 names every bad field at once; keep going so one bad row does not block the batch.
|
|
26
|
+
failed.push({ number: row.number, reason: err instanceof G8Error ? JSON.stringify(err.detail ?? err.message) : String(err) });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { ok, failed };
|
|
30
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# pages: landing pages & content
|
|
2
|
+
|
|
3
|
+
Editable React screens for the marketing-site side of a product: landing pages (list, section editor, publish),
|
|
4
|
+
a content library, the brand kit and SEO audits. Install with `npx @graph8/sdk add pages`.
|
|
5
|
+
|
|
6
|
+
| Component | What it is |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `PageList` | Landing pages with a status filter; create blank or with AI; publish, unpublish, delete. |
|
|
9
|
+
| `PageEditor` | Lite editor: page details, one card per section (its text fields), preview, version restore, publish. Not a WYSIWYG canvas. |
|
|
10
|
+
| `ContentLibrary` | Content inventory (blogs, website pages): filters, add by URL, edit sheet, SEO enrichment, delete. |
|
|
11
|
+
| `BrandKitEditor` | Palette, fonts and voice measured from the website, a re-scan action, and the page layouts new pages may use. |
|
|
12
|
+
| `SeoReport` | Run a site audit, read score and issues, track keyword rankings. |
|
|
13
|
+
|
|
14
|
+
## Flow, in order (and the ids passed between steps)
|
|
15
|
+
|
|
16
|
+
1. `PageList` calls `GET /landing-pages` and shows rows. Each row's `id` is the **page id** used by every later call.
|
|
17
|
+
2. Create: `POST /landing-pages` with `generate:false` (free, empty page) or `generate:true` (AI writes the page, **billable**,
|
|
18
|
+
confirmed in the UI). The response `data.id` is the new page id; the component hands it to `onOpen(pageId)`.
|
|
19
|
+
3. `PageEditor pageId=...` reads `GET /landing-pages/{id}`. `data.sections` is an array (sections mode) or `null` (freeform).
|
|
20
|
+
Saving a section calls `PATCH /landing-pages/{id}/sections/{idx}` with the **whole** content object (it replaces the old one).
|
|
21
|
+
4. Versions: `GET .../versions` lists snapshots; `POST .../versions/{version_num}/restore` rolls back (the current state is snapshotted first).
|
|
22
|
+
5. Publish: `POST .../publish` (**external**, confirmed). A published page keeps serving its published HTML until published again.
|
|
23
|
+
6. `ContentLibrary`: `GET /content/pages` -> row `id` -> `GET/PATCH /content/pages/{id}`; `POST .../enrich-seo` is **billable**; delete is **destructive**.
|
|
24
|
+
7. `BrandKitEditor`: `GET /brand-kit` (status `not_extracted | generating | extracted | failed`) -> `POST /brand-kit/extract` -> poll step 1 while `generating`.
|
|
25
|
+
8. `SeoReport`: `POST /seo/audits` (**billable**, reserves credits by `max_pages`) -> `data.id` = audit id -> poll `GET /seo/audits/{id}` until
|
|
26
|
+
`state.is_terminal` -> `GET /seo/audits/{id}/issues`.
|
|
27
|
+
|
|
28
|
+
## Mounting
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// app/pages/page.tsx
|
|
32
|
+
"use client";
|
|
33
|
+
import { useRouter } from "next/navigation";
|
|
34
|
+
import { PageList } from "@/components/g8/pages/components/page-list";
|
|
35
|
+
export default function Pages() { const router = useRouter(); return <PageList onOpen={(id) => router.push(`/pages/${id}`)} />; }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`PageEditor` takes `pageId`; `ContentLibrary`, `BrandKitEditor` and `SeoReport` take no required props.
|
|
39
|
+
|
|
40
|
+
## Prerequisites
|
|
41
|
+
|
|
42
|
+
- `foundation` installed, the `/api/g8` proxy route mounted with `allowedPrefixes` including `landing-pages`, `content`, `brand-kit`, `seo`.
|
|
43
|
+
- `@graph8/sdk/ui` peers installed (the CLI lists them).
|
|
44
|
+
- `where: "api"`: a company website on the workspace profile (the brand kit measures it), or pass a URL to the re-scan.
|
|
45
|
+
- `where: "app"`: a domain for published pages if the customer does not want the workspace default.
|
|
46
|
+
|
|
47
|
+
## Defaults the agent should use without asking
|
|
48
|
+
|
|
49
|
+
- Create from code with `generate:false`; the AI create stays behind a credits confirmation in the UI.
|
|
50
|
+
- 25 rows per page; Previous/Next.
|
|
51
|
+
- Mount `/pages` (list) and `/pages/[id]` (editor); put the other three screens behind tabs of a Marketing area.
|
|
52
|
+
|
|
53
|
+
## Ask the person first
|
|
54
|
+
|
|
55
|
+
- **Publishing** any page (it is public the moment the call returns).
|
|
56
|
+
- The SEO audit **page limit** (it reserves credits; the API has no credit-balance read, so the dialog explains rather than quotes).
|
|
57
|
+
- Re-scanning the brand kit with a **different URL** (replaces the stored design tokens).
|
|
58
|
+
|
|
59
|
+
## Known limits
|
|
60
|
+
|
|
61
|
+
- The editor changes text fields of sections-mode pages. Freeform pages (raw HTML) can be previewed, published and
|
|
62
|
+
versioned, but their HTML is edited with the HTML routes (`replaceLandingPageHtml`, `setLandingPageText`) or in the graph8 app.
|
|
63
|
+
- Responses for Studio routes are open-shaped; the screens read only the fields listed in `lib/studio.ts` and ignore the rest.
|
|
64
|
+
|
|
65
|
+
## Operations
|
|
66
|
+
|
|
67
|
+
| SDK method (`g8.api.*`) | Route | Tier | Scope |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `listLandingPages` | GET `/api/v1/landing-pages` | read | `campaigns:read` |
|
|
70
|
+
| `createLandingPage` | POST `/api/v1/landing-pages` | billable | `campaigns:run` |
|
|
71
|
+
| `publishLandingPage` | POST `/api/v1/landing-pages/{landing_page_id}/publish` | external | `campaigns:run` |
|
|
72
|
+
| `unpublishLandingPage` | POST `/api/v1/landing-pages/{landing_page_id}/unpublish` | external | `campaigns:run` |
|
|
73
|
+
| `deleteLandingPage` | DELETE `/api/v1/landing-pages/{landing_page_id}` | destructive | `campaigns:delete` |
|
|
74
|
+
| `getLandingPage` | GET `/api/v1/landing-pages/{landing_page_id}` | read | `campaigns:read` |
|
|
75
|
+
| `updateLandingPage` | PATCH `/api/v1/landing-pages/{landing_page_id}` | write | `campaigns:write` |
|
|
76
|
+
| `updateLandingPageSection` | PATCH `/api/v1/landing-pages/{landing_page_id}/sections/{section_idx}` | write | `campaigns:write` |
|
|
77
|
+
| `createLandingPagePreviewLink` | GET `/api/v1/landing-pages/{landing_page_id}/preview` | read | `campaigns:read` |
|
|
78
|
+
| `listLandingPageVersions` | GET `/api/v1/landing-pages/{landing_page_id}/versions` | read | `campaigns:read` |
|
|
79
|
+
| `restoreLandingPageVersion` | POST `/api/v1/landing-pages/{landing_page_id}/versions/{version_num}/restore` | write | `campaigns:write` |
|
|
80
|
+
| `listContentPages` | GET `/api/v1/content/pages` | read | `campaigns:read` |
|
|
81
|
+
| `createContentPage` | POST `/api/v1/content/pages` | write | `campaigns:write` |
|
|
82
|
+
| `getContentPage` | GET `/api/v1/content/pages/{page_id}` | read | `campaigns:read` |
|
|
83
|
+
| `updateContentPage` | PATCH `/api/v1/content/pages/{page_id}` | write | `campaigns:write` |
|
|
84
|
+
| `enrichContentPage` | POST `/api/v1/content/pages/{page_id}/enrich-seo` | billable | `campaigns:run` |
|
|
85
|
+
| `deleteContentPage` | DELETE `/api/v1/content/pages/{page_id}` | destructive | `campaigns:delete` |
|
|
86
|
+
| `getBrandKitOverview` | GET `/api/v1/brand-kit` | read | `campaigns:read` |
|
|
87
|
+
| `triggerBrandExtraction` | POST `/api/v1/brand-kit/extract` | write | `campaigns:write` |
|
|
88
|
+
| `listArchetypes` | GET `/api/v1/brand-kit/archetypes` | read | `campaigns:read` |
|
|
89
|
+
| `getLayoutSelections` | GET `/api/v1/brand-kit/layout-selections` | read | `campaigns:read` |
|
|
90
|
+
| `saveLayoutSelections` | PUT `/api/v1/brand-kit/layout-selections` | write | `campaigns:write` |
|
|
91
|
+
| `listSeoAudits` | GET `/api/v1/seo/audits` | read | `intent:read` |
|
|
92
|
+
| `startSeoAudit` | POST `/api/v1/seo/audits` | billable | `intent:run` |
|
|
93
|
+
| `getSeoAudit` | GET `/api/v1/seo/audits/{audit_id}` | read | `intent:read` |
|
|
94
|
+
| `getSeoAuditIssues` | GET `/api/v1/seo/audits/{audit_id}/issues` | read | `intent:read` |
|
|
95
|
+
| `listSeoTrackedKeywords` | GET `/api/v1/seo/rank-tracking` | read | `intent:read` |
|
|
96
|
+
| `saveSeoTrackedKeywords` | POST `/api/v1/seo/rank-tracking/keywords` | write | `intent:write` |
|
|
97
|
+
| `removeSeoTrackedKeywords` | POST `/api/v1/seo/rank-tracking/keywords/remove` | write | `intent:write` |
|
|
98
|
+
|
|
99
|
+
## Recipes
|
|
100
|
+
|
|
101
|
+
- `recipes/create-and-publish-page.ts`: Create a landing page, edit it, and publish it
|
|
102
|
+
- `recipes/edit-page-section.ts`: Change the text of one section of a landing page
|
|
103
|
+
- `recipes/audit-site-seo.ts`: Run an SEO site audit and list what to fix first
|
|
104
|
+
- `recipes/refresh-brand-kit.ts`: Re-scan the brand kit from the website
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "pages",
|
|
4
|
+
"title": "Landing pages & content",
|
|
5
|
+
"summary": "Landing pages (list, section editor, publish), content library, brand kit and SEO audits, as editable React screens.",
|
|
6
|
+
"features": [
|
|
7
|
+
"landing page list with create, publish, unpublish and delete",
|
|
8
|
+
"structured section editor with version restore",
|
|
9
|
+
"content inventory with SEO enrichment",
|
|
10
|
+
"measured brand kit with layout choices",
|
|
11
|
+
"site audits and rank tracking"
|
|
12
|
+
],
|
|
13
|
+
"dependsOn": [
|
|
14
|
+
"foundation"
|
|
15
|
+
],
|
|
16
|
+
"operations": [
|
|
17
|
+
"list_landing_pages_landing_pages_get",
|
|
18
|
+
"create_landing_page_landing_pages_post",
|
|
19
|
+
"publish_landing_page_landing_pages__landing_page_id__publish_post",
|
|
20
|
+
"unpublish_landing_page_landing_pages__landing_page_id__unpublish_post",
|
|
21
|
+
"delete_landing_page_landing_pages__landing_page_id__delete",
|
|
22
|
+
"get_landing_page_landing_pages__landing_page_id__get",
|
|
23
|
+
"update_landing_page_landing_pages__landing_page_id__patch",
|
|
24
|
+
"update_landing_page_section_landing_pages__landing_page_id__sections__section_idx__patch",
|
|
25
|
+
"create_landing_page_preview_link_landing_pages__landing_page_id__preview_get",
|
|
26
|
+
"list_landing_page_versions_landing_pages__landing_page_id__versions_get",
|
|
27
|
+
"restore_landing_page_version_landing_pages__landing_page_id__versions__version_num__restore_post",
|
|
28
|
+
"list_content_pages_content_pages_get",
|
|
29
|
+
"create_content_page_content_pages_post",
|
|
30
|
+
"get_content_page_content_pages__page_id__get",
|
|
31
|
+
"update_content_page_content_pages__page_id__patch",
|
|
32
|
+
"enrich_content_page_content_pages__page_id__enrich_seo_post",
|
|
33
|
+
"delete_content_page_content_pages__page_id__delete",
|
|
34
|
+
"get_brand_kit_overview_brand_kit_get",
|
|
35
|
+
"trigger_brand_extraction_brand_kit_extract_post",
|
|
36
|
+
"list_archetypes_brand_kit_archetypes_get",
|
|
37
|
+
"get_layout_selections_brand_kit_layout_selections_get",
|
|
38
|
+
"save_layout_selections_brand_kit_layout_selections_put",
|
|
39
|
+
"list_seo_audits_seo_audits_get",
|
|
40
|
+
"start_seo_audit_seo_audits_post",
|
|
41
|
+
"get_seo_audit_seo_audits__audit_id__get",
|
|
42
|
+
"get_seo_audit_issues_seo_audits__audit_id__issues_get",
|
|
43
|
+
"list_seo_tracked_keywords_seo_rank_tracking_get",
|
|
44
|
+
"save_seo_tracked_keywords_seo_rank_tracking_keywords_post",
|
|
45
|
+
"remove_seo_tracked_keywords_seo_rank_tracking_keywords_remove_post"
|
|
46
|
+
],
|
|
47
|
+
"components": [
|
|
48
|
+
{
|
|
49
|
+
"name": "PageList",
|
|
50
|
+
"file": "components/page-list.tsx",
|
|
51
|
+
"purpose": "Landing pages with status filter, AI or blank create, publish, unpublish and delete (each behind a confirm dialog).",
|
|
52
|
+
"operations": [
|
|
53
|
+
"list_landing_pages_landing_pages_get",
|
|
54
|
+
"create_landing_page_landing_pages_post",
|
|
55
|
+
"publish_landing_page_landing_pages__landing_page_id__publish_post",
|
|
56
|
+
"unpublish_landing_page_landing_pages__landing_page_id__unpublish_post",
|
|
57
|
+
"delete_landing_page_landing_pages__landing_page_id__delete"
|
|
58
|
+
],
|
|
59
|
+
"tiers": [
|
|
60
|
+
"read",
|
|
61
|
+
"billable",
|
|
62
|
+
"external",
|
|
63
|
+
"destructive"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "PageEditor",
|
|
68
|
+
"file": "components/page-editor.tsx",
|
|
69
|
+
"purpose": "Lite landing page editor: page details, one card per section with its text fields, preview, version restore, publish and unpublish.",
|
|
70
|
+
"operations": [
|
|
71
|
+
"get_landing_page_landing_pages__landing_page_id__get",
|
|
72
|
+
"update_landing_page_landing_pages__landing_page_id__patch",
|
|
73
|
+
"update_landing_page_section_landing_pages__landing_page_id__sections__section_idx__patch",
|
|
74
|
+
"create_landing_page_preview_link_landing_pages__landing_page_id__preview_get",
|
|
75
|
+
"list_landing_page_versions_landing_pages__landing_page_id__versions_get",
|
|
76
|
+
"restore_landing_page_version_landing_pages__landing_page_id__versions__version_num__restore_post",
|
|
77
|
+
"publish_landing_page_landing_pages__landing_page_id__publish_post",
|
|
78
|
+
"unpublish_landing_page_landing_pages__landing_page_id__unpublish_post"
|
|
79
|
+
],
|
|
80
|
+
"tiers": [
|
|
81
|
+
"read",
|
|
82
|
+
"write",
|
|
83
|
+
"external"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "ContentLibrary",
|
|
88
|
+
"file": "components/content-library.tsx",
|
|
89
|
+
"purpose": "Content inventory with type and status filters, add by URL, edit sheet, SEO enrichment and delete.",
|
|
90
|
+
"operations": [
|
|
91
|
+
"list_content_pages_content_pages_get",
|
|
92
|
+
"create_content_page_content_pages_post",
|
|
93
|
+
"get_content_page_content_pages__page_id__get",
|
|
94
|
+
"update_content_page_content_pages__page_id__patch",
|
|
95
|
+
"enrich_content_page_content_pages__page_id__enrich_seo_post",
|
|
96
|
+
"delete_content_page_content_pages__page_id__delete"
|
|
97
|
+
],
|
|
98
|
+
"tiers": [
|
|
99
|
+
"read",
|
|
100
|
+
"write",
|
|
101
|
+
"billable",
|
|
102
|
+
"destructive"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "BrandKitEditor",
|
|
107
|
+
"file": "components/brand-kit-editor.tsx",
|
|
108
|
+
"purpose": "Brand palette, fonts and voice measured from the website, a re-scan action, and the layout archetypes new pages may use.",
|
|
109
|
+
"operations": [
|
|
110
|
+
"get_brand_kit_overview_brand_kit_get",
|
|
111
|
+
"trigger_brand_extraction_brand_kit_extract_post",
|
|
112
|
+
"list_archetypes_brand_kit_archetypes_get",
|
|
113
|
+
"get_layout_selections_brand_kit_layout_selections_get",
|
|
114
|
+
"save_layout_selections_brand_kit_layout_selections_put"
|
|
115
|
+
],
|
|
116
|
+
"tiers": [
|
|
117
|
+
"read",
|
|
118
|
+
"write"
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "SeoReport",
|
|
123
|
+
"file": "components/seo-report.tsx",
|
|
124
|
+
"purpose": "Run a site audit (billable, polled), read the on-page score and issues, and track keyword rankings.",
|
|
125
|
+
"operations": [
|
|
126
|
+
"list_seo_audits_seo_audits_get",
|
|
127
|
+
"start_seo_audit_seo_audits_post",
|
|
128
|
+
"get_seo_audit_seo_audits__audit_id__get",
|
|
129
|
+
"get_seo_audit_issues_seo_audits__audit_id__issues_get",
|
|
130
|
+
"list_seo_tracked_keywords_seo_rank_tracking_get",
|
|
131
|
+
"save_seo_tracked_keywords_seo_rank_tracking_keywords_post",
|
|
132
|
+
"remove_seo_tracked_keywords_seo_rank_tracking_keywords_remove_post"
|
|
133
|
+
],
|
|
134
|
+
"tiers": [
|
|
135
|
+
"read",
|
|
136
|
+
"write",
|
|
137
|
+
"billable"
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"hooks": [],
|
|
142
|
+
"lib": [
|
|
143
|
+
{
|
|
144
|
+
"file": "lib/studio.ts",
|
|
145
|
+
"purpose": "Helpers for the open-shaped Studio payloads (list rows, text, dates) and the row types the screens use."
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"recipes": [
|
|
149
|
+
{
|
|
150
|
+
"file": "recipes/create-and-publish-page.ts",
|
|
151
|
+
"title": "Create a landing page, edit it, and publish it",
|
|
152
|
+
"when": "A new page is requested. Publish is external: confirm with the person first."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"file": "recipes/edit-page-section.ts",
|
|
156
|
+
"title": "Change the text of one section of a landing page",
|
|
157
|
+
"when": "A headline or label changes without re-running AI; the section content is replaced, so merge into the current one."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"file": "recipes/audit-site-seo.ts",
|
|
161
|
+
"title": "Run an SEO site audit and list what to fix first",
|
|
162
|
+
"when": "On-page score for a domain; billable, reserves credits by max_pages, polled until terminal."
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"file": "recipes/refresh-brand-kit.ts",
|
|
166
|
+
"title": "Re-scan the brand kit from the website",
|
|
167
|
+
"when": "Brand data looks stale or the site changed; asynchronous, polled via the overview."
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"prerequisites": [
|
|
171
|
+
{
|
|
172
|
+
"id": "website-url",
|
|
173
|
+
"description": "The workspace has a company website on its profile (or you pass one to the brand re-scan), so the brand kit has something to measure.",
|
|
174
|
+
"where": "api",
|
|
175
|
+
"verify": "GET /brand-kit returns source_url, or run recipes/refresh-brand-kit.ts with a website URL."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"id": "publish-domain",
|
|
179
|
+
"description": "A domain for published pages is connected in graph8; without it publishing uses the workspace default domain.",
|
|
180
|
+
"where": "app",
|
|
181
|
+
"verify": "GET /landing-pages/domains lists at least one domain, or accept the default domain."
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"scopes": [
|
|
185
|
+
"campaigns:delete",
|
|
186
|
+
"campaigns:read",
|
|
187
|
+
"campaigns:run",
|
|
188
|
+
"campaigns:write",
|
|
189
|
+
"intent:read",
|
|
190
|
+
"intent:run",
|
|
191
|
+
"intent:write"
|
|
192
|
+
],
|
|
193
|
+
"defaults": [
|
|
194
|
+
{
|
|
195
|
+
"decision": "Create a page with AI or blank",
|
|
196
|
+
"default": "Blank (generate:false) from code; the AI option stays a button behind a credits confirmation",
|
|
197
|
+
"askHuman": false
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"decision": "Publish a page to a public URL",
|
|
201
|
+
"default": "Never publish from code without the person's say-so; the UI always confirms",
|
|
202
|
+
"askHuman": true
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"decision": "Page size for lists",
|
|
206
|
+
"default": "25 rows with Previous and Next",
|
|
207
|
+
"askHuman": false
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"decision": "SEO audit page limit",
|
|
211
|
+
"default": "100 pages",
|
|
212
|
+
"askHuman": true
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"decision": "Which screens to mount",
|
|
216
|
+
"default": "/pages (PageList) and /pages/[id] (PageEditor); ContentLibrary, BrandKitEditor and SeoReport as tabs of a Marketing area",
|
|
217
|
+
"askHuman": false
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"peerDependencies": {
|
|
221
|
+
"react": ">=18"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BrandKitEditor: what graph8 knows about the brand (palette, fonts, voice) plus the page layouts it may use.
|
|
5
|
+
*
|
|
6
|
+
* The kit is MEASURED from the customer's website, not typed in. "Re-scan" runs the extraction again (against
|
|
7
|
+
* the current website, or another URL) and the screen polls the overview until the scan finishes. What a person
|
|
8
|
+
* edits here is which layout archetypes new landing pages may be built from.
|
|
9
|
+
*
|
|
10
|
+
* SDK operations, in the order the screen uses them:
|
|
11
|
+
* 1. getBrandKitOverview GET /brand-kit (read; status, tokens, logo, tagline, voice)
|
|
12
|
+
* 2. triggerBrandExtraction POST /brand-kit/extract (write; starts a scan, then poll #1)
|
|
13
|
+
* 3. listArchetypes GET /brand-kit/archetypes (read; the layout choices)
|
|
14
|
+
* 4. getLayoutSelections GET /brand-kit/layout-selections (read; the ticked ids)
|
|
15
|
+
* 5. saveLayoutSelections PUT /brand-kit/layout-selections (write; the COMPLETE set of ids)
|
|
16
|
+
*
|
|
17
|
+
* Nothing here is billable, external or destructive, so no confirmation dialog is needed. Re-scanning with a
|
|
18
|
+
* different URL replaces the stored design tokens; the screen says so next to the button.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useEffect, useState } from "react";
|
|
22
|
+
import { Button, Card, CardContent, CardHeader, CardTitle, Input, Skeleton } from "@graph8/sdk/ui";
|
|
23
|
+
import { g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
|
|
24
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
25
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
26
|
+
import { useJobPoll } from "@/g8/hooks/use-job-poll";
|
|
27
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
28
|
+
import { asRec, rowsOf, shortDate, text } from "../lib/studio";
|
|
29
|
+
import type { Rec } from "../lib/studio";
|
|
30
|
+
|
|
31
|
+
export interface BrandKitEditorProps {
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Swatch {
|
|
36
|
+
hex: string;
|
|
37
|
+
name: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The measured palette: `full_palette` entries when present, otherwise the `colors` map. */
|
|
41
|
+
export function swatchesOf(tokens: Rec): Swatch[] {
|
|
42
|
+
const full = Array.isArray(tokens.full_palette) ? (tokens.full_palette as Rec[]) : [];
|
|
43
|
+
const fromFull = full.map((c) => ({ hex: text(c.hex) || text(c.value), name: text(c.name) })).filter((s) => s.hex);
|
|
44
|
+
if (fromFull.length) return fromFull;
|
|
45
|
+
return Object.entries(asRec(tokens.colors))
|
|
46
|
+
.map(([key, v]) => ({ hex: typeof v === "string" ? v : text(asRec(v).value), name: text(asRec(v).name) || key }))
|
|
47
|
+
.filter((s) => s.hex);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Overview({ overview, onRescan, scanning, error }: { overview: Rec; onRescan: (url: string) => void; scanning: boolean; error: unknown }) {
|
|
51
|
+
const [url, setUrl] = useState("");
|
|
52
|
+
const tokens = asRec(overview.design_tokens);
|
|
53
|
+
const swatches = swatchesOf(tokens);
|
|
54
|
+
const typo = asRec(tokens.typography);
|
|
55
|
+
const voice = Array.isArray(overview.voice_traits) ? (overview.voice_traits as unknown[]).map((v) => text(v)) : [];
|
|
56
|
+
const status = text(overview.status, "not_extracted");
|
|
57
|
+
return (
|
|
58
|
+
<Card className="mb-4">
|
|
59
|
+
<CardHeader><CardTitle className="text-sm">Brand kit</CardTitle></CardHeader>
|
|
60
|
+
<CardContent className="space-y-4">
|
|
61
|
+
<p className="text-sm" data-testid="brand-status">
|
|
62
|
+
{status === "extracted" ? `Measured from ${text(overview.source_url)}${overview.captured_at ? ` on ${shortDate(overview.captured_at)}` : ""}.`
|
|
63
|
+
: status === "generating" ? "Scanning your website. This takes a minute."
|
|
64
|
+
: status === "failed" ? `The last scan failed. ${text(overview.scan_error)}`
|
|
65
|
+
: "No scan yet. Enter your website to measure your brand."}
|
|
66
|
+
</p>
|
|
67
|
+
{swatches.length ? (
|
|
68
|
+
<div className="flex flex-wrap gap-2" aria-label="Palette">
|
|
69
|
+
{swatches.map((s) => (
|
|
70
|
+
<div key={s.hex} className="w-20 text-center text-xs">
|
|
71
|
+
<div className="h-10 rounded-md border" style={{ background: s.hex }} />
|
|
72
|
+
<div className="mt-1 truncate">{s.name || s.hex}</div>
|
|
73
|
+
<div className="text-muted-foreground">{s.hex}</div>
|
|
74
|
+
</div>
|
|
75
|
+
))}
|
|
76
|
+
</div>
|
|
77
|
+
) : null}
|
|
78
|
+
{typo.heading_font || typo.body_font ? <p className="text-sm">Fonts: {text(typo.heading_font)}{typo.body_font ? ` / ${text(typo.body_font)}` : ""}</p> : null}
|
|
79
|
+
{overview.tagline ? <p className="text-sm italic">{text(overview.tagline)}</p> : null}
|
|
80
|
+
{voice.length ? <p className="text-sm">Voice: {voice.join(", ")}</p> : null}
|
|
81
|
+
{error ? <ApiError error={error} title="Could not start the scan" /> : null}
|
|
82
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
83
|
+
<Input aria-label="Website URL" placeholder={text(overview.source_url, "https://yourcompany.com")} value={url} onChange={(e) => setUrl(e.target.value)} className="w-72" />
|
|
84
|
+
<Button variant="outline" loading={scanning} onClick={() => onRescan(url.trim())}>Re-scan website</Button>
|
|
85
|
+
</div>
|
|
86
|
+
{url.trim() ? <p className="text-muted-foreground text-xs">Scanning a different site replaces your stored design tokens.</p> : null}
|
|
87
|
+
</CardContent>
|
|
88
|
+
</Card>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function Layouts() {
|
|
93
|
+
const archetypes = useG8Query((signal) => g8Get("/brand-kit/archetypes", undefined, { signal }), []);
|
|
94
|
+
const selections = useG8Query((signal) => g8Get("/brand-kit/layout-selections", undefined, { signal }), []);
|
|
95
|
+
// `edit` is null until the person ticks something, so the box state is the saved state with no effect-lag in between.
|
|
96
|
+
const [edit, setEdit] = useState<string[] | null>(null);
|
|
97
|
+
const rawSaved = asRec(selections.data?.data).archetype_ids;
|
|
98
|
+
const saved = Array.isArray(rawSaved) ? rawSaved.map((i) => text(i)) : [];
|
|
99
|
+
const picked = edit ?? saved;
|
|
100
|
+
const save = useG8Mutation(() => g8Put("/brand-kit/layout-selections", { archetype_ids: picked }), { onSuccess: () => { setEdit(null); void selections.refetch(); } });
|
|
101
|
+
const rows = rowsOf<Rec>(archetypes.data?.data);
|
|
102
|
+
const dirty = edit !== null && JSON.stringify([...picked].sort()) !== JSON.stringify([...saved].sort());
|
|
103
|
+
const toggle = (id: string) => setEdit(picked.includes(id) ? picked.filter((p) => p !== id) : [...picked, id]);
|
|
104
|
+
return (
|
|
105
|
+
<Card>
|
|
106
|
+
<CardHeader><CardTitle className="text-sm">Page layouts</CardTitle></CardHeader>
|
|
107
|
+
<CardContent className="space-y-3">
|
|
108
|
+
<p className="text-muted-foreground text-sm">New landing pages are built from the layouts you tick.</p>
|
|
109
|
+
{archetypes.error ? <ApiError error={archetypes.error} onRetry={() => void archetypes.refetch()} /> : null}
|
|
110
|
+
{archetypes.loading && !archetypes.data ? <Skeleton className="h-24 w-full" /> : null}
|
|
111
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
112
|
+
{rows.map((a) => {
|
|
113
|
+
const id = text(a.id);
|
|
114
|
+
return (
|
|
115
|
+
<label key={id} className="flex cursor-pointer items-start gap-2 rounded-md border p-3 text-sm">
|
|
116
|
+
<input type="checkbox" checked={picked.includes(id)} onChange={() => toggle(id)} aria-label={text(a.name)} />
|
|
117
|
+
<span><span className="font-medium">{text(a.name)}</span><br /><span className="text-muted-foreground">{text(a.description)}</span></span>
|
|
118
|
+
</label>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
{save.error ? <ApiError error={save.error} title="Could not save the layouts" /> : null}
|
|
123
|
+
<Button disabled={!dirty} loading={save.loading} onClick={() => void save.mutate(undefined)}>Save layouts</Button>
|
|
124
|
+
</CardContent>
|
|
125
|
+
</Card>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function BrandKitEditor({ className }: BrandKitEditorProps) {
|
|
130
|
+
const overview = useG8Query((signal) => g8Get("/brand-kit", undefined, { signal }), []);
|
|
131
|
+
const poll = useJobPoll<Rec>(async (signal) => asRec((await g8Get("/brand-kit", undefined, { signal })).data), {
|
|
132
|
+
enabled: false,
|
|
133
|
+
isTerminal: (o) => text(o.status) !== "generating",
|
|
134
|
+
onDone: () => void overview.refetch(),
|
|
135
|
+
});
|
|
136
|
+
const scan = useG8Mutation((url: string) => g8Post("/brand-kit/extract", url ? { website_url: url } : undefined), { onSuccess: () => poll.start() });
|
|
137
|
+
const data = asRec(poll.status ?? overview.data?.data);
|
|
138
|
+
const scanning = scan.loading || poll.polling || text(data.status) === "generating";
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (text(asRec(overview.data?.data).status) === "generating") poll.start();
|
|
142
|
+
}, [overview.data]);
|
|
143
|
+
|
|
144
|
+
if (overview.loading && !overview.data) return <div className={className} aria-busy="true"><Skeleton className="h-48 w-full" /></div>;
|
|
145
|
+
if (overview.error) return <ApiError error={overview.error} onRetry={() => void overview.refetch()} />;
|
|
146
|
+
return (
|
|
147
|
+
<div className={className}>
|
|
148
|
+
<Overview overview={data} onRescan={(u) => void scan.mutate(u)} scanning={scanning} error={scan.error} />
|
|
149
|
+
<Layouts />
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|