@graph8/sdk 0.373.0 → 0.374.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.
Files changed (279) hide show
  1. package/README.md +3 -3
  2. package/blocks/_foundation/README.md +47 -0
  3. package/blocks/_foundation/block.json +13 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/server/auth.ts +815 -0
  6. package/blocks/campaigns/README.md +1 -1
  7. package/blocks/campaigns/block.json +34 -1
  8. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  9. package/blocks/company-intelligence/components/research-run.tsx +6 -2
  10. package/blocks/crm/README.md +19 -0
  11. package/blocks/crm/block.json +205 -6
  12. package/blocks/crm/components/bulk-update.tsx +119 -0
  13. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  14. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  15. package/blocks/crm/components/contact-lookup-link.tsx +47 -6
  16. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  17. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  18. package/blocks/crm/components/contacts-screen.tsx +46 -5
  19. package/blocks/crm/components/owner-assign.tsx +132 -0
  20. package/blocks/crm/components/record-open-links.tsx +79 -0
  21. package/blocks/crm/components/tag-assign.tsx +112 -0
  22. package/blocks/crm/components/tag-manager.tsx +178 -0
  23. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  24. package/blocks/crm/components/verify-email-button.tsx +93 -0
  25. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  26. package/blocks/crm/lib/record-actions.ts +102 -0
  27. package/blocks/deals/README.md +1 -0
  28. package/blocks/deals/block.json +17 -0
  29. package/blocks/deals/components/create-deal-button.tsx +109 -0
  30. package/blocks/deals/components/deal-detail.tsx +5 -3
  31. package/blocks/dialer/components/call-log-screen.tsx +6 -1
  32. package/blocks/documents/README.md +86 -0
  33. package/blocks/documents/block.json +378 -0
  34. package/blocks/documents/components/account-bar.tsx +48 -0
  35. package/blocks/documents/components/correspondence-form.tsx +95 -0
  36. package/blocks/documents/components/correspondence-import.tsx +106 -0
  37. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  38. package/blocks/documents/components/documents-screen.tsx +76 -0
  39. package/blocks/documents/components/library-document-detail.tsx +175 -0
  40. package/blocks/documents/components/library-document-form.tsx +105 -0
  41. package/blocks/documents/components/library-folder-form.tsx +74 -0
  42. package/blocks/documents/components/library-panel.tsx +235 -0
  43. package/blocks/documents/components/library-upload.tsx +160 -0
  44. package/blocks/documents/components/question-row.tsx +130 -0
  45. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  46. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  47. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  48. package/blocks/documents/lib/documents-types.ts +252 -0
  49. package/blocks/documents/lib/download.ts +64 -0
  50. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  51. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  52. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  53. package/blocks/email-signatures/README.md +123 -0
  54. package/blocks/email-signatures/block.json +341 -0
  55. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  56. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  57. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  58. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  59. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  60. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  61. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  62. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  63. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  64. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  65. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  66. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  67. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  68. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  69. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  70. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  71. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  72. package/blocks/enrichment/README.md +22 -4
  73. package/blocks/enrichment/block.json +145 -5
  74. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  75. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  76. package/blocks/enrichment/components/bulk-enrich-panel.tsx +7 -7
  77. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  78. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  79. package/blocks/enrichment/components/enrichment-screen.tsx +34 -3
  80. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  81. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  82. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  83. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  84. package/blocks/enrichment/lib/enrichment-types.ts +12 -0
  85. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  86. package/blocks/forms/README.md +2 -0
  87. package/blocks/forms/components/forms-screen.tsx +6 -3
  88. package/blocks/inbox/README.md +40 -4
  89. package/blocks/inbox/block.json +130 -8
  90. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  91. package/blocks/inbox/components/compose-message.tsx +193 -0
  92. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  93. package/blocks/inbox/components/inbox-search.tsx +106 -0
  94. package/blocks/inbox/components/tag-manager.tsx +136 -0
  95. package/blocks/inbox/components/thread-actions.tsx +133 -0
  96. package/blocks/inbox/components/thread-view.tsx +2 -0
  97. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  98. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  99. package/blocks/newsletter/components/newsletter-screen.tsx +3 -1
  100. package/blocks/pages/block.json +10 -1
  101. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  102. package/blocks/programmatic-pages/README.md +82 -0
  103. package/blocks/programmatic-pages/block.json +292 -0
  104. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  105. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  106. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  107. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  108. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  109. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  110. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  111. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  112. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  113. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  114. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  115. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  116. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  117. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  118. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  119. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  120. package/blocks/quotes/README.md +25 -1
  121. package/blocks/quotes/block.json +123 -6
  122. package/blocks/quotes/components/manual-products.tsx +144 -0
  123. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  124. package/blocks/quotes/components/quote-documents.tsx +94 -0
  125. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  126. package/blocks/quotes/components/quote-list.tsx +23 -5
  127. package/blocks/quotes/components/quote-settings.tsx +184 -0
  128. package/blocks/quotes/components/quote-templates.tsx +189 -0
  129. package/blocks/quotes/components/quote-viewer.tsx +16 -3
  130. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  131. package/blocks/quotes/lib/quote-admin.ts +264 -0
  132. package/blocks/sequencer/README.md +55 -0
  133. package/blocks/sequencer/block.json +172 -5
  134. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  135. package/blocks/sequencer/components/sequence-builder.tsx +13 -3
  136. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  137. package/blocks/sequencer/components/sequence-detail.tsx +6 -0
  138. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  139. package/blocks/sequencer/components/sequence-enrollments.tsx +2 -1
  140. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  141. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  142. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  143. package/blocks/sequencer/components/sequences-screen.tsx +36 -6
  144. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  145. package/blocks/sequencer/components/step-editor.tsx +10 -1
  146. package/blocks/sequencer/components/step-templates.tsx +224 -0
  147. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  148. package/blocks/sequencer/lib/sequencer-utils.ts +4 -0
  149. package/blocks/signals/README.md +39 -0
  150. package/blocks/signals/block.json +287 -0
  151. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  152. package/blocks/signals/components/listener-form.tsx +184 -0
  153. package/blocks/signals/components/matched-posts.tsx +195 -0
  154. package/blocks/signals/components/post-engagements.tsx +114 -0
  155. package/blocks/signals/components/record-social-tab.tsx +49 -0
  156. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  157. package/blocks/signals/components/social-settings.tsx +128 -0
  158. package/blocks/signals/components/source-lists.tsx +83 -0
  159. package/blocks/signals/components/top-engagers.tsx +68 -0
  160. package/blocks/signals/components/x-integration.tsx +129 -0
  161. package/blocks/signals/lib/signals-types.ts +228 -0
  162. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  163. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  164. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  165. package/blocks/visitors/components/traffic-chart.tsx +3 -2
  166. package/blocks/web-analytics/README.md +102 -0
  167. package/blocks/web-analytics/block.json +292 -0
  168. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  169. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  170. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  171. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  172. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  173. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  174. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  175. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  176. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  177. package/blocks/web-analytics/components/report-card.tsx +71 -0
  178. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  179. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  180. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  181. package/blocks/web-analytics/lib/page-path.ts +14 -0
  182. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  183. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  184. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  185. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  186. package/blocks/workbench/README.md +54 -2
  187. package/blocks/workbench/block.json +234 -5
  188. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  189. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  190. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  191. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  192. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  193. package/blocks/workbench/components/workbench-grid.tsx +70 -7
  194. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  195. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  196. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  197. package/blocks/workbench/components/workbench-screen.tsx +50 -17
  198. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  199. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  200. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  201. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  202. package/blocks/workbench/lib/api-status.ts +8 -0
  203. package/blocks/workbench/lib/automation-types.ts +257 -0
  204. package/dist/cli.js +257 -51
  205. package/dist/index.browser.js +1 -1
  206. package/dist/index.browser.js.map +1 -1
  207. package/dist/index.browser.mjs +1 -1
  208. package/dist/index.browser.mjs.map +1 -1
  209. package/dist/index.js +1 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/index.mjs +1 -1
  212. package/dist/index.mjs.map +1 -1
  213. package/dist/react.browser.js +1 -1
  214. package/dist/react.browser.js.map +1 -1
  215. package/dist/react.browser.mjs +1 -1
  216. package/dist/react.browser.mjs.map +1 -1
  217. package/dist/react.js +1 -1
  218. package/dist/react.js.map +1 -1
  219. package/dist/react.mjs +1 -1
  220. package/dist/react.mjs.map +1 -1
  221. package/dist/types/blocks/auth.d.ts +45 -0
  222. package/dist/types/blocks/create.d.ts +5 -2
  223. package/dist/types/version.d.ts +2 -2
  224. package/docs/api/forms.md +1 -1
  225. package/package.json +1 -1
  226. package/stacks/6sense/pages/contacts.tsx.tmpl +2 -0
  227. package/stacks/6sense.json +20 -0
  228. package/stacks/apollo/pages/calls.tsx.tmpl +9 -9
  229. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  230. package/stacks/apollo/pages/contacts.tsx.tmpl +6 -0
  231. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +3 -3
  232. package/stacks/apollo/pages/lists.tsx.tmpl +14 -2
  233. package/stacks/apollo/pages/sequences.tsx.tmpl +8 -2
  234. package/stacks/apollo.json +33 -2
  235. package/stacks/clay/pages/contacts.tsx.tmpl +2 -0
  236. package/stacks/clay/pages/lists.tsx.tmpl +14 -2
  237. package/stacks/gong/pages/calls.tsx.tmpl +9 -9
  238. package/stacks/gong/pages/contacts.tsx.tmpl +2 -0
  239. package/stacks/hootsuite/pages/contacts.tsx.tmpl +2 -0
  240. package/stacks/hootsuite.json +10 -0
  241. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  242. package/stacks/hubspot/pages/companies.tsx.tmpl +2 -0
  243. package/stacks/hubspot/pages/contacts.tsx.tmpl +6 -0
  244. package/stacks/hubspot/pages/deals.tsx.tmpl +4 -0
  245. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  246. package/stacks/hubspot/pages/inbox.tsx.tmpl +2 -2
  247. package/stacks/hubspot/pages/sequences.tsx.tmpl +8 -2
  248. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  249. package/stacks/hubspot.json +45 -2
  250. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +3 -3
  251. package/stacks/intercom.json +10 -0
  252. package/stacks/mailchimp/pages/contacts.tsx.tmpl +2 -0
  253. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  254. package/stacks/mailchimp.json +2 -1
  255. package/stacks/outreach/pages/calls.tsx.tmpl +9 -9
  256. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  257. package/stacks/outreach/pages/contacts.tsx.tmpl +2 -0
  258. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +3 -3
  259. package/stacks/outreach/pages/sequences.tsx.tmpl +8 -2
  260. package/stacks/outreach.json +13 -2
  261. package/stacks/pipedrive/pages/contacts.tsx.tmpl +2 -0
  262. package/stacks/pipedrive/pages/inbox.tsx.tmpl +2 -2
  263. package/stacks/pipedrive.json +10 -0
  264. package/stacks/salesforce/pages/companies.tsx.tmpl +2 -0
  265. package/stacks/salesforce/pages/contacts.tsx.tmpl +6 -0
  266. package/stacks/salesforce/pages/deals.tsx.tmpl +4 -0
  267. package/stacks/salesforce.json +10 -0
  268. package/stacks/zoominfo/pages/contacts.tsx.tmpl +4 -0
  269. package/stacks/zoominfo/pages/lists.tsx.tmpl +14 -2
  270. package/stacks/zoominfo.json +20 -0
  271. package/styles/styles.css +1 -1
  272. package/templates/auth/auth-route.ts.tmpl +16 -0
  273. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  274. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  275. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  276. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  277. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  278. package/templates/auth/middleware.ts.tmpl +14 -0
  279. package/templates/next-app/README.md.tmpl +5 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `create --auth <preset>`: scaffold sign-in for a generated app so it can go live, using the presets in
