@graph8/sdk 0.370.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 (646) hide show
  1. package/README.md +22 -3
  2. package/blocks/_foundation/README.md +61 -0
  3. package/blocks/_foundation/block.json +57 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  6. package/blocks/_foundation/components/empty-state.tsx +5 -2
  7. package/blocks/_foundation/components/list-select.tsx +112 -0
  8. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  9. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  10. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  11. package/blocks/_foundation/components/screen/index.ts +1 -0
  12. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  13. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  14. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  15. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  16. package/blocks/_foundation/lib/paging.ts +57 -0
  17. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  18. package/blocks/_foundation/server/auth.ts +815 -0
  19. package/blocks/agents/README.md +81 -4
  20. package/blocks/agents/block.json +311 -2
  21. package/blocks/agents/components/agent-activity.tsx +112 -0
  22. package/blocks/agents/components/agent-approvals.tsx +135 -0
  23. package/blocks/agents/components/agent-builder.tsx +17 -3
  24. package/blocks/agents/components/agent-conversations.tsx +143 -0
  25. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
  26. package/blocks/agents/components/agent-operators.tsx +164 -0
  27. package/blocks/agents/components/agent-run-detail.tsx +20 -3
  28. package/blocks/agents/components/agent-runs.tsx +3 -3
  29. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  30. package/blocks/agents/components/agent-test-history.tsx +121 -0
  31. package/blocks/agents/components/agents-screen.tsx +53 -11
  32. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  33. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  34. package/blocks/agents/components/from-template-button.tsx +65 -0
  35. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  36. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  37. package/blocks/agents/components/knowledge-form.tsx +69 -0
  38. package/blocks/agents/components/name-dialog.tsx +32 -0
  39. package/blocks/agents/components/receptionist-button.tsx +46 -0
  40. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  41. package/blocks/agents/components/skill-editor.tsx +147 -0
  42. package/blocks/agents/components/skill-library.tsx +111 -0
  43. package/blocks/agents/lib/agent-types.ts +7 -1
  44. package/blocks/agents/lib/facts.tsx +13 -0
  45. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  46. package/blocks/agents/lib/load-more.tsx +141 -0
  47. package/blocks/agents/lib/skill-utils.ts +91 -0
  48. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +16 -3
  50. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  51. package/blocks/analytics/components/report-builder.tsx +15 -2
  52. package/blocks/appointments/README.md +6 -0
  53. package/blocks/appointments/block.json +27 -0
  54. package/blocks/appointments/components/booking-page.tsx +2 -2
  55. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  56. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  57. package/blocks/appointments/components/meeting-list.tsx +29 -13
  58. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  59. package/blocks/campaigns/README.md +1 -1
  60. package/blocks/campaigns/block.json +34 -1
  61. package/blocks/campaigns/components/campaign-assets.tsx +1 -1
  62. package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
  63. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  64. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  65. package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
  66. package/blocks/campaigns/components/icp-detail.tsx +11 -2
  67. package/blocks/campaigns/components/icp-editor.tsx +8 -3
  68. package/blocks/campaigns/components/persona-detail.tsx +11 -2
  69. package/blocks/campaigns/components/persona-editor.tsx +6 -1
  70. package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
  71. package/blocks/coach/components/call-scorecard.tsx +7 -7
  72. package/blocks/company-intelligence/README.md +132 -0
  73. package/blocks/company-intelligence/block.json +318 -0
  74. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  75. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  76. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  77. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  78. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  79. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  80. package/blocks/company-intelligence/components/research-run.tsx +165 -0
  81. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  82. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  83. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  84. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  85. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  86. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  87. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  88. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  89. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  90. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  91. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  92. package/blocks/copilot/components/copilot-panel.tsx +1 -1
  93. package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
  94. package/blocks/crm/README.md +25 -0
  95. package/blocks/crm/block.json +257 -9
  96. package/blocks/crm/components/bulk-update.tsx +119 -0
  97. package/blocks/crm/components/companies-screen.tsx +23 -9
  98. package/blocks/crm/components/company-detail.tsx +2 -2
  99. package/blocks/crm/components/contact-detail.tsx +4 -4
  100. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  101. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  102. package/blocks/crm/components/contact-lookup-link.tsx +85 -0
  103. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  104. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  105. package/blocks/crm/components/contacts-screen.tsx +77 -18
  106. package/blocks/crm/components/dedupe-review.tsx +6 -1
  107. package/blocks/crm/components/import-wizard.tsx +3 -7
  108. package/blocks/crm/components/list-select.tsx +20 -0
  109. package/blocks/crm/components/owner-assign.tsx +132 -0
  110. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  111. package/blocks/crm/components/record-form.tsx +1 -1
  112. package/blocks/crm/components/record-open-links.tsx +79 -0
  113. package/blocks/crm/components/tag-assign.tsx +112 -0
  114. package/blocks/crm/components/tag-manager.tsx +178 -0
  115. package/blocks/crm/components/tasks-screen.tsx +35 -15
  116. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  117. package/blocks/crm/components/verify-email-button.tsx +93 -0
  118. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  119. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  120. package/blocks/crm/hooks/use-page-query.ts +3 -1
  121. package/blocks/crm/lib/crm-types.ts +14 -0
  122. package/blocks/crm/lib/read-lists.ts +16 -0
  123. package/blocks/crm/lib/record-actions.ts +102 -0
  124. package/blocks/deals/README.md +25 -6
  125. package/blocks/deals/block.json +168 -2
  126. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  127. package/blocks/deals/components/create-deal-button.tsx +109 -0
  128. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  129. package/blocks/deals/components/deal-detail.tsx +7 -5
  130. package/blocks/deals/components/deal-filters.tsx +65 -0
  131. package/blocks/deals/components/deal-line-items.tsx +120 -0
  132. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  133. package/blocks/deals/components/deal-related.tsx +30 -9
  134. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  135. package/blocks/deals/components/deals-screen.tsx +57 -22
  136. package/blocks/deals/components/forecast-view.tsx +4 -4
  137. package/blocks/deals/components/pipeline-board.tsx +8 -6
  138. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  139. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  140. package/blocks/deals/components/stage-editor.tsx +1 -1
  141. package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
  142. package/blocks/deals/lib/deal-extras.ts +156 -0
  143. package/blocks/dialer/README.md +14 -5
  144. package/blocks/dialer/block.json +17 -2
  145. package/blocks/dialer/components/call-contact-button.tsx +26 -9
  146. package/blocks/dialer/components/call-log-screen.tsx +10 -2
  147. package/blocks/dialer/components/call-log.tsx +16 -4
  148. package/blocks/dialer/components/call-queue.tsx +3 -3
  149. package/blocks/dialer/components/dialer-screen.tsx +69 -27
  150. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  151. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  152. package/blocks/dialer/components/session-start-form.tsx +58 -11
  153. package/blocks/dialer/components/softphone.tsx +1 -1
  154. package/blocks/documents/README.md +86 -0
  155. package/blocks/documents/block.json +378 -0
  156. package/blocks/documents/components/account-bar.tsx +48 -0
  157. package/blocks/documents/components/correspondence-form.tsx +95 -0
  158. package/blocks/documents/components/correspondence-import.tsx +106 -0
  159. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  160. package/blocks/documents/components/documents-screen.tsx +76 -0
  161. package/blocks/documents/components/library-document-detail.tsx +175 -0
  162. package/blocks/documents/components/library-document-form.tsx +105 -0
  163. package/blocks/documents/components/library-folder-form.tsx +74 -0
  164. package/blocks/documents/components/library-panel.tsx +235 -0
  165. package/blocks/documents/components/library-upload.tsx +160 -0
  166. package/blocks/documents/components/question-row.tsx +130 -0
  167. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  168. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  169. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  170. package/blocks/documents/lib/documents-types.ts +252 -0
  171. package/blocks/documents/lib/download.ts +64 -0
  172. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  173. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  174. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  175. package/blocks/email-signatures/README.md +123 -0
  176. package/blocks/email-signatures/block.json +341 -0
  177. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  178. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  179. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  180. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  181. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  182. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  183. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  184. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  185. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  186. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  187. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  188. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  189. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  190. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  191. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  192. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  193. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  194. package/blocks/enrichment/README.md +38 -10
  195. package/blocks/enrichment/block.json +164 -12
  196. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  197. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  198. package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
  199. package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
  200. package/blocks/enrichment/components/enrich-button.tsx +1 -1
  201. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  202. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  203. package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
  204. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  205. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  206. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  207. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  208. package/blocks/enrichment/components/waterfall-config.tsx +27 -7
  209. package/blocks/enrichment/lib/enrichment-types.ts +51 -0
  210. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  211. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  212. package/blocks/forms/README.md +10 -0
  213. package/blocks/forms/block.json +107 -0
  214. package/blocks/forms/components/form-activity.tsx +71 -0
  215. package/blocks/forms/components/form-backfill.tsx +108 -0
  216. package/blocks/forms/components/form-builder.tsx +1 -1
  217. package/blocks/forms/components/form-embed.tsx +1 -1
  218. package/blocks/forms/components/form-groups.tsx +151 -0
  219. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  220. package/blocks/forms/components/form-route-history.tsx +72 -0
  221. package/blocks/forms/components/form-saved-views.tsx +81 -0
  222. package/blocks/forms/components/forms-screen.tsx +57 -20
  223. package/blocks/forms/components/more-rows.tsx +30 -0
  224. package/blocks/forms/components/submissions-export.tsx +67 -0
  225. package/blocks/forms/components/submissions-table.tsx +15 -6
  226. package/blocks/forms/lib/forms-extra.ts +67 -0
  227. package/blocks/inbox/README.md +45 -4
  228. package/blocks/inbox/block.json +145 -8
  229. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  230. package/blocks/inbox/components/compose-message.tsx +193 -0
  231. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  232. package/blocks/inbox/components/inbox-search.tsx +106 -0
  233. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  234. package/blocks/inbox/components/tag-manager.tsx +136 -0
  235. package/blocks/inbox/components/thread-actions.tsx +133 -0
  236. package/blocks/inbox/components/thread-badge.tsx +2 -2
  237. package/blocks/inbox/components/thread-view.tsx +3 -1
  238. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  239. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  240. package/blocks/integrations/README.md +54 -1
  241. package/blocks/integrations/block.json +189 -15
  242. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  243. package/blocks/integrations/components/connection-health.tsx +59 -0
  244. package/blocks/integrations/components/crm-detail.tsx +20 -1
  245. package/blocks/integrations/components/crm-records.tsx +103 -0
  246. package/blocks/integrations/components/field-mappings.tsx +61 -0
  247. package/blocks/integrations/components/integration-list.tsx +4 -4
  248. package/blocks/integrations/components/integrations-screen.tsx +20 -3
  249. package/blocks/integrations/components/migration-panel.tsx +133 -0
  250. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  251. package/blocks/integrations/components/sync-status.tsx +6 -6
  252. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  253. package/blocks/integrations/components/webhook-detail.tsx +26 -7
  254. package/blocks/integrations/components/webhook-manager.tsx +30 -17
  255. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  256. package/blocks/integrations/lib/crm-utils.ts +39 -0
  257. package/blocks/integrations/lib/facts.tsx +13 -0
  258. package/blocks/integrations/lib/integrations-utils.ts +5 -0
  259. package/blocks/integrations/lib/load-more.tsx +141 -0
  260. package/blocks/intent/components/account-intent-score.tsx +5 -5
  261. package/blocks/intent/components/intent-feed.tsx +8 -8
  262. package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
  263. package/blocks/intent/lib/intent-utils.ts +4 -4
  264. package/blocks/lifecycle/components/account-related.tsx +8 -1
  265. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  266. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  267. package/blocks/lists/README.md +7 -1
  268. package/blocks/lists/block.json +31 -3
  269. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  270. package/blocks/lists/components/add-to-list-button.tsx +17 -7
  271. package/blocks/lists/components/list-picker.tsx +13 -3
  272. package/blocks/lists/components/list-table.tsx +1 -1
  273. package/blocks/lists/components/lists-screen.tsx +4 -2
  274. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  275. package/blocks/lists/components/suppression-manager.tsx +27 -6
  276. package/blocks/lists/components/suppressions-screen.tsx +21 -12
  277. package/blocks/mailboxes/block.json +4 -0
  278. package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
  279. package/blocks/mailboxes/components/domain-detail.tsx +10 -4
  280. package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
  281. package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
  282. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  283. package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
  284. package/blocks/mailboxes/components/warmup-health.tsx +20 -20
  285. package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
  286. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  287. package/blocks/newsletter/README.md +12 -0
  288. package/blocks/newsletter/block.json +117 -0
  289. package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
  290. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  291. package/blocks/newsletter/components/more-rows.tsx +30 -0
  292. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  293. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  294. package/blocks/newsletter/components/newsletter-screen.tsx +38 -13
  295. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  296. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  297. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  298. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  299. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  300. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  301. package/blocks/nurture/components/nurture-builder.tsx +10 -2
  302. package/blocks/nurture/components/nurture-enroll.tsx +6 -7
  303. package/blocks/nurture/components/nurture-form.tsx +5 -8
  304. package/blocks/nurture/components/nurture-screen.tsx +16 -5
  305. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  306. package/blocks/objects/README.md +1 -1
  307. package/blocks/objects/block.json +13 -5
  308. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  309. package/blocks/objects/components/object-record-detail.tsx +8 -1
  310. package/blocks/objects/components/object-schema-editor.tsx +1 -1
  311. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  312. package/blocks/pages/README.md +14 -0
  313. package/blocks/pages/block.json +123 -2
  314. package/blocks/pages/components/more-rows.tsx +30 -0
  315. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  316. package/blocks/pages/components/page-analytics.tsx +176 -0
  317. package/blocks/pages/components/page-clone.tsx +99 -0
  318. package/blocks/pages/components/page-domains.tsx +167 -0
  319. package/blocks/pages/components/page-editor.tsx +1 -1
  320. package/blocks/pages/components/page-generation-status.tsx +48 -0
  321. package/blocks/pages/components/page-tracking.tsx +137 -0
  322. package/blocks/pages/components/pages-screen.tsx +57 -16
  323. package/blocks/pages/components/seo-report.tsx +6 -1
  324. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  325. package/blocks/pages/lib/studio.ts +7 -0
  326. package/blocks/phone-numbers/README.md +74 -0
  327. package/blocks/phone-numbers/block.json +262 -0
  328. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  329. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  330. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  331. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  332. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  333. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  334. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  335. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  336. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  337. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  338. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  339. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  340. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  341. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  342. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  343. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  344. package/blocks/pickers/README.md +23 -0
  345. package/blocks/pickers/block.json +103 -0
  346. package/blocks/pickers/components/company-picker.tsx +90 -0
  347. package/blocks/pickers/components/contact-picker.tsx +131 -0
  348. package/blocks/pickers/components/member-picker.tsx +99 -0
  349. package/blocks/pickers/components/search-picker.tsx +98 -0
  350. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  351. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  352. package/blocks/pickers/lib/use-debounced.ts +13 -0
  353. package/blocks/programmatic-pages/README.md +82 -0
  354. package/blocks/programmatic-pages/block.json +292 -0
  355. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  356. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  357. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  358. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  359. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  360. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  361. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  362. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  363. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  364. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  365. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  366. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  367. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  368. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  369. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  370. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  371. package/blocks/quotes/README.md +38 -1
  372. package/blocks/quotes/block.json +170 -6
  373. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  374. package/blocks/quotes/components/manual-products.tsx +144 -0
  375. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  376. package/blocks/quotes/components/quote-builder.tsx +1 -1
  377. package/blocks/quotes/components/quote-documents.tsx +94 -0
  378. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  379. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  380. package/blocks/quotes/components/quote-list.tsx +28 -8
  381. package/blocks/quotes/components/quote-related.tsx +3 -2
  382. package/blocks/quotes/components/quote-settings.tsx +184 -0
  383. package/blocks/quotes/components/quote-templates.tsx +189 -0
  384. package/blocks/quotes/components/quote-viewer.tsx +23 -5
  385. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  386. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  387. package/blocks/quotes/lib/quote-admin.ts +264 -0
  388. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  389. package/blocks/radar/components/radar-overview.tsx +4 -4
  390. package/blocks/radar/components/radar-screen.tsx +2 -2
  391. package/blocks/sequencer/README.md +61 -0
  392. package/blocks/sequencer/block.json +200 -6
  393. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  394. package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
  395. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  396. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  397. package/blocks/sequencer/components/sequence-builder.tsx +32 -23
  398. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  399. package/blocks/sequencer/components/sequence-detail.tsx +10 -2
  400. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  401. package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
  402. package/blocks/sequencer/components/sequence-list.tsx +1 -1
  403. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  404. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  405. package/blocks/sequencer/components/sequence-stats.tsx +8 -8
  406. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  407. package/blocks/sequencer/components/sequences-screen.tsx +39 -6
  408. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  409. package/blocks/sequencer/components/step-editor.tsx +21 -12
  410. package/blocks/sequencer/components/step-templates.tsx +224 -0
  411. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  412. package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
  413. package/blocks/signals/README.md +39 -0
  414. package/blocks/signals/block.json +287 -0
  415. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  416. package/blocks/signals/components/listener-form.tsx +184 -0
  417. package/blocks/signals/components/matched-posts.tsx +195 -0
  418. package/blocks/signals/components/post-engagements.tsx +114 -0
  419. package/blocks/signals/components/record-social-tab.tsx +49 -0
  420. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  421. package/blocks/signals/components/social-settings.tsx +128 -0
  422. package/blocks/signals/components/source-lists.tsx +83 -0
  423. package/blocks/signals/components/top-engagers.tsx +68 -0
  424. package/blocks/signals/components/x-integration.tsx +129 -0
  425. package/blocks/signals/lib/signals-types.ts +228 -0
  426. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  427. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  428. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  429. package/blocks/social-ads/README.md +8 -0
  430. package/blocks/social-ads/block.json +137 -4
  431. package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
  432. package/blocks/social-ads/components/audience-sync.tsx +6 -2
  433. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  434. package/blocks/social-ads/components/campaign-detail.tsx +6 -1
  435. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  436. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  437. package/blocks/social-ads/components/creative-detail.tsx +6 -1
  438. package/blocks/social-ads/components/creative-gallery.tsx +20 -2
  439. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  440. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  441. package/blocks/social-ads/components/post-composer.tsx +4 -2
  442. package/blocks/social-ads/components/post-detail.tsx +3 -0
  443. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  444. package/blocks/social-ads/components/social-performance.tsx +80 -0
  445. package/blocks/social-ads/components/social-screen.tsx +33 -6
  446. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  447. package/blocks/surveys/README.md +91 -0
  448. package/blocks/surveys/block.json +299 -0
  449. package/blocks/surveys/components/account-nps.tsx +69 -0
  450. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  451. package/blocks/surveys/components/org-summary.tsx +82 -0
  452. package/blocks/surveys/components/sending-domains.tsx +113 -0
  453. package/blocks/surveys/components/survey-form.tsx +126 -0
  454. package/blocks/surveys/components/survey-responses.tsx +145 -0
  455. package/blocks/surveys/components/survey-results.tsx +72 -0
  456. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  457. package/blocks/surveys/components/survey-send.tsx +162 -0
  458. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  459. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  460. package/blocks/surveys/lib/surveys-types.ts +228 -0
  461. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  462. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  463. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  464. package/blocks/visitors/components/snippet-install.tsx +18 -9
  465. package/blocks/visitors/components/traffic-chart.tsx +7 -6
  466. package/blocks/visitors/components/visitor-company-card.tsx +24 -14
  467. package/blocks/visitors/components/visitor-feed.tsx +5 -5
  468. package/blocks/web-analytics/README.md +102 -0
  469. package/blocks/web-analytics/block.json +292 -0
  470. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  471. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  472. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  473. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  474. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  475. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  476. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  477. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  478. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  479. package/blocks/web-analytics/components/report-card.tsx +71 -0
  480. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  481. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  482. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  483. package/blocks/web-analytics/lib/page-path.ts +14 -0
  484. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  485. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  486. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  487. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  488. package/blocks/webchat/components/chat-widget-config.tsx +1 -1
  489. package/blocks/webchat/components/conversation-list.tsx +1 -1
  490. package/blocks/workbench/README.md +54 -2
  491. package/blocks/workbench/block.json +234 -5
  492. package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
  493. package/blocks/workbench/components/run-progress.tsx +1 -1
  494. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  495. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  496. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  497. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  498. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  499. package/blocks/workbench/components/workbench-grid.tsx +71 -8
  500. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  501. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  502. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  503. package/blocks/workbench/components/workbench-screen.tsx +65 -22
  504. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  505. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  506. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  507. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  508. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  509. package/blocks/workbench/lib/api-status.ts +8 -0
  510. package/blocks/workbench/lib/automation-types.ts +257 -0
  511. package/blocks/workflows/components/workflow-editor.tsx +1 -1
  512. package/dist/cli.js +744 -394
  513. package/dist/index.browser.js +1 -1
  514. package/dist/index.browser.js.map +1 -1
  515. package/dist/index.browser.mjs +1 -1
  516. package/dist/index.browser.mjs.map +1 -1
  517. package/dist/index.d.mts +2 -2
  518. package/dist/index.d.ts +2 -2
  519. package/dist/index.js +1 -1
  520. package/dist/index.js.map +1 -1
  521. package/dist/index.mjs +1 -1
  522. package/dist/index.mjs.map +1 -1
  523. package/dist/react.browser.js +1 -1
  524. package/dist/react.browser.js.map +1 -1
  525. package/dist/react.browser.mjs +1 -1
  526. package/dist/react.browser.mjs.map +1 -1
  527. package/dist/react.d.mts +2 -2
  528. package/dist/react.d.ts +2 -2
  529. package/dist/react.js +1 -1
  530. package/dist/react.js.map +1 -1
  531. package/dist/react.mjs +1 -1
  532. package/dist/react.mjs.map +1 -1
  533. package/dist/types/blocks/auth.d.ts +45 -0
  534. package/dist/types/blocks/brand.d.ts +11 -2
  535. package/dist/types/blocks/create.d.ts +11 -4
  536. package/dist/types/blocks/doctor.d.ts +1 -0
  537. package/dist/types/blocks/flows.d.ts +7 -1
  538. package/dist/types/blocks/orgBrand.d.ts +34 -0
  539. package/dist/types/cli.d.ts +3 -0
  540. package/dist/types/version.d.ts +2 -2
  541. package/docs/api/desk.d.ts +2 -2
  542. package/docs/api/desk.md +3 -3
  543. package/docs/api/forms.md +1 -1
  544. package/package.json +1 -1
  545. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  546. package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
  547. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  548. package/stacks/6sense.json +37 -4
  549. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  550. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  551. package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
  552. package/stacks/apollo/pages/contacts.tsx.tmpl +35 -95
  553. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  554. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  555. package/stacks/apollo/pages/lists.tsx.tmpl +24 -0
  556. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  557. package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
  558. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  559. package/stacks/apollo.json +68 -9
  560. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  561. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  562. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  563. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  564. package/stacks/calendly.json +8 -4
  565. package/stacks/clay/pages/companies.tsx.tmpl +17 -1
  566. package/stacks/clay/pages/contacts.tsx.tmpl +14 -0
  567. package/stacks/clay/pages/lists.tsx.tmpl +24 -0
  568. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  569. package/stacks/clay.json +16 -3
  570. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  571. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  572. package/stacks/gong/pages/contacts.tsx.tmpl +34 -0
  573. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  574. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  575. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  576. package/stacks/gong.json +23 -6
  577. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  578. package/stacks/hootsuite/pages/contacts.tsx.tmpl +30 -0
  579. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  580. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  581. package/stacks/hootsuite.json +19 -4
  582. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  583. package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
  584. package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
  585. package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
  586. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  587. package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
  588. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  589. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  590. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  591. package/stacks/hubspot/pages/sequences.tsx.tmpl +18 -0
  592. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  593. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  594. package/stacks/hubspot.json +66 -7
  595. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  596. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  597. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  598. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  599. package/stacks/intercom.json +18 -4
  600. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  601. package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
  602. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  603. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  604. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  605. package/stacks/mailchimp.json +22 -6
  606. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  607. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  608. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  609. package/stacks/outreach/pages/contacts.tsx.tmpl +29 -66
  610. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  611. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  612. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  613. package/stacks/outreach/pages/sequences.tsx.tmpl +18 -0
  614. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  615. package/stacks/outreach.json +38 -9
  616. package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
  617. package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -35
  618. package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
  619. package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
  620. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  621. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  622. package/stacks/pipedrive.json +14 -2
  623. package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
  624. package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
  625. package/stacks/salesforce/pages/deals.tsx.tmpl +14 -1
  626. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  627. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  628. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  629. package/stacks/salesforce.json +17 -3
  630. package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
  631. package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
  632. package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
  633. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  634. package/stacks/zoominfo.json +36 -3
  635. package/styles/styles.css +1 -1
  636. package/templates/auth/auth-route.ts.tmpl +16 -0
  637. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  638. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  639. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  640. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  641. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  642. package/templates/auth/middleware.ts.tmpl +14 -0
  643. package/templates/next-app/README.md.tmpl +5 -0
  644. package/templates/next-app/app/globals.css.tmpl +349 -64
  645. package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
  646. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
