@agent-native/core 0.79.17 → 0.79.18

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 (344) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/docs/content/file-uploads.md +11 -4
  4. package/corpus/core/docs/content/locales/ar-SA/multi-app-workspace.md +3 -3
  5. package/corpus/core/docs/content/locales/de-DE/multi-app-workspace.md +3 -3
  6. package/corpus/core/docs/content/locales/es-ES/multi-app-workspace.md +3 -3
  7. package/corpus/core/docs/content/locales/fr-FR/multi-app-workspace.md +3 -3
  8. package/corpus/core/docs/content/locales/hi-IN/multi-app-workspace.md +3 -3
  9. package/corpus/core/docs/content/locales/ja-JP/multi-app-workspace.md +3 -3
  10. package/corpus/core/docs/content/locales/ko-KR/multi-app-workspace.md +3 -3
  11. package/corpus/core/docs/content/locales/pt-BR/multi-app-workspace.md +3 -3
  12. package/corpus/core/docs/content/locales/zh-CN/multi-app-workspace.md +3 -3
  13. package/corpus/core/docs/content/locales/zh-TW/multi-app-workspace.md +3 -3
  14. package/corpus/core/docs/content/multi-app-workspace.md +3 -3
  15. package/corpus/core/docs/content/template-clips.md +14 -5
  16. package/corpus/core/package.json +1 -1
  17. package/corpus/core/src/agent/engine/builder-engine.ts +8 -3
  18. package/corpus/core/src/cli/mcp.ts +11 -6
  19. package/corpus/core/src/client/AssistantChat.tsx +6 -2
  20. package/corpus/core/src/client/chat/markdown-renderer.tsx +1 -0
  21. package/corpus/core/src/client/index.ts +7 -1
  22. package/corpus/core/src/client/org/OrgSwitcher.tsx +5 -5
  23. package/corpus/core/src/client/org/TeamPage.tsx +11 -3
  24. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +216 -0
  25. package/corpus/core/src/client/settings/index.ts +5 -0
  26. package/corpus/core/src/file-upload/index.ts +1 -0
  27. package/corpus/core/src/file-upload/registry.ts +22 -6
  28. package/corpus/core/src/file-upload/types.ts +6 -1
  29. package/corpus/core/src/integrations/adapters/email.ts +26 -18
  30. package/corpus/core/src/integrations/adapters/slack.ts +8 -7
  31. package/corpus/core/src/integrations/adapters/telegram.ts +9 -11
  32. package/corpus/core/src/integrations/adapters/whatsapp.ts +12 -9
  33. package/corpus/core/src/integrations/plugin.ts +79 -13
  34. package/corpus/core/src/onboarding/default-steps.ts +6 -3
  35. package/corpus/core/src/onboarding/types.ts +1 -1
  36. package/corpus/core/src/org/handlers.ts +1 -1
  37. package/corpus/core/src/server/agent-chat-plugin.ts +4 -7
  38. package/corpus/core/src/server/better-auth-instance.ts +19 -62
  39. package/corpus/core/src/server/core-routes-plugin.ts +93 -180
  40. package/corpus/core/src/server/create-server.ts +55 -116
  41. package/corpus/core/src/server/email-actions.ts +3 -6
  42. package/corpus/core/src/server/email.ts +52 -17
  43. package/corpus/core/src/server/index.ts +2 -1
  44. package/corpus/core/src/server/scoped-key-storage.ts +223 -0
  45. package/corpus/core/src/shared/agent-env.ts +3 -3
  46. package/corpus/core/src/sharing/actions/share-resource.ts +1 -1
  47. package/corpus/core/src/styles/agent-native.css +47 -3
  48. package/corpus/core/src/templates/workspace-root/AGENTS.md +3 -2
  49. package/corpus/core/src/templates/workspace-root/README.md +1 -1
  50. package/corpus/core/src/templates/workspace-root/scripts/repair-workspace-org.ts +29 -90
  51. package/corpus/core/src/terminal/pty-server.ts +3 -38
  52. package/corpus/core/src/triggers/dispatcher.ts +1 -2
  53. package/corpus/templates/.retired/issues/package.json +7 -0
  54. package/corpus/templates/.retired/meeting-notes/package.json +7 -0
  55. package/corpus/templates/.retired/recruiting/package.json +7 -0
  56. package/corpus/templates/.retired/scheduling/package.json +7 -0
  57. package/corpus/templates/.retired/voice/package.json +7 -0
  58. package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
  59. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +0 -17
  60. package/corpus/templates/analytics/app/i18n/zh-TW.ts +7 -1
  61. package/corpus/templates/analytics/app/i18n-data.ts +71 -2
  62. package/corpus/templates/analytics/app/pages/Settings.tsx +120 -73
  63. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +0 -11
  64. package/corpus/templates/analytics/app/routes/catalog.tsx +33 -176
  65. package/corpus/templates/analytics/app/routes/team.tsx +3 -2
  66. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-templates-are-tucked-into-settings-and-show-simple.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-26-the-sessions-page-shows-a-single-page-title-instead-of-repea.md +6 -0
  68. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +37 -28
  69. package/corpus/templates/assets/app/routes/settings.tsx +65 -43
  70. package/corpus/templates/assets/app/routes/team.tsx +2 -10
  71. package/corpus/templates/brain/app/routes/settings.tsx +390 -358
  72. package/corpus/templates/brain/app/routes/team.tsx +2 -8
  73. package/corpus/templates/calendar/actions/db-connect.ts +5 -29
  74. package/corpus/templates/calendar/app/components/CloudUpgrade.tsx +8 -51
  75. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +2 -1
  76. package/corpus/templates/calendar/app/components/calendar/GoogleSetupWizard.tsx +4 -2
  77. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +0 -2
  78. package/corpus/templates/calendar/app/pages/Settings.tsx +304 -271
  79. package/corpus/templates/calendar/app/routes/_app.team.tsx +2 -17
  80. package/corpus/templates/calendar/server/handlers/google-auth.ts +22 -9
  81. package/corpus/templates/calendar/server/lib/booking-emails.ts +1 -1
  82. package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +1 -1
  83. package/corpus/templates/calendar/server/lib/google-calendar.ts +48 -28
  84. package/corpus/templates/chat/app/components/layout/Header.tsx +0 -1
  85. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +4 -2
  86. package/corpus/templates/chat/app/routes/settings.tsx +54 -54
  87. package/corpus/templates/chat/app/routes/team.tsx +2 -10
  88. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +7 -2
  89. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +1 -1
  90. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +7 -0
  91. package/corpus/templates/clips/AGENTS.md +8 -0
  92. package/corpus/templates/clips/actions/invite-member.ts +1 -1
  93. package/corpus/templates/clips/actions/request-transcript.ts +1 -1
  94. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -0
  95. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +629 -643
  96. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +219 -155
  97. package/corpus/templates/clips/changelog/2026-06-26-long-desktop-recordings-are-checkpointed-so-macos-finalizati.md +6 -0
  98. package/corpus/templates/clips/changelog/2026-06-26-non-mac-desktop-recordings-wait-for-upload-transcription.md +6 -0
  99. package/corpus/templates/clips/changelog/2026-06-26-s3-compatible-storage-keys-now-save-securely-from-clips-sett.md +6 -0
  100. package/corpus/templates/clips/changelog/2026-06-26-shared-clip-links-guide-agents-through-processing-transcripts.md +6 -0
  101. package/corpus/templates/clips/changelog/2026-06-26-the-clip-details-sidebar-now-collapses-into-a-slide-out-pane.md +6 -0
  102. package/corpus/templates/clips/desktop/src/lib/recorder.ts +55 -3
  103. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +217 -5
  104. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  105. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +176 -5
  106. package/corpus/templates/clips/server/lib/public-agent-context.ts +31 -0
  107. package/corpus/templates/clips/server/lib/s3-upload-provider.ts +60 -14
  108. package/corpus/templates/clips/server/lib/video-storage.ts +37 -9
  109. package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +5 -0
  110. package/corpus/templates/content/app/components/layout/Header.tsx +0 -1
  111. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +1 -0
  112. package/corpus/templates/content/app/routes/_app.settings.tsx +54 -55
  113. package/corpus/templates/content/app/routes/_app.team.tsx +2 -28
  114. package/corpus/templates/content/server/lib/notion.ts +43 -11
  115. package/corpus/templates/content/server/routes/api/notion/auth-url.get.ts +2 -2
  116. package/corpus/templates/content/server/routes/api/notion/status.get.ts +3 -3
  117. package/corpus/templates/design/app/routes/settings.tsx +60 -38
  118. package/corpus/templates/design/app/routes/team.tsx +2 -10
  119. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +2 -2
  120. package/corpus/templates/dispatch/app/routes/settings.tsx +70 -55
  121. package/corpus/templates/dispatch/app/routes/team.tsx +5 -1
  122. package/corpus/templates/forms/actions/db-connect.ts +7 -36
  123. package/corpus/templates/forms/actions/navigate.ts +2 -2
  124. package/corpus/templates/forms/app/components/CloudUpgrade.tsx +8 -47
  125. package/corpus/templates/forms/app/components/layout/Header.tsx +0 -1
  126. package/corpus/templates/forms/app/components/layout/Layout.tsx +42 -25
  127. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +35 -76
  128. package/corpus/templates/forms/app/global.css +9 -25
  129. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +4 -4
  130. package/corpus/templates/forms/app/i18n/ar-SA.ts +1 -0
  131. package/corpus/templates/forms/app/i18n/de-DE.ts +1 -0
  132. package/corpus/templates/forms/app/i18n/en-US.ts +1 -0
  133. package/corpus/templates/forms/app/i18n/es-ES.ts +1 -0
  134. package/corpus/templates/forms/app/i18n/fr-FR.ts +1 -0
  135. package/corpus/templates/forms/app/i18n/hi-IN.ts +1 -0
  136. package/corpus/templates/forms/app/i18n/ja-JP.ts +1 -0
  137. package/corpus/templates/forms/app/i18n/ko-KR.ts +1 -0
  138. package/corpus/templates/forms/app/i18n/pt-BR.ts +1 -0
  139. package/corpus/templates/forms/app/i18n/zh-CN.ts +1 -0
  140. package/corpus/templates/forms/app/i18n/zh-TW.ts +1 -0
  141. package/corpus/templates/forms/app/lib/route-prewarm.ts +9 -8
  142. package/corpus/templates/forms/app/pages/AskPage.tsx +70 -0
  143. package/corpus/templates/forms/app/pages/FormsListPage.tsx +74 -81
  144. package/corpus/templates/forms/app/root.tsx +1 -1
  145. package/corpus/templates/forms/app/routes/_app.ask.tsx +14 -0
  146. package/corpus/templates/forms/app/routes/_app.settings.tsx +54 -54
  147. package/corpus/templates/forms/app/routes/_app.team.tsx +2 -9
  148. package/corpus/templates/forms/app/routes/_index.tsx +21 -130
  149. package/corpus/templates/forms/changelog/2026-06-26-ask-forms-opens-as-a-standard-app-tab-with-a-simpler-sidebar.md +6 -0
  150. package/corpus/templates/forms/changelog/2026-06-26-forms-are-shown-in-a-tighter-row-list-with-a-denser-sidebar.md +6 -0
  151. package/corpus/templates/forms/shared/navigation.ts +4 -2
  152. package/corpus/templates/macros/app/routes/settings.tsx +54 -54
  153. package/corpus/templates/macros/app/routes/team.tsx +2 -10
  154. package/corpus/templates/mail/actions/helpers.ts +2 -2
  155. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +2 -1
  156. package/corpus/templates/mail/app/pages/SettingsPage.tsx +5 -5
  157. package/corpus/templates/mail/app/routes/team.tsx +3 -2
  158. package/corpus/templates/mail/server/handlers/emails.ts +3 -2
  159. package/corpus/templates/mail/server/handlers/google-auth.ts +19 -12
  160. package/corpus/templates/mail/server/lib/automation-engine.ts +3 -2
  161. package/corpus/templates/mail/server/lib/email-state.ts +2 -3
  162. package/corpus/templates/mail/server/lib/google-auth.ts +28 -11
  163. package/corpus/templates/mail/server/lib/jobs.ts +3 -2
  164. package/corpus/templates/mail/server/onboarding.ts +9 -6
  165. package/corpus/templates/mail/server/routes/api/attachments.get.ts +3 -3
  166. package/corpus/templates/plan/actions/request-plan-access.ts +1 -1
  167. package/corpus/templates/plan/app/components/layout/Header.tsx +0 -1
  168. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +4 -2
  169. package/corpus/templates/plan/app/lib/route-prewarm.ts +0 -4
  170. package/corpus/templates/plan/app/routes/settings.tsx +75 -73
  171. package/corpus/templates/plan/app/routes/team.tsx +2 -8
  172. package/corpus/templates/plan/server/lib/comment-notifications.ts +1 -1
  173. package/corpus/templates/slides/actions/check-image-gen.ts +2 -1
  174. package/corpus/templates/slides/actions/db-connect.ts +7 -45
  175. package/corpus/templates/slides/actions/edit-image.ts +6 -3
  176. package/corpus/templates/slides/actions/generate-image-api.ts +1 -1
  177. package/corpus/templates/slides/actions/generate-image.ts +2 -2
  178. package/corpus/templates/slides/actions/generate-slides-ai.ts +5 -3
  179. package/corpus/templates/slides/actions/image-gen-status.ts +4 -3
  180. package/corpus/templates/slides/actions/image-search.ts +6 -3
  181. package/corpus/templates/slides/actions/search-images.ts +6 -3
  182. package/corpus/templates/slides/app/components/CloudUpgrade.tsx +8 -51
  183. package/corpus/templates/slides/app/components/layout/Header.tsx +0 -1
  184. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +0 -2
  185. package/corpus/templates/slides/app/routes/settings.tsx +54 -54
  186. package/corpus/templates/slides/app/routes/team.tsx +2 -12
  187. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +9 -9
  188. package/corpus/templates/slides/server/handlers/image-gen.ts +5 -1
  189. package/corpus/templates/slides/server/handlers/image-providers/gemini.ts +9 -1
  190. package/corpus/templates/slides/server/handlers/image-providers/index.ts +18 -8
  191. package/corpus/templates/slides/server/handlers/image-providers/openai.ts +10 -4
  192. package/corpus/templates/slides/server/handlers/image-providers/types.ts +1 -0
  193. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +59 -34
  194. package/corpus/templates/slides/server/register-secrets.ts +60 -0
  195. package/corpus/templates/videos/actions/db-connect.ts +7 -45
  196. package/corpus/templates/videos/app/components/CloudUpgrade.tsx +8 -51
  197. package/corpus/templates/videos/app/components/Sidebar.tsx +1 -9
  198. package/corpus/templates/videos/app/components/layout/Header.tsx +0 -1
  199. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +0 -3
  200. package/corpus/templates/videos/app/routes/settings.tsx +54 -54
  201. package/corpus/templates/videos/app/routes/team.tsx +2 -8
  202. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  203. package/dist/agent/engine/builder-engine.js +8 -3
  204. package/dist/agent/engine/builder-engine.js.map +1 -1
  205. package/dist/cli/mcp.d.ts.map +1 -1
  206. package/dist/cli/mcp.js +3 -4
  207. package/dist/cli/mcp.js.map +1 -1
  208. package/dist/client/AssistantChat.d.ts.map +1 -1
  209. package/dist/client/AssistantChat.js +6 -2
  210. package/dist/client/AssistantChat.js.map +1 -1
  211. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  212. package/dist/client/chat/markdown-renderer.js +1 -0
  213. package/dist/client/chat/markdown-renderer.js.map +1 -1
  214. package/dist/client/index.d.ts +1 -1
  215. package/dist/client/index.d.ts.map +1 -1
  216. package/dist/client/index.js +1 -1
  217. package/dist/client/index.js.map +1 -1
  218. package/dist/client/org/OrgSwitcher.d.ts +3 -3
  219. package/dist/client/org/OrgSwitcher.js +2 -2
  220. package/dist/client/org/OrgSwitcher.js.map +1 -1
  221. package/dist/client/org/TeamPage.d.ts +6 -1
  222. package/dist/client/org/TeamPage.d.ts.map +1 -1
  223. package/dist/client/org/TeamPage.js +2 -2
  224. package/dist/client/org/TeamPage.js.map +1 -1
  225. package/dist/client/settings/SettingsTabsPage.d.ts +27 -0
  226. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -0
  227. package/dist/client/settings/SettingsTabsPage.js +108 -0
  228. package/dist/client/settings/SettingsTabsPage.js.map +1 -0
  229. package/dist/client/settings/index.d.ts +1 -0
  230. package/dist/client/settings/index.d.ts.map +1 -1
  231. package/dist/client/settings/index.js +1 -0
  232. package/dist/client/settings/index.js.map +1 -1
  233. package/dist/collab/routes.d.ts +1 -1
  234. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  235. package/dist/file-upload/index.d.ts +1 -1
  236. package/dist/file-upload/index.d.ts.map +1 -1
  237. package/dist/file-upload/index.js +1 -1
  238. package/dist/file-upload/index.js.map +1 -1
  239. package/dist/file-upload/registry.d.ts +1 -0
  240. package/dist/file-upload/registry.d.ts.map +1 -1
  241. package/dist/file-upload/registry.js +24 -6
  242. package/dist/file-upload/registry.js.map +1 -1
  243. package/dist/file-upload/types.d.ts +6 -1
  244. package/dist/file-upload/types.d.ts.map +1 -1
  245. package/dist/file-upload/types.js.map +1 -1
  246. package/dist/integrations/adapters/email.d.ts.map +1 -1
  247. package/dist/integrations/adapters/email.js +18 -18
  248. package/dist/integrations/adapters/email.js.map +1 -1
  249. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  250. package/dist/integrations/adapters/slack.js +8 -7
  251. package/dist/integrations/adapters/slack.js.map +1 -1
  252. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  253. package/dist/integrations/adapters/telegram.js +9 -9
  254. package/dist/integrations/adapters/telegram.js.map +1 -1
  255. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  256. package/dist/integrations/adapters/whatsapp.js +10 -9
  257. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  258. package/dist/integrations/plugin.d.ts.map +1 -1
  259. package/dist/integrations/plugin.js +47 -13
  260. package/dist/integrations/plugin.js.map +1 -1
  261. package/dist/notifications/routes.d.ts +1 -1
  262. package/dist/observability/routes.d.ts +2 -2
  263. package/dist/onboarding/default-steps.d.ts.map +1 -1
  264. package/dist/onboarding/default-steps.js +6 -5
  265. package/dist/onboarding/default-steps.js.map +1 -1
  266. package/dist/onboarding/types.d.ts +1 -1
  267. package/dist/onboarding/types.d.ts.map +1 -1
  268. package/dist/onboarding/types.js.map +1 -1
  269. package/dist/org/handlers.js +1 -1
  270. package/dist/org/handlers.js.map +1 -1
  271. package/dist/progress/routes.d.ts +1 -1
  272. package/dist/resources/handlers.d.ts +3 -3
  273. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  274. package/dist/server/agent-chat-plugin.js +4 -6
  275. package/dist/server/agent-chat-plugin.js.map +1 -1
  276. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  277. package/dist/server/better-auth-instance.d.ts.map +1 -1
  278. package/dist/server/better-auth-instance.js +17 -56
  279. package/dist/server/better-auth-instance.js.map +1 -1
  280. package/dist/server/core-routes-plugin.d.ts +1 -1
  281. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  282. package/dist/server/core-routes-plugin.js +67 -153
  283. package/dist/server/core-routes-plugin.js.map +1 -1
  284. package/dist/server/create-server.d.ts +3 -9
  285. package/dist/server/create-server.d.ts.map +1 -1
  286. package/dist/server/create-server.js +37 -91
  287. package/dist/server/create-server.js.map +1 -1
  288. package/dist/server/email-actions.d.ts +2 -3
  289. package/dist/server/email-actions.d.ts.map +1 -1
  290. package/dist/server/email-actions.js +3 -6
  291. package/dist/server/email-actions.js.map +1 -1
  292. package/dist/server/email.d.ts +3 -3
  293. package/dist/server/email.d.ts.map +1 -1
  294. package/dist/server/email.js +40 -19
  295. package/dist/server/email.js.map +1 -1
  296. package/dist/server/index.d.ts +3 -3
  297. package/dist/server/index.d.ts.map +1 -1
  298. package/dist/server/index.js +3 -3
  299. package/dist/server/index.js.map +1 -1
  300. package/dist/server/scoped-key-storage.d.ts +22 -0
  301. package/dist/server/scoped-key-storage.d.ts.map +1 -0
  302. package/dist/server/scoped-key-storage.js +152 -0
  303. package/dist/server/scoped-key-storage.js.map +1 -0
  304. package/dist/server/transcribe-voice.d.ts +1 -1
  305. package/dist/shared/agent-env.d.ts +3 -3
  306. package/dist/shared/agent-env.d.ts.map +1 -1
  307. package/dist/shared/agent-env.js +3 -3
  308. package/dist/shared/agent-env.js.map +1 -1
  309. package/dist/sharing/actions/share-resource.js +1 -1
  310. package/dist/sharing/actions/share-resource.js.map +1 -1
  311. package/dist/styles/agent-native.css +47 -3
  312. package/dist/templates/workspace-root/AGENTS.md +3 -2
  313. package/dist/templates/workspace-root/README.md +1 -1
  314. package/dist/templates/workspace-root/scripts/repair-workspace-org.ts +29 -90
  315. package/dist/terminal/pty-server.d.ts.map +1 -1
  316. package/dist/terminal/pty-server.js +3 -36
  317. package/dist/terminal/pty-server.js.map +1 -1
  318. package/dist/triggers/dispatcher.js +1 -1
  319. package/dist/triggers/dispatcher.js.map +1 -1
  320. package/docs/content/file-uploads.md +11 -4
  321. package/docs/content/locales/ar-SA/multi-app-workspace.md +3 -3
  322. package/docs/content/locales/de-DE/multi-app-workspace.md +3 -3
  323. package/docs/content/locales/es-ES/multi-app-workspace.md +3 -3
  324. package/docs/content/locales/fr-FR/multi-app-workspace.md +3 -3
  325. package/docs/content/locales/hi-IN/multi-app-workspace.md +3 -3
  326. package/docs/content/locales/ja-JP/multi-app-workspace.md +3 -3
  327. package/docs/content/locales/ko-KR/multi-app-workspace.md +3 -3
  328. package/docs/content/locales/pt-BR/multi-app-workspace.md +3 -3
  329. package/docs/content/locales/zh-CN/multi-app-workspace.md +3 -3
  330. package/docs/content/locales/zh-TW/multi-app-workspace.md +3 -3
  331. package/docs/content/multi-app-workspace.md +3 -3
  332. package/docs/content/template-clips.md +14 -5
  333. package/package.json +1 -1
  334. package/src/templates/workspace-root/AGENTS.md +3 -2
  335. package/src/templates/workspace-root/README.md +1 -1
  336. package/src/templates/workspace-root/scripts/repair-workspace-org.ts +29 -90
  337. package/corpus/core/src/server/env-var-writes.ts +0 -16
  338. package/corpus/templates/analytics/app/pages/Team.tsx +0 -9
  339. package/corpus/templates/calendar/app/pages/Team.tsx +0 -12
  340. package/corpus/templates/mail/app/pages/Team.tsx +0 -17
  341. package/dist/server/env-var-writes.d.ts +0 -10
  342. package/dist/server/env-var-writes.d.ts.map +0 -1
  343. package/dist/server/env-var-writes.js +0 -18
  344. package/dist/server/env-var-writes.js.map +0 -1
