@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
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ The most comprehensive GTM SDK. One `npm install`. Every graph8 capability.
|
|
|
7
7
|
> two client layers, safety tiers, errors and tested recipes, and links a per-area reference for every
|
|
8
8
|
> operation in [`docs/api/`](docs/api/README.md). It ships with the package, so it matches this version.
|
|
9
9
|
> Run `npx @graph8/sdk init-agent` to point Claude Code, Codex and Cursor in your project at it automatically.
|
|
10
|
+
>
|
|
11
|
+
> Blocks (copy-in components and hooks per feature): `npx @graph8/sdk list`, `search <text>`, `add <block>[/<Component>]`, `create <stack> <dir>` (a whole Next.js app: `npx @graph8/sdk create starter my-app`), and `doctor` to check your key, scopes and subscription. Each takes `--help`.
|
|
10
12
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
@@ -483,6 +485,85 @@ const { data: objects } = await service.request<{ data: unknown[] }>('/api/v1/ap
|
|
|
483
485
|
> The `g8.objects` resource above uses an API key for native workspace objects;
|
|
484
486
|
> it does not substitute for app-token authorization.
|
|
485
487
|
|
|
488
|
+
## Build a competitor in one command
|
|
489
|
+
|
|
490
|
+
A stack is a ready-made Next.js app: the `/api/g8` proxy (your API key stays on the server), an app shell, and one screen per feature, copied into your repo so you and your agent can edit it.
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
npx @graph8/sdk create mailchimp my-app # then: cd my-app && cp .env.local.example .env.local && npm run dev
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
| Stack | Screens |
|
|
497
|
+
|---|---|
|
|
498
|
+
| `starter` | An empty app: proxy, shell and one landing page |
|
|
499
|
+
| `mailchimp` | Contacts, companies, forms, submissions, lists, suppressions, newsletter, subscribers, nurture |
|
|
500
|
+
| `clay` | Contacts, companies, lists, suppressions, prospect search, bulk enrichment, workbench |
|
|
501
|
+
| `6sense` | Contacts, companies, intent feed, website visitors, competitor tracker |
|
|
502
|
+
| `intercom` | Contacts, companies, web chat conversations, mailboxes, AI inbox, AI agents |
|
|
503
|
+
| `outreach` | Contacts, companies, lists, mailboxes, sequences, inbox, dialer, call log, meetings, analytics |
|
|
504
|
+
| `apollo` | Outreach's screens plus prospect search, enrichment, intent, visitors, deals and integrations |
|
|
505
|
+
| `gong` | Contacts, companies, lists, dialer, call log, meetings, coaching feed, analytics |
|
|
506
|
+
| `salesforce` | Contacts, companies, custom objects, lists, lifecycle, deals, quotes, analytics, workflows, copilot, integrations |
|
|
507
|
+
| `hubspot` | The full CRM and marketing suite: forms, newsletter, nurture, web chat, sequences, inbox, pages, lifecycle, deals, quotes, workflows |
|
|
508
|
+
|
|
509
|
+
Every available stack is proven by `npm run build && npm run e2e:create -- <stack>`, which packs the SDK, scaffolds the app with the built CLI, installs it and requires `next build` to pass. More stacks appear here as their blocks land; `create` refuses a stack that is not available yet.
|
|
510
|
+
|
|
511
|
+
## UI kit (`@graph8/sdk/ui`)
|
|
512
|
+
|
|
513
|
+
The React component kit and design tokens behind the graph8 app (primitives, light and dark
|
|
514
|
+
tokens, Tailwind v4, React 18 or 19, no Next.js requirement), shipped as a subpath of this package.
|
|
515
|
+
It is optional: `import ... from "@graph8/sdk"` never loads it.
|
|
516
|
+
|
|
517
|
+
Install the peers only if you use the kit:
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
npm install @graph8/sdk react react-dom \
|
|
521
|
+
@radix-ui/react-avatar @radix-ui/react-collapsible @radix-ui/react-context-menu \
|
|
522
|
+
@radix-ui/react-dialog @radix-ui/react-label @radix-ui/react-separator @radix-ui/react-slider \
|
|
523
|
+
@radix-ui/react-slot @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-tooltip \
|
|
524
|
+
@dnd-kit/core @dnd-kit/modifiers @dnd-kit/sortable @dnd-kit/utilities \
|
|
525
|
+
class-variance-authority clsx lucide-react react-resizable-panels recharts tailwind-merge vaul
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Version notes: `react-resizable-panels` must be v4 or newer (the kit uses its `Group`/`Separator` API).
|
|
529
|
+
`recharts` 2.15+ and 3 both run, but the chart primitive is written against the recharts 2 types, so
|
|
530
|
+
recharts 3 apps that type-check library declarations (`skipLibCheck: false`) may see type errors from
|
|
531
|
+
`chart.d.ts`. `tailwind-merge` should be v3 to know the Tailwind v4 class names the kit uses; older
|
|
532
|
+
versions still install but may merge conflicting classes wrongly.
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
import { Button, Card, CardHeader, CardTitle, CardContent } from "@graph8/sdk/ui";
|
|
536
|
+
import { Dialog, DialogContent } from "@graph8/sdk/ui/primitives/dialog"; // single primitive
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Styles: pick one import (skipping this renders every component unstyled).**
|
|
540
|
+
|
|
541
|
+
| Import | Gives you | Use when |
|
|
542
|
+
|---|---|---|
|
|
543
|
+
| `@graph8/sdk/ui/theme.css` | tokens + `@theme inline` + dark/hover variants + `@source` | you run Tailwind v4 |
|
|
544
|
+
| `@graph8/sdk/ui/styles.css` | tokens + pre-compiled utilities (no preflight reset) | you do not run Tailwind |
|
|
545
|
+
| `@graph8/sdk/ui/tokens.css` | custom properties only (`:root`, `.dark`) | you want just the palette |
|
|
546
|
+
|
|
547
|
+
```css
|
|
548
|
+
@import "tailwindcss";
|
|
549
|
+
@import "@graph8/sdk/ui/theme.css";
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
`theme.css` declares `@source "../dist/ui"`, which is what lets Tailwind v4 (it skips `node_modules`)
|
|
553
|
+
see the class names inside the kit. Dark mode is class-based: put `class="dark"` on `<html>` or any subtree.
|
|
554
|
+
|
|
555
|
+
React Server Components: client primitives keep their own `"use client"` directive. These seven need
|
|
556
|
+
no client runtime and work directly in a Server Component:
|
|
557
|
+
|
|
558
|
+
<!-- UI_SERVER_SAFE_LIST -->
|
|
559
|
+
`Breadcrumb`, `Button`, `Card`, `Input`, `Item`, `Skeleton`, `Textarea`
|
|
560
|
+
<!-- /UI_SERVER_SAFE_LIST -->
|
|
561
|
+
|
|
562
|
+
`Sortable`'s short aliases (`Root`, `Item`, ...) are reachable only through
|
|
563
|
+
`@graph8/sdk/ui/primitives/sortable`. `content-state`, `confirm-dialog` and `skeletons` are app composites
|
|
564
|
+
and are not shipped. The kit is generated from the graph8 app (`npm run generate:ui` in `sdk/js`) and CI
|
|
565
|
+
fails on drift; do not edit `src/ui` or `styles/` by hand.
|
|
566
|
+
|
|
486
567
|
## Auth Modes
|
|
487
568
|
|
|
488
569
|
| Mode | Key | Use case |
|
package/blocks/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# graph8 SDK blocks
|
|
2
|
+
|
|
3
|
+
A block is **editable source** for one product feature (contacts table, sequence builder, inbox...):
|
|
4
|
+
React components, hooks and server-side SDK recipes, plus a manifest (`block.json`). A coding agent
|
|
5
|
+
copies the block into the customer's Next.js app, so the customer owns and edits the code. Nothing here
|
|
6
|
+
is imported from `node_modules` at runtime.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
blocks/<id>/block.json manifest (schema: blocks/block.schema.json, type: blocks/block.types.ts)
|
|
10
|
+
blocks/<id>/README.md the flow: what happens in what order, which ids pass where, tiers
|
|
11
|
+
blocks/<id>/components/*.tsx UI, copied into the app
|
|
12
|
+
blocks/<id>/hooks/*.ts data hooks, copied into the app
|
|
13
|
+
blocks/<id>/lib/*.ts plain modules
|
|
14
|
+
blocks/<id>/server/*.ts server-only files (route handlers)
|
|
15
|
+
blocks/<id>/recipes/*.ts server-side SDK recipes, type-checked in CI
|
|
16
|
+
stacks/<id>.json a named, ordered set of blocks + navigation
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`blocks/_foundation/` is the block with id `foundation`; every other block depends on it.
|
|
20
|
+
|
|
21
|
+
## What CI guarantees
|
|
22
|
+
|
|
23
|
+
`operations`, component `tiers` and `scopes` are derived from the API contract and verified by
|
|
24
|
+
`tests/blocks/`: an operation that does not exist, a tier that drifted, a missing file, a dependency
|
|
25
|
+
cycle or an import that is not a declared peer dependency fails the build. Every component, hook, lib,
|
|
26
|
+
server file and recipe is type-checked against the SDK sources.
|
|
27
|
+
|
|
28
|
+
Blocks build their screens with `@graph8/sdk/ui`, and so does the foundation's `ConfirmSpendDialog` (a
|
|
29
|
+
Radix Dialog, so it stacks correctly inside other dialogs and sheets). The rest of the foundation is plain
|
|
30
|
+
JSX and `className`. An app that installs the foundation must therefore import the UI kit CSS
|
|
31
|
+
(`@graph8/sdk/ui/styles.css`) and have the kit's optional peers installed: the foundation's `ui-styles`
|
|
32
|
+
prerequisite says how to verify it.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# foundation
|
|
2
|
+
|
|
3
|
+
Shared base every other block depends on. Install it first (the CLI does it for you).
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
| File | What it is |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `lib/g8-client.ts` | Browser caller: `g8Get/g8Post/g8Put/g8Patch/g8Delete` -> your own `/api/g8/...` proxy. No API key. Returns the API envelope `{ data, pagination? }`, throws `G8Error`. |
|
|
10
|
+
| `server/proxy.ts` | `createG8Proxy(config)`: the logic of the proxy (forwarding, allowlist, `authorize` hook). |
|
|
11
|
+
| `server/route.ts` | Installs as `app/api/g8/[...path]/route.ts` and mounts the proxy. |
|
|
12
|
+
| `hooks/use-g8-query.ts` | `useG8Query(fetcher, deps)` -> `{ data, error, loading, refetch }`. Latest request wins. |
|
|
13
|
+
| `hooks/use-g8-mutation.ts` | `useG8Mutation(fn)` -> `{ mutate, mutateAsync, data, error, loading, reset }`. |
|
|
14
|
+
| `hooks/use-paginated.ts` | `usePaginated(fetchPage, deps)` -> `{ items, hasMore, loadMore, reload, loading, loadingMore, error }`. Follows `pagination.next_cursor`. |
|
|
15
|
+
| `hooks/use-job-poll.ts` | `useJobPoll(fetchStatus, { isTerminal })` -> `{ status, done, polling, error, start, stop }`. For jobs: the API has no streams, so poll with backoff. |
|
|
16
|
+
| `components/confirm-spend-dialog.tsx` | `ConfirmSpendDialog`: shows tier, estimated credits, recipients, before a billable/external/destructive call. |
|
|
17
|
+
| `components/api-error.tsx` | `ApiError`: shows `detail`, 422 field errors, request id, retry. |
|
|
18
|
+
| `components/empty-state.tsx` | `EmptyState`: empty list with a first step. |
|
|
19
|
+
|
|
20
|
+
## Flow of every call
|
|
21
|
+
|
|
22
|
+
1. A component calls a hook: `useG8Query((signal) => g8Get("/contacts", { limit: 50 }, { signal }), [])`.
|
|
23
|
+
2. `g8-client` requests `/api/g8/contacts?limit=50` on your own origin.
|
|
24
|
+
3. The route handler checks `authorize`, then `allowedPrefixes`, then forwards to
|
|
25
|
+
`${G8_API_URL ?? "https://be.graph8.com"}/api/v1/contacts?limit=50` with `Authorization: Bearer $G8_API_KEY`.
|
|
26
|
+
4. The response (status, body, request id) comes back unchanged; failures become `G8Error`.
|
|
27
|
+
|
|
28
|
+
Ids that pass between calls always come from a previous response (a list's `id` into the next call's
|
|
29
|
+
path). Never invent one.
|
|
30
|
+
|
|
31
|
+
## Tiers: when to confirm
|
|
32
|
+
|
|
33
|
+
Every operation has a tier (`read`, `write`, `billable`, `external`, `destructive`); each component's
|
|
34
|
+
`tiers` in `block.json` is derived from the contract. Before a component triggers a `billable`,
|
|
35
|
+
`external` or `destructive` operation, open `ConfirmSpendDialog` (`tierRequiresConfirmation(tier)` tells
|
|
36
|
+
you). Say what will happen and how much: credits, recipients, records.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<ConfirmSpendDialog open={open} tier="external" title="Send to 240 contacts" recipients={240}
|
|
40
|
+
confirming={send.loading} onConfirm={() => send.mutate(...)} onCancel={() => setOpen(false)} />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Proxy security model
|
|
44
|
+
|
|
45
|
+
The browser never sees the API key. It lives in `G8_API_KEY` on the server (never `NEXT_PUBLIC_*`).
|
|
46
|
+
|
|
47
|
+
But the proxy route is **exactly as powerful as the key**: anyone who can reach `/api/g8` can do whatever
|
|
48
|
+
the key can. So:
|
|
49
|
+
|
|
50
|
+
The proxy therefore **refuses every request (500) until you configure it**:
|
|
51
|
+
|
|
52
|
+
1. **`authorize`** (required): check YOUR session and return a 401/403 `Response` to refuse.
|
|
53
|
+
2. **`allowedPrefixes`** (required): the API areas the UI actually uses (`["contacts", "companies"]`).
|
|
54
|
+
Anything else gets a 403. Prefixes match whole path segments.
|
|
55
|
+
3. **Use the narrowest key**: scopes of the installed blocks only (each `block.json` lists them).
|
|
56
|
+
4. **Agency keys and the org**: the browser's `X-Target-Org-Id` is NOT forwarded by default, because an
|
|
57
|
+
app session does not authorize switching between client orgs. Decide the org on the server from the
|
|
58
|
+
session: `targetOrgId: async (req) => (await getSession(req))?.g8OrgId`. (`allowTargetOrgHeader: true`
|
|
59
|
+
forwards the browser's header; do not use it with end-user traffic.)
|
|
60
|
+
5. Local prototypes only: `dangerouslyAllowUnauthenticated: true` and `dangerouslyAllowAllPaths: true`
|
|
61
|
+
switch the two gates off. Never ship them.
|
|
62
|
+
|
|
63
|
+
What the proxy does on its own: rejects `..`, encoded slashes and empty segments (400); forwards only
|
|
64
|
+
`content-type`, `accept`, `accept-language` and `idempotency-key` (never the browser's cookies or
|
|
65
|
+
`Authorization`); strips hop-by-hop headers, `set-cookie` and `location` from the response; does not follow
|
|
66
|
+
redirects (an upstream 3xx becomes a 502); caps the request body at 5 MiB (413), enforced while streaming so
|
|
67
|
+
an oversized upload is never held whole in memory; returns 500 with a clear message if `G8_API_KEY` is unset
|
|
68
|
+
and a generic 502 if graph8 is unreachable. The key is never logged, echoed or included in any response.
|
|
69
|
+
|
|
70
|
+
## Prerequisites
|
|
71
|
+
|
|
72
|
+
- `G8_API_KEY` in the server env; verify with `recipes/verify-proxy-key.ts`.
|
|
73
|
+
- `@graph8/sdk/ui/styles.css` imported in the app, plus the UI kit's optional peers (`ConfirmSpendDialog` uses the kit's Dialog).
|
|
74
|
+
- Next.js App Router, React 18+, the `@/*` import alias (files install under `g8/`, imported as `@/g8/...`).
|
|
75
|
+
|
|
76
|
+
## Defaults the agent should ask about
|
|
77
|
+
|
|
78
|
+
- How the proxy knows a caller is a signed-in user of the app (`authorize`), and which API areas to allow: **ask the person**. The proxy will not run until both are set.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "foundation",
|
|
4
|
+
"title": "Foundation",
|
|
5
|
+
"summary": "Shared base for every block: browser client, data hooks, Next.js proxy route, confirm/error/empty components.",
|
|
6
|
+
"features": ["browser-safe API calls via a server proxy", "query, mutation, pagination and job-polling hooks", "spend/send/delete confirmation dialog"],
|
|
7
|
+
"dependsOn": [],
|
|
8
|
+
"operations": [],
|
|
9
|
+
"components": [
|
|
10
|
+
{
|
|
11
|
+
"name": "ConfirmSpendDialog",
|
|
12
|
+
"file": "components/confirm-spend-dialog.tsx",
|
|
13
|
+
"purpose": "Show tier, estimated credits and recipients, and require a click, before a billable, external or destructive operation.",
|
|
14
|
+
"operations": [],
|
|
15
|
+
"tiers": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "ApiError",
|
|
19
|
+
"file": "components/api-error.tsx",
|
|
20
|
+
"purpose": "Inline error panel for a failed call: shows the API detail, 422 field errors, request id and a retry button.",
|
|
21
|
+
"operations": [],
|
|
22
|
+
"tiers": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "EmptyState",
|
|
26
|
+
"file": "components/empty-state.tsx",
|
|
27
|
+
"purpose": "Empty-list placeholder with a title, hint and an optional first-step action.",
|
|
28
|
+
"operations": [],
|
|
29
|
+
"tiers": []
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"hooks": [
|
|
33
|
+
{ "name": "useG8Query", "file": "hooks/use-g8-query.ts", "purpose": "Load data with loading/error/data, an out-of-order guard and refetch." },
|
|
34
|
+
{ "name": "useG8Mutation", "file": "hooks/use-g8-mutation.ts", "purpose": "Run a write with loading/error state and success/error callbacks." },
|
|
35
|
+
{ "name": "usePaginated", "file": "hooks/use-paginated.ts", "purpose": "Cursor pagination over the API envelope (pagination.next_cursor) with load-more and reload." },
|
|
36
|
+
{ "name": "useJobPoll", "file": "hooks/use-job-poll.ts", "purpose": "Poll a job-status fetcher with backoff until a terminal state (the API has no streams)." }
|
|
37
|
+
],
|
|
38
|
+
"lib": [
|
|
39
|
+
{ "file": "lib/g8-client.ts", "purpose": "Browser caller for the app's own /api/g8 proxy. Holds no key, returns the SDK envelope, throws G8Error." }
|
|
40
|
+
],
|
|
41
|
+
"server": [
|
|
42
|
+
{ "file": "server/proxy.ts", "purpose": "createG8Proxy(): forwards browser calls to the graph8 API with the server-side key, a required path allowlist and authorize hook (it refuses to run without them) and a server-side org hook.", "target": "g8/server/proxy.ts" },
|
|
43
|
+
{ "file": "server/route.ts", "purpose": "The Next.js App Router catch-all route that mounts the proxy.", "target": "app/api/g8/[...path]/route.ts" }
|
|
44
|
+
],
|
|
45
|
+
"recipes": [
|
|
46
|
+
{
|
|
47
|
+
"file": "recipes/verify-proxy-key.ts",
|
|
48
|
+
"title": "Check the proxy's server key before shipping",
|
|
49
|
+
"when": "After setting G8_API_KEY for the proxy route; fails fast on a wrong, expired or unscoped key."
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"prerequisites": [
|
|
53
|
+
{
|
|
54
|
+
"id": "api-key",
|
|
55
|
+
"description": "A graph8 API key with the scopes of the blocks you install, set as G8_API_KEY in the server environment (never NEXT_PUBLIC_*).",
|
|
56
|
+
"where": "app",
|
|
57
|
+
"verify": "G8_API_KEY is set in .env.local and `describeCurrentKey` succeeds (recipes/verify-proxy-key.ts)."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "nextjs-app-router",
|
|
61
|
+
"description": "A Next.js App Router app (React 18+) with the `@/*` import alias, so `@/g8/*` resolves to the installed g8 directory.",
|
|
62
|
+
"where": "app",
|
|
63
|
+
"verify": "app/ directory exists and tsconfig.json maps \"@/*\" to the project root or src/."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "ui-styles",
|
|
67
|
+
"description": "ConfirmSpendDialog is built on @graph8/sdk/ui (Radix Dialog in a portal). Its positioning, overlay and surface classes only exist if the app imports the UI kit CSS (`@graph8/sdk/ui/styles.css`, or tokens.css + theme.css with Tailwind scanning node_modules/@graph8/sdk/dist) and has the kit's optional peers installed (at least @radix-ui/react-dialog, lucide-react, clsx, tailwind-merge).",
|
|
68
|
+
"where": "app",
|
|
69
|
+
"verify": "app/layout.tsx (or the global CSS) imports @graph8/sdk/ui/styles.css, and `npm ls @radix-ui/react-dialog lucide-react` lists them."
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"scopes": [],
|
|
73
|
+
"defaults": [
|
|
74
|
+
{ "decision": "Which API paths the browser may call through the proxy", "default": "Only the areas the installed blocks use (allowedPrefixes, required), not all of /api/v1", "askHuman": false },
|
|
75
|
+
{ "decision": "How the proxy knows the caller is a signed-in user of the app", "default": "None: the route refuses every request until `authorize` is set", "askHuman": true },
|
|
76
|
+
{ "decision": "Styling", "default": "Tailwind utility classes on plain elements; overlays (ConfirmSpendDialog) and later blocks use @graph8/sdk/ui, so import its CSS", "askHuman": false }
|
|
77
|
+
],
|
|
78
|
+
"peerDependencies": { "react": ">=18" }
|
|
79
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { G8Error } from "@graph8/sdk";
|
|
4
|
+
|
|
5
|
+
export interface ApiErrorProps {
|
|
6
|
+
error: unknown;
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Shows a Retry button. */
|
|
9
|
+
onRetry?: () => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FieldError {
|
|
14
|
+
loc?: unknown[];
|
|
15
|
+
msg?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A readable one-line reason for any thrown value. 422 field errors are listed. */
|
|
19
|
+
export function describeError(error: unknown): { message: string; fields: string[]; status?: number; requestId?: string } {
|
|
20
|
+
if (error instanceof G8Error) {
|
|
21
|
+
const fields = Array.isArray(error.detail)
|
|
22
|
+
? (error.detail as FieldError[]).map((f) => `${(f.loc ?? []).filter((p) => p !== "body").join(".")}: ${f.msg ?? ""}`.trim())
|
|
23
|
+
: [];
|
|
24
|
+
const message =
|
|
25
|
+
typeof error.detail === "string" ? error.detail : fields.length ? "Some fields are invalid." : explain(error);
|
|
26
|
+
return { message, fields, status: error.status, requestId: error.requestId };
|
|
27
|
+
}
|
|
28
|
+
return { message: error instanceof Error ? error.message : "Something went wrong.", fields: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function explain(error: G8Error): string {
|
|
32
|
+
if (error.status === 401) return "Your session with graph8 is not valid. Check the server API key.";
|
|
33
|
+
if (error.status === 403) return "This API key is not allowed to do that (missing scope, or the proxy blocked the path).";
|
|
34
|
+
if (error.status === 429) return "graph8 is rate limiting requests. Wait a moment and retry.";
|
|
35
|
+
if (error.status === 0) return "Could not reach the server. Check your connection.";
|
|
36
|
+
return error.message;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Inline error panel for a failed graph8 call. Always shows `detail`, not just `message`. */
|
|
40
|
+
export function ApiError({ error, title = "Something went wrong", onRetry, className }: ApiErrorProps) {
|
|
41
|
+
if (error == null) return null;
|
|
42
|
+
const info = describeError(error);
|
|
43
|
+
return (
|
|
44
|
+
<div role="alert" className={`rounded border border-red-200 bg-red-50 p-4 text-sm ${className ?? ""}`}>
|
|
45
|
+
<p className="font-medium text-red-900">{title}</p>
|
|
46
|
+
<p className="mt-1 text-red-800">{info.message}</p>
|
|
47
|
+
{info.fields.length > 0 ? (
|
|
48
|
+
<ul className="mt-2 list-disc pl-5 text-red-800">
|
|
49
|
+
{info.fields.map((line) => (
|
|
50
|
+
<li key={line}>{line}</li>
|
|
51
|
+
))}
|
|
52
|
+
</ul>
|
|
53
|
+
) : null}
|
|
54
|
+
{info.requestId ? <p className="mt-2 text-xs text-red-700">Request id: {info.requestId}</p> : null}
|
|
55
|
+
{onRetry ? (
|
|
56
|
+
<button type="button" onClick={onRetry} className="mt-3 rounded border border-red-300 bg-white px-3 py-1 text-red-900">
|
|
57
|
+
Retry
|
|
58
|
+
</button>
|
|
59
|
+
) : null}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@graph8/sdk/ui";
|
|
6
|
+
|
|
7
|
+
/** The tiers that must be confirmed by a person before the call is made. */
|
|
8
|
+
export type ConfirmTier = "billable" | "external" | "destructive";
|
|
9
|
+
|
|
10
|
+
export interface ConfirmSpendDialogProps {
|
|
11
|
+
open: boolean;
|
|
12
|
+
tier: ConfirmTier;
|
|
13
|
+
/** What will happen, in one sentence: "Enrich 240 contacts". */
|
|
14
|
+
title: string;
|
|
15
|
+
description?: ReactNode;
|
|
16
|
+
/** Estimated credits the action will spend (billable). */
|
|
17
|
+
estimatedCredits?: number;
|
|
18
|
+
/** People who will be messaged or called (external). */
|
|
19
|
+
recipients?: number;
|
|
20
|
+
/** Records that will be removed (destructive). */
|
|
21
|
+
affected?: number;
|
|
22
|
+
confirmLabel?: string;
|
|
23
|
+
cancelLabel?: string;
|
|
24
|
+
/** Disable the buttons while the confirmed call runs. */
|
|
25
|
+
confirming?: boolean;
|
|
26
|
+
onConfirm: () => void;
|
|
27
|
+
onCancel: () => void;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TIER_COPY: Record<ConfirmTier, { badge: string; confirm: string; note: string }> = {
|
|
32
|
+
billable: { badge: "Uses credits", confirm: "Spend credits", note: "This action spends credits from your balance." },
|
|
33
|
+
external: {
|
|
34
|
+
badge: "Contacts people",
|
|
35
|
+
confirm: "Send",
|
|
36
|
+
note: "This reaches real people outside your workspace and cannot be recalled.",
|
|
37
|
+
},
|
|
38
|
+
destructive: { badge: "Cannot be undone", confirm: "Delete", note: "This permanently removes data." },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** True for the tiers an app should confirm before calling. read/write never need it. */
|
|
42
|
+
export function tierRequiresConfirmation(tier: string): tier is ConfirmTier {
|
|
43
|
+
return tier === "billable" || tier === "external" || tier === "destructive";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function facts(props: ConfirmSpendDialogProps): string[] {
|
|
47
|
+
const out: string[] = [];
|
|
48
|
+
if (props.estimatedCredits != null) out.push(`About ${props.estimatedCredits.toLocaleString()} credits`);
|
|
49
|
+
if (props.recipients != null) out.push(`${props.recipients.toLocaleString()} recipients`);
|
|
50
|
+
if (props.affected != null) out.push(`${props.affected.toLocaleString()} records affected`);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Show what an action will cost or do BEFORE a billable, external or
|
|
56
|
+
* destructive operation runs. The operation's tier is in the block manifest
|
|
57
|
+
* (`components[].tiers`) and in `g8.api.operation(id).tier`; read/write calls
|
|
58
|
+
* do not need this dialog.
|
|
59
|
+
*
|
|
60
|
+
* Built on the UI kit's Dialog (Radix) and rendered in a portal, so it stacks
|
|
61
|
+
* correctly when opened from inside another open Dialog or Sheet: a modal sets
|
|
62
|
+
* `pointer-events: none` on the page and marks everything outside itself inert,
|
|
63
|
+
* which made a plain fixed `div` unclickable there. Radix layers nest, so this
|
|
64
|
+
* dialog is interactive, Escape closes only the top layer, focus moves into it
|
|
65
|
+
* (on Cancel, the safe choice) and returns to where it was when it closes.
|
|
66
|
+
* A stray click outside never dismisses it.
|
|
67
|
+
*/
|
|
68
|
+
export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
|
|
69
|
+
const { open, tier, title, description, confirming, onConfirm, onCancel } = props;
|
|
70
|
+
const cancelRef = useRef<HTMLButtonElement>(null);
|
|
71
|
+
// Radix only restores focus to a Dialog.Trigger; this dialog is opened by state, so remember the opener.
|
|
72
|
+
const openerRef = useRef<HTMLElement | null>(null);
|
|
73
|
+
const copy = TIER_COPY[tier];
|
|
74
|
+
const summary = facts(props);
|
|
75
|
+
// Radix puts pointer-events: none on the body while a modal is open; its own
|
|
76
|
+
// layers opt back in, and the explicit style keeps that true under any host CSS.
|
|
77
|
+
const interactive = { pointerEvents: "auto" } as const;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Dialog open={open} onOpenChange={(next) => (!next && !confirming ? onCancel() : undefined)}>
|
|
81
|
+
<DialogContent
|
|
82
|
+
role="alertdialog"
|
|
83
|
+
showCloseButton={false}
|
|
84
|
+
data-tier={tier}
|
|
85
|
+
style={interactive}
|
|
86
|
+
className={`max-w-md sm:max-w-md bg-white p-5 shadow-xl ${props.className ?? ""}`}
|
|
87
|
+
onOpenAutoFocus={(event) => {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
90
|
+
cancelRef.current?.focus();
|
|
91
|
+
}}
|
|
92
|
+
onCloseAutoFocus={(event) => {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
const opener = openerRef.current;
|
|
95
|
+
if (opener?.isConnected) opener.focus();
|
|
96
|
+
openerRef.current = null;
|
|
97
|
+
}}
|
|
98
|
+
onInteractOutside={(event) => event.preventDefault()}
|
|
99
|
+
onEscapeKeyDown={(event) => confirming && event.preventDefault()}
|
|
100
|
+
>
|
|
101
|
+
<span className="inline-block w-fit rounded bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700">
|
|
102
|
+
{copy.badge}
|
|
103
|
+
</span>
|
|
104
|
+
<DialogTitle className="text-base font-semibold text-gray-900">{title}</DialogTitle>
|
|
105
|
+
<DialogDescription asChild>
|
|
106
|
+
<div className="space-y-2 text-sm text-gray-600">
|
|
107
|
+
{description ? <div>{description}</div> : null}
|
|
108
|
+
{summary.length > 0 ? (
|
|
109
|
+
<ul className="list-disc pl-5 text-gray-900">
|
|
110
|
+
{summary.map((line) => (
|
|
111
|
+
<li key={line}>{line}</li>
|
|
112
|
+
))}
|
|
113
|
+
</ul>
|
|
114
|
+
) : null}
|
|
115
|
+
<p>{copy.note}</p>
|
|
116
|
+
</div>
|
|
117
|
+
</DialogDescription>
|
|
118
|
+
<div className="flex justify-end gap-2">
|
|
119
|
+
<button
|
|
120
|
+
ref={cancelRef}
|
|
121
|
+
type="button"
|
|
122
|
+
disabled={confirming}
|
|
123
|
+
onClick={onCancel}
|
|
124
|
+
style={interactive}
|
|
125
|
+
className="rounded border border-gray-300 px-3 py-1.5 text-sm text-gray-700 disabled:opacity-50"
|
|
126
|
+
>
|
|
127
|
+
{props.cancelLabel ?? "Cancel"}
|
|
128
|
+
</button>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
disabled={confirming}
|
|
132
|
+
onClick={onConfirm}
|
|
133
|
+
style={interactive}
|
|
134
|
+
className="rounded bg-gray-900 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50"
|
|
135
|
+
>
|
|
136
|
+
{confirming ? "Working..." : (props.confirmLabel ?? copy.confirm)}
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
</DialogContent>
|
|
140
|
+
</Dialog>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
export interface EmptyStateProps {
|
|
6
|
+
title: string;
|
|
7
|
+
description?: ReactNode;
|
|
8
|
+
/** Usually a button that creates the first record. */
|
|
9
|
+
action?: ReactNode;
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Shown when a list is empty. Say what will appear here and offer the first step. */
|
|
15
|
+
export function EmptyState({ title, description, action, icon, className }: EmptyStateProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div className={`flex flex-col items-center justify-center gap-2 px-6 py-12 text-center ${className ?? ""}`}>
|
|
18
|
+
{icon ? <div className="text-gray-400">{icon}</div> : null}
|
|
19
|
+
<h3 className="text-sm font-semibold text-gray-900">{title}</h3>
|
|
20
|
+
{description ? <p className="max-w-sm text-sm text-gray-500">{description}</p> : null}
|
|
21
|
+
{action ? <div className="mt-2">{action}</div> : null}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export interface UseG8MutationOptions<TVars, TResult> {
|
|
6
|
+
onSuccess?: (result: TResult, vars: TVars) => void;
|
|
7
|
+
onError?: (error: unknown, vars: TVars) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UseG8MutationResult<TVars, TResult> {
|
|
11
|
+
/** Run the write. Never rejects: failures land in `error` (and `onError`). Resolves undefined on failure. */
|
|
12
|
+
mutate: (vars: TVars) => Promise<TResult | undefined>;
|
|
13
|
+
/** Same, but rejects on failure, for code that wants try/catch. */
|
|
14
|
+
mutateAsync: (vars: TVars) => Promise<TResult>;
|
|
15
|
+
data: TResult | undefined;
|
|
16
|
+
error: unknown;
|
|
17
|
+
loading: boolean;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wrap a write (create, update, enroll, send...) with loading and error state.
|
|
23
|
+
*
|
|
24
|
+
* Calls made while a request is in flight are still sent; the state shown is
|
|
25
|
+
* always the LATEST call's. `onSuccess`/`onError` still fire if the component
|
|
26
|
+
* unmounted meanwhile (so a toast or cache update is not lost); guard them yourself
|
|
27
|
+
* if they touch component state. To stop double submits, disable the button on
|
|
28
|
+
* `loading`, and pass a stable `idempotencyKey` for writes that may repeat.
|
|
29
|
+
*
|
|
30
|
+
* const save = useG8Mutation((vars: { id: number }) => g8Patch(`/contacts/${vars.id}`, vars));
|
|
31
|
+
*/
|
|
32
|
+
export function useG8Mutation<TVars, TResult>(
|
|
33
|
+
mutation: (vars: TVars) => Promise<TResult>,
|
|
34
|
+
options: UseG8MutationOptions<TVars, TResult> = {},
|
|
35
|
+
): UseG8MutationResult<TVars, TResult> {
|
|
36
|
+
const mutationRef = useRef(mutation);
|
|
37
|
+
mutationRef.current = mutation;
|
|
38
|
+
const optionsRef = useRef(options);
|
|
39
|
+
optionsRef.current = options;
|
|
40
|
+
const latest = useRef(0);
|
|
41
|
+
const mounted = useRef(true);
|
|
42
|
+
const [data, setData] = useState<TResult | undefined>(undefined);
|
|
43
|
+
const [error, setError] = useState<unknown>(undefined);
|
|
44
|
+
const [loading, setLoading] = useState(false);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
mounted.current = true;
|
|
48
|
+
return () => {
|
|
49
|
+
mounted.current = false;
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const mutateAsync = useCallback(async (vars: TVars): Promise<TResult> => {
|
|
54
|
+
const id = ++latest.current;
|
|
55
|
+
setLoading(true);
|
|
56
|
+
setError(undefined);
|
|
57
|
+
try {
|
|
58
|
+
const result = await mutationRef.current(vars);
|
|
59
|
+
if (id === latest.current && mounted.current) {
|
|
60
|
+
setData(result);
|
|
61
|
+
setLoading(false);
|
|
62
|
+
}
|
|
63
|
+
optionsRef.current.onSuccess?.(result, vars);
|
|
64
|
+
return result;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (id === latest.current && mounted.current) {
|
|
67
|
+
setError(err);
|
|
68
|
+
setLoading(false);
|
|
69
|
+
}
|
|
70
|
+
optionsRef.current.onError?.(err, vars);
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const mutate = useCallback(
|
|
76
|
+
(vars: TVars): Promise<TResult | undefined> => mutateAsync(vars).catch(() => undefined),
|
|
77
|
+
[mutateAsync],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const reset = useCallback(() => {
|
|
81
|
+
latest.current++;
|
|
82
|
+
setData(undefined);
|
|
83
|
+
setError(undefined);
|
|
84
|
+
setLoading(false);
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
return { mutate, mutateAsync, data, error, loading, reset };
|
|
88
|
+
}
|