package/README.md CHANGED
@@ -499,10 +499,10 @@ npx @graph8/sdk create mailchimp my-app # then: cd my-app && cp .env.local.exa
499
499
  | `mailchimp` | Newsletter, nurture, forms, contacts, companies, tasks, lists, suppressions |
500
500
  | `clay` | Prospect search, workbench, enrichment, lists, contacts, companies, tasks, suppressions |
501
501
  | `6sense` | Intent, visitors, competitors, companies, contacts, tasks |
502
- | `intercom` | Inbox, web chat conversations, contacts, companies, tasks, AI agents, mailboxes |
502
+ | `intercom` | Inbox, web chat conversations, contacts, companies, tasks, AI agents, mailboxes, email signatures |
503
503
  | `calendly` | Meetings and scheduling (event types, availability, booking pages) over contacts, companies and tasks |
504
- | `pipedrive` | Analytics, deals, quotes, contacts, companies, inbox, meetings, scheduling, tasks and mailboxes; deals and quotes open each other, replies can start a deal |
505
- | `outreach` | Analytics, contacts, companies, lists, sequences, campaigns, inbox, dialer, call log, meetings, scheduling, tasks, mailboxes, suppressions |
504
+ | `pipedrive` | Analytics, deals, quotes, contacts, companies, inbox, meetings, scheduling, tasks, mailboxes and email signatures; deals and quotes open each other, replies can start a deal |
505
+ | `outreach` | Analytics, contacts, companies, lists, sequences, campaigns, inbox, dialer, call log, meetings, scheduling, tasks, mailboxes, email signatures, suppressions |
506
506
  | `apollo` | Outreach's screens plus prospect search, enrichment, intent, visitors, deals and integrations; contacts get Enrich, Add to sequence and a Deals tab |
