@graph8/sdk 0.361.0 → 0.370.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 +14 -10
- package/blocks/_foundation/README.md +172 -1
- package/blocks/_foundation/block.json +234 -13
- package/blocks/_foundation/components/api-error.tsx +71 -25
- package/blocks/_foundation/components/brand-provider.tsx +23 -0
- package/blocks/_foundation/components/confirmed-action.tsx +90 -0
- package/blocks/_foundation/components/connect-graph8.tsx +280 -0
- package/blocks/_foundation/components/empty-state.tsx +39 -7
- package/blocks/_foundation/components/screen/bar-list.tsx +71 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +114 -0
- package/blocks/_foundation/components/screen/entity-avatar.tsx +56 -0
- package/blocks/_foundation/components/screen/form-sheet.tsx +73 -0
- package/blocks/_foundation/components/screen/index.ts +22 -0
- package/blocks/_foundation/components/screen/key-value-list.tsx +65 -0
- package/blocks/_foundation/components/screen/record-link.tsx +76 -0
- package/blocks/_foundation/components/screen/screen-layout.tsx +124 -0
- package/blocks/_foundation/components/screen/section-card.tsx +47 -0
- package/blocks/_foundation/components/screen/sparkline.tsx +77 -0
- package/blocks/_foundation/components/screen/split-view.tsx +125 -0
- package/blocks/_foundation/components/screen/stat-strip.tsx +121 -0
- package/blocks/_foundation/components/screen/status-pill.tsx +104 -0
- package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
- package/blocks/_foundation/components/screen/time-series-chart.tsx +32 -0
- package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
- package/blocks/_foundation/components/screen/toolbar.tsx +144 -0
- package/blocks/_foundation/hooks/use-brand.ts +52 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
- package/blocks/_foundation/hooks/use-paginated.ts +120 -0
- package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
- package/blocks/_foundation/hooks/use-url-state.ts +74 -0
- package/blocks/_foundation/lib/g8-cache.ts +454 -0
- package/blocks/_foundation/lib/g8-client.ts +1 -1
- package/blocks/_foundation/lib/plural.ts +23 -0
- package/blocks/_foundation/server/connect-route.ts +15 -0
- package/blocks/_foundation/server/connect.ts +127 -0
- package/blocks/_foundation/server/local-key.ts +70 -0
- package/blocks/_foundation/server/proxy.ts +103 -3
- package/blocks/_foundation/server/route.ts +16 -4
- package/blocks/agents/README.md +14 -0
- package/blocks/agents/block.json +33 -1
- package/blocks/agents/components/agent-builder.tsx +13 -8
- package/blocks/agents/components/agent-knowledge-panel.tsx +2 -1
- package/blocks/agents/components/agent-run-detail.tsx +102 -0
- package/blocks/agents/components/agent-runs.tsx +16 -7
- package/blocks/agents/components/agents-screen.tsx +184 -0
- package/blocks/analytics/README.md +13 -0
- package/blocks/analytics/block.json +45 -1
- package/blocks/analytics/components/analytics-screen.tsx +209 -0
- package/blocks/analytics/components/leaderboard.tsx +14 -7
- package/blocks/analytics/components/report-builder.tsx +23 -9
- package/blocks/analytics/components/report-detail.tsx +84 -0
- package/blocks/analytics/components/trend-chart.tsx +62 -0
- package/blocks/appointments/README.md +21 -1
- package/blocks/appointments/block.json +43 -2
- package/blocks/appointments/components/availability-editor.tsx +17 -5
- package/blocks/appointments/components/event-type-editor.tsx +2 -1
- package/blocks/appointments/components/event-type-related.tsx +77 -0
- package/blocks/appointments/components/meeting-list.tsx +14 -6
- package/blocks/appointments/components/meetings-screen.tsx +157 -0
- package/blocks/appointments/components/scheduling-screen.tsx +122 -0
- package/blocks/appointments/hooks/use-event-types.ts +17 -0
- package/blocks/block.schema.json +18 -0
- package/blocks/block.types.ts +26 -0
- package/blocks/campaigns/README.md +41 -11
- package/blocks/campaigns/block.json +105 -42
- package/blocks/campaigns/components/campaign-assets.tsx +16 -5
- package/blocks/campaigns/components/campaign-launch-step.tsx +102 -19
- package/blocks/campaigns/components/campaign-list.tsx +7 -3
- package/blocks/campaigns/components/campaign-wizard.tsx +43 -10
- package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
- package/blocks/campaigns/components/icp-detail.tsx +154 -0
- package/blocks/campaigns/components/icp-editor.tsx +15 -15
- package/blocks/campaigns/components/persona-detail.tsx +172 -0
- package/blocks/campaigns/components/persona-editor.tsx +14 -11
- package/blocks/campaigns/lib/campaigns-utils.ts +134 -1
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
- package/blocks/coach/README.md +10 -0
- package/blocks/coach/block.json +16 -1
- package/blocks/coach/components/coach-screen.tsx +222 -0
- package/blocks/copilot/README.md +13 -0
- package/blocks/copilot/block.json +14 -1
- package/blocks/copilot/components/copilot-panel.tsx +14 -5
- package/blocks/copilot/components/copilot-screen.tsx +148 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +7 -5
- package/blocks/crm/README.md +28 -0
- package/blocks/crm/block.json +58 -2
- package/blocks/crm/components/companies-screen.tsx +345 -0
- package/blocks/crm/components/company-detail.tsx +8 -6
- package/blocks/crm/components/company-table.tsx +11 -4
- package/blocks/crm/components/contact-detail.tsx +6 -3
- package/blocks/crm/components/contact-table.tsx +10 -3
- package/blocks/crm/components/contacts-screen.tsx +359 -0
- package/blocks/crm/components/dedupe-review.tsx +7 -5
- package/blocks/crm/components/import-wizard.tsx +4 -2
- package/blocks/crm/components/notes-panel.tsx +4 -4
- package/blocks/crm/components/record-form.tsx +5 -3
- package/blocks/crm/components/task-list.tsx +4 -4
- package/blocks/crm/components/tasks-screen.tsx +379 -0
- package/blocks/crm/hooks/use-custom-fields.ts +6 -1
- package/blocks/crm/hooks/use-page-query.ts +7 -1
- package/blocks/crm/lib/crm-types.ts +12 -2
- package/blocks/crm/lib/screen-parts.tsx +105 -0
- package/blocks/deals/README.md +34 -3
- package/blocks/deals/block.json +45 -4
- package/blocks/deals/components/deal-detail.tsx +27 -13
- package/blocks/deals/components/deal-related.tsx +87 -0
- package/blocks/deals/components/deals-screen.tsx +298 -0
- package/blocks/deals/components/pipeline-board.tsx +4 -2
- package/blocks/deals/components/stage-editor.tsx +4 -4
- package/blocks/deals/hooks/use-default-owner.ts +60 -0
- package/blocks/deals/hooks/use-pipeline-deals.ts +5 -1
- package/blocks/deals/hooks/use-pipelines.ts +1 -1
- package/blocks/deals/lib/deals-types.ts +14 -0
- package/blocks/dialer/README.md +34 -2
- package/blocks/dialer/block.json +67 -1
- package/blocks/dialer/components/call-contact-button.tsx +252 -0
- package/blocks/dialer/components/call-log-screen.tsx +205 -0
- package/blocks/dialer/components/call-log.tsx +7 -3
- package/blocks/dialer/components/call-queue.tsx +1 -1
- package/blocks/dialer/components/dialer-screen.tsx +319 -0
- package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
- package/blocks/dialer/components/live-calls-table.tsx +7 -3
- package/blocks/dialer/components/session-start-form.tsx +4 -2
- package/blocks/dialer/components/softphone.tsx +3 -1
- package/blocks/enrichment/README.md +14 -2
- package/blocks/enrichment/block.json +32 -4
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +10 -5
- package/blocks/enrichment/components/email-verify-badge.tsx +28 -8
- package/blocks/enrichment/components/enrich-button.tsx +32 -9
- package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +288 -0
- package/blocks/enrichment/components/waterfall-config.tsx +39 -10
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
- package/blocks/forms/README.md +12 -0
- package/blocks/forms/block.json +39 -17
- package/blocks/forms/components/form-builder.tsx +3 -1
- package/blocks/forms/components/form-embed.tsx +3 -1
- package/blocks/forms/components/forms-screen.tsx +353 -0
- package/blocks/forms/components/submissions-table.tsx +16 -6
- package/blocks/inbox/README.md +27 -3
- package/blocks/inbox/block.json +14 -2
- package/blocks/inbox/components/inbox-list.tsx +7 -3
- package/blocks/inbox/components/inbox-screen.tsx +294 -27
- package/blocks/inbox/components/reply-composer.tsx +65 -9
- package/blocks/inbox/components/tag-and-assign.tsx +4 -2
- package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
- package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
- package/blocks/inbox/lib/inbox-types.ts +47 -1
- package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
- package/blocks/integrations/README.md +9 -0
- package/blocks/integrations/block.json +46 -1
- package/blocks/integrations/components/crm-detail.tsx +100 -0
- package/blocks/integrations/components/integration-list.tsx +9 -4
- package/blocks/integrations/components/integrations-screen.tsx +135 -0
- package/blocks/integrations/components/sync-status.tsx +5 -3
- package/blocks/integrations/components/webhook-detail.tsx +160 -0
- package/blocks/integrations/components/webhook-manager.tsx +13 -8
- package/blocks/integrations/lib/integrations-utils.ts +6 -1
- package/blocks/intent/README.md +14 -0
- package/blocks/intent/block.json +27 -1
- package/blocks/intent/components/intent-feed.tsx +5 -2
- package/blocks/intent/components/intent-keyword-manager.tsx +8 -4
- package/blocks/intent/components/intent-screen.tsx +265 -0
- package/blocks/intent/components/keyword-detail.tsx +124 -0
- package/blocks/lifecycle/README.md +16 -0
- package/blocks/lifecycle/block.json +34 -7
- package/blocks/lifecycle/components/account-related.tsx +66 -0
- package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
- package/blocks/lifecycle/components/lifecycle-screen.tsx +149 -0
- package/blocks/lifecycle/lib/lifecycle.ts +1 -1
- package/blocks/lists/README.md +13 -0
- package/blocks/lists/block.json +39 -1
- package/blocks/lists/components/add-to-list-button.tsx +3 -2
- package/blocks/lists/components/list-picker.tsx +5 -1
- package/blocks/lists/components/list-table.tsx +11 -5
- package/blocks/lists/components/lists-screen.tsx +344 -0
- package/blocks/lists/components/suppression-manager.tsx +6 -2
- package/blocks/lists/components/suppressions-screen.tsx +255 -0
- package/blocks/mailboxes/README.md +17 -0
- package/blocks/mailboxes/block.json +65 -17
- package/blocks/mailboxes/components/connect-mailbox.tsx +23 -13
- package/blocks/mailboxes/components/domain-detail.tsx +142 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +6 -3
- package/blocks/mailboxes/components/mailbox-detail.tsx +156 -0
- package/blocks/mailboxes/components/mailboxes-screen.tsx +223 -0
- package/blocks/mailboxes/components/warmup-health.tsx +4 -2
- package/blocks/mailboxes/lib/mailboxes-utils.ts +22 -1
- package/blocks/newsletter/README.md +13 -0
- package/blocks/newsletter/block.json +47 -23
- package/blocks/newsletter/components/issue-scheduler.tsx +2 -1
- package/blocks/newsletter/components/newsletter-screen.tsx +404 -0
- package/blocks/newsletter/components/subscriber-table.tsx +12 -6
- package/blocks/nurture/README.md +12 -0
- package/blocks/nurture/block.json +35 -13
- package/blocks/nurture/components/nurture-builder.tsx +3 -1
- package/blocks/nurture/components/nurture-enroll.tsx +6 -3
- package/blocks/nurture/components/nurture-form.tsx +23 -3
- package/blocks/nurture/components/nurture-screen.tsx +283 -0
- package/blocks/nurture/lib/nurture-types.ts +1 -1
- package/blocks/objects/README.md +17 -0
- package/blocks/objects/block.json +29 -16
- package/blocks/objects/components/object-not-found.tsx +3 -1
- package/blocks/objects/components/object-record-detail.tsx +2 -1
- package/blocks/objects/components/object-schema-editor.tsx +3 -1
- package/blocks/objects/components/object-table.tsx +13 -3
- package/blocks/objects/components/objects-screen.tsx +147 -0
- package/blocks/objects/lib/object-types.ts +1 -1
- package/blocks/pages/README.md +12 -0
- package/blocks/pages/block.json +35 -22
- package/blocks/pages/components/brand-kit-editor.tsx +5 -2
- package/blocks/pages/components/content-library.tsx +46 -11
- package/blocks/pages/components/page-list.tsx +6 -3
- package/blocks/pages/components/pages-screen.tsx +154 -0
- package/blocks/quotes/README.md +17 -0
- package/blocks/quotes/block.json +38 -9
- package/blocks/quotes/components/product-picker.tsx +3 -1
- package/blocks/quotes/components/quote-list.tsx +7 -3
- package/blocks/quotes/components/quote-related.tsx +123 -0
- package/blocks/quotes/components/quotes-screen.tsx +169 -0
- package/blocks/radar/README.md +10 -0
- package/blocks/radar/block.json +41 -1
- package/blocks/radar/components/competitor-detail.tsx +117 -0
- package/blocks/radar/components/competitor-tracker.tsx +3 -1
- package/blocks/radar/components/radar-report.tsx +4 -2
- package/blocks/radar/components/radar-screen.tsx +214 -0
- package/blocks/radar/components/report-detail.tsx +104 -0
- package/blocks/search/README.md +10 -0
- package/blocks/search/block.json +16 -1
- package/blocks/search/components/filter-builder.tsx +3 -3
- package/blocks/search/components/prospect-results.tsx +5 -3
- package/blocks/search/components/prospect-search-screen.tsx +235 -0
- package/blocks/search/components/saved-searches.tsx +4 -1
- package/blocks/search/hooks/use-filter-options.ts +20 -6
- package/blocks/search/lib/search-types.ts +2 -0
- package/blocks/sequencer/README.md +49 -9
- package/blocks/sequencer/block.json +99 -19
- package/blocks/sequencer/components/enroll-dialog.tsx +9 -3
- package/blocks/sequencer/components/sequence-builder.tsx +211 -46
- package/blocks/sequencer/components/sequence-detail.tsx +259 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +114 -0
- package/blocks/sequencer/components/sequence-list.tsx +33 -8
- package/blocks/sequencer/components/sequence-stats.tsx +3 -2
- package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
- package/blocks/sequencer/components/sequences-screen.tsx +159 -0
- package/blocks/sequencer/components/step-editor.tsx +3 -1
- package/blocks/sequencer/lib/sequencer-utils.ts +128 -5
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
- package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
- package/blocks/social-ads/README.md +11 -0
- package/blocks/social-ads/block.json +101 -2
- package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
- package/blocks/social-ads/components/ads-screen.tsx +152 -0
- package/blocks/social-ads/components/audience-sync-detail.tsx +188 -0
- package/blocks/social-ads/components/audience-sync.tsx +11 -7
- package/blocks/social-ads/components/campaign-detail.tsx +133 -0
- package/blocks/social-ads/components/creative-detail.tsx +150 -0
- package/blocks/social-ads/components/creative-gallery.tsx +15 -5
- package/blocks/social-ads/components/post-composer.tsx +3 -1
- package/blocks/social-ads/components/post-detail.tsx +160 -0
- package/blocks/social-ads/components/social-post-list.tsx +11 -3
- package/blocks/social-ads/components/social-screen.tsx +160 -0
- package/blocks/visitors/README.md +10 -0
- package/blocks/visitors/block.json +25 -1
- package/blocks/visitors/components/snippet-install.tsx +3 -1
- package/blocks/visitors/components/visitor-company-card.tsx +2 -2
- package/blocks/visitors/components/visitor-detail.tsx +105 -0
- package/blocks/visitors/components/visitor-feed.tsx +11 -2
- package/blocks/visitors/components/visitors-screen.tsx +166 -0
- package/blocks/webchat/README.md +10 -0
- package/blocks/webchat/block.json +21 -7
- package/blocks/webchat/components/chat-widget-config.tsx +7 -4
- package/blocks/webchat/components/conversation-list.tsx +7 -3
- package/blocks/webchat/components/webchat-screen.tsx +245 -0
- package/blocks/workbench/README.md +11 -0
- package/blocks/workbench/block.json +48 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +4 -2
- package/blocks/workbench/components/workbench-grid.tsx +9 -3
- package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
- package/blocks/workbench/components/workbench-screen.tsx +192 -0
- package/blocks/workbench/components/workbench-sources.tsx +194 -0
- package/blocks/workflows/README.md +13 -0
- package/blocks/workflows/block.json +28 -9
- package/blocks/workflows/components/run-history.tsx +31 -9
- package/blocks/workflows/components/trigger-config.tsx +3 -1
- package/blocks/workflows/components/workflow-editor.tsx +3 -1
- package/blocks/workflows/components/workflow-list.tsx +13 -6
- package/blocks/workflows/components/workflows-screen.tsx +193 -0
- package/dist/cli.js +2785 -1637
- package/dist/errors.d.mts +34 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.mjs +21 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.browser.js +11 -5
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +11 -5
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +164 -25
- package/dist/index.d.ts +164 -25
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +10 -4
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +10 -4
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +142 -5
- package/dist/react.d.ts +142 -5
- package/dist/react.js +10 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +10 -4
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/add.d.ts +11 -4
- package/dist/types/blocks/brand.d.ts +113 -0
- package/dist/types/blocks/compose.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +25 -8
- package/dist/types/blocks/doctor.d.ts +6 -1
- package/dist/types/blocks/flows.d.ts +20 -0
- package/dist/types/blocks/navfile.d.ts +35 -0
- package/dist/types/blocks/paths.d.ts +4 -0
- package/dist/types/blocks/prefixes.d.ts +37 -0
- package/dist/types/blocks/registry.d.ts +32 -0
- package/dist/types/blocks/screens.d.ts +53 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/errors.d.ts +33 -0
- package/dist/types/http.d.ts +2 -18
- package/dist/types/version.d.ts +2 -2
- package/docs/agents.md +15 -4
- package/docs/api/README.md +2 -2
- package/docs/api/by-path.md +1 -0
- package/docs/api/desk.d.ts +98 -2
- package/docs/api/desk.md +123 -1
- package/docs/api/inbox.d.ts +10 -2
- package/docs/api/inbox.md +10 -2
- package/docs/api/seo.d.ts +19 -1
- package/docs/api/seo.md +24 -1
- package/package.json +16 -2
- package/stacks/6sense.json +41 -16
- package/stacks/apollo/pages/companies.tsx.tmpl +50 -0
- package/stacks/apollo/pages/contacts.tsx.tmpl +117 -0
- package/stacks/apollo.json +118 -36
- package/stacks/calendly.json +60 -0
- package/stacks/clay/pages/companies.tsx.tmpl +15 -0
- package/stacks/clay/pages/contacts.tsx.tmpl +19 -0
- package/stacks/clay.json +59 -26
- package/stacks/gong.json +81 -35
- package/stacks/hootsuite.json +99 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +47 -0
- package/stacks/hubspot/pages/contacts.tsx.tmpl +111 -0
- package/stacks/hubspot/pages/deals.tsx.tmpl +11 -0
- package/stacks/hubspot/pages/inbox.tsx.tmpl +92 -0
- package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
- package/stacks/hubspot.json +167 -66
- package/stacks/intercom.json +48 -20
- package/stacks/mailchimp.json +52 -33
- package/stacks/outreach/pages/contacts.tsx.tmpl +85 -0
- package/stacks/outreach.json +88 -25
- package/stacks/pipedrive/pages/companies.tsx.tmpl +47 -0
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +47 -0
- package/stacks/pipedrive/pages/deals.tsx.tmpl +11 -0
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +92 -0
- package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
- package/stacks/pipedrive.json +115 -0
- package/stacks/salesforce/pages/companies.tsx.tmpl +47 -0
- package/stacks/salesforce/pages/contacts.tsx.tmpl +47 -0
- package/stacks/salesforce/pages/deals.tsx.tmpl +11 -0
- package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
- package/stacks/salesforce.json +81 -31
- package/stacks/stack.schema.json +6 -1
- package/stacks/zoominfo/pages/companies.tsx.tmpl +15 -0
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +102 -0
- package/styles/styles.css +1 -1
- package/templates/next-app/README.md.tmpl +55 -8
- package/templates/next-app/app/globals.css.tmpl +105 -0
- package/templates/next-app/app/layout.tsx.tmpl +18 -4
- package/templates/next-app/components/app-shell.tsx.tmpl +248 -23
- package/templates/next-app/components/shell/connection-status.tsx.tmpl +109 -0
- package/templates/next-app/components/shell/nav-model.ts.tmpl +65 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +131 -0
- package/templates/next-app/components/shell/theme-toggle.tsx.tmpl +40 -0
- package/templates/next-app/dot-gitignore.tmpl +2 -0
- package/templates/next-app/lib/nav.ts.tmpl +1 -1
- package/templates/next-app/package.json.tmpl +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MeetingsScreen: the finished meetings product. Upcoming, past, cancelled and recorded meetings, a detail panel
|
|
5
|
+
* for the open one, and a "Schedule a meeting" flow. No props are required.
|
|
6
|
+
*
|
|
7
|
+
* It composes: MeetingList (views, search, paging), MeetingDetail (booking or recorded meeting) and BookingPage.
|
|
8
|
+
*
|
|
9
|
+
* Flows:
|
|
10
|
+
* - Headline numbers: meetings coming up and bookings waiting for your approval.
|
|
11
|
+
* - Open a booking (deep link ?sel=booking:<uid>, full page with &view=full) -> accept or decline a pending one, add an internal note,
|
|
12
|
+
* cancel (all confirmed because they email attendees). Open a recorded meeting -> summary, topics, action
|
|
13
|
+
* items and transcript.
|
|
14
|
+
* - Schedule a meeting: pick an event type, a day and time, enter the attendee, confirm, and the invite is
|
|
15
|
+
* emailed (external, confirmed inside BookingPage).
|
|
16
|
+
* - Calendar connection and working hours live in SchedulingScreen; Google or Microsoft sign-in is only possible
|
|
17
|
+
* in the graph8 app, so this screen links out instead of showing a dead button.
|
|
18
|
+
*
|
|
19
|
+
* SDK operations (through the composed parts): listBookings, getBooking, confirmBooking (external), cancelBooking
|
|
20
|
+
* (destructive), addBookingNote (external), listMeetings, getMeeting, listEventTypes, getEventType,
|
|
21
|
+
* getAvailableSlots, createBooking (external). Every confirmation uses ConfirmSpendDialog.
|
|
22
|
+
*
|
|
23
|
+
* Slots (all optional): renderDetailActions, extraDetailTabs, toolbarActions.
|
|
24
|
+
*/
|
|
25
|
+
import { useMemo, useState, type ReactNode } from "react";
|
|
26
|
+
import { CalendarClock, CalendarPlus, ExternalLink } from "lucide-react";
|
|
27
|
+
import { Button, Skeleton } from "@graph8/sdk/ui";
|
|
28
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
29
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
30
|
+
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
31
|
+
import { FormSheet } from "@/g8/components/screen/form-sheet";
|
|
32
|
+
import { ScreenLayout } from "@/g8/components/screen/screen-layout";
|
|
33
|
+
import { SectionCard } from "@/g8/components/screen/section-card";
|
|
34
|
+
import { SplitView } from "@/g8/components/screen/split-view";
|
|
35
|
+
import { useUrlState } from "@/g8/hooks/use-url-state";
|
|
36
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
37
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
38
|
+
import { useEventTypes } from "../hooks/use-event-types";
|
|
39
|
+
import { listOf, type Booking } from "../lib/appointments-types";
|
|
40
|
+
import { BookingPage } from "./booking-page";
|
|
41
|
+
import { MeetingDetail } from "./meeting-detail";
|
|
42
|
+
import { MeetingList, type MeetingSelection } from "./meeting-list";
|
|
43
|
+
import { useBrand } from "@/g8/hooks/use-brand";
|
|
44
|
+
|
|
45
|
+
export interface MeetingsScreenProps {
|
|
46
|
+
/** Where a person connects a calendar (graph8 app). */
|
|
47
|
+
connectCalendarUrl?: string;
|
|
48
|
+
/** Extra buttons in the detail header. */
|
|
49
|
+
renderDetailActions?: (selection: MeetingSelection) => ReactNode;
|
|
50
|
+
/** Extra tabs in the detail panel (for example the contact's CRM record). */
|
|
51
|
+
extraDetailTabs?: Array<{ id: string; label: string; render: (selection: MeetingSelection) => ReactNode }>;
|
|
52
|
+
/** Extra controls next to "Schedule a meeting". */
|
|
53
|
+
toolbarActions?: ReactNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const COUNT_CAP = 100;
|
|
57
|
+
|
|
58
|
+
function parseSelection(raw: string | null | undefined): MeetingSelection | null {
|
|
59
|
+
const [kind, ...rest] = (raw ?? "").split(":");
|
|
60
|
+
const id = rest.join(":");
|
|
61
|
+
return id && (kind === "booking" || kind === "meeting") ? { kind, id } : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function useBookingCounts(refreshKey: number) {
|
|
65
|
+
const now = useMemo(() => new Date().toISOString(), []);
|
|
66
|
+
const q = useG8Query(async (signal) => {
|
|
67
|
+
const [upcoming, pending] = await Promise.all([
|
|
68
|
+
g8Get("/appointments/bookings", { after_start: now, take: COUNT_CAP, skip: 0 }, { signal }),
|
|
69
|
+
g8Get("/appointments/bookings", { status: "pending", take: COUNT_CAP, skip: 0 }, { signal }),
|
|
70
|
+
]);
|
|
71
|
+
return { upcoming: listOf<Booking>(upcoming, "bookings"), pending: listOf<Booking>(pending, "bookings") };
|
|
72
|
+
}, [now, refreshKey]);
|
|
73
|
+
return q;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function countLabel(list: Booking[]): string {
|
|
77
|
+
return list.length >= COUNT_CAP ? `${COUNT_CAP}+` : String(list.length);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ScheduleSheet({ open, onOpenChange, onBooked }: { open: boolean; onOpenChange: (open: boolean) => void; onBooked: () => void }) {
|
|
81
|
+
const types = useEventTypes();
|
|
82
|
+
const [chosen, setChosen] = useState<number | null>(null);
|
|
83
|
+
const eventTypeId = chosen ?? types.eventTypes.find((t) => !t.hidden)?.id ?? types.eventTypes[0]?.id ?? null;
|
|
84
|
+
return (
|
|
85
|
+
<FormSheet open={open} onOpenChange={onOpenChange} title="Schedule a meeting" description="Pick a time that works for the host, then enter who is joining.">
|
|
86
|
+
{types.error ? <ApiError error={types.error} title="Could not load event types" onRetry={() => void types.refetch()} /> : types.loading && !types.data ? <Skeleton className="h-32 w-full" aria-label="Loading event types" /> : eventTypeId == null ? (
|
|
87
|
+
<EmptyState icon={<CalendarClock className="size-8" />} title="No event types yet" description="Create an event type in Scheduling first, then book meetings with it here." />
|
|
88
|
+
) : (
|
|
89
|
+
<div className="space-y-4">
|
|
90
|
+
<div className="space-y-1">
|
|
91
|
+
<label htmlFor="schedule-event-type" className="text-sm font-medium">Event type</label>
|
|
92
|
+
<select id="schedule-event-type" className="h-9 w-full rounded-md border bg-background px-2 text-sm" value={eventTypeId} onChange={(e) => setChosen(Number(e.target.value))}>
|
|
93
|
+
{types.eventTypes.map((t) => <option key={t.id} value={t.id}>{t.title} ({t.length} min)</option>)}
|
|
94
|
+
</select>
|
|
95
|
+
</div>
|
|
96
|
+
<BookingPage eventTypeId={eventTypeId} onBooked={onBooked} />
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</FormSheet>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function MeetingsScreen({ connectCalendarUrl: connectCalendarUrlProp, renderDetailActions, extraDetailTabs, toolbarActions }: MeetingsScreenProps) {
|
|
104
|
+
const brand = useBrand();
|
|
105
|
+
const connectCalendarUrl = connectCalendarUrlProp ?? brand.appUrl;
|
|
106
|
+
const [raw, setRaw] = useUrlState("sel");
|
|
107
|
+
const [mode, setMode] = useUrlState("view");
|
|
108
|
+
const selection = parseSelection(raw);
|
|
109
|
+
const [version, setVersion] = useState(0);
|
|
110
|
+
const [scheduling, setScheduling] = useState(false);
|
|
111
|
+
const counts = useBookingCounts(version);
|
|
112
|
+
const changed = () => setVersion((v) => v + 1);
|
|
113
|
+
const close = () => { setRaw(null); setMode(null); };
|
|
114
|
+
|
|
115
|
+
const stats = [
|
|
116
|
+
{ label: "Upcoming", value: counts.data ? countLabel(counts.data.upcoming) : "-", hint: "Meetings starting from now" },
|
|
117
|
+
{ label: "Awaiting approval", value: counts.data ? countLabel(counts.data.pending) : "-", tone: counts.data && counts.data.pending.length > 0 ? ("warning" as const) : undefined, hint: "Pending bookings you can accept or decline" },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const detail = selection ? (
|
|
121
|
+
<DetailPanel
|
|
122
|
+
title={selection.kind === "booking" ? "Booking" : "Recorded meeting"}
|
|
123
|
+
actions={renderDetailActions?.(selection)}
|
|
124
|
+
onClose={close}
|
|
125
|
+
onExpand={mode === "full" ? undefined : () => setMode("full")}
|
|
126
|
+
onCollapse={mode === "full" ? () => setMode(null) : undefined}
|
|
127
|
+
tabs={[
|
|
128
|
+
{ id: "overview", label: "Overview", content: <MeetingDetail selection={selection} onChanged={changed} /> },
|
|
129
|
+
...(extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(selection) })),
|
|
130
|
+
]}
|
|
131
|
+
/>
|
|
132
|
+
) : null;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ScreenLayout
|
|
136
|
+
title="Meetings"
|
|
137
|
+
description="Everything booked and recorded, and the next meeting to prepare for."
|
|
138
|
+
icon={<CalendarClock className="size-5" />}
|
|
139
|
+
actions={<><Button onClick={() => setScheduling(true)}><CalendarPlus className="size-4" aria-hidden />Schedule a meeting</Button>{toolbarActions}</>}
|
|
140
|
+
stats={stats}
|
|
141
|
+
>
|
|
142
|
+
{counts.error ? <ApiError error={counts.error} title="Could not load meeting numbers" onRetry={() => void counts.refetch()} /> : null}
|
|
143
|
+
<SplitView
|
|
144
|
+
detailOpen={Boolean(selection)}
|
|
145
|
+
onCloseDetail={close}
|
|
146
|
+
expanded={mode === "full" && Boolean(selection)}
|
|
147
|
+
onExpandedChange={(full) => setMode(full ? "full" : null)}
|
|
148
|
+
detail={detail}
|
|
149
|
+
list={<MeetingList linkParam="sel" refreshKey={version} selectedId={selection?.id ?? null} onSelect={(s) => setRaw(`${s.kind}:${s.id}`)} />}
|
|
150
|
+
/>
|
|
151
|
+
<SectionCard title="Calendar sync" description={`Google and Microsoft sign-in happens in the ${brand.linkLabel} app. Once connected, booked and recorded meetings appear here.`}>
|
|
152
|
+
<a className="inline-flex items-center gap-1 text-sm underline" href={connectCalendarUrl} target="_blank" rel="noreferrer">Connect a calendar in {brand.linkLabel}<ExternalLink className="size-3.5" aria-hidden /></a>
|
|
153
|
+
</SectionCard>
|
|
154
|
+
<ScheduleSheet open={scheduling} onOpenChange={setScheduling} onBooked={changed} />
|
|
155
|
+
</ScreenLayout>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SchedulingScreen: the finished scheduling product. Your event types (meeting links), their settings, a live
|
|
5
|
+
* preview of each booking page, and your weekly working hours and calendar status. No props are required.
|
|
6
|
+
*
|
|
7
|
+
* It composes: EventTypeEditor (create, edit, delete), BookingPage (the preview), AvailabilityEditor (hours,
|
|
8
|
+
* date overrides, connected calendars) and the useEventTypes list.
|
|
9
|
+
*
|
|
10
|
+
* Flows:
|
|
11
|
+
* - Event types tab (full page with &view=full): see every event type with its length and status, create one, open one (deep link
|
|
12
|
+
* ?id=<event type id>) for Overview (every field), Bookings (who booked it, open one to accept, decline, note or
|
|
13
|
+
* cancel), Settings (edit, delete confirmed) and its Booking page preview. Booking from the preview is a real booking and emails the attendee (confirmed).
|
|
14
|
+
* - Working hours tab: edit the weekly hours and date overrides of a schedule. Connecting a Google or Microsoft
|
|
15
|
+
* calendar is only possible in the graph8 app, so the editor links out.
|
|
16
|
+
*
|
|
17
|
+
* SDK operations (through the composed parts): listEventTypes, getEventType, createEventType, updateEventType,
|
|
18
|
+
* deleteEventType (destructive, confirmed), listSchedules, getAvailability, updateAvailability, listCalendars,
|
|
19
|
+
* getAvailableSlots, createBooking (external, confirmed).
|
|
20
|
+
*
|
|
21
|
+
* Slots (all optional): renderRowActions, renderDetailActions, extraDetailTabs, toolbarActions.
|
|
22
|
+
*/
|
|
23
|
+
import { useState, type ReactNode } from "react";
|
|
24
|
+
import { CalendarClock, Plus } from "lucide-react";
|
|
25
|
+
import { Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
|
|
26
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
27
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
28
|
+
import { DetailPanel } from "@/g8/components/screen/detail-panel";
|
|
29
|
+
import { FormSheet } from "@/g8/components/screen/form-sheet";
|
|
30
|
+
import { ScreenLayout } from "@/g8/components/screen/screen-layout";
|
|
31
|
+
import { SplitView } from "@/g8/components/screen/split-view";
|
|
32
|
+
import { StatusPill } from "@/g8/components/screen/status-pill";
|
|
33
|
+
import { useUrlState } from "@/g8/hooks/use-url-state";
|
|
34
|
+
import { useEventTypes } from "../hooks/use-event-types";
|
|
35
|
+
import type { EventType } from "../lib/appointments-types";
|
|
36
|
+
import { AvailabilityEditor } from "./availability-editor";
|
|
37
|
+
import { BookingPage } from "./booking-page";
|
|
38
|
+
import { EventTypeEditor } from "./event-type-editor";
|
|
39
|
+
import { EventTypeBookings, EventTypeOverview } from "./event-type-related";
|
|
40
|
+
import { rowActivationProps } from "@/g8/hooks/use-row-activation";
|
|
41
|
+
|
|
42
|
+
export interface SchedulingScreenProps {
|
|
43
|
+
/** Where a person connects a calendar (graph8 app). */
|
|
44
|
+
connectCalendarUrl?: string;
|
|
45
|
+
renderRowActions?: (eventType: EventType) => ReactNode;
|
|
46
|
+
renderDetailActions?: (eventType: EventType) => ReactNode;
|
|
47
|
+
extraDetailTabs?: Array<{ id: string; label: string; render: (eventType: EventType) => ReactNode }>;
|
|
48
|
+
toolbarActions?: ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const TABS = [{ id: "types", label: "Event types" }, { id: "hours", label: "Working hours" }];
|
|
52
|
+
|
|
53
|
+
export function SchedulingScreen({ connectCalendarUrl, renderRowActions, renderDetailActions, extraDetailTabs, toolbarActions }: SchedulingScreenProps) {
|
|
54
|
+
const [tabParam, setTab] = useUrlState("tab", "types");
|
|
55
|
+
const [idParam, setId] = useUrlState("id");
|
|
56
|
+
const [mode, setMode] = useUrlState("view");
|
|
57
|
+
const [creating, setCreating] = useState(false);
|
|
58
|
+
const types = useEventTypes();
|
|
59
|
+
const tab = tabParam === "hours" ? "hours" : "types";
|
|
60
|
+
const selected = types.eventTypes.find((t) => String(t.id) === idParam);
|
|
61
|
+
const selectedId = idParam && /^\d+$/.test(idParam) ? Number(idParam) : null;
|
|
62
|
+
const close = () => { setId(null); setMode(null); };
|
|
63
|
+
|
|
64
|
+
const detail = selectedId != null ? (
|
|
65
|
+
<DetailPanel
|
|
66
|
+
title={selected?.title ?? "Event type"}
|
|
67
|
+
subtitle={selected ? `${selected.length} minutes` : undefined}
|
|
68
|
+
badges={selected?.hidden ? <StatusPill tone="neutral">Hidden</StatusPill> : undefined}
|
|
69
|
+
actions={selected ? renderDetailActions?.(selected) : undefined}
|
|
70
|
+
onClose={close}
|
|
71
|
+
onExpand={mode === "full" ? undefined : () => setMode("full")}
|
|
72
|
+
onCollapse={mode === "full" ? () => setMode(null) : undefined}
|
|
73
|
+
tabs={[
|
|
74
|
+
{ id: "overview", label: "Overview", content: <EventTypeOverview key={selectedId} eventTypeId={selectedId} /> },
|
|
75
|
+
{ id: "bookings", label: "Bookings", content: <EventTypeBookings key={selectedId} eventTypeId={selectedId} /> },
|
|
76
|
+
{ id: "settings", label: "Settings", content: <EventTypeEditor key={selectedId} eventTypeId={selectedId} onSaved={() => void types.refetch()} onDeleted={() => { close(); void types.refetch(); }} /> },
|
|
77
|
+
{ id: "preview", label: "Booking page", content: <BookingPage key={selectedId} eventTypeId={selectedId} /> },
|
|
78
|
+
...(extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: selected ? t.render(selected) : null })),
|
|
79
|
+
]}
|
|
80
|
+
/>
|
|
81
|
+
) : null;
|
|
82
|
+
|
|
83
|
+
const list = types.error ? <ApiError error={types.error} title="Could not load event types" onRetry={() => void types.refetch()} /> : types.loading && !types.data ? (
|
|
84
|
+
<div className="space-y-2" aria-busy="true" aria-label="Loading event types">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-10 w-full" />)}</div>
|
|
85
|
+
) : types.eventTypes.length === 0 ? (
|
|
86
|
+
<EmptyState icon={<CalendarClock className="size-8" />} title="No event types yet" description="An event type is a meeting link, like a 30 minute intro call. Create one and share its booking page." action={<Button onClick={() => setCreating(true)}>New event type</Button>} />
|
|
87
|
+
) : (
|
|
88
|
+
<Table>
|
|
89
|
+
<TableHeader><TableRow><TableHead>Event type</TableHead><TableHead className="text-right">Length</TableHead><TableHead>Rules</TableHead>{renderRowActions ? <TableHead /> : null}</TableRow></TableHeader>
|
|
90
|
+
<TableBody>
|
|
91
|
+
{types.eventTypes.map((t) => (
|
|
92
|
+
<TableRow key={t.id} className="cursor-pointer" data-state={String(t.id) === idParam ? "selected" : undefined} {...rowActivationProps(() => setId(String(t.id)))}>
|
|
93
|
+
<TableCell><button type="button" className="font-medium hover:underline" onClick={(e) => { e.stopPropagation(); setId(String(t.id)); }}>{t.title}</button><div className="text-xs text-muted-foreground">/{t.slug}</div></TableCell>
|
|
94
|
+
<TableCell className="text-right tabular-nums">{t.length} min</TableCell>
|
|
95
|
+
<TableCell className="space-x-1">
|
|
96
|
+
{t.hidden ? <StatusPill tone="neutral">Hidden</StatusPill> : <StatusPill tone="positive" dot>Bookable</StatusPill>}
|
|
97
|
+
{t.requires_confirmation ? <StatusPill tone="info">Needs approval</StatusPill> : null}
|
|
98
|
+
</TableCell>
|
|
99
|
+
{renderRowActions ? <TableCell onClick={(e) => e.stopPropagation()}>{renderRowActions(t)}</TableCell> : null}
|
|
100
|
+
</TableRow>
|
|
101
|
+
))}
|
|
102
|
+
</TableBody>
|
|
103
|
+
</Table>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<ScreenLayout
|
|
108
|
+
title="Scheduling"
|
|
109
|
+
description="Meeting links people can book, and the hours you are available."
|
|
110
|
+
icon={<CalendarClock className="size-5" />}
|
|
111
|
+
actions={<>{toolbarActions}<Button onClick={() => setCreating(true)}><Plus className="size-4" aria-hidden />New event type</Button></>}
|
|
112
|
+
tabs={TABS.map((t) => (t.id === "types" ? { ...t, count: types.data ? types.eventTypes.length : undefined } : t))}
|
|
113
|
+
activeTab={tab}
|
|
114
|
+
onTabChange={(id) => { setTab(id); close(); }}
|
|
115
|
+
>
|
|
116
|
+
{tab === "hours" ? <AvailabilityEditor connectCalendarUrl={connectCalendarUrl} /> : <SplitView detailOpen={selectedId != null} onCloseDetail={close} expanded={mode === "full" && selectedId != null} onExpandedChange={(full) => setMode(full ? "full" : null)} detail={detail} list={list} />}
|
|
117
|
+
<FormSheet open={creating} onOpenChange={setCreating} title="New event type" description="Name it, set its length and pick the working hours it uses.">
|
|
118
|
+
{creating ? <EventTypeEditor onSaved={(et) => { setCreating(false); void types.refetch(); setTab("types"); setId(String(et.id)); }} /> : null}
|
|
119
|
+
</FormSheet>
|
|
120
|
+
</ScreenLayout>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useEventTypes: the org's bookable event types (meeting links), newest data first.
|
|
5
|
+
*
|
|
6
|
+
* SDK operations: GET /event-types?include_hidden= (gtmLaunchHelpers.listEventTypes), read tier, scope campaigns:read.
|
|
7
|
+
* This is the list operation the editor and booking screens were missing: ids no longer have to be remembered.
|
|
8
|
+
* Hidden event types are included so a rep can still edit them.
|
|
9
|
+
*/
|
|
10
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
11
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
12
|
+
import { listOf, type EventType } from "../lib/appointments-types";
|
|
13
|
+
|
|
14
|
+
export function useEventTypes() {
|
|
15
|
+
const query = useG8Query(async (signal) => listOf<EventType>(await g8Get("/event-types", { include_hidden: true }, { signal }), "event_types", "items"), []);
|
|
16
|
+
return { ...query, eventTypes: query.data ?? [] };
|
|
17
|
+
}
|
package/blocks/block.schema.json
CHANGED
|
@@ -36,6 +36,24 @@
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
+
"screens": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"description": "Optional. How the block's finished screens become routes and sidebar entries in an app made with `create --blocks`. Absent: derived from the components named `*Screen` (ContactsScreen -> /contacts, label Contacts, group = the block title, icon from a built-in per-block map).",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"required": ["component", "path", "label"],
|
|
46
|
+
"properties": {
|
|
47
|
+
"component": { "type": "string", "pattern": "^[A-Z][A-Za-z0-9]*$", "description": "A component of this block." },
|
|
48
|
+
"path": { "type": "string", "pattern": "^/[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)*$", "description": "Static route path. On a collision in a generated app it is prefixed with the block id." },
|
|
49
|
+
"label": { "type": "string", "minLength": 1 },
|
|
50
|
+
"icon": { "type": "string", "pattern": "^[A-Z][A-Za-z0-9]*$", "description": "A lucide-react export name." },
|
|
51
|
+
"group": { "type": "string", "minLength": 1, "description": "Sidebar section (default: the block title)." },
|
|
52
|
+
"description": { "type": "string", "minLength": 1 },
|
|
53
|
+
"order": { "type": "number", "description": "Sort position inside the block; lower first." }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
39
57
|
"hooks": {
|
|
40
58
|
"type": "array",
|
|
41
59
|
"items": {
|
package/blocks/block.types.ts
CHANGED
|
@@ -59,6 +59,21 @@ export interface BlockDefault {
|
|
|
59
59
|
askHuman: boolean;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Optional route metadata for a finished screen (`create --blocks`, `add` inside a created app). */
|
|
63
|
+
export interface BlockScreen {
|
|
64
|
+
/** A component of this block. */
|
|
65
|
+
component: string;
|
|
66
|
+
/** Static route path; prefixed with the block id when it collides in a generated app. */
|
|
67
|
+
path: string;
|
|
68
|
+
label: string;
|
|
69
|
+
/** lucide-react export name. */
|
|
70
|
+
icon?: string;
|
|
71
|
+
/** Sidebar section (default: the block title). */
|
|
72
|
+
group?: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
order?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
62
77
|
export interface BlockManifest {
|
|
63
78
|
$schema?: string;
|
|
64
79
|
id: string;
|
|
@@ -71,6 +86,8 @@ export interface BlockManifest {
|
|
|
71
86
|
/** Contract operationIds (keys of API_OPERATIONS). */
|
|
72
87
|
operations: string[];
|
|
73
88
|
components: BlockComponent[];
|
|
89
|
+
/** Optional. Absent: derived from the components named `*Screen`. */
|
|
90
|
+
screens?: BlockScreen[];
|
|
74
91
|
hooks: BlockHook[];
|
|
75
92
|
lib?: BlockFile[];
|
|
76
93
|
server?: BlockServerFile[];
|
|
@@ -94,6 +111,13 @@ export interface StackNavItem {
|
|
|
94
111
|
component: string;
|
|
95
112
|
/** JSON props passed to the component on its route (for a component that needs input to render anything). */
|
|
96
113
|
props?: Record<string, unknown>;
|
|
114
|
+
/** lucide-react export name shown beside the label in the sidebar (for example `Users`). */
|
|
115
|
+
icon?: string;
|
|
116
|
+
/** Sidebar section label. */
|
|
117
|
+
group?: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
/** Hand-written page template (`pages/*.tsx.tmpl`, relative to `stacks/<stack-id>/`) used instead of the generic route page. */
|
|
120
|
+
page?: string;
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
export interface StackManifest {
|
|
@@ -106,4 +130,6 @@ export interface StackManifest {
|
|
|
106
130
|
/** Install order: each block's `dependsOn` come earlier. */
|
|
107
131
|
blocks: string[];
|
|
108
132
|
nav: StackNavItem[];
|
|
133
|
+
/** A nav path that `/` redirects to. */
|
|
134
|
+
home?: string;
|
|
109
135
|
}
|
|
@@ -7,6 +7,26 @@ Apollo / HubSpot "campaigns" screen, with generation behind credit confirmations
|
|
|
7
7
|
Depends on: `lists` (the audience picker), `mailboxes` (senders and the connect-a-mailbox link). Install:
|
|
8
8
|
`npx @graph8/sdk add campaigns`.
|
|
9
9
|
|
|
10
|
+
## Screens
|
|
11
|
+
|
|
12
|
+
`CampaignsScreen` is the finished page: mount it as the route. Every record type has a complete detail view,
|
|
13
|
+
deep-linkable (`?tab=campaigns|profile|personas`, `?id=` the record, `?dtab=` the tab, `?doc=` a document).
|
|
14
|
+
|
|
15
|
+
- **Campaigns tab**: list (delete confirmed) beside a detail panel with Details (every field, audience, edit name,
|
|
16
|
+
goal and brief, confirmed), Metrics, Documents (each document opens with its fields and editor), Audience,
|
|
17
|
+
Sequence and Launch. "New campaign" runs the five-step wizard in a sheet.
|
|
18
|
+
- **Company profile tab**: the profile plus ICPs; `IcpDetail` shows every field and edits, pins or archives.
|
|
19
|
+
- **Personas tab**: `PersonaDetail` shows every field, the campaigns that target it, and edits, pins or archives.
|
|
20
|
+
|
|
21
|
+
Slot props: `toolbarActions`, `renderDetailActions(campaign)`, `extraDetailTabs`, `websiteUrl`. Prerequisites: a
|
|
22
|
+
company profile with the real website, an active mailbox, a contact list, and credits for AI generation.
|
|
23
|
+
`IcpEditor` and `PersonaEditor` gained optional `onOpen` and `refreshKey`; `CampaignAssets` optional
|
|
24
|
+
`documentId` and `onDocumentChange`.
|
|
25
|
+
|
|
26
|
+
Known limits: there is no single-ICP read, so `IcpDetail` finds the ICP in the first 200 (the API maximum).
|
|
27
|
+
`PersonaDetail` lists the campaigns whose `target_persona` matches the persona title among the first 100
|
|
28
|
+
campaigns; the API does not link campaigns to ICPs, so ICPs show none.
|
|
29
|
+
|
|
10
30
|
## Components
|
|
11
31
|
|
|
12
32
|
| Component | What it does | Tiers |
|
|
@@ -16,7 +36,7 @@ Depends on: `lists` (the audience picker), `mailboxes` (senders and the connect-
|
|
|
16
36
|
| `CampaignWizard` | A page. Idea (AI, polled) -> create -> audience -> steps -> launch. Also exports the three step components below. | all |
|
|
17
37
|
| `CampaignAudienceStep` | Attach or detach a contact list (uses the lists block `ListPicker`; reads `getCampaignFull`, since `getCampaign` omits the audience). | read, write, external |
|
|
18
38
|
| `CampaignSequenceStep` | Add, edit, remove and AI-write sequence steps. | read, billable, external, destructive |
|
|
19
|
-
| `CampaignLaunchStep` | Readiness checklist,
|
|
39
|
+
| `CampaignLaunchStep` | Readiness checklist (audience, steps, written copy, sender), deliverability and warm-up hold warnings, writes missing email copy with AI, confirmed launch. | read, billable, external |
|
|
20
40
|
| `CampaignList` | A page. All campaigns; open one for metrics and documents; delete. | read, billable, external, destructive |
|
|
21
41
|
| `CampaignAssets` | Campaign documents: read, edit, rewrite with AI, add, delete. Standalone: shows a campaign picker. | read, billable, external, destructive |
|
|
22
42
|
| `CampaignMetrics` | A page. Metrics for 7, 30, 90 or 365 days. Standalone: shows a campaign picker. | read |
|
|
@@ -40,15 +60,24 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
|
|
|
40
60
|
3. **Ideas.** `generateCampaignIdeasAsync` (billable) -> `task_id` -> poll `getCampaignIdeasTaskStatus` every
|
|
41
61
|
2-3 seconds until `completed`; `ideas[]` carry `campaign_name`, `core_concept`, `primary_hook`.
|
|
42
62
|
4. **Create.** `createCampaign` -> `id`. Store it at once; later steps reuse it and "Create" becomes
|
|
43
|
-
"Save changes" (`updateCampaign`). With `auto_generate_documents`
|
|
44
|
-
background: poll `getCampaignGenerationStatus
|
|
63
|
+
"Save changes" (`updateCampaign`). With `auto_generate_documents` (on by default in the wizard and the
|
|
64
|
+
recipe) the documents generate in the background: poll `getCampaignGenerationStatus`; a `generation_error`
|
|
65
|
+
there, or `generation_status: "failed_to_dispatch"` on create, means the writing never started.
|
|
45
66
|
5. **Audience.** `getCampaignFull` shows what is attached (`audience_list_id`, `audience`). `ListPicker` (lists block) -> a list `id` -> `attachAudienceToCampaign` with `list_id` as a
|
|
46
67
|
string. Build the list in the search/lists blocks first.
|
|
47
68
|
6. **Steps and copy.** `getCampaignSequence` -> `createCampaignStep` (channel -> mode matrix: email -> email,
|
|
48
69
|
phone -> call|voicemail, sms -> sms, social -> connect_note|dm|comment|reply) -> `generateCampaignStepContent`
|
|
49
|
-
(billable) per step. `CampaignAssets` edits the documents behind them.
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
(billable) per step. `CampaignAssets` edits the documents behind them. Writing an EMAIL step needs the
|
|
71
|
+
campaign's `emails` document to exist already (created by `auto_generate_documents`); without it the step
|
|
72
|
+
write answers 404.
|
|
73
|
+
7. **Launch.** `CampaignLaunchStep` -> `getCampaignFull` (audience attached with its size, >= 1 step, written copy,
|
|
74
|
+
`is_launched`), `listMailboxes` (active ones), `getSafeToSend` (`safe`, a warning) -> `launchCampaign` with
|
|
75
|
+
`sender_mailbox_ids`. The launch is refused (400 "no completed copy documents") unless a document other than
|
|
76
|
+
`sequence` and `step_catalog` has `status: "completed"`, so Launch stays disabled until one does; "Write the
|
|
77
|
+
email copy with AI" creates the `emails` document if missing (`createCampaignDocument`, once) and has AI write it
|
|
78
|
+
(`regenerateCampaignDocument`, billable). `is_launched` is true only for a live launch, so a failed launch can be
|
|
79
|
+
retried. A mailbox added less than 14 days ago is in its warm-up hold: the campaign goes live but sends nothing
|
|
80
|
+
from it until the hold ends (the step warns).
|
|
52
81
|
The one-step launch executes email only; configure other channels in the sequencer block.
|
|
53
82
|
8. **Measure.** `CampaignMetrics` -> `getCampaignMetrics` with `days`.
|
|
54
83
|
|
|
@@ -62,8 +91,9 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
|
|
|
62
91
|
|
|
63
92
|
## Defaults (do not ask the human)
|
|
64
93
|
|
|
65
|
-
- 5 ideas, goal `awareness`, channel `email`,
|
|
66
|
-
|
|
94
|
+
- 5 ideas, goal `awareness`, channel `email`, AI-written documents ON (a campaign without written copy cannot
|
|
95
|
+
launch; the create confirmation shows the spend), personas picked by AI when none are chosen
|
|
96
|
+
(`persona_mode: "ai_pick"`), sender = the first active mailbox.
|
|
67
97
|
- **Ask the human**: whether to launch and to which list (state the recipient count), and whether to approve
|
|
68
98
|
(lock) the company profile.
|
|
69
99
|
|
|
@@ -95,7 +125,7 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
|
|
|
95
125
|
| `get_campaign_ideas_task_status_campaigns_ideas_status__task_id__get` | GET /campaigns/ideas/status/{task_id} | read | CampaignWizard |
|
|
96
126
|
| `create_campaign_campaigns_post` | POST /campaigns | external | CampaignWizard |
|
|
97
127
|
| `update_campaign_campaigns__campaign_id__patch` | PATCH /campaigns/{campaign_id} | external | CampaignWizard |
|
|
98
|
-
| `get_campaign_generation_status_campaigns__campaign_id__status_get` | GET /campaigns/{campaign_id}/status | read | CampaignWizard |
|
|
128
|
+
| `get_campaign_generation_status_campaigns__campaign_id__status_get` | GET /campaigns/{campaign_id}/status | read | CampaignWizard, CampaignLaunchStep |
|
|
99
129
|
| `get_campaign_full_campaigns__campaign_id__full_get` | GET /campaigns/{campaign_id}/full | read | CampaignWizard, CampaignAudienceStep, CampaignLaunchStep |
|
|
100
130
|
| `attach_audience_to_campaign_campaigns__campaign_id__audience_put` | PUT /campaigns/{campaign_id}/audience | external | CampaignWizard, CampaignAudienceStep |
|
|
101
131
|
| `get_campaign_sequence_campaigns__campaign_id__sequence_get` | GET /campaigns/{campaign_id}/sequence | read | CampaignWizard, CampaignSequenceStep |
|
|
@@ -112,7 +142,7 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
|
|
|
112
142
|
| `get_campaign_metrics_campaigns__campaign_id__metrics_get` | GET /campaigns/{campaign_id}/metrics | read | CampaignList, CampaignMetrics |
|
|
113
143
|
| `list_campaign_documents_campaigns__campaign_id__documents_get` | GET /campaigns/{campaign_id}/documents | read | CampaignList, CampaignAssets |
|
|
114
144
|
| `get_campaign_document_campaigns__campaign_id__documents__document_id__get` | GET /campaigns/{campaign_id}/documents/{document_id} | read | CampaignList, CampaignAssets |
|
|
115
|
-
| `create_campaign_document_campaigns__campaign_id__documents_post` | POST /campaigns/{campaign_id}/documents | external | CampaignList, CampaignAssets |
|
|
145
|
+
| `create_campaign_document_campaigns__campaign_id__documents_post` | POST /campaigns/{campaign_id}/documents | external | CampaignWizard, CampaignLaunchStep, CampaignList, CampaignAssets |
|
|
116
146
|
| `update_campaign_document_campaigns__campaign_id__documents__document_id__put` | PUT /campaigns/{campaign_id}/documents/{document_id} | external | CampaignList, CampaignAssets |
|
|
117
147
|
| `delete_campaign_document_campaigns__campaign_id__documents__document_id__delete` | DELETE /campaigns/{campaign_id}/documents/{document_id} | destructive | CampaignList, CampaignAssets |
|
|
118
|
-
| `regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post` | POST /campaigns/{campaign_id}/documents/{document_id}/regenerate | billable | CampaignList, CampaignAssets |
|
|
148
|
+
| `regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post` | POST /campaigns/{campaign_id}/documents/{document_id}/regenerate | billable | CampaignWizard, CampaignLaunchStep, CampaignList, CampaignAssets |
|