@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
package/stacks/clay.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "./stack.schema.json",
|
|
3
3
|
"id": "clay",
|
|
4
4
|
"title": "Clay-style data workbench",
|
|
5
|
-
"summary": "
|
|
5
|
+
"summary": "Search, enrich and shape prospect data in a workbench, then keep it in lists and CRM records.",
|
|
6
6
|
"status": "available",
|
|
7
7
|
"blocks": [
|
|
8
8
|
"foundation",
|
|
@@ -12,48 +12,81 @@
|
|
|
12
12
|
"enrichment",
|
|
13
13
|
"workbench"
|
|
14
14
|
],
|
|
15
|
+
"home": "/workbench",
|
|
15
16
|
"nav": [
|
|
16
17
|
{
|
|
17
|
-
"label": "
|
|
18
|
-
"path": "/
|
|
19
|
-
"block": "
|
|
20
|
-
"component": "
|
|
18
|
+
"label": "Prospect search",
|
|
19
|
+
"path": "/prospect-search",
|
|
20
|
+
"block": "search",
|
|
21
|
+
"component": "ProspectSearchScreen",
|
|
22
|
+
"icon": "Search",
|
|
23
|
+
"group": "Prospect",
|
|
24
|
+
"description": "Find new people and companies, then save them"
|
|
21
25
|
},
|
|
22
26
|
{
|
|
23
|
-
"label": "
|
|
24
|
-
"path": "/
|
|
25
|
-
"block": "
|
|
26
|
-
"component": "
|
|
27
|
+
"label": "Workbench",
|
|
28
|
+
"path": "/workbench",
|
|
29
|
+
"block": "workbench",
|
|
30
|
+
"component": "WorkbenchScreen",
|
|
31
|
+
"icon": "Table2",
|
|
32
|
+
"group": "Prospect",
|
|
33
|
+
"description": "Build enrichment tables column by column"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"label": "Enrichment",
|
|
37
|
+
"path": "/enrichment",
|
|
38
|
+
"block": "enrichment",
|
|
39
|
+
"component": "EnrichmentScreen",
|
|
40
|
+
"icon": "Sparkles",
|
|
41
|
+
"group": "Prospect",
|
|
42
|
+
"description": "Fill in emails, phones and firmographics"
|
|
27
43
|
},
|
|
28
44
|
{
|
|
29
45
|
"label": "Lists",
|
|
30
46
|
"path": "/lists",
|
|
31
47
|
"block": "lists",
|
|
32
|
-
"component": "
|
|
48
|
+
"component": "ListsScreen",
|
|
49
|
+
"icon": "ListChecks",
|
|
50
|
+
"group": "Prospect",
|
|
51
|
+
"description": "Saved audiences you can sequence or sync"
|
|
33
52
|
},
|
|
34
53
|
{
|
|
35
|
-
"label": "
|
|
36
|
-
"path": "/
|
|
37
|
-
"block": "
|
|
38
|
-
"component": "
|
|
54
|
+
"label": "Contacts",
|
|
55
|
+
"path": "/contacts",
|
|
56
|
+
"block": "crm",
|
|
57
|
+
"component": "ContactsScreen",
|
|
58
|
+
"icon": "Users",
|
|
59
|
+
"group": "Prospect",
|
|
60
|
+
"description": "People you sell to, with notes, tasks and activity",
|
|
61
|
+
"page": "pages/contacts.tsx.tmpl"
|
|
39
62
|
},
|
|
40
63
|
{
|
|
41
|
-
"label": "
|
|
42
|
-
"path": "/
|
|
43
|
-
"block": "
|
|
44
|
-
"component": "
|
|
64
|
+
"label": "Companies",
|
|
65
|
+
"path": "/companies",
|
|
66
|
+
"block": "crm",
|
|
67
|
+
"component": "CompaniesScreen",
|
|
68
|
+
"icon": "Building2",
|
|
69
|
+
"group": "Prospect",
|
|
70
|
+
"description": "Accounts and the people at them",
|
|
71
|
+
"page": "pages/companies.tsx.tmpl"
|
|
45
72
|
},
|
|
46
73
|
{
|
|
47
|
-
"label": "
|
|
48
|
-
"path": "/
|
|
49
|
-
"block": "
|
|
50
|
-
"component": "
|
|
74
|
+
"label": "Tasks",
|
|
75
|
+
"path": "/tasks",
|
|
76
|
+
"block": "crm",
|
|
77
|
+
"component": "TasksScreen",
|
|
78
|
+
"icon": "CheckSquare",
|
|
79
|
+
"group": "Work",
|
|
80
|
+
"description": "Follow-ups due across your records"
|
|
51
81
|
},
|
|
52
82
|
{
|
|
53
|
-
"label": "
|
|
54
|
-
"path": "/
|
|
55
|
-
"block": "
|
|
56
|
-
"component": "
|
|
83
|
+
"label": "Suppressions",
|
|
84
|
+
"path": "/suppressions",
|
|
85
|
+
"block": "lists",
|
|
86
|
+
"component": "SuppressionsScreen",
|
|
87
|
+
"icon": "ShieldOff",
|
|
88
|
+
"group": "Setup",
|
|
89
|
+
"description": "People and domains you never contact"
|
|
57
90
|
}
|
|
58
91
|
]
|
|
59
92
|
}
|
package/stacks/gong.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "./stack.schema.json",
|
|
3
3
|
"id": "gong",
|
|
4
4
|
"title": "Gong-style call intelligence",
|
|
5
|
-
"summary": "
|
|
5
|
+
"summary": "Dialer, call log, meetings, coaching and analytics over your CRM records.",
|
|
6
6
|
"status": "available",
|
|
7
7
|
"blocks": [
|
|
8
8
|
"foundation",
|
|
@@ -13,60 +13,106 @@
|
|
|
13
13
|
"coach",
|
|
14
14
|
"analytics"
|
|
15
15
|
],
|
|
16
|
+
"home": "/analytics",
|
|
16
17
|
"nav": [
|
|
17
18
|
{
|
|
18
|
-
"label": "
|
|
19
|
-
"path": "/
|
|
20
|
-
"block": "
|
|
21
|
-
"component": "
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"path": "/companies",
|
|
26
|
-
"block": "crm",
|
|
27
|
-
"component": "CompanyTable"
|
|
19
|
+
"label": "Analytics",
|
|
20
|
+
"path": "/analytics",
|
|
21
|
+
"block": "analytics",
|
|
22
|
+
"component": "AnalyticsScreen",
|
|
23
|
+
"icon": "BarChart3",
|
|
24
|
+
"group": "Insights",
|
|
25
|
+
"description": "Pipeline, engagement and team KPIs"
|
|
28
26
|
},
|
|
29
27
|
{
|
|
30
|
-
"label": "
|
|
31
|
-
"path": "/
|
|
32
|
-
"block": "
|
|
33
|
-
"component": "
|
|
28
|
+
"label": "Call log",
|
|
29
|
+
"path": "/calls",
|
|
30
|
+
"block": "dialer",
|
|
31
|
+
"component": "CallLogScreen",
|
|
32
|
+
"icon": "PhoneCall",
|
|
33
|
+
"group": "Engage",
|
|
34
|
+
"description": "Every call with transcript and outcome"
|
|
34
35
|
},
|
|
35
36
|
{
|
|
36
|
-
"label": "
|
|
37
|
-
"path": "/
|
|
38
|
-
"block": "
|
|
39
|
-
"component": "
|
|
37
|
+
"label": "Coaching",
|
|
38
|
+
"path": "/coaching",
|
|
39
|
+
"block": "coach",
|
|
40
|
+
"component": "CoachScreen",
|
|
41
|
+
"icon": "GraduationCap",
|
|
42
|
+
"group": "Insights",
|
|
43
|
+
"description": "Scored calls and coaching feedback"
|
|
40
44
|
},
|
|
41
45
|
{
|
|
42
46
|
"label": "Dialer",
|
|
43
47
|
"path": "/dialer",
|
|
44
48
|
"block": "dialer",
|
|
45
|
-
"component": "
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"path": "/calls",
|
|
50
|
-
"block": "dialer",
|
|
51
|
-
"component": "CallLog"
|
|
49
|
+
"component": "DialerScreen",
|
|
50
|
+
"icon": "Phone",
|
|
51
|
+
"group": "Engage",
|
|
52
|
+
"description": "Call queues and live sessions"
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
"label": "Meetings",
|
|
55
56
|
"path": "/meetings",
|
|
56
57
|
"block": "appointments",
|
|
57
|
-
"component": "
|
|
58
|
+
"component": "MeetingsScreen",
|
|
59
|
+
"icon": "CalendarCheck",
|
|
60
|
+
"group": "Engage",
|
|
61
|
+
"description": "Booked, held and missed meetings"
|
|
58
62
|
},
|
|
59
63
|
{
|
|
60
|
-
"label": "
|
|
61
|
-
"path": "/
|
|
62
|
-
"block": "
|
|
63
|
-
"component": "
|
|
64
|
+
"label": "Scheduling",
|
|
65
|
+
"path": "/scheduling",
|
|
66
|
+
"block": "appointments",
|
|
67
|
+
"component": "SchedulingScreen",
|
|
68
|
+
"icon": "CalendarClock",
|
|
69
|
+
"group": "Setup",
|
|
70
|
+
"description": "Booking links and event types"
|
|
64
71
|
},
|
|
65
72
|
{
|
|
66
|
-
"label": "
|
|
67
|
-
"path": "/
|
|
68
|
-
"block": "
|
|
69
|
-
"component": "
|
|
73
|
+
"label": "Contacts",
|
|
74
|
+
"path": "/contacts",
|
|
75
|
+
"block": "crm",
|
|
76
|
+
"component": "ContactsScreen",
|
|
77
|
+
"icon": "Users",
|
|
78
|
+
"group": "Prospect",
|
|
79
|
+
"description": "People you sell to, with notes, tasks and activity"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"label": "Companies",
|
|
83
|
+
"path": "/companies",
|
|
84
|
+
"block": "crm",
|
|
85
|
+
"component": "CompaniesScreen",
|
|
86
|
+
"icon": "Building2",
|
|
87
|
+
"group": "Prospect",
|
|
88
|
+
"description": "Accounts and the people at them"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"label": "Tasks",
|
|
92
|
+
"path": "/tasks",
|
|
93
|
+
"block": "crm",
|
|
94
|
+
"component": "TasksScreen",
|
|
95
|
+
"icon": "CheckSquare",
|
|
96
|
+
"group": "Work",
|
|
97
|
+
"description": "Follow-ups due across your records"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"label": "Lists",
|
|
101
|
+
"path": "/lists",
|
|
102
|
+
"block": "lists",
|
|
103
|
+
"component": "ListsScreen",
|
|
104
|
+
"icon": "ListChecks",
|
|
105
|
+
"group": "Prospect",
|
|
106
|
+
"description": "Saved audiences you can sequence or sync"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"label": "Suppressions",
|
|
110
|
+
"path": "/suppressions",
|
|
111
|
+
"block": "lists",
|
|
112
|
+
"component": "SuppressionsScreen",
|
|
113
|
+
"icon": "ShieldOff",
|
|
114
|
+
"group": "Setup",
|
|
115
|
+
"description": "People and domains you never contact"
|
|
70
116
|
}
|
|
71
117
|
]
|
|
72
118
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./stack.schema.json",
|
|
3
|
+
"id": "hootsuite",
|
|
4
|
+
"title": "Hootsuite-style social and content",
|
|
5
|
+
"summary": "Plan social posts and ads, publish landing pages and read the results in analytics.",
|
|
6
|
+
"status": "available",
|
|
7
|
+
"blocks": [
|
|
8
|
+
"foundation",
|
|
9
|
+
"crm",
|
|
10
|
+
"lists",
|
|
11
|
+
"social-ads",
|
|
12
|
+
"pages",
|
|
13
|
+
"analytics"
|
|
14
|
+
],
|
|
15
|
+
"home": "/analytics",
|
|
16
|
+
"nav": [
|
|
17
|
+
{
|
|
18
|
+
"label": "Analytics",
|
|
19
|
+
"path": "/analytics",
|
|
20
|
+
"block": "analytics",
|
|
21
|
+
"component": "AnalyticsScreen",
|
|
22
|
+
"icon": "BarChart3",
|
|
23
|
+
"group": "Insights",
|
|
24
|
+
"description": "Pipeline, engagement and team KPIs"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"label": "Social",
|
|
28
|
+
"path": "/social",
|
|
29
|
+
"block": "social-ads",
|
|
30
|
+
"component": "SocialScreen",
|
|
31
|
+
"icon": "Share2",
|
|
32
|
+
"group": "Marketing",
|
|
33
|
+
"description": "Plan and publish social posts"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"label": "Ads",
|
|
37
|
+
"path": "/ads",
|
|
38
|
+
"block": "social-ads",
|
|
39
|
+
"component": "AdsScreen",
|
|
40
|
+
"icon": "Megaphone",
|
|
41
|
+
"group": "Marketing",
|
|
42
|
+
"description": "Ad campaigns and spend"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"label": "Pages",
|
|
46
|
+
"path": "/pages",
|
|
47
|
+
"block": "pages",
|
|
48
|
+
"component": "PagesScreen",
|
|
49
|
+
"icon": "LayoutTemplate",
|
|
50
|
+
"group": "Marketing",
|
|
51
|
+
"description": "Landing pages, brand kit and content"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"label": "Contacts",
|
|
55
|
+
"path": "/contacts",
|
|
56
|
+
"block": "crm",
|
|
57
|
+
"component": "ContactsScreen",
|
|
58
|
+
"icon": "Users",
|
|
59
|
+
"group": "Prospect",
|
|
60
|
+
"description": "People you sell to, with notes, tasks and activity"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"label": "Companies",
|
|
64
|
+
"path": "/companies",
|
|
65
|
+
"block": "crm",
|
|
66
|
+
"component": "CompaniesScreen",
|
|
67
|
+
"icon": "Building2",
|
|
68
|
+
"group": "Prospect",
|
|
69
|
+
"description": "Accounts and the people at them"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"label": "Tasks",
|
|
73
|
+
"path": "/tasks",
|
|
74
|
+
"block": "crm",
|
|
75
|
+
"component": "TasksScreen",
|
|
76
|
+
"icon": "CheckSquare",
|
|
77
|
+
"group": "Work",
|
|
78
|
+
"description": "Follow-ups due across your records"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"label": "Lists",
|
|
82
|
+
"path": "/lists",
|
|
83
|
+
"block": "lists",
|
|
84
|
+
"component": "ListsScreen",
|
|
85
|
+
"icon": "ListChecks",
|
|
86
|
+
"group": "Prospect",
|
|
87
|
+
"description": "Saved audiences you can sequence or sync"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"label": "Suppressions",
|
|
91
|
+
"path": "/suppressions",
|
|
92
|
+
"block": "lists",
|
|
93
|
+
"component": "SuppressionsScreen",
|
|
94
|
+
"icon": "ShieldOff",
|
|
95
|
+
"group": "Setup",
|
|
96
|
+
"description": "People and domains you never contact"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
|
|
4
|
+
// other blocks of this app plugged into its slots. Edit freely: this file is yours.
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { Skeleton } from "@graph8/sdk/ui";
|
|
7
|
+
import { Handshake } from "lucide-react";
|
|
8
|
+
import { ApiError } from "@/components/g8/_foundation/api-error";
|
|
9
|
+
import { EmptyState } from "@/components/g8/_foundation/empty-state";
|
|
10
|
+
import { useG8Query } from "@/components/g8/_foundation/hooks/use-g8-query";
|
|
11
|
+
import { g8Get } from "@/components/g8/_foundation/lib/g8-client";
|
|
12
|
+
import { CompaniesScreen } from "@/components/g8/crm/companies-screen";
|
|
13
|
+
import { dataOf, formatMoney } from "@/components/g8/deals/lib/deals-types";
|
|
14
|
+
|
|
15
|
+
/** One row of GET /contacts/{id}/deals and GET /companies/{id}/deals (the API's ContactDealResponse). */
|
|
16
|
+
interface LinkedDeal { deal_id?: string | null; name?: string | null; stage?: string | null; value?: number | null; currency?: string | null; role?: string | null }
|
|
17
|
+
|
|
18
|
+
/** The deals linked to one contact or company, each opening in the Deals page. */
|
|
19
|
+
function LinkedDeals({ kind, id }: { kind: "contacts" | "companies"; id: number }) {
|
|
20
|
+
const q = useG8Query(async (signal) => {
|
|
21
|
+
const inner = dataOf<{ items?: LinkedDeal[] } | LinkedDeal[]>(await g8Get(`/${kind}/${id}/deals`, undefined, { signal }));
|
|
22
|
+
return (Array.isArray(inner) ? inner : (inner.items ?? [])).filter((d) => d.deal_id);
|
|
23
|
+
}, [kind, id]);
|
|
24
|
+
if (q.error) return <ApiError error={q.error} title="Could not load deals" onRetry={() => void q.refetch()} />;
|
|
25
|
+
if (!q.data) return <Skeleton className="h-24 w-full" aria-label="Loading deals" />;
|
|
26
|
+
if (q.data.length === 0) return <EmptyState compact icon={<Handshake className="size-8" />} title="No deals yet" description="Deals linked to this record show up here." />;
|
|
27
|
+
return (
|
|
28
|
+
<ul className="divide-y rounded-md border" aria-label="Deals">
|
|
29
|
+
{q.data.map((d) => (
|
|
30
|
+
<li key={d.deal_id}>
|
|
31
|
+
<Link href={`/deals?id=${encodeURIComponent(d.deal_id as string)}`} className="flex items-center justify-between gap-3 p-3 text-sm hover:bg-muted">
|
|
32
|
+
<span className="min-w-0 truncate font-medium">{d.name || "Untitled deal"}</span>
|
|
33
|
+
<span className="shrink-0 text-xs text-muted-foreground">{[d.stage, d.value != null ? formatMoney(d.value, d.currency) : null].filter(Boolean).join(" · ")}</span>
|
|
34
|
+
</Link>
|
|
35
|
+
</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function Page() {
|
|
42
|
+
return (
|
|
43
|
+
<CompaniesScreen
|
|
44
|
+
extraDetailTabs={[{ id: "deals", label: "Deals", render: (c) => <LinkedDeals kind="companies" id={c.id} /> }]}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
|
|
4
|
+
// other blocks of this app plugged into its slots. Edit freely: this file is yours.
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import Link from "next/link";
|
|
7
|
+
import { Button, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Skeleton } from "@graph8/sdk/ui";
|
|
8
|
+
import { Handshake, ListPlus } from "lucide-react";
|
|
9
|
+
import { ApiError } from "@/components/g8/_foundation/api-error";
|
|
10
|
+
import { EmptyState } from "@/components/g8/_foundation/empty-state";
|
|
11
|
+
import { useG8Query } from "@/components/g8/_foundation/hooks/use-g8-query";
|
|
12
|
+
import { g8Get } from "@/components/g8/_foundation/lib/g8-client";
|
|
13
|
+
import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
|
|
14
|
+
import { dataOf, formatMoney } from "@/components/g8/deals/lib/deals-types";
|
|
15
|
+
import { EnrollDialog } from "@/components/g8/sequencer/enroll-dialog";
|
|
16
|
+
import { listOf, statusGroup, type SequenceRow } from "@/components/g8/sequencer/lib/sequencer-utils";
|
|
17
|
+
|
|
18
|
+
/** Every active sequence, paging through the whole list (the API answers one page at a time). */
|
|
19
|
+
async function activeSequences(signal: AbortSignal): Promise<SequenceRow[]> {
|
|
20
|
+
const out: SequenceRow[] = [];
|
|
21
|
+
for (let page = 1; page <= 50; page++) {
|
|
22
|
+
const env = await g8Get("/sequences", { page, limit: 100 }, { signal });
|
|
23
|
+
const rows = listOf<SequenceRow>(env);
|
|
24
|
+
out.push(...rows.filter((s) => statusGroup(s.status) === "live"));
|
|
25
|
+
const more = (env as { pagination?: { has_next?: boolean } }).pagination?.has_next;
|
|
26
|
+
if (!more || rows.length === 0) break;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Pick a sequence, then enroll the contacts in it. The enroll dialog shows the sending preflight before anything is sent. */
|
|
32
|
+
function AddToSequence({ contactIds, onEnrolled, variant = "outline" }: { contactIds: number[]; onEnrolled?: () => void; variant?: "outline" | "default" }) {
|
|
33
|
+
const [step, setStep] = useState<"closed" | "pick" | "enroll">("closed");
|
|
34
|
+
const [sequenceId, setSequenceId] = useState("");
|
|
35
|
+
const sequences = useG8Query(activeSequences, [], { enabled: step === "pick" });
|
|
36
|
+
const chosen = (sequences.data ?? []).find((s) => s.id === sequenceId);
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Button size="sm" variant={variant} disabled={contactIds.length === 0} onClick={() => { setSequenceId(""); setStep("pick"); }}>
|
|
40
|
+
<ListPlus className="mr-1 size-4" aria-hidden />
|
|
41
|
+
Add to sequence
|
|
42
|
+
</Button>
|
|
43
|
+
<Sheet open={step === "pick"} onOpenChange={(o) => { if (!o) setStep("closed"); }}>
|
|
44
|
+
<SheetContent className="w-full sm:max-w-md">
|
|
45
|
+
<SheetHeader>
|
|
46
|
+
<SheetTitle>Add to sequence</SheetTitle>
|
|
47
|
+
<SheetDescription>{contactIds.length === 1 ? "1 contact" : `${contactIds.length} contacts`} will be enrolled in the sequence you pick.</SheetDescription>
|
|
48
|
+
</SheetHeader>
|
|
49
|
+
<div className="space-y-3 p-4">
|
|
50
|
+
{sequences.error ? <ApiError error={sequences.error} title="Could not load sequences" onRetry={() => void sequences.refetch()} /> : null}
|
|
51
|
+
{sequences.loading && !sequences.data ? <Skeleton className="h-9 w-full" aria-label="Loading sequences" /> : null}
|
|
52
|
+
{sequences.data && sequences.data.length === 0 ? <EmptyState compact title="No active sequences" description="Launch a sequence on the Sequences page first." /> : null}
|
|
53
|
+
{sequences.data && sequences.data.length > 0 ? (
|
|
54
|
+
<>
|
|
55
|
+
<label htmlFor="enroll-sequence" className="text-sm font-medium">Sequence</label>
|
|
56
|
+
<select id="enroll-sequence" className="h-9 w-full rounded-md border bg-background px-2 text-sm" value={sequenceId} onChange={(e) => setSequenceId(e.target.value)}>
|
|
57
|
+
<option value="">Choose a sequence</option>
|
|
58
|
+
{sequences.data.map((s) => <option key={s.id} value={s.id}>{s.name || s.id}</option>)}
|
|
59
|
+
</select>
|
|
60
|
+
<Button disabled={!sequenceId} onClick={() => setStep("enroll")}>Continue</Button>
|
|
61
|
+
</>
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
</SheetContent>
|
|
65
|
+
</Sheet>
|
|
66
|
+
{step === "enroll" && chosen ? (
|
|
67
|
+
<EnrollDialog open sequenceId={chosen.id} sequenceName={chosen.name ?? undefined} contactIds={contactIds} onClose={() => setStep("closed")} onEnrolled={() => { setStep("closed"); onEnrolled?.(); }} />
|
|
68
|
+
) : null}
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One row of GET /contacts/{id}/deals and GET /companies/{id}/deals (the API's ContactDealResponse). */
|
|
74
|
+
interface LinkedDeal { deal_id?: string | null; name?: string | null; stage?: string | null; value?: number | null; currency?: string | null; role?: string | null }
|
|
75
|
+
|
|
76
|
+
/** The deals linked to one contact or company, each opening in the Deals page. */
|
|
77
|
+
function LinkedDeals({ kind, id }: { kind: "contacts" | "companies"; id: number }) {
|
|
78
|
+
const q = useG8Query(async (signal) => {
|
|
79
|
+
const inner = dataOf<{ items?: LinkedDeal[] } | LinkedDeal[]>(await g8Get(`/${kind}/${id}/deals`, undefined, { signal }));
|
|
80
|
+
return (Array.isArray(inner) ? inner : (inner.items ?? [])).filter((d) => d.deal_id);
|
|
81
|
+
}, [kind, id]);
|
|
82
|
+
if (q.error) return <ApiError error={q.error} title="Could not load deals" onRetry={() => void q.refetch()} />;
|
|
83
|
+
if (!q.data) return <Skeleton className="h-24 w-full" aria-label="Loading deals" />;
|
|
84
|
+
if (q.data.length === 0) return <EmptyState compact icon={<Handshake className="size-8" />} title="No deals yet" description="Deals linked to this record show up here." />;
|
|
85
|
+
return (
|
|
86
|
+
<ul className="divide-y rounded-md border" aria-label="Deals">
|
|
87
|
+
{q.data.map((d) => (
|
|
88
|
+
<li key={d.deal_id}>
|
|
89
|
+
<Link href={`/deals?id=${encodeURIComponent(d.deal_id as string)}`} className="flex items-center justify-between gap-3 p-3 text-sm hover:bg-muted">
|
|
90
|
+
<span className="min-w-0 truncate font-medium">{d.name || "Untitled deal"}</span>
|
|
91
|
+
<span className="shrink-0 text-xs text-muted-foreground">{[d.stage, d.value != null ? formatMoney(d.value, d.currency) : null].filter(Boolean).join(" · ")}</span>
|
|
92
|
+
</Link>
|
|
93
|
+
</li>
|
|
94
|
+
))}
|
|
95
|
+
</ul>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default function Page() {
|
|
100
|
+
return (
|
|
101
|
+
<ContactsScreen
|
|
102
|
+
renderBulkActions={({ ids, clear }) => <AddToSequence contactIds={ids} onEnrolled={clear} />}
|
|
103
|
+
renderDetailActions={(c) => (
|
|
104
|
+
<>
|
|
105
|
+
<AddToSequence contactIds={[c.id]} />
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
extraDetailTabs={[{ id: "deals", label: "Deals", render: (c) => <LinkedDeals kind="contacts" id={c.id} /> }]}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
|
|
4
|
+
// other blocks of this app plugged into its slots. Edit freely: this file is yours.
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { DealsScreen } from "@/components/g8/deals/deals-screen";
|
|
7
|
+
|
|
8
|
+
export default function Page() {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
return <DealsScreen onOpenQuote={(quoteId) => router.push(`/quotes?id=${encodeURIComponent(quoteId)}`)} />;
|
|
11
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
|
|
4
|
+
// other blocks of this app plugged into its slots. Edit freely: this file is yours.
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { useRouter } from "next/navigation";
|
|
7
|
+
import { Button, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Skeleton } from "@graph8/sdk/ui";
|
|
8
|
+
import { Handshake } from "lucide-react";
|
|
9
|
+
import { ApiError } from "@/components/g8/_foundation/api-error";
|
|
10
|
+
import { EmptyState } from "@/components/g8/_foundation/empty-state";
|
|
11
|
+
import { useG8Query } from "@/components/g8/_foundation/hooks/use-g8-query";
|
|
12
|
+
import { g8Get } from "@/components/g8/_foundation/lib/g8-client";
|
|
13
|
+
import { DealDetail } from "@/components/g8/deals/deal-detail";
|
|
14
|
+
import { useDefaultOwner } from "@/components/g8/deals/hooks/use-default-owner";
|
|
15
|
+
import { InboxScreen } from "@/components/g8/inbox/inbox-screen";
|
|
16
|
+
import type { InboxThread } from "@/components/g8/inbox/lib/inbox-types";
|
|
17
|
+
|
|
18
|
+
interface Match { id: number; full_name?: string | null; first_name?: string | null; last_name?: string | null; work_email?: string | null; job_title?: string | null }
|
|
19
|
+
const nameOf = (m: Match): string => m.full_name || [m.first_name, m.last_name].filter(Boolean).join(" ") || m.work_email || `Contact ${m.id}`;
|
|
20
|
+
|
|
21
|
+
/** The new deal's form. Mounted only while the sheet is open, so browsing threads never loads the team. When the key has no person, the owner is chosen here instead of silently being the first member. */
|
|
22
|
+
function NewDeal({ contactId, onSaved }: { contactId: number; onSaved: (dealId: string) => void }) {
|
|
23
|
+
const owner = useDefaultOwner();
|
|
24
|
+
const [ownerId, setOwnerId] = useState<string | null>(null);
|
|
25
|
+
if (owner.loading) return <Skeleton className="h-24 w-full" aria-label="Loading owners" />;
|
|
26
|
+
const effective = ownerId ?? owner.defaultOwner?.id ?? "";
|
|
27
|
+
if (!effective) return <ApiError error={owner.error ?? new Error("No team member found to own this deal.")} title="A deal needs an owner" onRetry={() => void owner.reload()} />;
|
|
28
|
+
return (
|
|
29
|
+
<div className="space-y-3">
|
|
30
|
+
{owner.source === "first-member" ? (
|
|
31
|
+
<div className="space-y-1">
|
|
32
|
+
<label htmlFor="inbox-deal-owner" className="text-sm font-medium">Owner</label>
|
|
33
|
+
<select id="inbox-deal-owner" className="h-9 w-full rounded-md border bg-background px-2 text-sm" value={effective} onChange={(e) => setOwnerId(e.target.value)}>
|
|
34
|
+
{owner.owners.map((o) => <option key={o.id} value={o.id}>{o.name}{o.email && o.email !== o.name ? ` (${o.email})` : ""}</option>)}
|
|
35
|
+
</select>
|
|
36
|
+
<p className="text-xs text-muted-foreground">This key has no person of its own, so choose who owns the deal.</p>
|
|
37
|
+
</div>
|
|
38
|
+
) : null}
|
|
39
|
+
<DealDetail ownerId={effective} contactIds={[contactId]} onSaved={(deal) => onSaved(deal.id)} />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Create a deal from a reply: find the sender in your CRM, pick them, then fill in the deal. */
|
|
45
|
+
function CreateDeal({ thread }: { thread: InboxThread }) {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
const [open, setOpen] = useState(false);
|
|
48
|
+
const [contactId, setContactId] = useState<number | null>(null);
|
|
49
|
+
const name = thread.contact?.name?.trim() ?? "";
|
|
50
|
+
const matches = useG8Query(async (signal) => ((await g8Get<Match[]>("/contacts", { name, limit: 8, page: 1 }, { signal })).data ?? []), [name], { enabled: open && name.length > 1 });
|
|
51
|
+
const email = thread.contact?.email?.toLowerCase();
|
|
52
|
+
const list = matches.data ?? [];
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<Button size="sm" variant="outline" onClick={() => { setContactId(null); setOpen(true); }}>
|
|
56
|
+
<Handshake className="mr-1 size-4" aria-hidden />
|
|
57
|
+
Create deal
|
|
58
|
+
</Button>
|
|
59
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
60
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-xl">
|
|
61
|
+
<SheetHeader>
|
|
62
|
+
<SheetTitle>Create deal</SheetTitle>
|
|
63
|
+
<SheetDescription>A deal needs a contact in your CRM. Pick the person who wrote this reply.</SheetDescription>
|
|
64
|
+
</SheetHeader>
|
|
65
|
+
<div className="space-y-4 p-4">
|
|
66
|
+
{name.length < 2 ? <EmptyState compact title="No sender name on this thread" description="Open the contact in Contacts and create the deal from the Deals page." /> : null}
|
|
67
|
+
{matches.error ? <ApiError error={matches.error} title="Could not search contacts" onRetry={() => void matches.refetch()} /> : null}
|
|
68
|
+
{matches.loading && !matches.data ? <Skeleton className="h-16 w-full" aria-label="Searching contacts" /> : null}
|
|
69
|
+
{matches.data && list.length === 0 ? <EmptyState compact title="Not in your CRM yet" description={`Add ${name} in Contacts first, then come back.`} /> : null}
|
|
70
|
+
{list.length > 0 ? (
|
|
71
|
+
<ul className="divide-y rounded-md border" aria-label="Matching contacts">
|
|
72
|
+
{list.map((m) => (
|
|
73
|
+
<li key={m.id}>
|
|
74
|
+
<button type="button" aria-pressed={contactId === m.id} className="flex w-full items-center justify-between gap-3 p-3 text-left text-sm hover:bg-muted aria-pressed:bg-muted" onClick={() => setContactId(m.id)}>
|
|
75
|
+
<span className="min-w-0 truncate font-medium">{nameOf(m)}</span>
|
|
76
|
+
<span className="shrink-0 text-xs text-muted-foreground">{[m.job_title, m.work_email && m.work_email.toLowerCase() === email ? "Same email" : m.work_email].filter(Boolean).join(" · ")}</span>
|
|
77
|
+
</button>
|
|
78
|
+
</li>
|
|
79
|
+
))}
|
|
80
|
+
</ul>
|
|
81
|
+
) : null}
|
|
82
|
+
{contactId != null ? <NewDeal key={contactId} contactId={contactId} onSaved={(id) => router.push(`/deals?id=${encodeURIComponent(id)}`)} /> : null}
|
|
83
|
+
</div>
|
|
84
|
+
</SheetContent>
|
|
85
|
+
</Sheet>
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default function Page() {
|
|
91
|
+
return <InboxScreen renderDetailActions={(thread) => <CreateDeal thread={thread} />} />;
|
|
92
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
|
|
4
|
+
// other blocks of this app plugged into its slots. Edit freely: this file is yours.
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { QuotesScreen } from "@/components/g8/quotes/quotes-screen";
|
|
7
|
+
|
|
8
|
+
export default function Page() {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
return <QuotesScreen onOpenDeal={(dealId) => router.push(`/deals?id=${encodeURIComponent(dealId)}`)} />;
|
|
11
|
+
}
|