507
507
  | `gong` | Analytics, call log, coaching, dialer, meetings, scheduling, contacts, companies, tasks, lists, suppressions |
508
508
  | `salesforce` | Analytics, contacts, companies, lists, deals, quotes, lifecycle, tasks, workflows, copilot, custom objects, suppressions, integrations |
@@ -512,6 +512,25 @@ npx @graph8/sdk create mailchimp my-app # then: cd my-app && cp .env.local.exa
512
512
 
513
513
  Every available stack is proven by `npm run build && npm run e2e:create -- <stack>`, which packs the SDK, scaffolds the app with the built CLI, installs it and requires `next build` to pass. More stacks appear here as their blocks land; `create` refuses a stack that is not available yet.
514
514
 
515
+ ### Branding: your Brand Kit, automatically
516
+
517
+ If your graph8 organization already has a Brand Kit (Studio), `create` uses it. With a key in `G8_API_KEY` (environment or `.env.local`), the new app gets your organization's name, primary colour and logo without any flags; the logo is downloaded into `public/brand/` (never hot-linked), and the colour becomes `--brand` / `--primary` with button text checked for WCAG AA.
518
+
519
+ ```bash
520
+ G8_API_KEY=... npx @graph8/sdk create apollo my-app # picks up the Brand Kit
521
+ npx @graph8/sdk create apollo my-app --no-org-brand # skip it
522
+ npx @graph8/sdk create apollo my-app --brand-color "#0f62fe" # your colour wins; the kit still fills the name and logo
523
+ npx @graph8/sdk brand --from-org # brand an existing app from the kit
524
+ ```
525
+
526
+ | Brand Kit field (Developer API) | App brand input |
527
+ |---|---|
528
+ | `GET /me` `org_name` | `--name` (`g8.config.ts` `name`) |
529
+ | `GET /brand-kit` `design_tokens.colors.primary` (else `brand`, `accent`, button background) | `--brand-color` |
530
+ | `GET /brand-kit` `logo_url` (one logo; no light, dark or icon variant) | `--logo` (`public/brand/logo.*`) |
531
+
532
+ Explicit flags always win, field by field. With no key, a missing or empty kit, no `campaigns:read` scope or no network, the app keeps the default look and the CLI prints one info line; it never fails. The key is never printed or written. Fonts, tagline and voice traits are in the kit but have no brand input, so they are not applied. Icons stay Lucide.
533
+
515
534
  ## UI kit (`@graph8/sdk/ui`)
