@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
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Small helpers for the newsletter management components (settings, versions, list syncs, templates).
5
+ * Responses for these endpoints are open dicts in the contract, so every reader accepts what is present.
6
+ */
7
+ import type { PaginatedResponse } from "@graph8/sdk";
8
+
9
+ export type Rec = Record<string, unknown>;
10
+
11
+ export function asRec(value: unknown): Rec {
12
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Rec) : {};
13
+ }
14
+
15
+ export function str(value: unknown, fallback = ""): string {
16
+ return typeof value === "string" ? value : value == null ? fallback : String(value);
17
+ }
18
+
19
+ export function num(value: unknown): number | undefined {
20
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
21
+ }
22
+
23
+ /** The rows of a list payload: a bare array, or the first of `keys` (then `items`) that holds one. */
24
+ export function rowsOf<T = Rec>(data: unknown, ...keys: string[]): T[] {
25
+ if (Array.isArray(data)) return data as T[];
26
+ const rec = asRec(data);
27
+ for (const key of [...keys, "items"]) if (Array.isArray(rec[key])) return rec[key] as T[];
28
+ return [];
29
+ }
30
+
31
+ /** The fields of `draft` that differ from `saved`: the body of a partial update. Strings are compared trimmed. */
32
+ export function changedFields<T extends Record<string, string | boolean>>(saved: T, draft: T): Partial<T> {
33
+ const out: Partial<T> = {};
34
+ for (const key of Object.keys(draft) as Array<keyof T>) {
35
+ const a = saved[key];
36
+ const b = draft[key];
37
+ if (typeof b === "string" ? b.trim() !== String(a).trim() : b !== a) out[key] = (typeof b === "string" ? b.trim() : b) as T[keyof T];
38
+ }
39
+ return out;
40
+ }
41
+
42
+ /** True for a bare hostname such as updates.acme.com (no scheme, path or spaces). */
43
+ export function isDomain(raw: string): boolean {
44
+ return /^(?=.{4,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test(raw.trim());
45
+ }
46
+
47
+ /** The DNS records a verify-domain answer lists, under whichever key it used. */
48
+ export function dnsRecordsOf(data: unknown): Rec[] {
49
+ return rowsOf<Rec>(data, "dns_records", "records");
50
+ }
51
+
52
+ /** A version row's number, under whichever key it used. */
53
+ export function versionNumber(v: Rec): number | undefined {
54
+ return num(v.version_num) ?? num(v.version) ?? num(v.number) ?? num(v.id);
55
+ }
56
+
57
+ /** Wrap rows and the API's "next" marker (a cursor or an offset; null or absent when there is no more) for `usePaginated`. */
58
+ export function pageOf<T>(rows: T[], next: unknown): PaginatedResponse<T> {
59
+ return { data: rows, pagination: { next_cursor: next == null || next === "" ? null : String(next) } } as unknown as PaginatedResponse<T>;
60
+ }
@@ -17,6 +17,9 @@
17
17
  import { useState } from "react";
18
18
  import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
19
19
  import { ApiError } from "@/g8/components/api-error";
20
+ import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
21
+ import { byPage, hasNextOf } from "@/g8/lib/paging";
22
+ import { LoadMore } from "@/g8/components/paging-notices";
20
23
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
21
24
  import { EmptyState } from "@/g8/components/empty-state";
22
25
  import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
@@ -40,7 +43,11 @@ export function NurtureBuilder({ onSelectNurture, pageSize = 50 }: NurtureBuilde
40
43
  const [form, setForm] = useState<{ nurture?: Nurture } | null>(null);
41
44
  const [toArchive, setToArchive] = useState<Nurture | null>(null);
42
45
  const [toResume, setToResume] = useState<Nurture | null>(null);
43
- const list = useG8Query((signal) => g8Get<{ nurtures?: Nurture[] }>("/nurtures", { page: 1, limit: pageSize }, { signal }).then((r) => r.data?.nurtures ?? []), [pageSize]);
46
+ const list = useLoadMoreList(byPage<Nurture>(pageSize, async (page, signal) => {
47
+ const r = await g8Get<{ nurtures?: Nurture[] }>("/nurtures", { page, limit: pageSize }, { signal });
48
+ const rows = r.data?.nurtures ?? [];
49
+ return { rows, hasNext: hasNextOf(r, pageSize, rows.length) };
50
+ }), [pageSize]);
44
51
  const [busy, setBusy] = useState<string | null>(null);
45
52
  const act = useG8Mutation((a: Action) => g8Post(`/nurtures/${encodeURIComponent(a.nurture.id)}/${a.kind}`, a.kind === "duplicate" ? {} : undefined), { onSuccess: () => void list.refetch() });
46
53
  const run = async (a: Action): Promise<void> => {
@@ -65,7 +72,7 @@ export function NurtureBuilder({ onSelectNurture, pageSize = 50 }: NurtureBuilde
65
72
  return (
66
73
  <div className="grid gap-3">
67
74
  <div className="flex items-center justify-between">
68
- <h2 className="text-lg font-semibold">Nurtures</h2>
75
+ <h2 className="text-lg font-medium">Nurtures</h2>
69
76
  <Button onClick={() => setForm({})}>New nurture</Button>
70
77
  </div>
71
78
  {act.error ? <ApiError error={act.error} title="That did not work" /> : null}
@@ -91,6 +98,7 @@ export function NurtureBuilder({ onSelectNurture, pageSize = 50 }: NurtureBuilde
91
98
  ))}
92
99
  </ul>
93
100
  )}
101
+ <LoadMore shown={nurtures.length} noun="nurtures" hasMore={list.hasMore} loading={list.loadingMore} onLoadMore={list.loadMore} />
94
102
  <ConfirmSpendDialog
95
103
  open={toResume !== null}
96
104
  tier="external"
@@ -17,13 +17,15 @@
17
17
  import { useEffect, useState } from "react";
18
18
  import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
19
19
  import { ApiError } from "@/g8/components/api-error";
20
+ import { useAllLists } from "@/g8/crm/hooks/use-all-lists";
21
+ import { ListSelect } from "@/g8/crm/components/list-select";
20
22
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
21
23
  import { EmptyState } from "@/g8/components/empty-state";
22
24
  import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
23
25
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
24
26
  import { useG8Query } from "@/g8/hooks/use-g8-query";
25
27
  import { isDrafted, isNurtureMissing, isNurtureOff, statusOf } from "../lib/nurture-types";
26
- import type { ListOption, Nurture } from "../lib/nurture-types";
28
+ import type { Nurture } from "../lib/nurture-types";
27
29
  import { pluralWord } from "@/g8/lib/plural";
28
30
  import { useBrand } from "@/g8/hooks/use-brand";
29
31
 
@@ -38,7 +40,7 @@ export function NurtureEnroll({ nurtureId, contactIds = [], onLaunched }: Nurtur
38
40
  const brand = useBrand();
39
41
  const base = `/nurtures/${encodeURIComponent(nurtureId)}`;
40
42
  const nurture = useG8Query((signal) => g8Get<Nurture>(base, {}, { signal }).then((r) => r.data), [base]);
41
- const lists = useG8Query((signal) => g8Get<ListOption[]>("/lists", { limit: 200 }, { signal }).then((r) => r.data ?? []), []);
43
+ const lists = useAllLists([]);
42
44
  const [listId, setListId] = useState("");
43
45
  const [consent, setConsent] = useState(false);
44
46
  const [confirming, setConfirming] = useState(false);
@@ -63,7 +65,7 @@ export function NurtureEnroll({ nurtureId, contactIds = [], onLaunched }: Nurtur
63
65
  if (nurture.error) return <ApiError error={nurture.error} title="Could not load the nurture" onRetry={() => void nurture.refetch()} />;
64
66
  if (!nurture.data) return <Skeleton className="h-40 w-full" aria-busy="true" />;
65
67
 
66
- const list = (lists.data ?? []).find((l) => String(l.id) === listId);
68
+ const list = lists.items.find((l) => String(l.id) === listId);
67
69
  const attached = nurture.data.associated_list_id ? String(nurture.data.associated_list_id) === listId : false;
68
70
  const audience = list?.contact_count ?? 0;
69
71
  const canLaunch = isDrafted(nurture.data) && attached && audience > 0 && consent;
@@ -73,10 +75,7 @@ export function NurtureEnroll({ nurtureId, contactIds = [], onLaunched }: Nurtur
73
75
  <CardContent className="grid gap-4">
74
76
  <div className="grid max-w-md gap-1">
75
77
  <label htmlFor="g8-nurture-audience" className="text-sm font-medium">Audience list</label>
76
- <select id="g8-nurture-audience" className="border-input bg-background h-9 rounded-md border px-2 text-sm" value={listId} disabled={!isDrafted(nurture.data)} onChange={(e) => setListId(e.target.value)}>
77
- <option value="">Choose a list</option>
78
- {(lists.data ?? []).map((l) => <option key={l.id} value={l.id}>{l.title} ({(l.contact_count ?? 0).toLocaleString()})</option>)}
79
- </select>
78
+ <ListSelect id="g8-nurture-audience" label="Audience list" emptyOption="Choose a list" pageSize={200} optionLabel={(l) => `${l.title} (${(l.contact_count ?? 0).toLocaleString()})`} value={listId} valueLabel={list?.title} disabled={!isDrafted(nurture.data)} onChange={(l) => setListId(l ? String(l.id) : "")} />
80
79
  <div className="flex gap-2">
81
80
  <Button size="sm" variant="outline" loading={setAudience.loading} disabled={!listId || attached || !isDrafted(nurture.data)} onClick={() => void setAudience.mutate(Number(listId))}>Use this list</Button>
82
81
  {contactIds.length > 0 ? <Button size="sm" variant="outline" loading={addContacts.loading} disabled={!attached} onClick={() => void addContacts.mutate(Number(listId))}>Add {contactIds.length} selected {pluralWord(contactIds.length, "contact")}</Button> : null}
@@ -15,11 +15,12 @@
15
15
  import { useState } from "react";
16
16
  import { Button, Input, Label, Skeleton, Textarea } from "@graph8/sdk/ui";
17
17
  import { ApiError } from "@/g8/components/api-error";
18
+ import { ListSelect } from "@/g8/crm/components/list-select";
18
19
  import { g8Get, g8Patch, g8Post } from "@/g8/lib/g8-client";
19
20
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
20
21
  import { useG8Query } from "@/g8/hooks/use-g8-query";
21
22
  import { channelPayload, stepsPayload } from "../lib/nurture-types";
22
- import type { ListOption, MailboxOption, Nurture, StepDraft } from "../lib/nurture-types";
23
+ import type { MailboxOption, Nurture, StepDraft } from "../lib/nurture-types";
23
24
  import { useBrand } from "@/g8/hooks/use-brand";
24
25
 
25
26
  export interface NurtureFormProps {
@@ -57,7 +58,6 @@ function NurtureFormFields({ nurture, onSaved, onCancel }: NurtureFormProps) {
57
58
  const [unsubscribe, setUnsubscribe] = useState(nurture?.include_unsubscribe_cta ?? true);
58
59
  const [steps, setSteps] = useState<StepDraft[]>([{ waitDays: 0, subject: "", body: "" }]);
59
60
  const mailboxes = useG8Query((signal) => g8Get<{ mailboxes?: MailboxOption[] }>("/mailboxes/my", {}, { signal }).then((r) => (r.data?.mailboxes ?? []).filter((m) => !m.is_archived)), []);
60
- const lists = useG8Query((signal) => g8Get<ListOption[]>("/lists", { limit: 200 }, { signal }).then((r) => r.data ?? []), []);
61
61
 
62
62
  const mailbox = (mailboxes.data ?? []).find((m) => String(m.id) === mailboxId);
63
63
  const stepsOk = steps.length > 0 && steps.every((s) => s.subject.trim() && s.body.trim());
@@ -95,14 +95,11 @@ function NurtureFormFields({ nurture, onSaved, onCancel }: NurtureFormProps) {
95
95
  <option value="">{mailboxes.loading ? "Loading..." : "Choose a mailbox"}</option>
96
96
  {(mailboxes.data ?? []).map((m) => <option key={m.id} value={m.id}>{m.email}</option>)}
97
97
  </select>
98
- {!editing && !mailboxes.loading && (mailboxes.data ?? []).length === 0 ? <p className="mt-1 text-xs text-amber-700">No mailbox is connected. Connect one in {brand.linkLabel} first (Google and Microsoft sign-in happens in the app).</p> : null}
98
+ {!editing && !mailboxes.loading && (mailboxes.data ?? []).length === 0 ? <p className="mt-1 text-xs text-warning-foreground">No mailbox is connected. Connect one in {brand.linkLabel} first (Google and Microsoft sign-in happens in the app).</p> : null}
99
99
  </div>
100
100
  <div>
101
101
  <Label htmlFor="g8-nurture-list" className="mb-1">Audience list (consented contacts)</Label>
102
- <select id="g8-nurture-list" className={SELECT} value={listId} onChange={(e) => setListId(e.target.value)}>
103
- <option value="">Choose later</option>
104
- {(lists.data ?? []).map((l) => <option key={l.id} value={l.id}>{l.title}</option>)}
105
- </select>
102
+ <ListSelect id="g8-nurture-list" label="Audience list (consented contacts)" emptyOption="Choose later" pageSize={200} value={listId} valueLabel={nurture?.associated_list_id ? `List ${nurture.associated_list_id}` : undefined} selectClassName={SELECT} onChange={(l) => setListId(l ? String(l.id) : "")} />
106
103
  </div>
107
104
  </div>
108
105
  <fieldset className="flex flex-wrap gap-4 text-sm">
@@ -113,7 +110,7 @@ function NurtureFormFields({ nurture, onSaved, onCancel }: NurtureFormProps) {
113
110
  </fieldset>
114
111
  {editing ? <p className="text-muted-foreground text-sm">The emails and the sender are fixed once created. To change them, duplicate this nurture and build the copy.</p> : (
115
112
  <div className="grid gap-3">
116
- <h3 className="text-sm font-semibold">Emails</h3>
113
+ <h3 className="text-sm font-medium">Emails</h3>
117
114
  {steps.map((s, i) => (
118
115
  <div key={i} className="grid gap-2 rounded-md border p-3">
119
116
  <div className="flex items-end gap-2">
@@ -25,6 +25,10 @@ import type { ReactNode } from "react";
25
25
  import { Copy, ExternalLink, HeartHandshake, Pause, Play, Plus, Trash2 } from "lucide-react";
26
26
  import { Button, Skeleton } from "@graph8/sdk/ui";
27
27
  import { ApiError } from "@/g8/components/api-error";
28
+ import { useLoadMoreList } from "@/g8/hooks/use-load-more-list";
29
+ import { byPage, hasNextOf } from "@/g8/lib/paging";
30
+ import { LoadMore } from "@/g8/components/paging-notices";
31
+ import { useAllLists } from "@/g8/crm/hooks/use-all-lists";
28
32
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
29
33
  import { EmptyState } from "@/g8/components/empty-state";
30
34
  import { DetailPanel } from "@/g8/components/screen/detail-panel";
@@ -41,7 +45,7 @@ import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
41
45
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
42
46
  import { useG8Query } from "@/g8/hooks/use-g8-query";
43
47
  import { isDrafted, isFinished, isLiveLike, isNurtureOff, isPaused, statusOf } from "../lib/nurture-types";
44
- import type { ListOption, MailboxOption, Nurture } from "../lib/nurture-types";
48
+ import type { MailboxOption, Nurture } from "../lib/nurture-types";
45
49
  import { NurtureEnroll } from "./nurture-enroll";
46
50
  import { NurtureForm } from "./nurture-form";
47
51
  import { NurtureStats } from "./nurture-stats";
@@ -79,6 +83,9 @@ function matchesFilter(n: Nurture, f: string): boolean {
79
83
  return isFinished(n);
80
84
  }
81
85
 
86
+ /** Nurtures per read; "Load more" appends the next page. */
87
+ const NURTURE_PAGE = 100;
88
+
82
89
  interface StepRow { step_order?: number; time_interval?: number; step_data?: { subject?: string; body?: string } }
83
90
 
84
91
  const when = (v: string | null | undefined): string => (v ? new Date(v).toLocaleString() : "");
@@ -87,12 +94,12 @@ const yesNo = (v: boolean | undefined, fallback: boolean): string => ((v ?? fall
87
94
  /** Every field of one nurture, its audience and sender by name, and each email of the series. */
88
95
  function NurtureDetails({ nurtureId }: { nurtureId: string }) {
89
96
  const one = useG8Query((signal) => g8Get<Nurture>(`/nurtures/${encodeURIComponent(nurtureId)}`, {}, { signal }).then((r) => r.data), [nurtureId]);
90
- const lists = useG8Query((signal) => g8Get<ListOption[]>("/lists", { limit: 200 }, { signal }).then((r) => r.data ?? []), []);
97
+ const lists = useAllLists([]);
91
98
  const boxes = useG8Query((signal) => g8Get<{ mailboxes?: MailboxOption[] }>("/mailboxes/my", {}, { signal }).then((r) => r.data?.mailboxes ?? []), []);
92
99
  if (one.error) return <ApiError error={one.error} title="Could not load the nurture" onRetry={() => void one.refetch()} />;
93
100
  if (!one.data) return <Skeleton className="h-40 w-full" aria-busy="true" />;
94
101
  const n = one.data;
95
- const list = (lists.data ?? []).find((l) => l.id === n.associated_list_id);
102
+ const list = lists.items.find((l) => l.id === n.associated_list_id);
96
103
  const box = (boxes.data ?? []).find((m) => m.id === n.pinned_mailbox_id);
97
104
  const steps = Array.isArray(n.steps) ? (n.steps as StepRow[]) : [];
98
105
  return (
@@ -147,7 +154,11 @@ export function NurtureScreen({ connectMailboxUrl: connectMailboxUrlProp, contac
147
154
  const [toArchive, setToArchive] = useState<Nurture | null>(null);
148
155
  const [busy, setBusy] = useState<string | null>(null);
149
156
 
150
- const list = useG8Query((signal) => g8Get<{ nurtures?: Nurture[] }>("/nurtures", { page: 1, limit: 100 }, { signal }).then((r) => r.data?.nurtures ?? []), []);
157
+ const list = useLoadMoreList(byPage<Nurture>(NURTURE_PAGE, async (page, signal) => {
158
+ const r = await g8Get<{ nurtures?: Nurture[] }>("/nurtures", { page, limit: NURTURE_PAGE }, { signal });
159
+ const rows = r.data?.nurtures ?? [];
160
+ return { rows, hasNext: hasNextOf(r, NURTURE_PAGE, rows.length) };
161
+ }), []);
151
162
  const mailboxes = useG8Query((signal) => g8Get<{ mailboxes?: MailboxOption[] }>("/mailboxes/my", {}, { signal }).then((r) => (r.data?.mailboxes ?? []).filter((m) => !m.is_archived)), [], { enabled: creating });
152
163
 
153
164
  const act = useG8Mutation((a: { kind: "pause" | "duplicate"; nurture: Nurture }) => g8Post(`/nurtures/${encodeURIComponent(a.nurture.id)}/${a.kind}`, a.kind === "duplicate" ? {} : undefined), { onSuccess: () => void list.refetch() });
@@ -247,7 +258,7 @@ export function NurtureScreen({ connectMailboxUrl: connectMailboxUrlProp, contac
247
258
  )}
248
259
  >
249
260
  {act.error ? <ApiError error={act.error} title="That did not work" /> : null}
250
- <SplitView list={rows} detail={detail} detailOpen={Boolean(selected)} onCloseDetail={() => { setId(null); setView(null); }} expanded={view === "full"} onExpandedChange={(e) => setView(e ? "full" : null)} detailWidth="xl" />
261
+ <SplitView list={<>{rows}<LoadMore shown={all.length} noun="nurtures" hasMore={list.hasMore} loading={list.loadingMore} onLoadMore={list.loadMore} /></>} detail={detail} detailOpen={Boolean(selected)} onCloseDetail={() => { setId(null); setView(null); }} expanded={view === "full"} onExpandedChange={(e) => setView(e ? "full" : null)} detailWidth="xl" />
251
262
  <FormSheet open={creating} onOpenChange={setCreating} title="New nurture" description="Pick the sender and the audience, then write the emails. Nothing is sent until you go live.">
252
263
  <div className="grid gap-4">
253
264
  {noMailbox ? (
@@ -15,11 +15,12 @@
15
15
  import { useState } from "react";
16
16
  import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
17
17
  import { ApiError } from "@/g8/components/api-error";
18
+ import { useAllLists } from "@/g8/crm/hooks/use-all-lists";
18
19
  import { EmptyState } from "@/g8/components/empty-state";
19
20
  import { g8Get } from "@/g8/lib/g8-client";
20
21
  import { useG8Query } from "@/g8/hooks/use-g8-query";
21
22
  import { isNurtureMissing, isNurtureOff, percentOf, statusOf } from "../lib/nurture-types";
22
- import type { ListOption, Nurture, NurturePerformance } from "../lib/nurture-types";
23
+ import type { Nurture, NurturePerformance } from "../lib/nurture-types";
23
24
 
24
25
  export interface NurtureStatsProps {
25
26
  nurtureId: string;
@@ -31,7 +32,7 @@ function Tile({ label, value }: { label: string; value: string }) {
31
32
  return (
32
33
  <div className="rounded-md border p-3">
33
34
  <p className="text-muted-foreground text-xs">{label}</p>
34
- <p className="text-xl font-semibold">{value}</p>
35
+ <p className="text-xl font-medium">{value}</p>
35
36
  </div>
36
37
  );
37
38
  }
@@ -39,7 +40,7 @@ function Tile({ label, value }: { label: string; value: string }) {
39
40
  export function NurtureStats({ nurtureId, days = 30 }: NurtureStatsProps) {
40
41
  const [windowDays, setWindowDays] = useState(days);
41
42
  const nurture = useG8Query((signal) => g8Get<Nurture>(`/nurtures/${encodeURIComponent(nurtureId)}`, {}, { signal }).then((r) => r.data), [nurtureId]);
42
- const lists = useG8Query((signal) => g8Get<ListOption[]>("/lists", { limit: 200 }, { signal }).then((r) => r.data ?? []), []);
43
+ const lists = useAllLists([]);
43
44
  const perf = useG8Query(
44
45
  (signal) => g8Get<{ data?: NurturePerformance }>(`/analytics/outbound/sequence-performance/${encodeURIComponent(nurtureId)}`, { days: windowDays }, { signal }).then((r) => (r.data?.data ?? (r.data as NurturePerformance | undefined)) ?? null),
45
46
  [nurtureId, windowDays],
@@ -49,7 +50,7 @@ export function NurtureStats({ nurtureId, days = 30 }: NurtureStatsProps) {
49
50
  if (isNurtureMissing(nurture.error)) return <EmptyState title="This nurture no longer exists" description="It may have been deleted, or it is not yours to see. Pick another nurture." />;
50
51
  if (nurture.error) return <ApiError error={nurture.error} title="Could not load the nurture" onRetry={() => void nurture.refetch()} />;
51
52
  if (!nurture.data) return <Skeleton className="h-32 w-full" aria-busy="true" />;
52
- const audience = (lists.data ?? []).find((l) => l.id === nurture.data?.associated_list_id);
53
+ const audience = lists.items.find((l) => l.id === nurture.data?.associated_list_id);
53
54
  const p = perf.data;
54
55
  const hasNumbers = Boolean(p && (p.contacts_total || p.emails_sent));
55
56
  return (
@@ -51,7 +51,7 @@ Installs `foundation` first if it is missing. Everything lands under `g8/objects
51
51
  5. **Archive**: the only delete. The record leaves lists but stays readable by id with its history; `restoreObjectRecord` brings it back.
52
52
  Archiving a record, a field or a whole object always goes through `ConfirmSpendDialog`.
53
53
 
54
- For `contacts`, `companies` and `deals`, `ObjectRecordDetail` also renders `RecordRolesPanel` (SDR / AE / CSM owner slots).
54
+ For `contacts`, `companies` and `deals`, `ObjectRecordDetail` also renders `RecordRolesPanel` (SDR / AE / CSM owner slots; each slot is a teammate picker, so nobody types a user id; `userOptions` still gives a fixed list). A person attribute (`actor_reference`) is a teammate picker too.
55
55
  Custom objects have no role slots.
56
56
 
57
57
  ```tsx
@@ -12,7 +12,8 @@
12
12
  "finished ObjectsScreen composing the picker, table, record detail and schema editor"
13
13
  ],
14
14
  "dependsOn": [
15
- "foundation"
15
+ "foundation",
16
+ "pickers"
16
17
  ],
17
18
  "operations": [
18
19
  "archive_attribute_objects__object_slug__attributes__attr_slug__delete",
@@ -31,6 +32,7 @@
31
32
  "list_object_attributes_objects__object_slug__attributes_get",
32
33
  "list_object_records_objects__object_slug__records_get",
33
34
  "list_objects_objects_get",
35
+ "list_team_members_team_members_get",
34
36
  "restore_object_record_objects__object_slug__records__record_id__restore_post",
35
37
  "update_attribute_objects__object_slug__attributes__attr_slug__patch",
36
38
  "update_object_objects__object_slug__patch",
@@ -104,8 +106,12 @@
104
106
  "name": "AttributeFieldInput",
105
107
  "file": "components/attribute-field-input.tsx",
106
108
  "purpose": "One form control chosen by attribute type (text, number, date, select, checkbox...).",
107
- "operations": [],
108
- "tiers": []
109
+ "operations": [
110
+ "list_team_members_team_members_get"
111
+ ],
112
+ "tiers": [
113
+ "read"
114
+ ]
109
115
  },
110
116
  {
111
117
  "name": "ObjectNotFound",
@@ -145,7 +151,8 @@
145
151
  "operations": [
146
152
  "get_record_role_slots_records__entity_type___entity_id__role_slots_get",
147
153
  "assign_record_role_records__entity_type___entity_id__role_assignments_post",
148
- "clear_record_role_records__entity_type___entity_id__role_assignments__role_slot__delete"
154
+ "clear_record_role_records__entity_type___entity_id__role_assignments__role_slot__delete",
155
+ "list_team_members_team_members_get"
149
156
  ],
150
157
  "tiers": [
151
158
  "read",
@@ -220,7 +227,8 @@
220
227
  "objects:delete",
221
228
  "objects:manage",
222
229
  "objects:read",
223
- "objects:write"
230
+ "objects:write",
231
+ "team:read"
224
232
  ],
225
233
  "defaults": [
226
234
  {
@@ -5,10 +5,12 @@
5
5
  *
6
6
  * Purpose: render the right input for an attribute's type (text, number, date, select, checkbox...)
7
7
  * and report the raw draft value. Convert it with `fromInputValue` from ../lib/object-types.
8
- * SDK operations: none (presentational). Used by RecordFormDialog and ObjectRecordDetail.
8
+ * SDK operations: none of its own. A person attribute (`actor_reference`) uses the pickers block MemberPicker (GET /team-members,
9
+ * read) so nobody types a user id; the raw id stays under "Use an id instead". Used by RecordFormDialog and ObjectRecordDetail.
9
10
  */
10
11
 
11
12
  import { Input } from "@graph8/sdk/ui";
13
+ import { MemberPicker } from "@/g8/pickers/components/member-picker";
12
14
  import { selectOptions } from "../lib/object-types";
13
15
  import type { ObjectAttribute } from "../lib/object-types";
14
16
 
@@ -58,6 +60,17 @@ export function AttributeFieldInput({ attribute, value, onChange, disabled, id }
58
60
  </select>
59
61
  );
60
62
  }
63
+ if (attribute.attribute_type === "actor_reference" && !attribute.is_multiselect) {
64
+ return (
65
+ <div className="flex flex-col gap-1">
66
+ <MemberPicker id={fieldId} label={attribute.title} value={String(value) || null} disabled={disabled} placeholder="Choose a person" onChange={(m) => onChange(m.id)} />
67
+ <details className="text-xs">
68
+ <summary className="text-muted-foreground cursor-pointer">Use an id instead</summary>
69
+ <Input aria-label={`${attribute.title} user id`} placeholder="User id" className="mt-1 h-8 w-40" value={String(value)} disabled={disabled} onChange={(e) => onChange(e.target.value)} />
70
+ </details>
71
+ </div>
72
+ );
73
+ }
61
74
  const hint = attribute.is_multiselect
62
75
  ? "Separate several values with commas"
63
76
  : attribute.attribute_type === "record_reference"
@@ -17,6 +17,7 @@
17
17
  import { useEffect, useState } from "react";
18
18
  import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "@graph8/sdk/ui";
19
19
  import { ApiError } from "@/g8/components/api-error";
20
+ import { FirstNotice } from "@/g8/components/paging-notices";
20
21
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
21
22
  import { EmptyState } from "@/g8/components/empty-state";
22
23
  import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
@@ -37,6 +38,10 @@ export interface ObjectRecordDetailProps {
37
38
  onBack?: () => void;
38
39
  }
39
40
 
41
+ /** The relationships and history APIs have no paging: a full read is announced under the list. */
42
+ const CHANGE_LIMIT = 20;
43
+ const RELATION_LIMIT = 25;
44
+
40
45
  interface ChangeEntry { id: string; action: string; occurred_at: string; actor_type?: string; changes?: Record<string, unknown> }
41
46
  interface Relationship { id: string; relationship_label?: string; source_record_id: string; target_record_id: string; source_label?: string; target_label?: string }
42
47
 
@@ -70,7 +75,7 @@ export function ObjectRecordDetail({ objectSlug, recordId, onArchived, onBack }:
70
75
  <div className="flex items-center justify-between gap-2">
71
76
  <div>
72
77
  {onBack ? <Button variant="link" size="sm" onClick={onBack}>Back</Button> : null}
73
- <h2 className="text-lg font-semibold">{recordTitle(schema.definition, schema.attributes, current)}</h2>
78
+ <h2 className="text-lg font-medium">{recordTitle(schema.definition, schema.attributes, current)}</h2>
74
79
  {current.is_archived ? <p className="text-muted-foreground text-sm">This record is archived.</p> : null}
75
80
  </div>
76
81
  <div className="flex gap-2">
@@ -108,6 +113,7 @@ export function ObjectRecordDetail({ objectSlug, recordId, onArchived, onBack }:
108
113
  ))}
109
114
  </ul>
110
115
  )}
116
+ <FirstNotice shown={(relationships.data ?? []).length} limit={RELATION_LIMIT} noun="related records" />
111
117
  </CardContent>
112
118
  </Card>
113
119
  <Card>
@@ -126,6 +132,7 @@ export function ObjectRecordDetail({ objectSlug, recordId, onArchived, onBack }:
126
132
  ))}
127
133
  </ul>
128
134
  )}
135
+ <FirstNotice shown={(changes.data ?? []).length} limit={CHANGE_LIMIT} noun="changes" />
129
136
  </CardContent>
130
137
  </Card>
131
138
  <RecordFormDialog open={editing} objectSlug={objectSlug} singularNoun={noun} attributes={schema.attributes} record={current} onClose={() => setEditing(false)} onSaved={() => { setEditing(false); refreshAll(); }} />
@@ -148,7 +148,7 @@ export function ObjectSchemaEditor({ initialObjectSlug, onSelectObject }: Object
148
148
  {schema.error ? <ApiError error={schema.error} title="Could not load the fields" onRetry={() => void schema.refetch()} /> : null}
149
149
  {!current.is_system ? <ObjectDetailsForm key={current.slug} object={current} attributes={schema.attributes} onSaved={() => void objects.refetch()} /> : <p className="text-muted-foreground text-sm">Standard objects can gain fields but cannot be renamed or archived.</p>}
150
150
  <div className="flex items-center justify-between">
151
- <h3 className="text-sm font-semibold">Fields</h3>
151
+ <h3 className="text-sm font-medium">Fields</h3>
152
152
  <Button size="sm" onClick={() => setEditing("new")}>Add field</Button>
153
153
  </div>
154
154
  {schema.loading ? <Skeleton className="h-24 w-full" /> : schema.attributes.length === 0 ? (
@@ -8,7 +8,8 @@
8
8
  * 2. assignRecordRole (POST /records/{entity_type}/{entity_id}/role-assignments) write
9
9
  * 3. clearRecordRole (DELETE /records/{entity_type}/{entity_id}/role-assignments/{role_slot}) destructive -> ConfirmSpendDialog
10
10
  * Only the standard objects (contacts, companies, deals) have role slots; custom objects do not.
11
- * The API takes a user id, not a name: pass `userOptions` to give people a picker.
11
+ * The API takes a user id, not a name, so nobody types one: each slot has a MemberPicker (search teammates by name;
12
+ * GET /team-members, read). Pass `userOptions` for your own fixed list; a raw id still works under "Use an id instead".
12
13
  */
13
14
 
14
15
  import { useEffect, useState } from "react";
@@ -18,6 +19,7 @@ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
18
19
  import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
19
20
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
20
21
  import { useG8Query } from "@/g8/hooks/use-g8-query";
22
+ import { MemberPicker } from "@/g8/pickers/components/member-picker";
21
23
 
22
24
  type Slot = "sdr" | "ae" | "csm";
23
25
  const SLOTS: ReadonlyArray<{ slot: Slot; label: string }> = [
@@ -32,7 +34,7 @@ interface RoleSlots { sdr?: SlotInfo | null; ae?: SlotInfo | null; csm?: SlotInf
32
34
  export interface RecordRolesPanelProps {
33
35
  entityType: "contact" | "company" | "deal";
34
36
  entityId: string;
35
- /** Optional people to pick from. Without it the user id is typed. */
37
+ /** Optional people to pick from (your own list). Without it a MemberPicker searches the team. */
36
38
  userOptions?: ReadonlyArray<{ id: string; name: string }>;
37
39
  }
38
40
 
@@ -40,10 +42,11 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
40
42
  const base = `/records/${entityType}/${encodeURIComponent(entityId)}`;
41
43
  const slots = useG8Query((signal) => g8Get<RoleSlots>(`${base}/role-slots`, {}, { signal }).then((r) => r.data), [base]);
42
44
  const [draft, setDraft] = useState<Record<string, string>>({});
45
+ const [draftNames, setDraftNames] = useState<Record<string, string>>({});
43
46
  const [clearing, setClearing] = useState<Slot | null>(null);
44
47
  const [busySlot, setBusySlot] = useState<Slot | null>(null);
45
48
  const assign = useG8Mutation((vars: { slot: Slot; userId: string }) => g8Post(`${base}/role-assignments`, { role_slot: vars.slot, user_id: vars.userId }), {
46
- onSuccess: (_r, vars) => { setDraft((d) => ({ ...d, [vars.slot]: "" })); void slots.refetch(); },
49
+ onSuccess: (_r, vars) => { setDraft((d) => ({ ...d, [vars.slot]: "" })); setDraftNames((n) => ({ ...n, [vars.slot]: "" })); void slots.refetch(); },
47
50
  });
48
51
  const assignSlot = async (slot: Slot, userId: string): Promise<void> => {
49
52
  setBusySlot(slot);
@@ -53,6 +56,7 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
53
56
  // Typed ids and open dialogs belong to ONE record.
54
57
  useEffect(() => {
55
58
  setDraft({});
59
+ setDraftNames({});
56
60
  setClearing(null);
57
61
  assign.reset();
58
62
  clear.reset();
@@ -65,7 +69,7 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
65
69
  const canModify = slots.data != null && slots.data.can_modify !== false;
66
70
  return (
67
71
  <section aria-label="Owners" className="grid gap-2">
68
- <h3 className="text-sm font-semibold">Owners</h3>
72
+ <h3 className="text-sm font-medium">Owners</h3>
69
73
  {slots.loading && !slots.data ? <p className="text-muted-foreground text-sm">Loading owners...</p> : null}
70
74
  {SLOTS.map(({ slot, label }) => {
71
75
  const holder = slots.data?.[slot];
@@ -81,7 +85,13 @@ export function RecordRolesPanel({ entityType, entityId, userOptions }: RecordRo
81
85
  {userOptions.map((u) => <option key={u.id} value={u.id}>{u.name}</option>)}
82
86
  </select>
83
87
  ) : (
84
- <Input aria-label={`${label} user id`} placeholder="User id" className="h-8 w-40" value={draft[slot] ?? ""} onChange={(e) => setDraft((d) => ({ ...d, [slot]: e.target.value }))} />
88
+ <>
89
+ <MemberPicker label={`${label} person`} value={draft[slot] || null} selectedName={draftNames[slot]} placeholder="Choose a person" onChange={(m) => { setDraft((d) => ({ ...d, [slot]: m.id })); setDraftNames((n) => ({ ...n, [slot]: m.name })); }} />
90
+ <details className="text-xs">
91
+ <summary className="text-muted-foreground cursor-pointer">Use an id instead</summary>
92
+ <Input aria-label={`${label} user id`} placeholder="User id" className="mt-1 h-8 w-40" value={draft[slot] ?? ""} onChange={(e) => { setDraft((d) => ({ ...d, [slot]: e.target.value })); setDraftNames((n) => ({ ...n, [slot]: "" })); }} />
93
+ </details>
94
+ </>
85
95
  )}
86
96
  <Button size="sm" variant="outline" loading={busySlot === slot} disabled={!draft[slot] || busySlot !== null} onClick={() => void assignSlot(slot, draft[slot])}>Assign</Button>
87
97
  {holder ? <Button size="sm" variant="ghost" onClick={() => { clear.reset(); setClearing(slot); }}>Clear</Button> : null}
@@ -114,3 +114,17 @@ export default function Pages() { const router = useRouter(); return <PageList o
114
114
  - `recipes/edit-page-section.ts`: Change the text of one section of a landing page
115
115
  - `recipes/audit-site-seo.ts`: Run an SEO site audit and list what to fix first
116
116
  - `recipes/refresh-brand-kit.ts`: Re-scan the brand kit from the website
117
+
118
+ ## Depth added (C7c)
119
+
120
+ Landing pages now cover the whole life of a page, not only create and publish. Each open page has these tabs beside Edit and Details:
121
+
122
+ - **Publishing** (`PageDomains`): the URLs the page is live on, publish to a connected domain (domain, subdomain and path are sent together, behind a confirm), take one URL down (destructive confirm), download the HTML.
123
+ - **Tracking** (`PageTracking`): tracking mode, form label, chat widget, conversion goal (only changed fields are sent), a dedicated stream through Provision, reset consent (destructive confirm).
124
+ - **AI tools** (`PageAiTools`): edit by instruction, review (quick check is 0 credits), apply exact fixes for free, fix one finding with AI, write search metadata. Every AI pass is a billable confirm; the API quotes no price in advance, so the dialog says what runs.
125
+ - **Analytics** (`PageAnalytics`): visits, people, companies, sources and forms for 7/30/90 days and a traffic source; the top-level Analytics tab is portfolio-wide. People and companies page with Load more.
126
+ - "Clone a page" (`PageClone`, confirmed spend) rebuilds a URL or pasted HTML as a draft; `PageGenerationStatus` shows progress of an AI-built page.
127
+
128
+ The headline numbers read the API totals, and a page past the first 100 still opens with its real title.
129
+
130
+ Not covered (and why): HTML-level edits (`importLandingPageHtml`, `replaceLandingPageHtml`, `updateLandingPageHtml`, `setLandingPageText/Media`, element edits, anchors) need element ids a form cannot show; templates and components (`duplicateLandingPageTemplate`, `promoteLandingPageTemplate`, `ensureLandingPageBaseTemplate`, `extract components`) are template admin; `agent-sessions` and `getLandingPageConversation` are the hosted editor's chat; consent default (`/landing-page-consent/*`) is workspace admin; clone-homepage duplicates clone-url. Content calendar, programmatic pages and brand-kit template ops belong to later blocks.