3
+ * blocks/_foundation/server/auth.ts. Pure planning helpers (string in, string out) plus one file write for the
4
+ * development secrets, so they are easy to test. Without `--auth`, nothing here runs and the app is generated
5
+ * exactly as before (the proxy keeps refusing a production build until `authorize` is set).
6
+ *
7
+ * Files a preset adds or changes:
8
+ * - g8.auth.ts (every preset): `export const appAuth = <preset>()`, documented
9
+ * - app/api/g8/[...path]/route.ts: `authorize: appAuth.authorize`
10
+ * - app/api/g8-auth/route.ts + app/layout.tsx wrapped in <G8AuthGate> (password, jwt, session: the sign-in screen)
11
+ * - middleware.ts (basic: the browser's prompt protects every page)
12
+ * - README.md "Sign-in" section and .env.local.example variables; .env.local gets fresh secrets (password, basic)
13
+ */
14
+ export declare const AUTH_PRESETS: readonly ["password", "basic", "jwt", "session", "anonymous"];
15
+ export type AuthPreset = (typeof AUTH_PRESETS)[number];
16
+ export declare const AUTH_CONFIG_FILE = "g8.auth.ts";
17
+ export declare const AUTH_ROUTE_FILE = "app/api/g8-auth/route.ts";
18
+ export declare function isAuthPreset(v: string): v is AuthPreset;
19
+ /** Presets whose visitors see the in-app sign-in screen (G8AuthGate + /api/g8-auth). */
20
+ export declare function usesSignInScreen(p: AuthPreset): boolean;
21
+ export declare function authSummary(p: AuthPreset): string;
22
+ /** The variables a preset reads, as `.env.local.example` lines (no values). */
23
+ export declare function authEnvLines(p: AuthPreset): string[];
24
+ export interface AuthFile {
25
+ rel: string;
26
+ content: string;
27
+ }
28
+ /** The new files a preset writes (g8.auth.ts, the sign-in route or middleware.ts). */
29
+ export declare function planAuthFiles(p: AuthPreset, templatesRoot: string): AuthFile[];
30
+ /**
31
+ * The generated proxy route with `authorize: appAuth.authorize` set (the commented example line replaced) and
32
+ * the g8.auth import added. Null when the route does not have the generated shape (the user owns it).
33
+ */
34
+ export declare function applyAuthToRoute(source: string): string | null;
35
+ /** app/layout.tsx with the app inside <G8AuthGate> (on brand through BrandProvider) and a Sign out button. */
36
+ export declare function applyAuthToLayout(source: string): string | null;
37
+ /** README.md with a "Sign-in" section before "Go live" and the `authorize` checklist item marked as done by the preset. */
38
+ export declare function applyAuthToReadme(source: string, p: AuthPreset): string | null;
39
+ /** .env.local.example with the preset's variables appended. */
40
+ export declare function applyAuthToEnvExample(source: string, p: AuthPreset): string;
41
+ /**
42
+ * Fresh development secrets for presets that need them, appended to `.env.local` (git-ignored by the template)
43
+ * without touching variables that are already set. Returns the names written. Values are never printed.
44
+ */
45
+ export declare function writeAuthSecrets(root: string, p: AuthPreset): string[];
@@ -9,9 +9,10 @@
9
9
  */
10
10
  import { type OrgBrandValues, type BrandOptions } from "./brand";
11
11
  import { type AddIO, type PackageManager } from "./add";
12
+ import { type AuthPreset } from "./auth";
12
13
  import { componentImportPath, navParts, navUsesExtras, pageFileFor, render } from "./navfile";
13
14
  import { type Registry, type Stack, type StackNav } from "./registry";
14
- export declare const CREATE_USAGE = "Usage: npx @graph8/sdk create <stack> <dir> [options]\n\nScaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and\ninstall the stack's blocks into it. Run `graph8-sdk list` to see the stacks.\n --package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm\n --no-install write the files and blocks, do not install dependencies\n --dry-run print what would be written, write and install nothing\n --force allow a non-empty <dir> (template files are overwritten, and graph8.json\n is reset: blocks you added to that app earlier are no longer recorded)\nThen: `npm run dev` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run\n`npx @graph8/sdk doctor --stack <stack>` (add --production before going live).\n\nOr pick the features yourself, for any app (also: `create custom --blocks ...`):\n npx @graph8/sdk create --blocks <id,id,...> [--title \"Acme CRM\"] <dir> [options]\nEvery requested block's dependencies come with it. The app gets one route and one sidebar entry per finished\nScreen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives\nyou crm's screens). `/` opens the analytics screen if you asked for one, else the first screen.\n --title <text> the app's name (default: graph8 app)\n --with-deps-screens also add the plain fallback page of dependency blocks that have no Screen\n --no-deps-screens add screens only for the blocks you named\nRun `graph8-sdk list` to see each block and which have Screens.\n\nBrand it (any stack or --blocks; the same as running `graph8-sdk brand` in the new app afterwards):\n --name <text> the app's name (with --blocks, the same as --title)\n --brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode\n --logo <path> svg/png/jpg/webp/gif, shown in the sidebar\n --app-url <url> white-label: where \"Open in graph8\" links go; --link-label <text>: what they call it\nWith a graph8 API key (G8_API_KEY in the environment or .env.local) the org's Brand Kit fills in what you did not\npass: the app name, the primary colour and the logo (downloaded into public/brand/). No key, no kit or an error:\nthe default look, one info line, never a failure. --no-org-brand skips it.\nA directory literally named `custom` would read as the alias: pass `--blocks crm ./custom`.";
15
+ export declare const CREATE_USAGE = "Usage: npx @graph8/sdk create <stack> <dir> [options]\n\nScaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and\ninstall the stack's blocks into it. Run `graph8-sdk list` to see the stacks.\n --package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm\n --no-install write the files and blocks, do not install dependencies\n --dry-run print what would be written, write and install nothing\n --force allow a non-empty <dir> (template files are overwritten, and graph8.json\n is reset: blocks you added to that app earlier are no longer recorded)\n --auth <preset> scaffold sign-in so the app can go live (sets `authorize` on the proxy route):\n password one shared team password, session cookie, sign-in screen\n basic HTTP Basic auth on every page (the browser asks)\n jwt your identity provider's JWT (OIDC JWKS or an HS256 secret)\n session your own session library (Clerk, NextAuth, Supabase): wire g8.auth.ts\n anonymous NO sign-in: anyone who can reach the app uses its key (public demos)\n Without --auth: local preview only, and a production build refuses /api/g8 until you\n set `authorize` yourself (unchanged).\nThen: `npm run dev` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run\n`npx @graph8/sdk doctor --stack <stack>` (add --production before going live).\n\nOr pick the features yourself, for any app (also: `create custom --blocks ...`):\n npx @graph8/sdk create --blocks <id,id,...> [--title \"Acme CRM\"] <dir> [options]\nEvery requested block's dependencies come with it. The app gets one route and one sidebar entry per finished\nScreen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives\nyou crm's screens). `/` opens the analytics screen if you asked for one, else the first screen.\n --title <text> the app's name (default: graph8 app)\n --with-deps-screens also add the plain fallback page of dependency blocks that have no Screen\n --no-deps-screens add screens only for the blocks you named\nRun `graph8-sdk list` to see each block and which have Screens.\n\nBrand it (any stack or --blocks; the same as running `graph8-sdk brand` in the new app afterwards):\n --name <text> the app's name (with --blocks, the same as --title)\n --brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode\n --logo <path> svg/png/jpg/webp/gif, shown in the sidebar\n --app-url <url> white-label: where \"Open in graph8\" links go; --link-label <text>: what they call it\nWith a graph8 API key (G8_API_KEY in the environment or .env.local) the org's Brand Kit fills in what you did not\npass: the app name, the primary colour and the logo (downloaded into public/brand/). No key, no kit or an error:\nthe default look, one info line, never a failure. --no-org-brand skips it.\nA directory literally named `custom` would read as the alias: pass `--blocks crm ./custom`.";
15
16
  export interface CreateOptions {
16
17
  stack: string;
17
18
  dir: string;
@@ -28,6 +29,8 @@ export interface CreateOptions {
28
29
  brand?: Omit<BrandOptions, "dir" | "dryRun">;
29
30
  /** false with `--no-org-brand`: do not pull the org's Brand Kit. */
30
31
  orgBrand?: false;
32
+ /** `--auth <preset>`: scaffold sign-in (g8.auth.ts, `authorize` on the proxy route). */
33
+ auth?: AuthPreset;
31
34
  }
32
35
  export declare function parseCreateArgs(argv: string[]): CreateOptions;
33
36
  /** What `graph8.json` records about a custom app, so `add` and `doctor` know its routes. */
@@ -61,7 +64,7 @@ interface OutFile {
61
64
  export declare function stackProblem(reg: Registry, stack: Stack): string | null;
62
65
  /** Absolute path of a nav entry's hand-written page template, or null when it uses the generic route page. */
63
66
  export declare function stackPageFile(reg: Registry, stack: Stack, n: StackNav): string | null;
64
- export declare function planCreate(reg: Registry, stack: Stack, appName: string, pm: PackageManager, io: CreateIO, app?: AppRecord): OutFile[];
67
+ export declare function planCreate(reg: Registry, stack: Stack, appName: string, pm: PackageManager, io: CreateIO, app?: AppRecord, auth?: AuthPreset): OutFile[];
65
68
  /** Run `create`. Returns the process exit code. */
66
69
  export declare function runCreate(given: CreateOptions, reg: Registry, io: CreateIO, cwd: string): number;
67
70
  export { componentImportPath, navParts, navUsesExtras, pageFileFor, render };
@@ -5,6 +5,6 @@
5
5
  * both together (the version-parity gate already pins package.json, the
6
6
  * lockfile and the CHANGELOG to each other).
7
7
  */
8
- export declare const SDK_VERSION = "0.373.0";
8
+ export declare const SDK_VERSION = "0.374.0";
9
9
  /** Value of the `X-G8-Client` header every SDK request carries (I-13). */
10
- export declare const SDK_CLIENT_ID = "js-sdk/0.373.0";
10
+ export declare const SDK_CLIENT_ID = "js-sdk/0.374.0";
package/docs/api/forms.md CHANGED
@@ -1507,7 +1507,7 @@ The inventory filter set a saved view stores. Mirrors `PaginationRequest`.
1507
1507
  | `mapping_state` | `"mapped" \| "unmapped" \| null` | no | |
1508
1508
  | `owner_id` | `string \| null` | no | max length 200 |
1509
1509
  | `page` | `integer` | no | default `1`, min 1 |
1510
- | `per_page` | `integer` | no | default `20`, min 1 |
1510
+ | `per_page` | `integer` | no | default `20`, min 1, max 500 |
1511
1511
  | `saved_view` | `"live" \| "needs_setup" \| "failing" \| "ignored" \| "healthy" \| "failures" \| "new_fields" \| "no_recent_submissions" \| … \| null` | no | |
1512
1512
  | `search` | `string \| null` | no | max length 200 |
1513
1513
  | `sort_field` | `"submissions_30d" \| "total_submissions" \| "latest_submitted_at" \| "form_tag_id" \| null` | no | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graph8/sdk",
3
- "version": "0.373.0",
3
+ "version": "0.374.0",
4
4
  "description": "graph8 SDK - CRM, enrichment, sequences, campaigns, and tracking for B2B",
5
5
  "license": "MIT",
6
6
  "author": "graph8 <support@graph8.com> (https://graph8.com)",
@@ -4,6 +4,7 @@
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
6
6
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
7
+ import { RecordSocialTab } from "@/components/g8/signals/record-social-tab";
7
8
  import { useRouter } from "next/navigation";
8
9
 
9
10
  export default function Page() {
@@ -13,6 +14,7 @@ export default function Page() {
13
14
  onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
14
15
  extraDetailTabs={[
15
16
  { id: "engagement", label: "Engagement", render: (c) => <RecordEngagementTab kind="contacts" id={c.id} /> },
17
+ { id: "social", label: "Social posts", render: (c) => <RecordSocialTab contactId={c.id} onOpenListeners={() => router.push("/social-listening")} /> },
16
18
  ]}
17
19
  />
18
20
  );
@@ -8,7 +8,9 @@
8
8
  "foundation",
9
9
  "crm",
10
10
  "intent",
11
+ "signals",
11
12
  "visitors",
13
+ "web-analytics",
12
14
  "radar",
13
15
  "company-intelligence"
14
16
  ],
@@ -23,6 +25,15 @@
23
25
  "group": "Signals",
24
26
  "description": "Accounts showing buying intent"
25
27
  },
28
+ {
29
+ "label": "Social listening",
30
+ "path": "/social-listening",
31
+ "block": "signals",
32
+ "component": "SocialListenersScreen",
33
+ "icon": "Ear",
34
+ "group": "Signals",
35
+ "description": "Posts about you, competitors and keywords"
36
+ },
26
37
  {
27
38
  "label": "Visitors",
28
39
  "path": "/visitors",
@@ -32,6 +43,15 @@
32
43
  "group": "Signals",
33
44
  "description": "Who is on your website right now"
34
45
  },
46
+ {
47
+ "label": "Web analytics",
48
+ "path": "/web-analytics",
49
+ "block": "web-analytics",
50
+ "component": "WebAnalyticsScreen",
51
+ "icon": "ChartNoAxesCombined",
52
+ "group": "Signals",
53
+ "description": "Traffic, audience, engagement and conversions for your sites"
54
+ },
35
55
  {
36
56
  "label": "Competitors",
37
57
  "path": "/competitors",
@@ -3,21 +3,21 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { useRouter } from "next/navigation";
6
- import { Button } from "@graph8/sdk/ui";
7
- import { UserRound } from "lucide-react";
6
+ import { RecordOpenLinks } from "@/components/g8/crm/record-open-links";
8
7
  import { CallLogScreen } from "@/components/g8/dialer/call-log-screen";
9
8
 
10
- /** A call opens the person it was made to. */
9
+ /** A call opens the person it was made to, and their company. The ids are checked against your CRM first, so a call whose contact is gone shows no link. */
11
10
  export default function Page() {
12
11
  const router = useRouter();
13
12
  return (
14
13
  <CallLogScreen
15
- renderDetailActions={(call) => call.contact_id ? (
16
- <Button size="sm" variant="outline" onClick={() => router.push(`/contacts?id=${encodeURIComponent(call.contact_id as string)}`)}>
17
- <UserRound className="mr-1 size-4" aria-hidden />
18
- Open contact
19
- </Button>
20
- ) : null}
14
+ renderDetailActions={(call) => (
15
+ <RecordOpenLinks
16
+ contactId={call.contact_id}
17
+ onOpenContact={(id) => router.push(`/contacts?id=${id}`)}
18
+ onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
19
+ />
20
+ )}
21
21
  />
22
22
  );
23
23
  }
@@ -0,0 +1,27 @@
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 { CampaignLinksTab } from "@/components/g8/campaigns/campaign-links";
7
+ import { CampaignsScreen } from "@/components/g8/campaigns/campaigns-screen";
8
+
9
+ /** A campaign lists its landing pages, workflows and newsletters. This app has no screens for them, so the rows show names only. */
10
+ export default function Page() {
11
+ const router = useRouter();
12
+ return (
13
+ <CampaignsScreen
14
+ extraDetailTabs={[
15
+ {
16
+ id: "links",
17
+ label: "Links",
18
+ render: (c) => (
19
+ <CampaignLinksTab
20
+ campaignId={c.id}
21
+ />
22
+ ),
23
+ },
24
+ ]}
25
+ />
26
+ );
27
+ }
@@ -3,8 +3,10 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { RecordMeetingsTab } from "@/components/g8/appointments/record-meetings-tab";
6
+ import { MemberPicker } from "@/components/g8/pickers/member-picker";
6
7
  import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
7
8
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
9
+ import { CreateDealButton } from "@/components/g8/deals/create-deal-button";
8
10
  import { RecordDealsTab } from "@/components/g8/deals/record-deals-tab";
9
11
  import { CallContactButton } from "@/components/g8/dialer/call-contact-button";
10
12
  import { RecordCallsTab } from "@/components/g8/dialer/record-calls-tab";
@@ -14,12 +16,14 @@ import { AddToListButton } from "@/components/g8/lists/add-to-list-button";
14
16
  import { RecordListsTab } from "@/components/g8/lists/record-lists-tab";
15
17
  import { AddToSequenceButton } from "@/components/g8/sequencer/add-to-sequence-button";
16
18
  import { RecordSequencesTab } from "@/components/g8/sequencer/record-sequences-tab";
19
+ import { RecordSocialTab } from "@/components/g8/signals/record-social-tab";
17
20
  import { useRouter } from "next/navigation";
18
21
 
19
22
  export default function Page() {
20
23
  const router = useRouter();
21
24
  return (
22
25
  <ContactsScreen
26
+ renderOwnerPicker={(picker) => <MemberPicker label="New owner" {...picker} />}
23
27
  onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
24
28
  renderRowActions={(c, { reload }) => <EnrichButton kind="person" lookup={{ email: c.work_email ?? undefined, linkedin_url: c.linkedin_url ?? undefined, first_name: c.first_name ?? undefined, last_name: c.last_name ?? undefined }} record={{ ...c }} onApplied={reload} label="Enrich" size="sm" variant="ghost" />}
25
29
  renderBulkActions={({ ids, clear }) => (
@@ -35,6 +39,7 @@ export default function Page() {
35
39
  <AddToListButton contactIds={[c.id]} />
36
40
  {/* One call to this contact through the dialer; confirmed, and your softphone joins it. */}
37
41
  <CallContactButton contact={{ id: c.id, first_name: c.first_name, last_name: c.last_name, full_name: c.full_name, email: c.work_email, company: c.company_name, mobile_phone: c.mobile_phone, direct_phone: c.direct_phone }} />
42
+ <CreateDealButton contactId={c.id} onCreated={(id) => router.push(`/deals?id=${encodeURIComponent(id)}`)} />
38
43
  </>
39
44
  )}
40
45
  extraDetailTabs={[
@@ -45,6 +50,7 @@ export default function Page() {
45
50
  { id: "deals", label: "Deals", render: (c) => <RecordDealsTab kind="contacts" id={c.id} onOpenDeal={(id) => router.push(`/deals?id=${encodeURIComponent(id)}`)} /> },
46
51
  { id: "conversations", label: "Conversations", render: (c) => <RecordConversationsTab email={c.work_email} alsoEmails={[c.personal_emails]} onOpenThread={(sel) => router.push(`/inbox?id=${encodeURIComponent(sel)}`)} /> },
47
52
  { id: "engagement", label: "Engagement", render: (c) => <RecordEngagementTab kind="contacts" id={c.id} /> },
53
+ { id: "social", label: "Social posts", render: (c) => <RecordSocialTab contactId={c.id} onOpenListeners={() => router.push("/social-listening")} /> },
48
54
  ]}
49
55
  />
50
56
  );
@@ -3,11 +3,11 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { useRouter } from "next/navigation";
6
- import { ContactLookupLink } from "@/components/g8/crm/contact-lookup-link";
6
+ import { ContactLookupLink, splitName } from "@/components/g8/crm/contact-lookup-link";
7
7
  import { InboxScreen } from "@/components/g8/inbox/inbox-screen";
8
8
 
9
- /** A conversation opens the sender when they are a contact in your CRM. */
9
+ /** A conversation opens the sender when they are a contact in your CRM, or adds them when they are new. */
10
10
  export default function Page() {
11
11
  const router = useRouter();
12
- return <InboxScreen renderDetailActions={(thread) => <ContactLookupLink email={thread.contact?.email} onOpen={(id) => router.push(`/contacts?id=${id}`)} />} />;
12
+ return <InboxScreen renderDetailActions={(thread) => <ContactLookupLink email={thread.contact?.email} createIfMissing={splitName(thread.contact?.name)} onOpen={(id) => router.push(`/contacts?id=${id}`)} />} />;
13
13
  }
@@ -3,10 +3,22 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { useRouter } from "next/navigation";
6
+ import { Button } from "@graph8/sdk/ui";
7
+ import { Sparkles } from "lucide-react";
6
8
  import { ListsScreen } from "@/components/g8/lists/lists-screen";
7
9
 
8
- /** A list member opens in Contacts. */
10
+ /** A list member opens in Contacts; an open list can be enriched (its pipelines, priced before they run). */
9
11
  export default function Page() {
10
12
  const router = useRouter();
11
- return <ListsScreen onOpenContact={(id) => router.push(`/contacts?id=${id}`)} />;
13
+ return (
14
+ <ListsScreen
15
+ onOpenContact={(id) => router.push(`/contacts?id=${id}`)}
16
+ renderDetailActions={(list) => (
17
+ <Button size="sm" variant="outline" onClick={() => router.push(`/enrichment?tab=list-pipelines&list=${list.id}`)}>
18
+ <Sparkles className="mr-1 size-4" aria-hidden />
19
+ Enrich this list
20
+ </Button>
21
+ )}
22
+ />
23
+ );
12
24
  }
@@ -3,10 +3,16 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { useRouter } from "next/navigation";
6
+ import { SequenceCampaignLink } from "@/components/g8/sequencer/sequence-campaign-link";
6
7
  import { SequencesScreen } from "@/components/g8/sequencer/sequences-screen";
7
8
 
8
- /** An enrolled person opens in Contacts. */
9
+ /** An enrolled person opens in Contacts; a sequence opens, links or unlinks its campaign. */
9
10
  export default function Page() {
10
11
  const router = useRouter();
11
- return <SequencesScreen onOpenContact={(id) => router.push(`/contacts?id=${id}`)} />;
12
+ return (
13
+ <SequencesScreen
14
+ onOpenContact={(id) => router.push(`/contacts?id=${id}`)}
15
+ renderDetailActions={(sequence) => <SequenceCampaignLink sequence={sequence} onOpenCampaign={(id) => router.push(`/campaigns?id=${encodeURIComponent(id)}`)} />}
16
+ />
17
+ );
12
18
  }
@@ -12,7 +12,9 @@
12
12
  "search",
13
13
  "enrichment",
14
14
  "intent",
15
+ "signals",
15
16
  "visitors",
17
+ "web-analytics",
16
18
  "mailboxes",
17
19
  "sequencer",
18
20
  "campaigns",
@@ -23,7 +25,8 @@
23
25
  "deals",
24
26
  "analytics",
25
27
  "integrations",
26
- "company-intelligence"
28
+ "company-intelligence",
29
+ "email-signatures"
27
30
  ],
28
31
  "home": "/analytics",
29
32
  "nav": [
@@ -102,6 +105,15 @@
102
105
  "group": "Signals",
103
106
  "description": "Accounts showing buying intent"
104
107
  },
108
+ {
109
+ "label": "Social listening",
110
+ "path": "/social-listening",
111
+ "block": "signals",
112
+ "component": "SocialListenersScreen",
113
+ "icon": "Ear",
114
+ "group": "Signals",
115
+ "description": "Posts about you, competitors and keywords"
116
+ },
105
117
  {
106
118
  "label": "Visitors",
107
119
  "path": "/visitors",
@@ -111,6 +123,15 @@
111
123
  "group": "Signals",
112
124
  "description": "Who is on your website right now"
113
125
  },
126
+ {
127
+ "label": "Web analytics",
128
+ "path": "/web-analytics",
129
+ "block": "web-analytics",
130
+ "component": "WebAnalyticsScreen",
131
+ "icon": "ChartNoAxesCombined",
132
+ "group": "Signals",
133
+ "description": "Traffic, audience, engagement and conversions for your sites"
134
+ },
114
135
  {
115
136
  "label": "Sequences",
116
137
  "path": "/sequences",
@@ -128,7 +149,8 @@
128
149
  "component": "CampaignsScreen",
129
150
  "icon": "Rocket",
130
151
  "group": "Engage",
131
- "description": "Campaigns from brief to launch"
152
+ "description": "Campaigns from brief to launch",
153
+ "page": "pages/campaigns.tsx.tmpl"
132
154
  },
133
155
  {
134
156
  "label": "Inbox",
@@ -207,6 +229,15 @@
207
229
  "group": "Setup",
208
230
  "description": "Sending mailboxes and warmup health"
209
231
  },
232
+ {
233
+ "label": "Email signatures",
234
+ "path": "/email-signatures",
235
+ "block": "email-signatures",
236
+ "component": "EmailSignaturesScreen",
237
+ "icon": "PenLine",
238
+ "group": "Setup",
239
+ "description": "One signature for the team, kept on every mailbox"
240
+ },
210
241
  {
211
242
  "label": "Phone numbers",
212
243
  "path": "/phone-numbers",
@@ -2,6 +2,7 @@
2
2
 
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
+ import { MemberPicker } from "@/components/g8/pickers/member-picker";
5
6
  import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
6
7
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
7
8
  import { EnrichButton } from "@/components/g8/enrichment/enrich-button";
@@ -13,6 +14,7 @@ export default function Page() {
13
14
  const router = useRouter();
14
15
  return (
15
16
  <ContactsScreen
17
+ renderOwnerPicker={(picker) => <MemberPicker label="New owner" {...picker} />}
16
18
  onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
17
19
  renderRowActions={(c, { reload }) => <EnrichButton kind="person" lookup={{ email: c.work_email ?? undefined, linkedin_url: c.linkedin_url ?? undefined, first_name: c.first_name ?? undefined, last_name: c.last_name ?? undefined }} record={{ ...c }} onApplied={reload} label="Enrich" size="sm" variant="ghost" />}
18
20
  renderBulkActions={({ ids, clear }) => <AddToListButton contactIds={ids} onAdded={clear} />}
@@ -3,10 +3,22 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { useRouter } from "next/navigation";
6
+ import { Button } from "@graph8/sdk/ui";
7
+ import { Sparkles } from "lucide-react";
6
8
  import { ListsScreen } from "@/components/g8/lists/lists-screen";
7
9
 
8
- /** A list member opens in Contacts. */
10
+ /** A list member opens in Contacts; an open list can be enriched (its pipelines, priced before they run). */
9
11
  export default function Page() {
10
12
  const router = useRouter();
11
- return <ListsScreen onOpenContact={(id) => router.push(`/contacts?id=${id}`)} />;
13
+ return (
14
+ <ListsScreen
15
+ onOpenContact={(id) => router.push(`/contacts?id=${id}`)}
16
+ renderDetailActions={(list) => (
17
+ <Button size="sm" variant="outline" onClick={() => router.push(`/enrichment?tab=list-pipelines&list=${list.id}`)}>
18
+ <Sparkles className="mr-1 size-4" aria-hidden />
19
+ Enrich this list
20
+ </Button>
21
+ )}
22
+ />
23
+ );
12
24
  }
@@ -3,21 +3,21 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { useRouter } from "next/navigation";
6
- import { Button } from "@graph8/sdk/ui";
7
- import { UserRound } from "lucide-react";
6
+ import { RecordOpenLinks } from "@/components/g8/crm/record-open-links";
8
7
  import { CallLogScreen } from "@/components/g8/dialer/call-log-screen";
9
8
 
10
- /** A call opens the person it was made to. */
9
+ /** A call opens the person it was made to, and their company. The ids are checked against your CRM first, so a call whose contact is gone shows no link. */
11
10
  export default function Page() {
12
11
  const router = useRouter();
13
12
  return (
14
13
  <CallLogScreen
15
- renderDetailActions={(call) => call.contact_id ? (
16
- <Button size="sm" variant="outline" onClick={() => router.push(`/contacts?id=${encodeURIComponent(call.contact_id as string)}`)}>
17
- <UserRound className="mr-1 size-4" aria-hidden />
18
- Open contact
19
- </Button>
20
- ) : null}
14
+ renderDetailActions={(call) => (
15
+ <RecordOpenLinks
16
+ contactId={call.contact_id}
17
+ onOpenContact={(id) => router.push(`/contacts?id=${id}`)}
18
+ onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
19
+ />
20
+ )}
21
21
  />
22
22
  );
23
23
  }
@@ -3,6 +3,7 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { RecordMeetingsTab } from "@/components/g8/appointments/record-meetings-tab";
6
+ import { MemberPicker } from "@/components/g8/pickers/member-picker";
6
7
  import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
7
8
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
8
9
  import { RecordCallsTab } from "@/components/g8/dialer/record-calls-tab";
@@ -14,6 +15,7 @@ export default function Page() {
14
15
  const router = useRouter();
15
16
  return (
16
17
  <ContactsScreen
18
+ renderOwnerPicker={(picker) => <MemberPicker label="New owner" {...picker} />}
17
19
  onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
18
20
  renderBulkActions={({ ids, clear }) => <AddToListButton contactIds={ids} onAdded={clear} />}
19
21
  renderDetailActions={(c) => (
@@ -2,6 +2,7 @@
2
2
 
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
+ import { MemberPicker } from "@/components/g8/pickers/member-picker";
5
6
  import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
6
7
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
7
8
  import { AddToListButton } from "@/components/g8/lists/add-to-list-button";
@@ -12,6 +13,7 @@ export default function Page() {
12
13
  const router = useRouter();
13
14
  return (
14
15
  <ContactsScreen
16
+ renderOwnerPicker={(picker) => <MemberPicker label="New owner" {...picker} />}
15
17
  onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
16
18
  renderBulkActions={({ ids, clear }) => <AddToListButton contactIds={ids} onAdded={clear} />}
17
19
  renderDetailActions={(c) => (
@@ -11,6 +11,7 @@
11
11
  "lists",
12
12
  "social-ads",
13
13
  "pages",
14
+ "programmatic-pages",
14
15
  "analytics"
15
16
  ],
16
17
  "home": "/analytics",
@@ -51,6 +52,15 @@
51
52
  "group": "Marketing",
52
53
  "description": "Landing pages, brand kit and content"
53
54
  },
55
+ {
56
+ "label": "Programmatic pages",
57
+ "path": "/programmatic-pages",
58
+ "block": "programmatic-pages",
59
+ "component": "ProgrammaticPagesScreen",
60
+ "icon": "Layers",
61
+ "group": "Marketing",
62
+ "description": "A page per row: render, preview, publish and roll back"
63
+ },
54
64
  {
55
65
  "label": "Contacts",
56
66
  "path": "/contacts",
@@ -0,0 +1,30 @@
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 { CampaignLinksTab } from "@/components/g8/campaigns/campaign-links";
7
+ import { CampaignsScreen } from "@/components/g8/campaigns/campaigns-screen";
8
+
9
+ /** A campaign lists its landing pages, workflows and newsletters, and opens each in its own screen. */
10
+ export default function Page() {
11
+ const router = useRouter();
12
+ return (
13
+ <CampaignsScreen
14
+ extraDetailTabs={[
15
+ {
16
+ id: "links",
17
+ label: "Links",
18
+ render: (c) => (
19
+ <CampaignLinksTab
20
+ campaignId={c.id}
21
+ onOpenLandingPage={(id) => router.push(`/pages?id=${encodeURIComponent(id)}`)}
22
+ onOpenWorkflow={(id) => router.push(`/workflows?id=${encodeURIComponent(id)}`)}
23
+ onOpenNewsletter={(id) => router.push(`/newsletter?newsletter=${encodeURIComponent(id)}`)}
24
+ />
25
+ ),
26
+ },
27
+ ]}
28
+ />
29
+ );
30
+ }
@@ -5,6 +5,7 @@
5
5
  import { CompaniesScreen } from "@/components/g8/crm/companies-screen";
6
6
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
7
7
  import { RecordDealsTab } from "@/components/g8/deals/record-deals-tab";
8
+ import { RecordDocumentsTab } from "@/components/g8/documents/record-documents-tab";
8
9
  import { AddCompanyPeopleToListButton } from "@/components/g8/lists/add-company-people-to-list-button";
9
10
  import { RecordQuotesTab } from "@/components/g8/quotes/record-quotes-tab";
10
11
  import { useRouter } from "next/navigation";
@@ -19,6 +20,7 @@ export default function Page() {
19
20
  extraDetailTabs={[
20
21
  { id: "quotes", label: "Quotes", render: (c) => <RecordQuotesTab kind="companies" id={c.id} onOpenQuote={(id) => router.push(`/quotes?id=${encodeURIComponent(id)}`)} /> },
21
22
  { id: "deals", label: "Deals", render: (c) => <RecordDealsTab kind="companies" id={c.id} onOpenDeal={(id) => router.push(`/deals?id=${encodeURIComponent(id)}`)} /> },
23
+ { id: "documents", label: "Documents", render: (c) => <RecordDocumentsTab companyId={c.id} onOpenDocuments={(href) => router.push(href)} /> },
22
24
  { id: "engagement", label: "Engagement", render: (c) => <RecordEngagementTab kind="companies" id={c.id} /> },
23
25
  ]}
24
26
  />
@@ -3,9 +3,12 @@
3
3
  // Generated by `graph8-sdk create {{stackId}}`. The {{block}} block's {{component}} (source in {{blockDir}}), with the
4
4
  // other blocks of this app plugged into its slots. Edit freely: this file is yours.
5
5
  import { RecordMeetingsTab } from "@/components/g8/appointments/record-meetings-tab";
6
+ import { MemberPicker } from "@/components/g8/pickers/member-picker";
6
7
  import { ContactsScreen } from "@/components/g8/crm/contacts-screen";
7
8
  import { RecordEngagementTab } from "@/components/g8/crm/record-engagement-tab";
9
+ import { CreateDealButton } from "@/components/g8/deals/create-deal-button";
8
10
  import { RecordDealsTab } from "@/components/g8/deals/record-deals-tab";
11
+ import { RecordDocumentsTab } from "@/components/g8/documents/record-documents-tab";
9
12
  import { RecordConversationsTab } from "@/components/g8/inbox/record-conversations-tab";
10
13
  import { AddToListButton } from "@/components/g8/lists/add-to-list-button";
11
14
  import { RecordListsTab } from "@/components/g8/lists/record-lists-tab";
@@ -18,6 +21,7 @@ export default function Page() {
18
21
  const router = useRouter();
19
22
  return (
20
23
  <ContactsScreen
24
+ renderOwnerPicker={(picker) => <MemberPicker label="New owner" {...picker} />}
21
25
  onOpenCompany={(id) => router.push(`/companies?id=${id}`)}
22
26
  renderBulkActions={({ ids, clear }) => (
23
27
  <>
@@ -29,6 +33,7 @@ export default function Page() {
29
33
  <>
30
34
  <AddToSequenceButton contactIds={[c.id]} />
31
35
  <AddToListButton contactIds={[c.id]} />
36
+ <CreateDealButton contactId={c.id} onCreated={(id) => router.push(`/deals?id=${encodeURIComponent(id)}`)} />
32
37
  </>
33
38
  )}
34
39
  extraDetailTabs={[
@@ -38,6 +43,7 @@ export default function Page() {
38
43
  { id: "quotes", label: "Quotes", render: (c) => <RecordQuotesTab kind="contacts" id={c.id} onOpenQuote={(id) => router.push(`/quotes?id=${encodeURIComponent(id)}`)} /> },
39
44
  { id: "deals", label: "Deals", render: (c) => <RecordDealsTab kind="contacts" id={c.id} onOpenDeal={(id) => router.push(`/deals?id=${encodeURIComponent(id)}`)} /> },
40
45
  { id: "conversations", label: "Conversations", render: (c) => <RecordConversationsTab email={c.work_email} alsoEmails={[c.personal_emails]} onOpenThread={(sel) => router.push(`/inbox?id=${encodeURIComponent(sel)}`)} /> },
46
+ { id: "documents", label: "Documents", render: (c) => <RecordDocumentsTab contactId={c.id} onOpenDocuments={(href) => router.push(href)} /> },
41
47
  { id: "engagement", label: "Engagement", render: (c) => <RecordEngagementTab kind="contacts" id={c.id} /> },
42
48
  ]}
43
49
  />