516
535
 
517
536
  The React component kit and design tokens behind the graph8 app (primitives, light and dark
@@ -10,6 +10,8 @@ Shared base every other block depends on. Install it first (the CLI does it for
10
10
  | `server/proxy.ts` | `createG8Proxy(config)`: the logic of the proxy (forwarding, allowlist, `authorize` hook). |
11
11
  | `server/route.ts` | Installs as `app/api/g8/[...path]/route.ts` and mounts the proxy. `allowedPrefixes` is generated from the installed blocks' operations; local preview is on. |
12
12
  | `server/local-key.ts`, `server/connect.ts`, `server/connect-route.ts` | Local preview key store: `app/api/g8-connect/route.ts` checks a pasted key (GET /me) and keeps it in `.g8/local-key` on the server. 404 in production. |
13
+ | `server/auth.ts` | Optional `authorize` presets (sign-in) for the proxy: `sharedPassword`, `basicAuth`, `jwtAuth`, `sessionAuth`, `allowAnonymous`, and `createG8AuthRoute`. See "Sign-in presets" below. |
14
+ | `components/auth-gate.tsx` | `G8AuthGate`, `SignInPanel`, `G8SignOutButton`: the branded sign-in screen for apps that use a preset. |
13
15
  | `components/connect-graph8.tsx` | `ConnectGraph8Panel` + `G8ConnectGate`: the first-run Connect graph8 panel in `next dev`; a no-op in production. |
14
16
  | `hooks/use-g8-query.ts` | `useG8Query(fetcher, deps)` -> `{ data, error, loading, refetch }`. Latest request wins. |
15
17
  | `hooks/use-g8-mutation.ts` | `useG8Mutation(fn)` -> `{ mutate, mutateAsync, data, error, loading, reset }`. |
@@ -122,6 +124,51 @@ Connect graph8 panel, which the server checks against `GET /me` and stores in `.
122
124
  `npx @graph8/sdk doctor --production` fails until the route is safe to deploy;
123
125
  `npx @graph8/sdk doctor --fix-prefixes` regenerates `allowedPrefixes` after you add or remove blocks.
124
126
 
127
+ ### Sign-in presets (optional): going live with `authorize`
128
+
129
+ `authorize` is any function `(request) => Response | void`; your own session check is always fine. For the
130
+ common cases `server/auth.ts` ships presets, and `npx @graph8/sdk create <stack> <dir> --auth <preset>` wires one
131
+ up (g8.auth.ts, the route's `authorize`, the sign-in screen, README and `.env.local.example`). Nothing changes for
132
+ apps that do not use one: without `authorize` the proxy behaves exactly as described above.
133
+
134
+ | Preset | What it checks | Server environment |
135
+ |---|---|---|
136
+ | `sharedPassword()` | one team password; signs an HttpOnly, SameSite=Lax session cookie (HMAC-SHA-256, 7 days); sign-in screen | `G8_APP_PASSWORD` (12+), `G8_SESSION_SECRET` (32+) |
137
+ | `basicAuth()` | HTTP Basic auth; pair with a `middleware.ts` so every page asks; refuses cross-site writes; 20 wrong tries per client per 15 min | `G8_BASIC_AUTH` (`user:pass,user2:pass2`; passwords 12+ characters, no commas) |
138
+ | `jwtAuth()` | a JWT from your OIDC provider (JWKS: RS/PS/ES256 family, discovered from the issuer) or an HS256 secret; exp, nbf, iss, aud; optional `claims` check | `G8_JWT_ISSUER`, `G8_JWT_JWKS_URL`, `G8_JWT_AUDIENCE`, `G8_JWT_COOKIE` or a Bearer header, `G8_JWT_SECRET` |
139
+ | `sessionAuth(fn)` | your session library: return the user to allow (Clerk `auth()`, NextAuth `auth()`, Supabase `getUser()`) | `G8_SIGN_IN_URL`, `G8_SIGN_OUT_URL` (for the screen) |
140
+ | `allowAnonymous()` | nothing: anyone who can reach the app uses the key. Logs a warning; `doctor` flags it | |
141
+
142
+ Wire one by hand in an existing app:
143
+
144
+ ```ts
145
+ // g8.auth.ts
146
+ import { sharedPassword } from "@/components/g8/_foundation/server/auth";
147
+ export const appAuth = sharedPassword();
148
+
149
+ // app/api/g8/[...path]/route.ts: inside createG8Proxy({ ... })
150
+ authorize: appAuth.authorize,
151
+
152
+ // app/api/g8-auth/route.ts (the sign-in screen's server side; password, jwt and session presets)
153
+ import { createG8AuthRoute } from "@/components/g8/_foundation/server/auth";
154
+ import { appAuth } from "@/g8.auth";
155
+ export const { GET, POST, DELETE } = createG8AuthRoute(appAuth);
156
+
157
+ // app/layout.tsx: wrap the shell (BrandProvider keeps the screen on brand)
158
+ <BrandProvider brand={brand}><G8AuthGate><AppShell headerSlot={<G8SignOutButton />}>...</AppShell></G8AuthGate></BrandProvider>
159
+ ```
160
+
161
+ Security: passwords and secrets are compared in constant time; cookie and Basic presets refuse cross-site writes (CSRF:
162
+ `Sec-Fetch-Site`/`Origin`); JWTs with alg `none`, an algorithm outside the allow-list, a key of the wrong type
163
+ (no HS/RS confusion), `crit` headers or no `exp` are refused; sign-in attempts are rate limited per client (in
164
+ memory: best effort on serverless, so pick a long password). `sharedPassword` also caps WRONG passwords from all
165
+ clients together (`maxFailuresGlobal`, default 100 per 15 min): anyone who can reach the sign-in route can keep
166
+ NEW sign-ins paused that way (existing sessions keep working); with a long random password raise it or pass
167
+ `Infinity`. `jwtAuth` with a JWKS and no audience accepts any token the issuer minted for other apps on the same
168
+ tenant: set `G8_JWT_AUDIENCE` or a `claims` check (it warns at startup, and `doctor` warns). `sharedPassword` and
169
+ `basicAuth` read the environment on every request; `jwtAuth` and `sessionAuth` once at startup. A missing setting is a 500 that names the variable,
170
+ never its value. The sign-in screen is UX only: the proxy's `authorize` is the gate.
171
+
125
172
  What the proxy does on its own: rejects `..`, encoded slashes and empty segments (400); forwards only
126
173
  `content-type`, `accept`, `accept-language` and `idempotency-key` (never the browser's cookies or
127
174
  `Authorization`); strips hop-by-hop headers, `set-cookie` and `location` from the response; does not follow
@@ -238,6 +285,20 @@ Rules the kit encodes, so screens stay consistent:
238
285
  - Loading is visible and distinct from empty: `StatStrip loading`, `Skeleton` rows.
239
286
  - Dates show short text; the full timestamp is the `title` of a `<time dateTime>`.
240
287
 
288
+ ## Long lists: paging, notices and filters (no silent truncation)
289
+
290
+ A list read that sends a `limit` must page or say it stopped. The foundation ships the pieces:
291
+
292
+ | Need | Use |
293
+ |---|---|
294
+ | Show rows with a "Load more" button (the API pages by `page` or `offset`) | `useLoadMoreList(byPage(size, read))` or `byOffset(size, read)` (`lib/paging.ts`), then `<LoadMore>` under the rows |
295
+ | The API has only a `limit` (no paging) | `<FirstNotice shown={rows.length} limit={LIMIT} noun="mailboxes" />`: "Showing the first N ..." appears only when the read came back full |
296
+ | A screen needs EVERY row (a lookup by id, a full dropdown) | `useAllPages(read, deps)`: reads page after page, reports `truncated` if the page cap cut it short |
297
+ | Choose one audience list | crm's `ListSelect`: a native select with server search and "Load more lists" |
298
+ | Filters for a list screen | `useUrlFilters(keys, { prefix })` keeps them in the URL (`c_email=...`); `TextFilter`, `SelectFilter`, `RangeFilter` and `MoreFilters` (a "More filters" disclosure) go in the Toolbar `filters` slot |
299
+
300
+ `tests/blocks/list-paging-guard.test.ts` in the SDK repo fails on a new `g8Get(..., { limit })` read that does none of these.
301
+
241
302
  ## Prerequisites
242
303
 
243
304
  - `G8_API_KEY` in the server env; verify with `recipes/verify-proxy-key.ts`.
@@ -9,6 +9,7 @@
9
9
  "spend/send/delete confirmation dialog",
10
10
  "screen kit: layout, stats, split view, detail panel, toolbar, status pills, avatars, cards, form sheet, charts",
11
11
  "local preview: a Connect graph8 screen stores the server's key for `next dev`; generated allowedPrefixes",
12
+ "go live: optional sign-in presets for the proxy's authorize (team password, Basic, OIDC/JWT, Clerk/NextAuth/Supabase sessions) and a branded sign-in screen",
12
13
  "white-label: BrandProvider + useBrand give every block's copy and link-outs your platform name and app URL (default graph8)"
13
14
  ],
14
15
  "dependsOn": [],
@@ -149,12 +150,40 @@
149
150
  "operations": [],
150
151
  "tiers": []
151
152
  },
153
+ {
154
+ "name": "G8AuthGate",
155
+ "file": "components/auth-gate.tsx",
156
+ "purpose": "Sign-in for apps with an auth preset (create --auth): G8AuthGate shows SignInPanel (team password form, provider redirect, or the missing server setting) until the visitor is signed in, G8SignOutButton for the header. Talks only to the app's /api/g8-auth route; renders the app unchanged when that route does not exist.",
157
+ "operations": [],
158
+ "tiers": []
159
+ },
152
160
  {
153
161
  "name": "BrandProvider",
154
162
  "file": "components/brand-provider.tsx",
155
163
  "purpose": "White-label context: the app's name, logo, and what the platform is called and where it lives, for every block's copy and link-outs. Fields left out stay graph8's.",
156
164
  "operations": [],
157
165
  "tiers": []
166
+ },
167
+ {
168
+ "name": "ListSelect",
169
+ "file": "components/list-select.tsx",
170
+ "purpose": "Audience-list dropdown, searchable and paged (native select + server search + Load more). It owns no API call: the block that uses it passes a `read` (crm ships ListSelect with the /lists read wired in).",
171
+ "operations": [],
172
+ "tiers": []
173
+ },
174
+ {
175
+ "name": "FirstNotice",
176
+ "file": "components/paging-notices.tsx",
177
+ "purpose": "FirstNotice (\"Showing the first N ...\" when a read without paging came back full) and LoadMore (\"Showing N\" + Load more): the two honest endings of a capped list.",
178
+ "operations": [],
179
+ "tiers": []
180
+ },
181
+ {
182
+ "name": "TextFilter",
183
+ "file": "components/screen/filter-controls.tsx",
184
+ "purpose": "Toolbar filter controls: TextFilter, SelectFilter, RangeFilter (from/to) and MoreFilters (a disclosure that opens by itself when a hidden filter is set).",
185
+ "operations": [],
186
+ "tiers": []
158
187
  }
159
188
  ],
160
189
  "hooks": [
@@ -192,6 +221,21 @@
192
221
  "name": "useBrand",
193
222
  "file": "hooks/use-brand.ts",
194
223
  "purpose": "The current brand ({ name, appUrl, linkLabel, logo }) from the nearest BrandProvider, else graph8's defaults; brandUrl(brand, path) builds a link-out."
224
+ },
225
+ {
226
+ "name": "useAllPages",
227
+ "file": "hooks/use-all-pages.ts",
228
+ "purpose": "Read every page of a list a screen needs in full (lookups, full dropdowns), up to a page cap, and report when the cap cut it short."
229
+ },
230
+ {
231
+ "name": "useLoadMoreList",
232
+ "file": "hooks/use-load-more-list.ts",
233
+ "purpose": "A list read with Load more: the shape of a useG8Query list (data, loading, error, refetch) plus hasMore / loadMore. Pair with byPage / byOffset and the LoadMore notice."
234
+ },
235
+ {
236
+ "name": "useUrlFilters",
237
+ "file": "hooks/use-url-filters.ts",
238
+ "purpose": "List-screen filters kept in the URL (debounced, prefixed, SSR-safe): draft for the inputs, query for the read, clear()."
195
239
  }
196
240
  ],
197
241
  "lib": [
@@ -218,6 +262,14 @@
218
262
  {
219
263
  "file": "lib/plural.ts",
220
264
  "purpose": "plural(n, 'contact') -> '1 contact' / '2 contacts' (and pluralWord): one correct plural helper for titles, counts and bulk dialogs."
265
+ },
266
+ {
267
+ "file": "lib/paging.ts",
268
+ "purpose": "byPage / byOffset adapt page-number and offset lists to usePaginated's cursor shape; hasNextOf reads the API's has_next / next_cursor."
269
+ },
270
+ {
271
+ "file": "lib/use-debounced-value.ts",
272
+ "purpose": "useDebounced: a value after it stopped changing (foundation-local copy)."
221
273
  }
222
274
  ],
223
275
  "server": [
@@ -226,6 +278,11 @@
226
278
  "purpose": "createG8Proxy(): forwards browser calls to the graph8 API with the server-side key, a required path allowlist and authorize hook (it refuses to run without them) and a server-side org hook.",
227
279
  "target": "g8/server/proxy.ts"
228
280
  },
281
+ {
282
+ "file": "server/auth.ts",
283
+ "purpose": "Optional authorize presets for the proxy: sharedPassword (signed session cookie + sign-in route), basicAuth, jwtAuth (OIDC JWKS or HS256), sessionAuth (Clerk/NextAuth/Supabase adapter), allowAnonymous; createG8AuthRoute() backs the sign-in screen. Web APIs only (Node and Edge).",
284
+ "target": "g8/server/auth.ts"
285
+ },
229
286
  {
230
287
  "file": "server/local-key.ts",
231
288
  "purpose": "Node only: the local-preview key file (.g8/local-key, 0600, self-gitignored) and resolveApiKey() (environment first, then the file outside production).",
@@ -0,0 +1,363 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Sign-in for a created app that uses an auth preset (`create --auth <preset>`, g8/server/auth.ts). The proxy
5
+ * enforces sign-in on the server; this is the screen people see instead of empty, refused data.
6
+ *
7
+ * Calls this app's own `/api/g8-auth` route (createG8AuthRoute), never graph8:
8
+ * GET the session status: signed in or not, which preset, the sign-in URL, or what the server is missing
9
+ * POST { password } (shared-password preset): sets an HttpOnly session cookie
10
+ * DELETE sign out (shared-password preset)
11
+ * No billable, external or destructive graph8 operation. The password goes from the input to the server once
12
+ * and is never kept in browser state after submit, storage or the URL.
13
+ *
14
+ * Exports:
15
+ * - G8AuthGate: wraps the app. Shows a loading screen while it checks, SignInPanel when signed out, the app when
16
+ * signed in. Re-checks when the tab becomes visible again. If /api/g8-auth does not exist it renders the
17
+ * app unchanged (apps without a preset).
18
+ * - SignInPanel: the sign-in screen (password form, provider redirect, or the setup problem). No required props.
19
+ * - G8SignOutButton: a header button (AppShell `headerSlot`); renders nothing outside a signed-in gate.
20
+ * - useG8Auth: { status, signOut, refresh } inside the gate, else null.
21
+ * The app's name and logo come from the nearest BrandProvider (g8.config.ts), the colours from the theme tokens
22
+ * (the Brand Kit region in globals.css), so the screen is on brand without edits.
23
+ */
24
+
25
+ import { createContext, useCallback, useContext, useEffect, useId, useRef, useState, type FormEvent, type ReactNode } from "react";
26
+ import { AlertTriangle, ArrowRight, Eye, EyeOff, Loader2, LockKeyhole, LogIn, LogOut, RefreshCw } from "lucide-react";
27
+ import { Button, Input, cn } from "@graph8/sdk/ui";
28
+ import { useBrand } from "../hooks/use-brand";
29
+
30
+ export const AUTH_PATH = "/api/g8-auth";
31
+
32
+ export type AuthKind = "password" | "basic" | "jwt" | "session" | "anonymous";
33
+
34
+ export interface AuthStatus {
35
+ kind: AuthKind;
36
+ signedIn: boolean;
37
+ signInUrl?: string;
38
+ signOutUrl?: string;
39
+ problem?: string;
40
+ }
41
+
42
+ /** The status, null when the route does not exist (no preset), or "error" when the server could not be reached. */
43
+ export async function fetchAuthStatus(): Promise<AuthStatus | null | "error"> {
44
+ try {
45
+ const res = await fetch(AUTH_PATH, { headers: { accept: "application/json" }, cache: "no-store", credentials: "same-origin" });
46
+ if (res.status === 404) return null;
47
+ if (!res.ok) return "error";
48
+ const body = (await res.json().catch(() => null)) as AuthStatus | null;
49
+ return body && typeof body.signedIn === "boolean" ? body : "error";
50
+ } catch {
51
+ return "error";
52
+ }
53
+ }
54
+
55
+ interface AuthContextValue {
56
+ status: AuthStatus;
57
+ signOut: () => Promise<void>;
58
+ refresh: () => Promise<void>;
59
+ }
60
+
61
+ const AuthContext = createContext<AuthContextValue | null>(null);
62
+
63
+ /** Inside G8AuthGate: the session and its actions. Null elsewhere. */
64
+ export function useG8Auth(): AuthContextValue | null {
65
+ return useContext(AuthContext);
66
+ }
67
+
68
+ /** Only same-app paths or http(s) URLs are followed; anything else (javascript:, data:) is dropped. */
69
+ function safeUrl(url: string | undefined): string | undefined {
70
+ if (!url) return undefined;
71
+ if (url.startsWith("/") && !url.startsWith("//")) return url;
72
+ return /^https?:\/\//i.test(url) ? url : undefined;
73
+ }
74
+
75
+ function Frame({ children, labelledBy }: { children: ReactNode; labelledBy?: string }) {
76
+ return (
77
+ <main aria-labelledby={labelledBy} className="flex min-h-dvh items-center justify-center bg-background px-4 py-12">
78
+ <div className="w-full max-w-sm">{children}</div>
79
+ </main>
80
+ );
81
+ }
82
+
83
+ function BrandMark() {
84
+ const brand = useBrand();
85
+ return (
86
+ <div className="flex items-center gap-2.5">
87
+ {brand.logo ? (
88
+ // eslint-disable-next-line @next/next/no-img-element
89
+ <img src={brand.logo} alt="" className="size-8 rounded-md object-contain" />
90
+ ) : (
91
+ <span aria-hidden className="flex size-8 items-center justify-center rounded-md bg-primary text-sm font-medium text-primary-foreground">
92
+ {brand.name.trim().charAt(0).toUpperCase() || "A"}
93
+ </span>
94
+ )}
95
+ <span className="truncate text-sm font-medium text-foreground">{brand.name}</span>
96
+ </div>
97
+ );
98
+ }
99
+
100
+ function ErrorLine({ id, children }: { id?: string; children: ReactNode }) {
101
+ return (
102
+ <p id={id} role="alert" className="flex items-start gap-1.5 text-xs leading-5 text-error-foreground">
103
+ <AlertTriangle aria-hidden className="mt-0.5 size-3.5 shrink-0" strokeWidth={1.5} />
104
+ <span>{children}</span>
105
+ </p>
106
+ );
107
+ }
108
+
109
+ function PasswordForm({ onSignedIn }: { onSignedIn: (s: AuthStatus) => void }) {
110
+ const [password, setPassword] = useState("");
111
+ const [show, setShow] = useState(false);
112
+ const [busy, setBusy] = useState(false);
113
+ const [error, setError] = useState<string | null>(null);
114
+ const id = useId();
115
+
116
+ const submit = async (e: FormEvent) => {
117
+ e.preventDefault();
118
+ if (!password || busy) return;
119
+ setBusy(true);
120
+ setError(null);
121
+ try {
122
+ const res = await fetch(AUTH_PATH, { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ password }) });
123
+ const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;
124
+ setPassword("");
125
+ if (res.ok && body.signedIn === true) onSignedIn(body as unknown as AuthStatus);
126
+ else setError(typeof body.message === "string" ? body.message : `Could not sign in (HTTP ${res.status}).`);
127
+ } catch {
128
+ setError("Could not reach this app's server. Check your connection and try again.");
129
+ } finally {
130
+ setBusy(false);
131
+ }
132
+ };
133
+
134
+ return (
135
+ <form onSubmit={submit} className="flex flex-col gap-3" noValidate>
136
+ <div className="flex flex-col gap-1.5">
137
+ <label htmlFor={`${id}-pw`} className="text-sm font-medium text-foreground">
138
+ Password
139
+ </label>
140
+ <div className="relative">
141
+ <Input
142
+ id={`${id}-pw`}
143
+ type={show ? "text" : "password"}
144
+ autoComplete="current-password"
145
+ autoFocus
146
+ value={password}
147
+ onChange={(e) => setPassword(e.target.value)}
148
+ className="pr-10"
149
+ aria-invalid={error ? true : undefined}
150
+ aria-describedby={error ? `${id}-err` : undefined}
151
+ />
152
+ <button
153
+ type="button"
154
+ onClick={() => setShow((v) => !v)}
155
+ aria-label={show ? "Hide password" : "Show password"}
156
+ aria-pressed={show}
157
+ className="absolute inset-y-0 right-0 flex w-10 items-center justify-center rounded-r-md text-text-color-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
158
+ >
159
+ {show ? <EyeOff aria-hidden className="size-4" strokeWidth={1.5} /> : <Eye aria-hidden className="size-4" strokeWidth={1.5} />}
160
+ </button>
161
+ </div>
162
+ </div>
163
+ {error ? <ErrorLine id={`${id}-err`}>{error}</ErrorLine> : null}
164
+ <Button type="submit" className="w-full" disabled={!password || busy}>
165
+ {busy ? <Loader2 aria-hidden className="size-4 animate-spin" /> : <ArrowRight aria-hidden className="size-4" strokeWidth={1.5} />}
166
+ {busy ? "Signing in" : "Sign in"}
167
+ </Button>
168
+ </form>
169
+ );
170
+ }
171
+
172
+ export interface SignInPanelProps {
173
+ /** Status from a previous GET (the gate passes it); fetched on mount when absent. */
174
+ status?: AuthStatus | null;
175
+ /** Called after a successful sign-in. */
176
+ onSignedIn?: (status: AuthStatus) => void;
177
+ className?: string;
178
+ }
179
+
180
+ /** The sign-in screen: a password form, a "Continue to sign in" link to your provider, or what the server lacks. */
181
+ export function SignInPanel({ status: initial, onSignedIn, className }: SignInPanelProps) {
182
+ const [status, setStatus] = useState<AuthStatus | null | undefined>(initial);
183
+ const brand = useBrand();
184
+ const titleId = useId();
185
+
186
+ useEffect(() => {
187
+ if (initial === undefined) void fetchAuthStatus().then((s) => setStatus(s === "error" ? null : s));
188
+ }, [initial]);
189
+
190
+ const signInUrl = safeUrl(status?.signInUrl);
191
+ const problem = status?.problem;
192
+
193
+ return (
194
+ <section aria-labelledby={titleId} className={cn("flex flex-col gap-6", className)}>
195
+ <BrandMark />
196
+ <div className="flex flex-col gap-1.5">
197
+ <div aria-hidden className="mb-1 flex size-10 items-center justify-center rounded-lg border border-border bg-muted text-foreground">
198
+ {problem ? <AlertTriangle className="size-5" strokeWidth={1.5} /> : <LockKeyhole className="size-5" strokeWidth={1.5} />}
199
+ </div>
200
+ <h1 id={titleId} className="text-lg font-medium text-foreground">
201
+ {problem ? "Sign-in is not set up yet" : `Sign in to ${brand.name}`}
202
+ </h1>
203
+ <p className="text-sm leading-5 text-text-color-muted">
204
+ {problem
205
+ ? "This app asks people to sign in before it shows any data, but its server is missing a setting."
206
+ : status?.kind === "password"
207
+ ? "Enter the team password to continue."
208
+ : "Use your account to continue."}
209
+ </p>
210
+ </div>
211
+
212
+ {status === undefined ? (
213
+ <div className="flex h-10 items-center gap-2 text-sm text-text-color-muted" role="status">
214
+ <Loader2 aria-hidden className="size-4 animate-spin" />
215
+ Checking your session
216
+ </div>
217
+ ) : problem ? (
218
+ <div className="rounded-lg border border-border bg-muted/40 p-3 text-xs leading-5 text-foreground">
219
+ <p>{problem}</p>
220
+ <p className="mt-2 text-text-color-muted">The settings go in the server environment (.env.local while developing), never in NEXT_PUBLIC_ variables.</p>
221
+ </div>
222
+ ) : status?.kind === "password" ? (
223
+ <PasswordForm
224
+ onSignedIn={(s) => {
225
+ setStatus(s);
226
+ onSignedIn?.(s);
227
+ }}
228
+ />
229
+ ) : signInUrl ? (
230
+ <Button asChild className="w-full">
231
+ <a href={signInUrl}>
232
+ <LogIn aria-hidden className="size-4" strokeWidth={1.5} />
233
+ Continue to sign in
234
+ </a>
235
+ </Button>
236
+ ) : (
237
+ <p className="rounded-lg border border-border p-3 text-xs leading-5 text-text-color-muted">
238
+ Sign in through your identity provider, then reload this page. To show a sign-in button here, set G8_SIGN_IN_URL in the server environment.
239
+ </p>
240
+ )}
241
+ </section>
242
+ );
243
+ }
244
+
245
+ export interface G8AuthGateProps {
246
+ children: ReactNode;
247
+ }
248
+
249
+ /**
250
+ * Shows SignInPanel instead of the app until the visitor is signed in. Renders the app unchanged when the app has
251
+ * no /api/g8-auth route. The server (the proxy's `authorize`) is the real gate; this only decides what to show.
252
+ */
253
+ export function G8AuthGate({ children }: G8AuthGateProps) {
254
+ const [status, setStatus] = useState<AuthStatus | null | "error" | undefined>(undefined);
255
+ const [retrying, setRetrying] = useState(false);
256
+ const titleId = useId();
257
+ const latest = useRef(0);
258
+
259
+ const refresh = useCallback(async () => {
260
+ const n = ++latest.current;
261
+ const next = await fetchAuthStatus();
262
+ if (n === latest.current) setStatus(next);
263
+ }, []);
264
+
265
+ useEffect(() => {
266
+ void refresh();
267
+ const onVisible = () => {
268
+ if (document.visibilityState === "visible") void refresh();
269
+ };
270
+ document.addEventListener("visibilitychange", onVisible);
271
+ return () => document.removeEventListener("visibilitychange", onVisible);
272
+ }, [refresh]);
273
+
274
+ const signOut = useCallback(async () => {
275
+ const current = typeof status === "object" ? status : null;
276
+ const out = safeUrl(current?.signOutUrl);
277
+ if (current?.kind !== "password") {
278
+ if (out) window.location.assign(out);
279
+ return;
280
+ }
281
+ await fetch(AUTH_PATH, { method: "DELETE", credentials: "same-origin" }).catch(() => undefined);
282
+ latest.current++;
283
+ setStatus({ ...current, signedIn: false });
284
+ }, [status]);
285
+
286
+ if (status === null) return <>{children}</>;
287
+ if (status === undefined) {
288
+ return (
289
+ <Frame>
290
+ <div role="status" className="flex flex-col items-center gap-3 text-sm text-text-color-muted">
291
+ <Loader2 aria-hidden className="size-5 animate-spin" />
292
+ Checking your session
293
+ </div>
294
+ </Frame>
295
+ );
296
+ }
297
+ if (status === "error") {
298
+ return (
299
+ <Frame labelledBy={titleId}>
300
+ <div className="flex flex-col gap-4">
301
+ <BrandMark />
302
+ <h1 id={titleId} className="text-lg font-medium text-foreground">
303
+ Could not check your session
304
+ </h1>
305
+ <p className="text-sm leading-5 text-text-color-muted">This app&apos;s server did not answer. Check your connection and try again.</p>
306
+ <Button
307
+ type="button"
308
+ variant="outline"
309
+ className="w-fit"
310
+ disabled={retrying}
311
+ onClick={async () => {
312
+ setRetrying(true);
313
+ await refresh();
314
+ setRetrying(false);
315
+ }}
316
+ >
317
+ {retrying ? <Loader2 aria-hidden className="size-4 animate-spin" /> : <RefreshCw aria-hidden className="size-4" strokeWidth={1.5} />}
318
+ Try again
319
+ </Button>
320
+ </div>
321
+ </Frame>
322
+ );
323
+ }
324
+ if (!status.signedIn) {
325
+ return (
326
+ <Frame>
327
+ <SignInPanel
328
+ status={status}
329
+ onSignedIn={(s) => {
330
+ latest.current++;
331
+ setStatus(s);
332
+ }}
333
+ />
334
+ </Frame>
335
+ );
336
+ }
337
+ return <AuthContext.Provider value={{ status, signOut, refresh }}>{children}</AuthContext.Provider>;
338
+ }
339
+
340
+ /** Sign out, for the AppShell header. Nothing outside a signed-in G8AuthGate, or when sign-out lives at your provider without a G8_SIGN_OUT_URL. */
341
+ export function G8SignOutButton({ className }: { className?: string }) {
342
+ const auth = useG8Auth();
343
+ const [busy, setBusy] = useState(false);
344
+ if (!auth || !auth.status.signedIn) return null;
345
+ if (auth.status.kind !== "password" && !safeUrl(auth.status.signOutUrl)) return null;
346
+ return (
347
+ <Button
348
+ type="button"
349
+ variant="ghost"
350
+ size="sm"
351
+ className={className}
352
+ disabled={busy}
353
+ onClick={async () => {
354
+ setBusy(true);
355
+ await auth.signOut();
356
+ setBusy(false);
357
+ }}
358
+ >
359
+ {busy ? <Loader2 aria-hidden className="size-4 animate-spin" /> : <LogOut aria-hidden className="size-4" strokeWidth={1.5} />}
360
+ Sign out
361
+ </Button>
362
+ );
363
+ }
@@ -39,6 +39,13 @@ const TIER_COPY: Record<ConfirmTier, { badge: string; confirm: string; note: str
39
39
  };
40
40
 
41
41
  /** True for the tiers an app should confirm before calling. read/write never need it. */
42
+ /** The tier badge: ember for what cannot be undone, ochre for spend and outreach. Token classes only. */
43
+ const TIER_BADGE: Record<ConfirmTier, string> = {
44
+ billable: "bg-warning text-warning-foreground",
45
+ external: "bg-warning text-warning-foreground",
46
+ destructive: "bg-error text-error-foreground",
47
+ };
48
+
42
49
  export function tierRequiresConfirmation(tier: string): tier is ConfirmTier {
43
50
  return tier === "billable" || tier === "external" || tier === "destructive";
44
51
  }
@@ -83,7 +90,7 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
83
90
  showCloseButton={false}
84
91
  data-tier={tier}
85
92
  style={interactive}
86
- className={`max-w-md sm:max-w-md bg-white p-5 shadow-xl ${props.className ?? ""}`}
93
+ className={`max-w-md gap-4 border-border bg-background p-5 shadow-xl sm:max-w-md ${props.className ?? ""}`}
87
94
  onOpenAutoFocus={(event) => {
88
95
  event.preventDefault();
89
96
  openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -98,15 +105,15 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
98
105
  onInteractOutside={(event) => event.preventDefault()}
99
106
  onEscapeKeyDown={(event) => confirming && event.preventDefault()}
100
107
  >
101
- <span className="inline-block w-fit rounded bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700">
108
+ <span className={`inline-flex h-5 w-fit items-center rounded-full px-2 text-[11px] font-medium leading-none ${TIER_BADGE[tier]}`}>
102
109
  {copy.badge}
103
110
  </span>
104
- <DialogTitle className="text-base font-semibold text-gray-900">{title}</DialogTitle>
111
+ <DialogTitle className="text-base font-medium leading-6 text-foreground">{title}</DialogTitle>
105
112
  <DialogDescription asChild>
106
- <div className="space-y-2 text-sm text-gray-600">
113
+ <div className="space-y-2 text-sm leading-5 text-muted-foreground">
107
114
  {description ? <div>{description}</div> : null}
108
115
  {summary.length > 0 ? (
109
- <ul className="list-disc pl-5 text-gray-900">
116
+ <ul className="list-disc space-y-0.5 pl-5 tabular-nums text-foreground">
110
117
  {summary.map((line) => (
111
118
  <li key={line}>{line}</li>
112
119
  ))}
@@ -115,14 +122,14 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
115
122
  <p>{copy.note}</p>
116
123
  </div>
117
124
  </DialogDescription>
118
- <div className="flex justify-end gap-2">
125
+ <div className="flex justify-end gap-2 pt-1">
119
126
  <button
120
127
  ref={cancelRef}
121
128
  type="button"
122
129
  disabled={confirming}
123
130
  onClick={onCancel}
124
131
  style={interactive}
125
- className="rounded border border-gray-300 px-3 py-1.5 text-sm text-gray-700 disabled:opacity-50"
132
+ className="inline-flex h-8 items-center rounded-md border border-border bg-background px-3 text-sm font-medium text-foreground transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
126
133
  >
127
134
  {props.cancelLabel ?? "Cancel"}
128
135
  </button>
@@ -131,7 +138,7 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
131
138
  disabled={confirming}
132
139
  onClick={onConfirm}
133
140
  style={interactive}
134
- className="rounded bg-gray-900 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50"
141
+ className={`inline-flex h-8 items-center rounded-md px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:opacity-50 ${tier === "destructive" ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "bg-primary text-primary-foreground hover:bg-primary/90"}`}
135
142
  >
136
143
  {confirming ? "Working..." : (props.confirmLabel ?? copy.confirm)}
137
144
  </button>