@@ -3,14 +3,15 @@ import {
3
3
  agentNativePath,
4
4
  appApiPath,
5
5
  LanguagePicker,
6
- parseChangelog,
7
6
  useActionQuery,
8
7
  useBuilderConnectFlow,
9
8
  useBuilderStatus,
10
9
  ChangelogSettingsCard,
10
+ SettingsTabsPage,
11
11
  openAgentSettings,
12
12
  useT,
13
13
  } from "@agent-native/core/client";
14
+ import { TeamPage } from "@agent-native/core/client/org";
14
15
  import {
15
16
  IconBrain,
16
17
  IconBrandSlack,
@@ -24,7 +25,7 @@ import {
24
25
  IconTrash,
25
26
  IconUser,
26
27
  } from "@tabler/icons-react";
27
- import { useCallback, useEffect, useMemo, useState } from "react";
28
+ import { useCallback, useEffect, useState } from "react";
28
29
  import { toast } from "sonner";
29
30
 
30
31
  import { PageHeader } from "@/components/library/page-header";
@@ -287,17 +288,24 @@ async function saveS3StorageSettings(
287
288
  throw new Error("Enter at least one storage value.");
288
289
  }
289
290
 
290
- const res = await fetch(agentNativePath("/_agent-native/env-vars"), {
291
- method: "POST",
292
- headers: { "Content-Type": "application/json" },
293
- body: JSON.stringify({ vars, scope: "workspace" }),
294
- });
291
+ for (const { key, value } of vars) {
292
+ const res = await fetch(agentNativePath("/_agent-native/secrets/adhoc"), {
293
+ method: "POST",
294
+ headers: { "Content-Type": "application/json" },
295
+ body: JSON.stringify({
296
+ name: key,
297
+ value,
298
+ scope: "workspace",
299
+ description: "Clips S3-compatible storage", // i18n-ignore -- secret metadata description, not visible UI
300
+ }),
301
+ });
295
302
 
296
- if (!res.ok) {
297
- const body = (await res.json().catch(() => null)) as {
298
- error?: string;
299
- } | null;
300
- throw new Error(body?.error ?? `Save failed (${res.status})`);
303
+ if (!res.ok) {
304
+ const body = (await res.json().catch(() => null)) as {
305
+ error?: string;
306
+ } | null;
307
+ throw new Error(body?.error ?? `Save failed (${res.status})`);
308
+ }
301
309
  }
302
310
  }
303
311
 
@@ -340,60 +348,6 @@ async function saveRegisteredSecret(key: string, value: string): Promise<void> {
340
348
  }
341
349
  }
342
350
 
343
- function CompactChangelogAside() {
344
- const t = useT();
345
- const localizedChangelog = t("settings.changelogMarkdown");
346
- const entries = useMemo(
347
- () => parseChangelog(localizedChangelog),
348
- [localizedChangelog],
349
- );
350
- const [expanded, setExpanded] = useState(false);
351
-
352
- if (entries.length === 0) return null;
353
-
354
- const latestBody = entries[0]?.body ?? "";
355
- const canExpand = latestBody.length > 260 || entries.length > 1;
356
-
357
- return (
358
- <aside className="2xl:sticky 2xl:top-6 2xl:self-start">
359
- <div className="relative">
360
- <div
361
- className={cn(
362
- "overflow-hidden transition-[max-height] duration-200 ease-out",
363
- expanded ? "max-h-[70vh]" : "max-h-72",
364
- )}
365
- >
366
- <ChangelogSettingsCard
367
- markdown={localizedChangelog}
368
- limit={1}
369
- title={t("settings.whatsNew")}
370
- closeLabel={t("common.cancel")}
371
- emptyText={t("settings.changelogEmpty")}
372
- viewAllLabel={t("settings.viewAllUpdates")}
373
- className="rounded-md"
374
- />
375
- </div>
376
- {canExpand && !expanded ? (
377
- <div className="pointer-events-none absolute inset-x-px bottom-10 h-16 bg-gradient-to-t from-card to-transparent" />
378
- ) : null}
379
- {canExpand ? (
380
- <div className="mt-2 flex justify-end">
381
- <Button
382
- type="button"
383
- variant="ghost"
384
- size="sm"
385
- className="h-7 px-2 text-xs text-muted-foreground"
386
- onClick={() => setExpanded((value) => !value)}
387
- >
388
- {expanded ? t("settings.collapse") : t("settings.expand")}
389
- </Button>
390
- </div>
391
- ) : null}
392
- </div>
393
- </aside>
394
- );
395
- }
396
-
397
351
  export default function SettingsIndexRoute() {
398
352
  const { session } = useSession();
399
353
  const t = useT();
@@ -633,6 +587,7 @@ export default function SettingsIndexRoute() {
633
587
  const slackOauthConfigured = slackStatus.data?.oauthConfigured ?? false;
634
588
  const slackSigningConfigured = slackStatus.data?.signingConfigured ?? false;
635
589
  const slackConnected = slackInstallations.length > 0;
590
+ const localizedChangelog = t("settings.changelogMarkdown");
636
591
 
637
592
  return (
638
593
  <>
@@ -641,632 +596,663 @@ export default function SettingsIndexRoute() {
641
596
  {t("settings.title")}
642
597
  </h1>
643
598
  </PageHeader>
644
- <div className="mx-auto w-full max-w-6xl p-6">
645
- <div className="space-y-6">
646
- <div className="min-w-0 space-y-6">
647
- <p className="text-sm text-muted-foreground">
648
- {t("settings.intro")}
649
- </p>
599
+ <SettingsTabsPage
600
+ whatsNewLabel={t("settings.whatsNew")}
601
+ general={
602
+ <div className="mx-auto w-full max-w-4xl space-y-6">
603
+ <div className="min-w-0 space-y-6">
604
+ <p className="text-sm text-muted-foreground">
605
+ {t("settings.intro")}
606
+ </p>
650
607
 
651
- <Card>
652
- <CardHeader>
653
- <CardTitle className="text-base">
654
- {t("settings.languageTitle")}
655
- </CardTitle>
656
- <CardDescription>
657
- {t("settings.languageDescription")}
658
- </CardDescription>
659
- </CardHeader>
660
- <CardContent className="max-w-xs space-y-1.5">
661
- <Label>{t("settings.languageLabel")}</Label>
662
- <LanguagePicker label={t("settings.languageLabel")} />
663
- </CardContent>
664
- </Card>
608
+ <Card>
609
+ <CardHeader>
610
+ <CardTitle className="text-base">
611
+ {t("settings.languageTitle")}
612
+ </CardTitle>
613
+ <CardDescription>
614
+ {t("settings.languageDescription")}
615
+ </CardDescription>
616
+ </CardHeader>
617
+ <CardContent className="max-w-xs space-y-1.5">
618
+ <Label>{t("settings.languageLabel")}</Label>
619
+ <LanguagePicker label={t("settings.languageLabel")} />
620
+ </CardContent>
621
+ </Card>
665
622
 
666
- <Card>
667
- <CardHeader>
668
- <CardTitle className="text-base">
669
- {t("settings.agentTitle")}
670
- </CardTitle>
671
- <CardDescription>
672
- {t("settings.agentDescription")}
673
- </CardDescription>
674
- </CardHeader>
675
- <CardContent>
676
- <Button variant="outline" onClick={() => openAgentSettings()}>
677
- {t("settings.openAgentSettings")}
678
- </Button>
679
- </CardContent>
680
- </Card>
623
+ <Card>
624
+ <CardHeader>
625
+ <CardTitle className="text-base">
626
+ {t("settings.agentTitle")}
627
+ </CardTitle>
628
+ <CardDescription>
629
+ {t("settings.agentDescription")}
630
+ </CardDescription>
631
+ </CardHeader>
632
+ <CardContent>
633
+ <Button variant="outline" onClick={() => openAgentSettings()}>
634
+ {t("settings.openAgentSettings")}
635
+ </Button>
636
+ </CardContent>
637
+ </Card>
681
638
 
682
- <Card id="video-storage" className="scroll-mt-16">
683
- <CardHeader>
684
- <CardTitle className="text-base flex items-center gap-2">
685
- <IconCloud className="size-4 text-primary" />
686
- {t("settings.videoStorage")}
687
- </CardTitle>
688
- <CardDescription>
689
- {t("settings.videoStorageDescription")}
690
- </CardDescription>
691
- </CardHeader>
692
- <CardContent className="space-y-4">
693
- <div
694
- className={cn(
695
- "flex flex-col gap-3 rounded-md border px-3 py-3 sm:flex-row sm:items-center sm:justify-between",
696
- builderConnected
697
- ? "border-primary/35 bg-primary/5"
698
- : "border-border bg-accent/30",
699
- )}
700
- >
701
- <div className="min-w-0">
702
- <div className="flex items-center gap-2 text-sm font-medium">
703
- {builderConnected ? (
704
- <IconCheck className="h-4 w-4 text-primary" />
705
- ) : (
706
- <IconKey className="h-4 w-4 text-muted-foreground" />
707
- )}
708
- {builderStatusLoading
709
- ? t("settings.checkingBuilder")
710
- : builderConnected
711
- ? t("settings.builderConnected")
712
- : t("settings.connectBuilder")}
639
+ <Card id="video-storage" className="scroll-mt-16">
640
+ <CardHeader>
641
+ <CardTitle className="text-base flex items-center gap-2">
642
+ <IconCloud className="size-4 text-primary" />
643
+ {t("settings.videoStorage")}
644
+ </CardTitle>
645
+ <CardDescription>
646
+ {t("settings.videoStorageDescription")}
647
+ </CardDescription>
648
+ </CardHeader>
649
+ <CardContent className="space-y-4">
650
+ <div
651
+ className={cn(
652
+ "flex flex-col gap-3 rounded-md border px-3 py-3 sm:flex-row sm:items-center sm:justify-between",
653
+ builderConnected
654
+ ? "border-primary/35 bg-primary/5"
655
+ : "border-border bg-accent/30",
656
+ )}
657
+ >
658
+ <div className="min-w-0">
659
+ <div className="flex items-center gap-2 text-sm font-medium">
660
+ {builderConnected ? (
661
+ <IconCheck className="h-4 w-4 text-primary" />
662
+ ) : (
663
+ <IconKey className="h-4 w-4 text-muted-foreground" />
664
+ )}
665
+ {builderStatusLoading
666
+ ? t("settings.checkingBuilder")
667
+ : builderConnected
668
+ ? t("settings.builderConnected")
669
+ : t("settings.connectBuilder")}
670
+ </div>
671
+ <p className="mt-0.5 text-xs text-muted-foreground">
672
+ {builderConnected
673
+ ? builderOrgName
674
+ ? t("settings.builderConnectedFor", {
675
+ orgName: builderOrgName,
676
+ })
677
+ : t("settings.builderConnectedGeneric")
678
+ : t("settings.builderIncludes")}
679
+ </p>
713
680
  </div>
714
- <p className="mt-0.5 text-xs text-muted-foreground">
715
- {builderConnected
716
- ? builderOrgName
717
- ? t("settings.builderConnectedFor", {
718
- orgName: builderOrgName,
719
- })
720
- : t("settings.builderConnectedGeneric")
721
- : t("settings.builderIncludes")}
722
- </p>
681
+ {builderConnected ? (
682
+ <Badge variant="secondary" className="shrink-0">
683
+ {t("common.connected")}
684
+ </Badge>
685
+ ) : (
686
+ <Button
687
+ type="button"
688
+ variant="outline"
689
+ size="sm"
690
+ className="shrink-0"
691
+ onClick={() =>
692
+ builderConnect.start({
693
+ trackingSource: "clips_settings_video_storage",
694
+ trackingFlow: "video_storage",
695
+ })
696
+ }
697
+ disabled={
698
+ builderConnect.connecting || builderStatusLoading
699
+ }
700
+ >
701
+ {builderConnect.connecting ? (
702
+ <IconLoader2 className="h-4 w-4 animate-spin" />
703
+ ) : (
704
+ <IconExternalLink className="h-4 w-4" />
705
+ )}
706
+ {t("settings.connectBuilder")}
707
+ </Button>
708
+ )}
723
709
  </div>
724
- {builderConnected ? (
725
- <Badge variant="secondary" className="shrink-0">
726
- {t("common.connected")}
727
- </Badge>
728
- ) : (
710
+
711
+ <Collapsible
712
+ open={builderConnected ? !s3Collapsed : true}
713
+ onOpenChange={(open) => setS3Expanded(open)}
714
+ >
715
+ <div className="rounded-md border border-border">
716
+ <div className="flex flex-col gap-3 px-3 py-3 sm:flex-row sm:items-center sm:justify-between">
717
+ <div className="min-w-0">
718
+ <div className="flex flex-wrap items-center gap-2 text-sm font-medium">
719
+ <IconServer className="h-4 w-4 text-muted-foreground" />
720
+ {t("settings.s3Title")}
721
+ <Badge variant="outline" className="text-[10px]">
722
+ {t("settings.secondary")}
723
+ </Badge>
724
+ {s3Configured ? (
725
+ <Badge
726
+ variant="secondary"
727
+ className="text-[10px]"
728
+ >
729
+ {t("settings.active")}
730
+ </Badge>
731
+ ) : null}
732
+ </div>
733
+ <p className="mt-0.5 text-xs text-muted-foreground">
734
+ {builderConnected
735
+ ? t("settings.s3BuilderConnectedDescription")
736
+ : storageConfigured && activeProviderName
737
+ ? t("settings.s3CurrentProvider", {
738
+ providerName: activeProviderName,
739
+ })
740
+ : t("settings.s3OwnBucketDescription")}
741
+ </p>
742
+ </div>
743
+ {builderConnected ? (
744
+ <CollapsibleTrigger asChild>
745
+ <Button
746
+ type="button"
747
+ variant="ghost"
748
+ size="sm"
749
+ className="shrink-0"
750
+ >
751
+ {s3Collapsed
752
+ ? t("settings.configureS3")
753
+ : t("settings.hideS3")}
754
+ <IconChevronDown
755
+ className={cn(
756
+ "h-4 w-4 transition-transform",
757
+ !s3Collapsed && "rotate-180",
758
+ )}
759
+ />
760
+ </Button>
761
+ </CollapsibleTrigger>
762
+ ) : null}
763
+ </div>
764
+
765
+ <CollapsibleContent>
766
+ <div className="space-y-4 border-t border-border px-3 py-4">
767
+ <div className="grid gap-4 sm:grid-cols-2">
768
+ {S3_STORAGE_FIELDS.map((field) => (
769
+ <div key={field.key} className="space-y-1.5">
770
+ <Label htmlFor={field.key}>
771
+ {t(field.labelKey)}
772
+ </Label>
773
+ <Input
774
+ id={field.key}
775
+ type={
776
+ "secret" in field && field.secret
777
+ ? "password"
778
+ : "text"
779
+ }
780
+ value={s3Values[field.key] ?? ""}
781
+ onChange={(event) =>
782
+ setS3Values((current) => ({
783
+ ...current,
784
+ [field.key]: event.target.value,
785
+ }))
786
+ }
787
+ placeholder={field.placeholder}
788
+ autoComplete="off"
789
+ disabled={savingStorage}
790
+ />
791
+ </div>
792
+ ))}
793
+ </div>
794
+
795
+ <div className="flex justify-end">
796
+ <Button
797
+ onClick={handleSaveS3Storage}
798
+ disabled={
799
+ savingStorage || storageStatus.isLoading
800
+ }
801
+ >
802
+ {savingStorage && (
803
+ <IconLoader2 className="h-4 w-4 animate-spin" />
804
+ )}
805
+ {t("settings.saveStorage")}
806
+ </Button>
807
+ </div>
808
+ </div>
809
+ </CollapsibleContent>
810
+ </div>
811
+ </Collapsible>
812
+ </CardContent>
813
+ </Card>
814
+
815
+ <Card id="slack" className="scroll-mt-16">
816
+ <CardHeader>
817
+ <CardTitle className="text-base flex items-center gap-2">
818
+ <IconBrandSlack className="size-4 text-primary" />
819
+ {t("settings.slackTitle")}
820
+ </CardTitle>
821
+ <CardDescription>
822
+ {t("settings.slackDescription")}
823
+ </CardDescription>
824
+ </CardHeader>
825
+ <CardContent className="space-y-4">
826
+ <div className="flex flex-col gap-3 rounded-md border border-border bg-accent/30 px-3 py-3 sm:flex-row sm:items-center sm:justify-between">
827
+ <div className="min-w-0">
828
+ <div className="flex items-center gap-2 text-sm font-medium">
829
+ <IconBrandSlack className="h-4 w-4 text-muted-foreground" />
830
+ {slackStatus.isLoading
831
+ ? t("settings.checkingSlack")
832
+ : slackConnected
833
+ ? t("settings.slackConnected", {
834
+ count: slackInstallations.length,
835
+ })
836
+ : slackOauthConfigured
837
+ ? t("common.notConnected")
838
+ : t("settings.slackOauthNeeded")}
839
+ </div>
840
+ <p className="mt-0.5 text-xs text-muted-foreground">
841
+ {t("settings.slackPreviewDescription")}
842
+ </p>
843
+ </div>
729
844
  <Button
730
845
  type="button"
731
846
  variant="outline"
732
847
  size="sm"
733
848
  className="shrink-0"
734
- onClick={() =>
735
- builderConnect.start({
736
- trackingSource: "clips_settings_video_storage",
737
- trackingFlow: "video_storage",
738
- })
739
- }
849
+ onClick={handleConnectSlack}
740
850
  disabled={
741
- builderConnect.connecting || builderStatusLoading
851
+ connectingSlack ||
852
+ slackStatus.isLoading ||
853
+ !slackOauthConfigured
742
854
  }
743
855
  >
744
- {builderConnect.connecting ? (
856
+ {connectingSlack ? (
745
857
  <IconLoader2 className="h-4 w-4 animate-spin" />
746
858
  ) : (
747
859
  <IconExternalLink className="h-4 w-4" />
748
860
  )}
749
- {t("settings.connectBuilder")}
861
+ {t("settings.connectSlack")}
750
862
  </Button>
751
- )}
752
- </div>
863
+ </div>
753
864
 
754
- <Collapsible
755
- open={builderConnected ? !s3Collapsed : true}
756
- onOpenChange={(open) => setS3Expanded(open)}
757
- >
758
- <div className="rounded-md border border-border">
759
- <div className="flex flex-col gap-3 px-3 py-3 sm:flex-row sm:items-center sm:justify-between">
760
- <div className="min-w-0">
761
- <div className="flex flex-wrap items-center gap-2 text-sm font-medium">
762
- <IconServer className="h-4 w-4 text-muted-foreground" />
763
- {t("settings.s3Title")}
764
- <Badge variant="outline" className="text-[10px]">
765
- {t("settings.secondary")}
766
- </Badge>
767
- {s3Configured ? (
768
- <Badge variant="secondary" className="text-[10px]">
769
- {t("settings.active")}
770
- </Badge>
771
- ) : null}
772
- </div>
773
- <p className="mt-0.5 text-xs text-muted-foreground">
774
- {builderConnected
775
- ? t("settings.s3BuilderConnectedDescription")
776
- : storageConfigured && activeProviderName
777
- ? t("settings.s3CurrentProvider", {
778
- providerName: activeProviderName,
779
- })
780
- : t("settings.s3OwnBucketDescription")}
781
- </p>
782
- </div>
783
- {builderConnected ? (
784
- <CollapsibleTrigger asChild>
865
+ {!slackOauthConfigured ? (
866
+ <div className="rounded-md border border-border p-3 text-xs text-muted-foreground">
867
+ {t("settings.slackClientMissing")}
868
+ </div>
869
+ ) : null}
870
+
871
+ {!slackSigningConfigured ? (
872
+ <div className="rounded-md border border-border p-3 text-xs text-muted-foreground">
873
+ {t("settings.slackSigningMissing")}
874
+ </div>
875
+ ) : null}
876
+
877
+ {slackInstallations.length > 0 ? (
878
+ <div className="space-y-2">
879
+ {slackInstallations.map((installation) => (
880
+ <div
881
+ key={installation.id}
882
+ className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2"
883
+ >
884
+ <div className="min-w-0">
885
+ <div className="truncate text-sm font-medium">
886
+ {installation.teamName || installation.teamId}
887
+ </div>
888
+ <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
889
+ <span>{installation.status}</span>
890
+ {installation.enterpriseName ? (
891
+ <span>{installation.enterpriseName}</span>
892
+ ) : null}
893
+ <span>
894
+ {t("settings.connectedBy", {
895
+ email: installation.ownerEmail,
896
+ })}
897
+ </span>
898
+ </div>
899
+ </div>
785
900
  <Button
786
901
  type="button"
787
902
  variant="ghost"
788
- size="sm"
903
+ size="icon"
789
904
  className="shrink-0"
905
+ aria-label={t("settings.disconnectSlackLabel", {
906
+ team:
907
+ installation.teamName || installation.teamId,
908
+ })}
909
+ onClick={() =>
910
+ setDisconnectSlackTarget(installation)
911
+ }
790
912
  >
791
- {s3Collapsed
792
- ? t("settings.configureS3")
793
- : t("settings.hideS3")}
794
- <IconChevronDown
795
- className={cn(
796
- "h-4 w-4 transition-transform",
797
- !s3Collapsed && "rotate-180",
798
- )}
799
- />
913
+ <IconTrash className="h-4 w-4" />
800
914
  </Button>
801
- </CollapsibleTrigger>
802
- ) : null}
803
- </div>
804
-
805
- <CollapsibleContent>
806
- <div className="space-y-4 border-t border-border px-3 py-4">
807
- <div className="grid gap-4 sm:grid-cols-2">
808
- {S3_STORAGE_FIELDS.map((field) => (
809
- <div key={field.key} className="space-y-1.5">
810
- <Label htmlFor={field.key}>
811
- {t(field.labelKey)}
812
- </Label>
813
- <Input
814
- id={field.key}
815
- type={
816
- "secret" in field && field.secret
817
- ? "password"
818
- : "text"
819
- }
820
- value={s3Values[field.key] ?? ""}
821
- onChange={(event) =>
822
- setS3Values((current) => ({
823
- ...current,
824
- [field.key]: event.target.value,
825
- }))
826
- }
827
- placeholder={field.placeholder}
828
- autoComplete="off"
829
- disabled={savingStorage}
830
- />
831
- </div>
832
- ))}
833
915
  </div>
916
+ ))}
917
+ </div>
918
+ ) : null}
919
+ </CardContent>
920
+ </Card>
834
921
 
835
- <div className="flex justify-end">
836
- <Button
837
- onClick={handleSaveS3Storage}
838
- disabled={savingStorage || storageStatus.isLoading}
839
- >
840
- {savingStorage && (
841
- <IconLoader2 className="h-4 w-4 animate-spin" />
842
- )}
843
- {t("settings.saveStorage")}
844
- </Button>
845
- </div>
922
+ <Card id="ai-providers" className="scroll-mt-16">
923
+ <CardHeader>
924
+ <CardTitle className="text-base flex items-center gap-2">
925
+ <IconBrain className="size-4 text-primary" />
926
+ {t("settings.apiSetup")}
927
+ </CardTitle>
928
+ <CardDescription>
929
+ {t("settings.apiSetupDescription")}
930
+ </CardDescription>
931
+ </CardHeader>
932
+ <CardContent className="space-y-4">
933
+ <div
934
+ className={cn(
935
+ "flex flex-col gap-3 rounded-md border px-3 py-3 sm:flex-row sm:items-center sm:justify-between",
936
+ builderConnected
937
+ ? "border-primary/35 bg-primary/5"
938
+ : "border-border bg-accent/30",
939
+ )}
940
+ >
941
+ <div className="min-w-0">
942
+ <div className="flex items-center gap-2 text-sm font-medium">
943
+ {builderConnected ? (
944
+ <IconCheck className="h-4 w-4 text-primary" />
945
+ ) : (
946
+ <IconKey className="h-4 w-4 text-muted-foreground" />
947
+ )}
948
+ {builderStatusLoading
949
+ ? t("settings.checkingBuilder")
950
+ : builderConnected
951
+ ? t("settings.builderConnected")
952
+ : t("settings.builderEasySetup")}
846
953
  </div>
847
- </CollapsibleContent>
848
- </div>
849
- </Collapsible>
850
- </CardContent>
851
- </Card>
852
-
853
- <Card id="slack" className="scroll-mt-16">
854
- <CardHeader>
855
- <CardTitle className="text-base flex items-center gap-2">
856
- <IconBrandSlack className="size-4 text-primary" />
857
- {t("settings.slackTitle")}
858
- </CardTitle>
859
- <CardDescription>
860
- {t("settings.slackDescription")}
861
- </CardDescription>
862
- </CardHeader>
863
- <CardContent className="space-y-4">
864
- <div className="flex flex-col gap-3 rounded-md border border-border bg-accent/30 px-3 py-3 sm:flex-row sm:items-center sm:justify-between">
865
- <div className="min-w-0">
866
- <div className="flex items-center gap-2 text-sm font-medium">
867
- <IconBrandSlack className="h-4 w-4 text-muted-foreground" />
868
- {slackStatus.isLoading
869
- ? t("settings.checkingSlack")
870
- : slackConnected
871
- ? t("settings.slackConnected", {
872
- count: slackInstallations.length,
873
- })
874
- : slackOauthConfigured
875
- ? t("common.notConnected")
876
- : t("settings.slackOauthNeeded")}
954
+ <p className="mt-0.5 text-xs text-muted-foreground">
955
+ {builderConnected
956
+ ? t("settings.builderAiAvailable")
957
+ : t("settings.builderAiDescription")}
958
+ </p>
877
959
  </div>
878
- <p className="mt-0.5 text-xs text-muted-foreground">
879
- {t("settings.slackPreviewDescription")}
880
- </p>
881
- </div>
882
- <Button
883
- type="button"
884
- variant="outline"
885
- size="sm"
886
- className="shrink-0"
887
- onClick={handleConnectSlack}
888
- disabled={
889
- connectingSlack ||
890
- slackStatus.isLoading ||
891
- !slackOauthConfigured
892
- }
893
- >
894
- {connectingSlack ? (
895
- <IconLoader2 className="h-4 w-4 animate-spin" />
960
+ {builderConnected ? (
961
+ <Badge variant="secondary" className="shrink-0">
962
+ {t("common.connected")}
963
+ </Badge>
896
964
  ) : (
897
- <IconExternalLink className="h-4 w-4" />
965
+ <Button
966
+ type="button"
967
+ variant="outline"
968
+ size="sm"
969
+ className="shrink-0"
970
+ onClick={() =>
971
+ builderConnect.start({
972
+ trackingSource: "clips_settings_ai_setup",
973
+ trackingFlow: "connect_llm",
974
+ })
975
+ }
976
+ disabled={
977
+ builderConnect.connecting || builderStatusLoading
978
+ }
979
+ >
980
+ {builderConnect.connecting ? (
981
+ <IconLoader2 className="h-4 w-4 animate-spin" />
982
+ ) : (
983
+ <IconExternalLink className="h-4 w-4" />
984
+ )}
985
+ {t("settings.connectBuilder")}
986
+ </Button>
898
987
  )}
899
- {t("settings.connectSlack")}
900
- </Button>
901
- </div>
902
-
903
- {!slackOauthConfigured ? (
904
- <div className="rounded-md border border-border p-3 text-xs text-muted-foreground">
905
- {t("settings.slackClientMissing")}
906
- </div>
907
- ) : null}
908
-
909
- {!slackSigningConfigured ? (
910
- <div className="rounded-md border border-border p-3 text-xs text-muted-foreground">
911
- {t("settings.slackSigningMissing")}
912
988
  </div>
913
- ) : null}
914
989
 
915
- {slackInstallations.length > 0 ? (
916
- <div className="space-y-2">
917
- {slackInstallations.map((installation) => (
918
- <div
919
- key={installation.id}
920
- className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2"
921
- >
922
- <div className="min-w-0">
923
- <div className="truncate text-sm font-medium">
924
- {installation.teamName || installation.teamId}
925
- </div>
926
- <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
927
- <span>{installation.status}</span>
928
- {installation.enterpriseName ? (
929
- <span>{installation.enterpriseName}</span>
930
- ) : null}
931
- <span>
932
- {t("settings.connectedBy", {
933
- email: installation.ownerEmail,
934
- })}
935
- </span>
936
- </div>
937
- </div>
938
- <Button
990
+ <Collapsible
991
+ open={apiKeysExpanded}
992
+ onOpenChange={setApiKeysExpanded}
993
+ >
994
+ <div className="rounded-md border border-border">
995
+ <CollapsibleTrigger asChild>
996
+ <button
939
997
  type="button"
940
- variant="ghost"
941
- size="icon"
942
- className="shrink-0"
943
- aria-label={t("settings.disconnectSlackLabel", {
944
- team: installation.teamName || installation.teamId,
945
- })}
946
- onClick={() => setDisconnectSlackTarget(installation)}
998
+ className="flex w-full items-center justify-between gap-3 px-3 py-3 text-start"
947
999
  >
948
- <IconTrash className="h-4 w-4" />
949
- </Button>
950
- </div>
951
- ))}
952
- </div>
953
- ) : null}
954
- </CardContent>
955
- </Card>
956
-
957
- <Card id="ai-providers" className="scroll-mt-16">
958
- <CardHeader>
959
- <CardTitle className="text-base flex items-center gap-2">
960
- <IconBrain className="size-4 text-primary" />
961
- {t("settings.apiSetup")}
962
- </CardTitle>
963
- <CardDescription>
964
- {t("settings.apiSetupDescription")}
965
- </CardDescription>
966
- </CardHeader>
967
- <CardContent className="space-y-4">
968
- <div
969
- className={cn(
970
- "flex flex-col gap-3 rounded-md border px-3 py-3 sm:flex-row sm:items-center sm:justify-between",
971
- builderConnected
972
- ? "border-primary/35 bg-primary/5"
973
- : "border-border bg-accent/30",
974
- )}
975
- >
976
- <div className="min-w-0">
977
- <div className="flex items-center gap-2 text-sm font-medium">
978
- {builderConnected ? (
979
- <IconCheck className="h-4 w-4 text-primary" />
980
- ) : (
981
- <IconKey className="h-4 w-4 text-muted-foreground" />
982
- )}
983
- {builderStatusLoading
984
- ? t("settings.checkingBuilder")
985
- : builderConnected
986
- ? t("settings.builderConnected")
987
- : t("settings.builderEasySetup")}
988
- </div>
989
- <p className="mt-0.5 text-xs text-muted-foreground">
990
- {builderConnected
991
- ? t("settings.builderAiAvailable")
992
- : t("settings.builderAiDescription")}
993
- </p>
994
- </div>
995
- {builderConnected ? (
996
- <Badge variant="secondary" className="shrink-0">
997
- {t("common.connected")}
998
- </Badge>
999
- ) : (
1000
- <Button
1001
- type="button"
1002
- variant="outline"
1003
- size="sm"
1004
- className="shrink-0"
1005
- onClick={() =>
1006
- builderConnect.start({
1007
- trackingSource: "clips_settings_ai_setup",
1008
- trackingFlow: "connect_llm",
1009
- })
1010
- }
1011
- disabled={
1012
- builderConnect.connecting || builderStatusLoading
1013
- }
1014
- >
1015
- {builderConnect.connecting ? (
1016
- <IconLoader2 className="h-4 w-4 animate-spin" />
1017
- ) : (
1018
- <IconExternalLink className="h-4 w-4" />
1019
- )}
1020
- {t("settings.connectBuilder")}
1021
- </Button>
1022
- )}
1023
- </div>
1024
-
1025
- <Collapsible
1026
- open={apiKeysExpanded}
1027
- onOpenChange={setApiKeysExpanded}
1028
- >
1029
- <div className="rounded-md border border-border">
1030
- <CollapsibleTrigger asChild>
1031
- <button
1032
- type="button"
1033
- className="flex w-full items-center justify-between gap-3 px-3 py-3 text-start"
1034
- >
1035
- <div className="min-w-0">
1036
- <div className="flex items-center gap-2 text-sm font-medium">
1037
- <IconKey className="h-4 w-4 text-muted-foreground" />
1038
- {t("settings.providerKeyTitle")}
1039
- {configuredApiKeyCount > 0 ? (
1040
- <Badge
1041
- variant="secondary"
1042
- className="text-[10px]"
1043
- >
1044
- {t("settings.providerKeysSet", {
1045
- count: configuredApiKeyCount,
1046
- })}
1047
- </Badge>
1048
- ) : null}
1049
- </div>
1050
- <p className="mt-0.5 text-xs text-muted-foreground">
1051
- {t("settings.providerKeyDescription")}
1052
- </p>
1053
- </div>
1054
- <IconChevronDown
1055
- className={cn(
1056
- "h-4 w-4 shrink-0 text-muted-foreground transition-transform",
1057
- apiKeysExpanded && "rotate-180",
1058
- )}
1059
- />
1060
- </button>
1061
- </CollapsibleTrigger>
1062
- <CollapsibleContent>
1063
- <div className="space-y-3 border-t border-border px-3 py-4">
1064
- {apiKeyStatusLoading ? (
1065
- <div className="text-xs text-muted-foreground">
1066
- {t("settings.checkingProviderKeys")}
1000
+ <div className="min-w-0">
1001
+ <div className="flex items-center gap-2 text-sm font-medium">
1002
+ <IconKey className="h-4 w-4 text-muted-foreground" />
1003
+ {t("settings.providerKeyTitle")}
1004
+ {configuredApiKeyCount > 0 ? (
1005
+ <Badge
1006
+ variant="secondary"
1007
+ className="text-[10px]"
1008
+ >
1009
+ {t("settings.providerKeysSet", {
1010
+ count: configuredApiKeyCount,
1011
+ })}
1012
+ </Badge>
1013
+ ) : null}
1014
+ </div>
1015
+ <p className="mt-0.5 text-xs text-muted-foreground">
1016
+ {t("settings.providerKeyDescription")}
1017
+ </p>
1067
1018
  </div>
1068
- ) : null}
1069
- <div className="grid gap-3 sm:grid-cols-2">
1070
- {AI_PROVIDER_FIELDS.map((field) => {
1071
- const configured = Boolean(apiKeyStatus[field.key]);
1072
- const savingThisKey = savingApiKey === field.key;
1073
- return (
1074
- <div key={field.key} className="space-y-1.5">
1075
- <div className="flex items-center justify-between gap-2">
1076
- <Label htmlFor={field.key}>
1077
- {field.label}
1078
- </Label>
1079
- {configured ? (
1080
- <span className="flex items-center gap-1 text-[10px] font-medium text-primary">
1081
- <IconCheck className="h-3 w-3" />
1082
- {t("settings.keySet")}
1083
- </span>
1084
- ) : null}
1085
- </div>
1086
- <div className="flex gap-2">
1087
- <Input
1088
- id={field.key}
1089
- type="password"
1090
- value={apiKeyValues[field.key] ?? ""}
1091
- onChange={(event) =>
1092
- setApiKeyValues((current) => ({
1093
- ...current,
1094
- [field.key]: event.target.value,
1095
- }))
1096
- }
1097
- onKeyDown={(event) => {
1098
- if (event.key === "Enter") {
1099
- void handleSaveApiKey(field.key);
1019
+ <IconChevronDown
1020
+ className={cn(
1021
+ "h-4 w-4 shrink-0 text-muted-foreground transition-transform",
1022
+ apiKeysExpanded && "rotate-180",
1023
+ )}
1024
+ />
1025
+ </button>
1026
+ </CollapsibleTrigger>
1027
+ <CollapsibleContent>
1028
+ <div className="space-y-3 border-t border-border px-3 py-4">
1029
+ {apiKeyStatusLoading ? (
1030
+ <div className="text-xs text-muted-foreground">
1031
+ {t("settings.checkingProviderKeys")}
1032
+ </div>
1033
+ ) : null}
1034
+ <div className="grid gap-3 sm:grid-cols-2">
1035
+ {AI_PROVIDER_FIELDS.map((field) => {
1036
+ const configured = Boolean(
1037
+ apiKeyStatus[field.key],
1038
+ );
1039
+ const savingThisKey = savingApiKey === field.key;
1040
+ return (
1041
+ <div key={field.key} className="space-y-1.5">
1042
+ <div className="flex items-center justify-between gap-2">
1043
+ <Label htmlFor={field.key}>
1044
+ {field.label}
1045
+ </Label>
1046
+ {configured ? (
1047
+ <span className="flex items-center gap-1 text-[10px] font-medium text-primary">
1048
+ <IconCheck className="h-3 w-3" />
1049
+ {t("settings.keySet")}
1050
+ </span>
1051
+ ) : null}
1052
+ </div>
1053
+ <div className="flex gap-2">
1054
+ <Input
1055
+ id={field.key}
1056
+ type="password"
1057
+ value={apiKeyValues[field.key] ?? ""}
1058
+ onChange={(event) =>
1059
+ setApiKeyValues((current) => ({
1060
+ ...current,
1061
+ [field.key]: event.target.value,
1062
+ }))
1063
+ }
1064
+ onKeyDown={(event) => {
1065
+ if (event.key === "Enter") {
1066
+ void handleSaveApiKey(field.key);
1067
+ }
1068
+ }}
1069
+ placeholder={
1070
+ configured
1071
+ ? t("settings.replaceKey")
1072
+ : field.placeholder
1073
+ }
1074
+ autoComplete="off"
1075
+ disabled={savingThisKey}
1076
+ />
1077
+ <Button
1078
+ type="button"
1079
+ variant="outline"
1080
+ size="sm"
1081
+ className="shrink-0"
1082
+ onClick={() =>
1083
+ handleSaveApiKey(field.key)
1100
1084
  }
1101
- }}
1102
- placeholder={
1103
- configured
1104
- ? t("settings.replaceKey")
1105
- : field.placeholder
1106
- }
1107
- autoComplete="off"
1108
- disabled={savingThisKey}
1109
- />
1110
- <Button
1111
- type="button"
1112
- variant="outline"
1113
- size="sm"
1114
- className="shrink-0"
1115
- onClick={() => handleSaveApiKey(field.key)}
1116
- disabled={
1117
- savingThisKey ||
1118
- !(apiKeyValues[field.key] ?? "").trim()
1119
- }
1120
- >
1121
- {savingThisKey ? (
1122
- <IconLoader2 className="h-4 w-4 animate-spin" />
1123
- ) : (
1124
- t("common.save")
1125
- )}
1126
- </Button>
1085
+ disabled={
1086
+ savingThisKey ||
1087
+ !(apiKeyValues[field.key] ?? "").trim()
1088
+ }
1089
+ >
1090
+ {savingThisKey ? (
1091
+ <IconLoader2 className="h-4 w-4 animate-spin" />
1092
+ ) : (
1093
+ t("common.save")
1094
+ )}
1095
+ </Button>
1096
+ </div>
1127
1097
  </div>
1128
- </div>
1129
- );
1130
- })}
1098
+ );
1099
+ })}
1100
+ </div>
1131
1101
  </div>
1132
- </div>
1133
- </CollapsibleContent>
1134
- </div>
1135
- </Collapsible>
1136
- </CardContent>
1137
- </Card>
1138
-
1139
- <Card>
1140
- <CardHeader>
1141
- <CardTitle className="text-base flex items-center gap-2">
1142
- <IconUser className="size-4 text-primary" />
1143
- {t("settings.profile")}
1144
- </CardTitle>
1145
- </CardHeader>
1146
- <CardContent className="space-y-4">
1147
- <div className="space-y-1.5">
1148
- <Label htmlFor="email">{t("settings.email")}</Label>
1149
- <Input id="email" value={email} readOnly disabled />
1150
- </div>
1151
- <div className="space-y-1.5">
1152
- <Label htmlFor="display-name">
1153
- {t("settings.displayName")}
1154
- </Label>
1155
- <Input
1156
- id="display-name"
1157
- value={displayName}
1158
- onChange={(e) => setDisplayName(e.target.value)}
1159
- placeholder={t("settings.displayNamePlaceholder")}
1160
- disabled={loading}
1161
- />
1162
- </div>
1163
- </CardContent>
1164
- </Card>
1165
-
1166
- <Card>
1167
- <CardHeader>
1168
- <CardTitle className="text-base">
1169
- {t("settings.playback")}
1170
- </CardTitle>
1171
- </CardHeader>
1172
- <CardContent className="space-y-4">
1173
- <div className="space-y-1.5">
1174
- <Label htmlFor="speed">
1175
- {t("settings.defaultPlaybackSpeed")}
1176
- </Label>
1177
- <Select
1178
- value={defaultSpeed}
1179
- onValueChange={setDefaultSpeed}
1180
- disabled={loading}
1181
- >
1182
- <SelectTrigger id="speed" className="w-40">
1183
- <SelectValue />
1184
- </SelectTrigger>
1185
- <SelectContent>
1186
- {SPEEDS.map((s) => (
1187
- <SelectItem key={s} value={s}>
1188
- {s}×
1189
- </SelectItem>
1190
- ))}
1191
- </SelectContent>
1192
- </Select>
1193
- <p className="text-xs text-muted-foreground">
1194
- {t("settings.playbackDescription")}
1195
- </p>
1196
- </div>
1197
- </CardContent>
1198
- </Card>
1102
+ </CollapsibleContent>
1103
+ </div>
1104
+ </Collapsible>
1105
+ </CardContent>
1106
+ </Card>
1199
1107
 
1200
- <Card>
1201
- <CardHeader>
1202
- <CardTitle className="text-base">
1203
- {t("settings.transcript")}
1204
- </CardTitle>
1205
- </CardHeader>
1206
- <CardContent>
1207
- <div className="flex items-center justify-between gap-4">
1208
- <div>
1209
- <Label
1210
- htmlFor="transcript-cleanup"
1211
- className="cursor-pointer"
1212
- >
1213
- {t("settings.transcriptCleanup")}
1108
+ <Card>
1109
+ <CardHeader>
1110
+ <CardTitle className="text-base flex items-center gap-2">
1111
+ <IconUser className="size-4 text-primary" />
1112
+ {t("settings.profile")}
1113
+ </CardTitle>
1114
+ </CardHeader>
1115
+ <CardContent className="space-y-4">
1116
+ <div className="space-y-1.5">
1117
+ <Label htmlFor="email">{t("settings.email")}</Label>
1118
+ <Input id="email" value={email} readOnly disabled />
1119
+ </div>
1120
+ <div className="space-y-1.5">
1121
+ <Label htmlFor="display-name">
1122
+ {t("settings.displayName")}
1214
1123
  </Label>
1215
- <p className="text-xs text-muted-foreground mt-0.5">
1216
- {t("settings.transcriptCleanupDescription")}
1217
- </p>
1124
+ <Input
1125
+ id="display-name"
1126
+ value={displayName}
1127
+ onChange={(e) => setDisplayName(e.target.value)}
1128
+ placeholder={t("settings.displayNamePlaceholder")}
1129
+ disabled={loading}
1130
+ />
1218
1131
  </div>
1219
- <Switch
1220
- id="transcript-cleanup"
1221
- checked={transcriptCleanupEnabled}
1222
- onCheckedChange={setTranscriptCleanupEnabled}
1223
- disabled={loading}
1224
- />
1225
- </div>
1226
- </CardContent>
1227
- </Card>
1132
+ </CardContent>
1133
+ </Card>
1228
1134
 
1229
- <Card>
1230
- <CardHeader>
1231
- <CardTitle className="text-base">
1232
- {t("settings.notifications")}
1233
- </CardTitle>
1234
- </CardHeader>
1235
- <CardContent>
1236
- <div className="flex items-center justify-between">
1237
- <div>
1238
- <Label htmlFor="email-notif" className="cursor-pointer">
1239
- {t("settings.emailNotifications")}
1135
+ <Card>
1136
+ <CardHeader>
1137
+ <CardTitle className="text-base">
1138
+ {t("settings.playback")}
1139
+ </CardTitle>
1140
+ </CardHeader>
1141
+ <CardContent className="space-y-4">
1142
+ <div className="space-y-1.5">
1143
+ <Label htmlFor="speed">
1144
+ {t("settings.defaultPlaybackSpeed")}
1240
1145
  </Label>
1241
- <p className="text-xs text-muted-foreground mt-0.5">
1242
- {t("settings.emailNotificationsDescription")}
1146
+ <Select
1147
+ value={defaultSpeed}
1148
+ onValueChange={setDefaultSpeed}
1149
+ disabled={loading}
1150
+ >
1151
+ <SelectTrigger id="speed" className="w-40">
1152
+ <SelectValue />
1153
+ </SelectTrigger>
1154
+ <SelectContent>
1155
+ {SPEEDS.map((s) => (
1156
+ <SelectItem key={s} value={s}>
1157
+ {s}×
1158
+ </SelectItem>
1159
+ ))}
1160
+ </SelectContent>
1161
+ </Select>
1162
+ <p className="text-xs text-muted-foreground">
1163
+ {t("settings.playbackDescription")}
1243
1164
  </p>
1244
1165
  </div>
1245
- <Switch
1246
- id="email-notif"
1247
- checked={emailNotifications}
1248
- onCheckedChange={setEmailNotifications}
1249
- disabled={loading}
1250
- />
1251
- </div>
1252
- </CardContent>
1253
- </Card>
1166
+ </CardContent>
1167
+ </Card>
1254
1168
 
1255
- <div className="flex justify-end">
1256
- <Button
1257
- onClick={handleSave}
1258
- disabled={loading || saving}
1259
- className="bg-primary hover:bg-primary/90"
1260
- >
1261
- {saving ? t("common.saving") : t("common.saveChanges")}
1262
- </Button>
1169
+ <Card>
1170
+ <CardHeader>
1171
+ <CardTitle className="text-base">
1172
+ {t("settings.transcript")}
1173
+ </CardTitle>
1174
+ </CardHeader>
1175
+ <CardContent>
1176
+ <div className="flex items-center justify-between gap-4">
1177
+ <div>
1178
+ <Label
1179
+ htmlFor="transcript-cleanup"
1180
+ className="cursor-pointer"
1181
+ >
1182
+ {t("settings.transcriptCleanup")}
1183
+ </Label>
1184
+ <p className="text-xs text-muted-foreground mt-0.5">
1185
+ {t("settings.transcriptCleanupDescription")}
1186
+ </p>
1187
+ </div>
1188
+ <Switch
1189
+ id="transcript-cleanup"
1190
+ checked={transcriptCleanupEnabled}
1191
+ onCheckedChange={setTranscriptCleanupEnabled}
1192
+ disabled={loading}
1193
+ />
1194
+ </div>
1195
+ </CardContent>
1196
+ </Card>
1197
+
1198
+ <Card>
1199
+ <CardHeader>
1200
+ <CardTitle className="text-base">
1201
+ {t("settings.notifications")}
1202
+ </CardTitle>
1203
+ </CardHeader>
1204
+ <CardContent>
1205
+ <div className="flex items-center justify-between">
1206
+ <div>
1207
+ <Label htmlFor="email-notif" className="cursor-pointer">
1208
+ {t("settings.emailNotifications")}
1209
+ </Label>
1210
+ <p className="text-xs text-muted-foreground mt-0.5">
1211
+ {t("settings.emailNotificationsDescription")}
1212
+ </p>
1213
+ </div>
1214
+ <Switch
1215
+ id="email-notif"
1216
+ checked={emailNotifications}
1217
+ onCheckedChange={setEmailNotifications}
1218
+ disabled={loading}
1219
+ />
1220
+ </div>
1221
+ </CardContent>
1222
+ </Card>
1223
+
1224
+ <div className="flex justify-end">
1225
+ <Button
1226
+ onClick={handleSave}
1227
+ disabled={loading || saving}
1228
+ className="bg-primary hover:bg-primary/90"
1229
+ >
1230
+ {saving ? t("common.saving") : t("common.saveChanges")}
1231
+ </Button>
1232
+ </div>
1263
1233
  </div>
1264
1234
  </div>
1265
- </div>
1266
- <div className="mt-6">
1267
- <CompactChangelogAside />
1268
- </div>
1269
- </div>
1235
+ }
1236
+ team={
1237
+ <div className="mx-auto w-full max-w-3xl">
1238
+ <TeamPage
1239
+ showTitle={false}
1240
+ createOrgDescription={t("organizationSettings.description")}
1241
+ />
1242
+ </div>
1243
+ }
1244
+ whatsNew={
1245
+ <div className="mx-auto w-full max-w-3xl">
1246
+ <ChangelogSettingsCard
1247
+ markdown={localizedChangelog}
1248
+ title={t("settings.whatsNew")}
1249
+ closeLabel={t("common.cancel")}
1250
+ emptyText={t("settings.changelogEmpty")}
1251
+ viewAllLabel={t("settings.viewAllUpdates")}
1252
+ />
1253
+ </div>
1254
+ }
1255
+ />
1270
1256
  <AlertDialog
1271
1257
  open={!!disconnectSlackTarget}
1272
1258
  onOpenChange={(open) => {