@vertesia/ui 1.6.0-dev.20260918.090710Z → 1.6.0-dev.20260923.085325Z

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 (288) hide show
  1. package/lib/boot/branding.d.ts +13 -0
  2. package/lib/boot/branding.d.ts.map +1 -1
  3. package/lib/boot/branding.js +7 -0
  4. package/lib/boot/branding.js.map +1 -1
  5. package/lib/boot/index.d.ts +1 -1
  6. package/lib/boot/index.d.ts.map +1 -1
  7. package/lib/boot/index.js +5 -23
  8. package/lib/boot/index.js.map +1 -1
  9. package/lib/boot/loading.d.ts +4 -0
  10. package/lib/boot/loading.d.ts.map +1 -0
  11. package/lib/boot/loading.js +28 -0
  12. package/lib/boot/loading.js.map +1 -0
  13. package/lib/core/components/SelectList.d.ts.map +1 -1
  14. package/lib/core/components/SelectList.js +3 -3
  15. package/lib/core/components/SelectList.js.map +1 -1
  16. package/lib/core/components/SidePanel.d.ts.map +1 -1
  17. package/lib/core/components/SidePanel.js +2 -1
  18. package/lib/core/components/SidePanel.js.map +1 -1
  19. package/lib/core/components/shadcn/theme/ThemeProvider.d.ts.map +1 -1
  20. package/lib/core/components/shadcn/theme/ThemeProvider.js +21 -5
  21. package/lib/core/components/shadcn/theme/ThemeProvider.js.map +1 -1
  22. package/lib/env/index.d.ts +11 -0
  23. package/lib/env/index.d.ts.map +1 -1
  24. package/lib/env/index.js +16 -3
  25. package/lib/env/index.js.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentConversation.js +4 -1
  27. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +5 -1
  29. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  30. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +9 -2
  31. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  32. package/lib/features/magic-pdf/GroundedExtractionView.js +3 -3
  33. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -1
  34. package/lib/features/media-viewer/AudioPanel.d.ts +10 -2
  35. package/lib/features/media-viewer/AudioPanel.d.ts.map +1 -1
  36. package/lib/features/media-viewer/AudioPanel.js +78 -13
  37. package/lib/features/media-viewer/AudioPanel.js.map +1 -1
  38. package/lib/features/oauth/McpConnectionsButton.d.ts +2 -2
  39. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
  40. package/lib/features/oauth/McpConnectionsButton.js +9 -7
  41. package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
  42. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
  43. package/lib/features/oauth/McpConnectionsDialog.js +7 -3
  44. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
  45. package/lib/features/oauth/useMcpConnections.d.ts +4 -2
  46. package/lib/features/oauth/useMcpConnections.d.ts.map +1 -1
  47. package/lib/features/oauth/useMcpConnections.js +15 -9
  48. package/lib/features/oauth/useMcpConnections.js.map +1 -1
  49. package/lib/features/store/objects/selection/ObjectsActionContext.d.ts.map +1 -1
  50. package/lib/features/store/objects/selection/ObjectsActionContext.js +2 -0
  51. package/lib/features/store/objects/selection/ObjectsActionContext.js.map +1 -1
  52. package/lib/features/store/objects/selection/SelectionActions.d.ts +4 -2
  53. package/lib/features/store/objects/selection/SelectionActions.d.ts.map +1 -1
  54. package/lib/features/store/objects/selection/SelectionActions.js +6 -3
  55. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  56. package/lib/features/store/objects/selection/actions/StartWorkflowAction.d.ts +4 -0
  57. package/lib/features/store/objects/selection/actions/StartWorkflowAction.d.ts.map +1 -0
  58. package/lib/features/store/objects/selection/actions/StartWorkflowAction.js +81 -0
  59. package/lib/features/store/objects/selection/actions/StartWorkflowAction.js.map +1 -0
  60. package/lib/features/store/objects/selection/actions/index.d.ts +1 -0
  61. package/lib/features/store/objects/selection/actions/index.d.ts.map +1 -1
  62. package/lib/features/store/objects/selection/actions/index.js +1 -0
  63. package/lib/features/store/objects/selection/actions/index.js.map +1 -1
  64. package/lib/features/store/types/TypeRegistry.d.ts +345 -155
  65. package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
  66. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +345 -155
  67. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
  68. package/lib/i18n/locales/ar.json +10 -2
  69. package/lib/i18n/locales/de.json +6 -2
  70. package/lib/i18n/locales/en.json +6 -2
  71. package/lib/i18n/locales/es.json +6 -2
  72. package/lib/i18n/locales/fr.json +7 -2
  73. package/lib/i18n/locales/it.json +6 -2
  74. package/lib/i18n/locales/ja.json +6 -2
  75. package/lib/i18n/locales/ko.json +6 -2
  76. package/lib/i18n/locales/pt.json +6 -2
  77. package/lib/i18n/locales/ru.json +8 -2
  78. package/lib/i18n/locales/tr.json +6 -2
  79. package/lib/i18n/locales/zh-TW.json +6 -2
  80. package/lib/i18n/locales/zh.json +6 -2
  81. package/lib/session/UserSession.d.ts +3 -0
  82. package/lib/session/UserSession.d.ts.map +1 -1
  83. package/lib/session/UserSession.js +18 -8
  84. package/lib/session/UserSession.js.map +1 -1
  85. package/lib/session/UserSessionProvider.d.ts.map +1 -1
  86. package/lib/session/UserSessionProvider.js +34 -24
  87. package/lib/session/UserSessionProvider.js.map +1 -1
  88. package/lib/session/auth/authRoundTrip.d.ts +8 -0
  89. package/lib/session/auth/authRoundTrip.d.ts.map +1 -0
  90. package/lib/session/auth/authRoundTrip.js +22 -0
  91. package/lib/session/auth/authRoundTrip.js.map +1 -0
  92. package/lib/session/auth/composable.d.ts.map +1 -1
  93. package/lib/session/auth/composable.js +9 -7
  94. package/lib/session/auth/composable.js.map +1 -1
  95. package/lib/session/auth/domainRouting.d.ts +13 -6
  96. package/lib/session/auth/domainRouting.d.ts.map +1 -1
  97. package/lib/session/auth/domainRouting.js +2 -21
  98. package/lib/session/auth/domainRouting.js.map +1 -1
  99. package/lib/session/auth/oauth.d.ts +6 -1
  100. package/lib/session/auth/oauth.d.ts.map +1 -1
  101. package/lib/session/auth/oauth.js +145 -8
  102. package/lib/session/auth/oauth.js.map +1 -1
  103. package/lib/session/auth/tokenIssuer.d.ts +3 -0
  104. package/lib/session/auth/tokenIssuer.d.ts.map +1 -0
  105. package/lib/session/auth/tokenIssuer.js +27 -0
  106. package/lib/session/auth/tokenIssuer.js.map +1 -0
  107. package/lib/session/auth/vertesiaHosts.d.ts +47 -0
  108. package/lib/session/auth/vertesiaHosts.d.ts.map +1 -0
  109. package/lib/session/auth/vertesiaHosts.js +79 -0
  110. package/lib/session/auth/vertesiaHosts.js.map +1 -0
  111. package/lib/session/index.d.ts +1 -0
  112. package/lib/session/index.d.ts.map +1 -1
  113. package/lib/session/index.js +1 -0
  114. package/lib/session/index.js.map +1 -1
  115. package/lib/shell/AuthScreenPreview.d.ts +1 -1
  116. package/lib/shell/AuthScreenPreview.d.ts.map +1 -1
  117. package/lib/shell/AuthScreenPreview.js +2 -5
  118. package/lib/shell/AuthScreenPreview.js.map +1 -1
  119. package/lib/shell/BrandedAuthScreens.d.ts +1 -4
  120. package/lib/shell/BrandedAuthScreens.d.ts.map +1 -1
  121. package/lib/shell/BrandedAuthScreens.js +5 -26
  122. package/lib/shell/BrandedAuthScreens.js.map +1 -1
  123. package/lib/shell/BrandedLoadingIndicator.d.ts +11 -0
  124. package/lib/shell/BrandedLoadingIndicator.d.ts.map +1 -0
  125. package/lib/shell/BrandedLoadingIndicator.js +27 -0
  126. package/lib/shell/BrandedLoadingIndicator.js.map +1 -0
  127. package/lib/shell/SplashScreen.d.ts.map +1 -1
  128. package/lib/shell/SplashScreen.js +2 -1
  129. package/lib/shell/SplashScreen.js.map +1 -1
  130. package/lib/shell/apps/AppInstallationProvider.d.ts +16 -4
  131. package/lib/shell/apps/AppInstallationProvider.d.ts.map +1 -1
  132. package/lib/shell/apps/iframe-auth.d.ts +2 -0
  133. package/lib/shell/apps/iframe-auth.d.ts.map +1 -1
  134. package/lib/shell/apps/iframe-auth.js +6 -2
  135. package/lib/shell/apps/iframe-auth.js.map +1 -1
  136. package/lib/shell/index.d.ts +1 -0
  137. package/lib/shell/index.d.ts.map +1 -1
  138. package/lib/shell/index.js +1 -0
  139. package/lib/shell/index.js.map +1 -1
  140. package/lib/shell/login/SignInAuthPending.js +2 -2
  141. package/lib/shell/login/SignInAuthPending.js.map +1 -1
  142. package/lib/shell/login/SignInEmailStep.d.ts.map +1 -1
  143. package/lib/shell/login/SignInEmailStep.js +2 -2
  144. package/lib/shell/login/SignInEmailStep.js.map +1 -1
  145. package/lib/shell/login/TerminalLogin.d.ts.map +1 -1
  146. package/lib/shell/login/TerminalLogin.js +28 -21
  147. package/lib/shell/login/TerminalLogin.js.map +1 -1
  148. package/lib/vertesia-ui-boot.js +33 -29
  149. package/lib/vertesia-ui-boot.js.map +1 -1
  150. package/lib/vertesia-ui-core.js +34 -34
  151. package/lib/vertesia-ui-core.js.map +1 -1
  152. package/lib/vertesia-ui-env.js +1 -1
  153. package/lib/vertesia-ui-env.js.map +1 -1
  154. package/lib/vertesia-ui-features.js +163 -163
  155. package/lib/vertesia-ui-features.js.map +1 -1
  156. package/lib/vertesia-ui-i18n.Bl8KeZUK.chunk.js +5 -0
  157. package/lib/vertesia-ui-i18n.Bl8KeZUK.chunk.js.map +1 -0
  158. package/lib/vertesia-ui-i18n.BmT06XFI.chunk.js +5 -0
  159. package/lib/vertesia-ui-i18n.BmT06XFI.chunk.js.map +1 -0
  160. package/lib/vertesia-ui-i18n.Ch-Jjj0L.chunk.js +5 -0
  161. package/lib/vertesia-ui-i18n.Ch-Jjj0L.chunk.js.map +1 -0
  162. package/lib/vertesia-ui-i18n.CjhloW9s.chunk.js +5 -0
  163. package/lib/vertesia-ui-i18n.CjhloW9s.chunk.js.map +1 -0
  164. package/lib/vertesia-ui-i18n.CkfHdQzY.chunk.js +5 -0
  165. package/lib/vertesia-ui-i18n.CkfHdQzY.chunk.js.map +1 -0
  166. package/lib/vertesia-ui-i18n.DFuKfTe1.chunk.js +5 -0
  167. package/lib/vertesia-ui-i18n.DFuKfTe1.chunk.js.map +1 -0
  168. package/lib/vertesia-ui-i18n.DHqNVekZ.chunk.js +5 -0
  169. package/lib/vertesia-ui-i18n.DHqNVekZ.chunk.js.map +1 -0
  170. package/lib/vertesia-ui-i18n.DOaKbmaj.chunk.js +5 -0
  171. package/lib/vertesia-ui-i18n.DOaKbmaj.chunk.js.map +1 -0
  172. package/lib/vertesia-ui-i18n.Dge3Gl-h.chunk.js +5 -0
  173. package/lib/vertesia-ui-i18n.Dge3Gl-h.chunk.js.map +1 -0
  174. package/lib/vertesia-ui-i18n.GLt-10Lx.chunk.js +5 -0
  175. package/lib/vertesia-ui-i18n.GLt-10Lx.chunk.js.map +1 -0
  176. package/lib/vertesia-ui-i18n.T-XtAGbw.chunk.js +5 -0
  177. package/lib/vertesia-ui-i18n.T-XtAGbw.chunk.js.map +1 -0
  178. package/lib/vertesia-ui-i18n.VOJyTpMM.chunk.js +5 -0
  179. package/lib/vertesia-ui-i18n.VOJyTpMM.chunk.js.map +1 -0
  180. package/lib/vertesia-ui-i18n.js +3 -3
  181. package/lib/vertesia-ui-i18n.js.map +1 -1
  182. package/lib/vertesia-ui-layout.js +15 -15
  183. package/lib/vertesia-ui-layout.js.map +1 -1
  184. package/lib/vertesia-ui-rich-text.js +44 -44
  185. package/lib/vertesia-ui-rich-text.js.map +1 -1
  186. package/lib/vertesia-ui-session.js +1 -1
  187. package/lib/vertesia-ui-session.js.map +1 -1
  188. package/lib/vertesia-ui-shell.js +72 -48
  189. package/lib/vertesia-ui-shell.js.map +1 -1
  190. package/lib/vertesia-ui-widgets.js +51 -51
  191. package/lib/vertesia-ui-widgets.js.map +1 -1
  192. package/package.json +19 -19
  193. package/src/boot/branding.ts +8 -0
  194. package/src/boot/index.ts +6 -23
  195. package/src/boot/loading.ts +27 -0
  196. package/src/core/components/SelectList.tsx +5 -2
  197. package/src/core/components/SidePanel.tsx +65 -60
  198. package/src/core/components/shadcn/theme/ThemeProvider.test.tsx +101 -0
  199. package/src/core/components/shadcn/theme/ThemeProvider.tsx +21 -9
  200. package/src/env/index.test.ts +21 -0
  201. package/src/env/index.ts +33 -2
  202. package/src/features/agent/chat/ModernAgentConversation.test.tsx +81 -0
  203. package/src/features/agent/chat/ModernAgentConversation.tsx +5 -0
  204. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +17 -1
  205. package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
  206. package/src/features/media-viewer/AudioPanel.test.ts +84 -0
  207. package/src/features/media-viewer/AudioPanel.tsx +97 -14
  208. package/src/features/oauth/McpConnectionsButton.test.tsx +56 -0
  209. package/src/features/oauth/McpConnectionsButton.tsx +9 -6
  210. package/src/features/oauth/McpConnectionsDialog.tsx +14 -5
  211. package/src/features/oauth/useMcpConnections.test.ts +10 -0
  212. package/src/features/oauth/useMcpConnections.ts +18 -10
  213. package/src/features/store/objects/selection/ObjectsActionContext.tsx +2 -0
  214. package/src/features/store/objects/selection/SelectionActions.tsx +13 -6
  215. package/src/features/store/objects/selection/actions/StartWorkflowAction.tsx +162 -0
  216. package/src/features/store/objects/selection/actions/index.ts +1 -0
  217. package/src/i18n/locales/ar.json +10 -2
  218. package/src/i18n/locales/de.json +6 -2
  219. package/src/i18n/locales/en.json +6 -2
  220. package/src/i18n/locales/es.json +6 -2
  221. package/src/i18n/locales/fr.json +7 -2
  222. package/src/i18n/locales/it.json +6 -2
  223. package/src/i18n/locales/ja.json +6 -2
  224. package/src/i18n/locales/ko.json +6 -2
  225. package/src/i18n/locales/pt.json +6 -2
  226. package/src/i18n/locales/ru.json +8 -2
  227. package/src/i18n/locales/tr.json +6 -2
  228. package/src/i18n/locales/zh-TW.json +6 -2
  229. package/src/i18n/locales/zh.json +6 -2
  230. package/src/session/UserSession.test.ts +48 -3
  231. package/src/session/UserSession.ts +20 -8
  232. package/src/session/UserSessionProvider.handoff.test.tsx +103 -0
  233. package/src/session/UserSessionProvider.iframe.test.tsx +178 -0
  234. package/src/session/UserSessionProvider.state-mismatch.test.tsx +43 -0
  235. package/src/session/UserSessionProvider.tsx +35 -23
  236. package/src/session/auth/auth-flow.md +18 -5
  237. package/src/session/auth/authRoundTrip.ts +24 -0
  238. package/src/session/auth/composable.test.ts +23 -1
  239. package/src/session/auth/composable.ts +10 -8
  240. package/src/session/auth/domainRouting.ts +4 -25
  241. package/src/session/auth/oauth.test.ts +121 -4
  242. package/src/session/auth/oauth.ts +172 -11
  243. package/src/session/auth/tokenIssuer.test.ts +25 -0
  244. package/src/session/auth/tokenIssuer.ts +25 -0
  245. package/src/session/auth/vertesiaHosts.test.ts +26 -0
  246. package/src/session/auth/vertesiaHosts.ts +85 -0
  247. package/src/session/index.ts +1 -0
  248. package/src/shell/AuthScreenPreview.tsx +2 -6
  249. package/src/shell/BrandedAuthScreens.test.tsx +26 -0
  250. package/src/shell/BrandedAuthScreens.tsx +4 -40
  251. package/src/shell/BrandedLoadingIndicator.tsx +42 -0
  252. package/src/shell/SplashScreen.tsx +4 -4
  253. package/src/shell/apps/iframe-auth.test.ts +8 -11
  254. package/src/shell/apps/iframe-auth.ts +10 -1
  255. package/src/shell/index.tsx +1 -0
  256. package/src/shell/login/SignInAuthPending.tsx +2 -2
  257. package/src/shell/login/SignInEmailStep.tsx +4 -2
  258. package/src/shell/login/TerminalLogin.test.tsx +111 -0
  259. package/src/shell/login/TerminalLogin.tsx +61 -37
  260. package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts +0 -7
  261. package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts.map +0 -1
  262. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +0 -51
  263. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +0 -1
  264. package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js +0 -5
  265. package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js.map +0 -1
  266. package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js +0 -5
  267. package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js.map +0 -1
  268. package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js +0 -5
  269. package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js.map +0 -1
  270. package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js +0 -5
  271. package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js.map +0 -1
  272. package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js +0 -5
  273. package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js.map +0 -1
  274. package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js +0 -5
  275. package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js.map +0 -1
  276. package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js +0 -5
  277. package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js.map +0 -1
  278. package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js +0 -5
  279. package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js.map +0 -1
  280. package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js +0 -5
  281. package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js.map +0 -1
  282. package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js +0 -5
  283. package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js.map +0 -1
  284. package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js +0 -5
  285. package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js.map +0 -1
  286. package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js +0 -5
  287. package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js.map +0 -1
  288. package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +0 -104
@@ -9,6 +9,7 @@ import {
9
9
  BrandedPermissionLoadingScreen,
10
10
  BrandedSignInScreen,
11
11
  } from './BrandedAuthScreens';
12
+ import SignInAuthPending from './login/SignInAuthPending';
12
13
  import { SignInFlowSteps, useSignInFlow } from './login/SignInFlow';
13
14
  import { DefaultSignInScreen, type SignInRecoveryMode } from './login/SigninScreen';
14
15
  import { DefaultAuthLoadingScreen } from './SplashScreen';
@@ -41,6 +42,31 @@ function SignInPreview() {
41
42
  }
42
43
 
43
44
  describe('configuration-driven auth screens', () => {
45
+ it('keeps the configured loading logo through the Firebase provider handoff', () => {
46
+ const brand = {
47
+ name: 'Workspace',
48
+ loadingIcon: { light: '/workspace-light.svg', dark: '/workspace-dark.svg' },
49
+ };
50
+ const cancel = vi.fn();
51
+ const view = render(
52
+ <AppBrandingProvider branding={brand}>
53
+ <BrandedAuthLoadingScreen />
54
+ </AppBrandingProvider>,
55
+ );
56
+ const loadingIcons = Array.from(view.container.querySelectorAll('img'), (img) => img.getAttribute('src'));
57
+ view.rerender(
58
+ <AppBrandingProvider branding={brand}>
59
+ <SignInAuthPending provider="google" onCancel={cancel} />
60
+ </AppBrandingProvider>,
61
+ );
62
+ expect(
63
+ Array.from(screen.getByRole('status').querySelectorAll('img'), (img) => img.getAttribute('src')),
64
+ ).toEqual(loadingIcons);
65
+ expect(loadingIcons).toEqual(['/workspace-light.svg', '/workspace-dark.svg']);
66
+ fireEvent.click(screen.getByRole('button'));
67
+ expect(cancel).toHaveBeenCalledOnce();
68
+ });
69
+
44
70
  it('keeps brand copy local to each shell and preserves the shared translations', () => {
45
71
  const original = i18nInstance.t('auth.email.title', { ns: NAMESPACE });
46
72
  render(
@@ -1,12 +1,13 @@
1
1
  import { DefaultPermissionLoadingScreen, type PermissionLoadingScreenProps } from '@vertesia/ui/features';
2
2
  import { UITranslationOverrides } from '@vertesia/ui/i18n';
3
- import { createContext, type ReactNode, useContext, useEffect, useId, useMemo, useState } from 'react';
3
+ import { type ReactNode, useContext, useId, useMemo } from 'react';
4
4
  import { type AppBranding, renderBrandStyles } from '../boot/branding.js';
5
+ import { BrandedLoadingIndicator, BrandingContext, BrandLoadingIcon } from './BrandedLoadingIndicator';
5
6
  import { DefaultSignInScreen, type SignInScreenViewProps } from './login/SigninScreen';
6
- import { type AuthLoadingScreenProps, DefaultAuthLoadingScreen, LoadingAnimation } from './SplashScreen';
7
+ import { type AuthLoadingScreenProps, DefaultAuthLoadingScreen } from './SplashScreen';
7
8
  import type { AuthScreens } from './VertesiaShell';
8
9
 
9
- const BrandingContext = createContext<AppBranding>({ name: '' });
10
+ export { BrandedLoadingIndicator } from './BrandedLoadingIndicator';
10
11
 
11
12
  export function AppBrandingProvider({ branding, children }: { branding: AppBranding; children: ReactNode }) {
12
13
  const id = useId().replace(/[^a-zA-Z0-9_-]/g, '');
@@ -30,20 +31,6 @@ export function AppBrandingProvider({ branding, children }: { branding: AppBrand
30
31
  );
31
32
  }
32
33
 
33
- function BrandLoadingIcon({ brand }: { brand: AppBranding }) {
34
- if (!brand.loadingIcon) return null;
35
- return (
36
- <>
37
- <img src={brand.loadingIcon.light} alt="" className="w-10 h-auto rounded-full block dark:hidden" />
38
- <img
39
- src={brand.loadingIcon.dark ?? brand.loadingIcon.light}
40
- alt=""
41
- className="w-10 h-auto rounded-full hidden dark:block"
42
- />
43
- </>
44
- );
45
- }
46
-
47
34
  export function BrandedAuthLoadingScreen({ loadingIcon }: AuthLoadingScreenProps) {
48
35
  const brand = useContext(BrandingContext);
49
36
  return (
@@ -92,26 +79,3 @@ export const brandedAuthScreens: AuthScreens = {
92
79
  Loading: BrandedAuthLoadingScreen,
93
80
  Permissions: BrandedPermissionLoadingScreen,
94
81
  };
95
-
96
- /** Brand-aware loading indicator that stays inside its container instead of covering the app shell. */
97
- export function BrandedLoadingIndicator({ loadingIcon, delayMs = 0 }: AuthLoadingScreenProps & { delayMs?: number }) {
98
- const brand = useContext(BrandingContext);
99
- const [visible, setVisible] = useState(delayMs <= 0);
100
- useEffect(() => {
101
- if (delayMs <= 0) {
102
- setVisible(true);
103
- return;
104
- }
105
- setVisible(false);
106
- const timer = setTimeout(() => setVisible(true), delayMs);
107
- return () => clearTimeout(timer);
108
- }, [delayMs]);
109
- if (!visible) return null;
110
- return (
111
- <div role="status" aria-label={brand.copy?.loading ?? 'Loading'}>
112
- <LoadingAnimation
113
- loadingIcon={loadingIcon ?? (brand.loadingIcon ? <BrandLoadingIcon brand={brand} /> : undefined)}
114
- />
115
- </div>
116
- );
117
- }
@@ -0,0 +1,42 @@
1
+ import { createContext, useContext, useEffect, useState } from 'react';
2
+ import type { AppBranding } from '../boot/branding.js';
3
+ import { type AuthLoadingScreenProps, LoadingAnimation } from './SplashScreen';
4
+
5
+ export const BrandingContext = createContext<AppBranding>({ name: '' });
6
+
7
+ export function BrandLoadingIcon({ brand }: { brand: AppBranding }) {
8
+ if (!brand.loadingIcon) return null;
9
+ return (
10
+ <>
11
+ <img src={brand.loadingIcon.light} alt="" className="vertesia-loading-icon block dark:hidden" />
12
+ <img
13
+ src={brand.loadingIcon.dark ?? brand.loadingIcon.light}
14
+ alt=""
15
+ className="vertesia-loading-icon hidden dark:block"
16
+ />
17
+ </>
18
+ );
19
+ }
20
+
21
+ /** Brand-aware loading indicator that stays inside its container instead of covering the app shell. */
22
+ export function BrandedLoadingIndicator({ loadingIcon, delayMs = 0 }: AuthLoadingScreenProps & { delayMs?: number }) {
23
+ const brand = useContext(BrandingContext);
24
+ const [visible, setVisible] = useState(delayMs <= 0);
25
+ useEffect(() => {
26
+ if (delayMs <= 0) {
27
+ setVisible(true);
28
+ return;
29
+ }
30
+ setVisible(false);
31
+ const timer = setTimeout(() => setVisible(true), delayMs);
32
+ return () => clearTimeout(timer);
33
+ }, [delayMs]);
34
+ if (!visible) return null;
35
+ return (
36
+ <div role="status" aria-label={brand.copy?.loading ?? 'Loading'}>
37
+ <LoadingAnimation
38
+ loadingIcon={loadingIcon ?? (brand.loadingIcon ? <BrandLoadingIcon brand={brand} /> : undefined)}
39
+ />
40
+ </div>
41
+ );
42
+ }
@@ -1,5 +1,6 @@
1
1
  import { useUserSession } from '@vertesia/ui/session';
2
2
  import type { ComponentType, ReactNode } from 'react';
3
+ import { LOADING_INDICATOR_STYLES } from '../boot/loading.js';
3
4
 
4
5
  export interface AuthLoadingScreenProps {
5
6
  loadingIcon?: ReactNode;
@@ -81,10 +82,9 @@ function LoadingIcon() {
81
82
  /** Shared logo motion for full-screen and in-content loading states. */
82
83
  export function LoadingAnimation({ loadingIcon }: AuthLoadingScreenProps) {
83
84
  return (
84
- <div className="animate-[var(--vertesia-loading-animation,spin_4s_linear_infinite)] motion-reduce:animate-none">
85
- <div className="animate-[var(--vertesia-loading-pulse-animation,pulse_2s_cubic-bezier(0.4,0,0.6,1)_infinite)] motion-reduce:animate-none rounded-full bg-transparent">
86
- {loadingIcon || <LoadingIcon />}
87
- </div>
85
+ <div className="vertesia-loading-motion">
86
+ <style>{LOADING_INDICATOR_STYLES}</style>
87
+ {loadingIcon || <LoadingIcon />}
88
88
  </div>
89
89
  );
90
90
  }
@@ -82,7 +82,7 @@ describe('iframe host authentication', () => {
82
82
  expect(resolveIframeHostOrigin()).toBe('http://localhost:5173');
83
83
  });
84
84
 
85
- it('ignores an empty early response and waits for a usable host token', async () => {
85
+ it.each([false, true])('settles a denied or expired host response immediately (expired=%s)', async (expired) => {
86
86
  const requests: unknown[] = [];
87
87
  const parentWindow = { postMessage: (message: unknown) => requests.push(message) } as unknown as Window;
88
88
  Object.defineProperty(window, 'parent', { configurable: true, value: parentWindow });
@@ -98,21 +98,18 @@ describe('iframe host authentication', () => {
98
98
 
99
99
  window.dispatchEvent(
100
100
  new MessageEvent('message', {
101
- data: { type: IFRAME_AUTH_RESPONSE, requestId: request.requestId },
101
+ data: {
102
+ type: IFRAME_AUTH_RESPONSE,
103
+ requestId: request.requestId,
104
+ ...(expired ? { token: 'expired', expiresAt: Date.now() - 1 } : {}),
105
+ },
102
106
  origin: 'https://cloud.vertesia.io',
103
107
  source: parentWindow,
104
108
  }),
105
109
  );
106
110
  await Promise.resolve();
107
- expect(settled).toBe(false);
111
+ expect(settled).toBe(true);
108
112
 
109
- window.dispatchEvent(
110
- new MessageEvent('message', {
111
- data: { type: IFRAME_AUTH_RESPONSE, requestId: request.requestId, token: 'fresh-token' },
112
- origin: 'https://cloud.vertesia.io',
113
- source: parentWindow,
114
- }),
115
- );
116
- await expect(tokenPromise).resolves.toBe('fresh-token');
113
+ await expect(tokenPromise).resolves.toBeUndefined();
117
114
  });
118
115
  });
@@ -19,6 +19,8 @@ export interface IframeAuthResponse {
19
19
  type: typeof IFRAME_AUTH_RESPONSE;
20
20
  requestId: string;
21
21
  token?: string;
22
+ /** Unix epoch milliseconds. */
23
+ expiresAt?: number;
22
24
  }
23
25
 
24
26
  export interface IframeAppContextRequest {
@@ -178,7 +180,14 @@ export function requestIframeHostAuthToken(timeoutMs = 5000): Promise<string | u
178
180
  if (event.source !== window.parent || event.origin !== parentOrigin || !isIframeAuthResponse(event.data)) {
179
181
  return;
180
182
  }
181
- if (event.data.requestId === requestId && event.data.token) finish(event.data.token);
183
+ if (event.data.requestId === requestId) {
184
+ const expires = event.data.expiresAt;
185
+ finish(
186
+ expires !== undefined && (!Number.isFinite(expires) || expires <= Date.now())
187
+ ? undefined
188
+ : event.data.token,
189
+ );
190
+ }
182
191
  };
183
192
  const request = { type: IFRAME_AUTH_REQUEST, requestId } satisfies IframeAuthRequest;
184
193
  const sendRequest = () => {
@@ -1,3 +1,4 @@
1
+ export { vertesiaBranding } from '../boot/branding.js';
1
2
  export * from './AuthScreenPreview';
2
3
  export * from './apps/index';
3
4
  export * from './BrandedAuthScreens';
@@ -1,5 +1,5 @@
1
- import { Spinner } from '@vertesia/ui/core';
2
1
  import { useUITranslation } from '@vertesia/ui/i18n';
2
+ import { BrandedLoadingIndicator } from '../BrandedLoadingIndicator';
3
3
  import { providerIcon } from './SignInIcons';
4
4
  import { SignInIconBadge, SignInStepButton, SignInStepHeader, SignInStepLayout } from './SignInPrimitives';
5
5
  import { type ProviderId, providerLabel } from './signInUtils';
@@ -29,7 +29,7 @@ export default function SignInAuthPending({ provider, onCancel }: SignInAuthPend
29
29
 
30
30
  <div className="w-full flex flex-col gap-2">
31
31
  <div className="flex justify-center">
32
- <Spinner />
32
+ <BrandedLoadingIndicator />
33
33
  </div>
34
34
  <span>{t('auth.pending.authenticating')}</span>
35
35
  {onCancel && (
@@ -1,9 +1,9 @@
1
1
  import type { UIResolvedTenant } from '@vertesia/common';
2
- import { Spinner } from '@vertesia/ui/core';
3
2
  import { useUITranslation } from '@vertesia/ui/i18n';
4
3
  import { setFirebaseTenant } from '@vertesia/ui/session';
5
4
  import { ArrowRight } from 'lucide-react';
6
5
  import { useEffect, useRef, useState } from 'react';
6
+ import { BrandedLoadingIndicator } from '../BrandedLoadingIndicator';
7
7
  import { SignInEmailField, SignInStepButton, SignInStepHeader, SignInStepLayout } from './SignInPrimitives';
8
8
  import { isValidEmail } from './signInUtils';
9
9
 
@@ -73,7 +73,9 @@ export default function SignInEmailStep({ initialEmail, onProceed }: SignInEmail
73
73
  aria-label={t('auth.pending.authenticating')}
74
74
  className="h-[42px] flex items-center justify-center"
75
75
  >
76
- <Spinner />
76
+ <div aria-hidden="true">
77
+ <BrandedLoadingIndicator />
78
+ </div>
77
79
  </div>
78
80
  ) : (
79
81
  <SignInStepButton type="submit">
@@ -0,0 +1,111 @@
1
+ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import { Env } from '@vertesia/ui/env';
3
+ import { afterEach, beforeEach, expect, it, vi } from 'vitest';
4
+ import { vertesiaBranding } from '../../boot/branding';
5
+ import { BrandingContext } from '../BrandedLoadingIndicator';
6
+ import { TerminalLogin } from './TerminalLogin';
7
+
8
+ const mocks = vi.hoisted(() => ({ exchange: vi.fn(), token: vi.fn(), projects: vi.fn() }));
9
+ const account = { id: 'account-a', name: 'Account A' };
10
+ const project = { id: 'project-a', name: 'Project A', account: account.id };
11
+ const session = {
12
+ user: { sub: 'user-a' },
13
+ account,
14
+ project,
15
+ accounts: [account],
16
+ client: { projects: { list: mocks.projects } },
17
+ get rawAuthToken() {
18
+ return mocks.token();
19
+ },
20
+ };
21
+ vi.mock('@vertesia/ui/session', () => ({
22
+ useUserSession: () => session,
23
+ fetchComposableTokenFromVertesiaToken: mocks.exchange,
24
+ }));
25
+ vi.mock('@vertesia/ui/router', () => ({ useLocation: () => window.location }));
26
+ vi.mock('@vertesia/ui/i18n', async (original) => ({
27
+ ...(await original<typeof import('@vertesia/ui/i18n')>()),
28
+ useUITranslation: () => ({ t: (key: string) => key }),
29
+ }));
30
+ vi.mock('@vertesia/ui/core', async (original) => ({
31
+ ...(await original<typeof import('@vertesia/ui/core')>()),
32
+ useToast: () => vi.fn(),
33
+ }));
34
+
35
+ beforeEach(() => {
36
+ Env.init({
37
+ version: 'test',
38
+ isLocalDev: true,
39
+ isDocker: false,
40
+ type: 'development',
41
+ name: 'test',
42
+ endpoints: {
43
+ studio: 'https://studio.example.test',
44
+ zeno: 'https://zeno.example.test',
45
+ sts: 'https://branch-sts.example.test',
46
+ },
47
+ });
48
+ history.replaceState({}, '', '/cli?redirect_uri=http%3A%2F%2Flocalhost%3A49369&code=1716&profile=custom');
49
+ mocks.exchange.mockReset().mockResolvedValue('cli-token');
50
+ mocks.token.mockReset();
51
+ mocks.projects.mockReset().mockResolvedValue([project]);
52
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('{}')));
53
+ });
54
+ afterEach(() => {
55
+ cleanup();
56
+ vi.unstubAllGlobals();
57
+ });
58
+
59
+ it.each(['oauth-session', 'firebase-session', 'central-session'])(
60
+ 'exchanges the active %s and returns the deployment STS to custom CLI profiles',
61
+ async (token) => {
62
+ mocks.token.mockResolvedValue(token);
63
+ render(<TerminalLogin />);
64
+ fireEvent.click(await screen.findByRole('button', { name: 'login.terminal.authorizeClient' }));
65
+ await waitFor(() => expect(fetch).toHaveBeenCalledOnce());
66
+ expect(mocks.exchange).toHaveBeenCalledWith(token, account.id, project.id, 86400);
67
+ const [, init] = vi.mocked(fetch).mock.calls[0];
68
+ expect(JSON.parse(init?.body as string)).toMatchObject({
69
+ token: 'cli-token',
70
+ profile: 'custom',
71
+ account: account.id,
72
+ project: project.id,
73
+ oauth_server_url: 'https://branch-sts.example.test',
74
+ });
75
+ },
76
+ );
77
+
78
+ it('does not send a credential to the CLI when the exchange fails', async () => {
79
+ mocks.token.mockResolvedValue('oauth-session');
80
+ mocks.exchange.mockRejectedValue(new Error('Exchange denied'));
81
+ render(<TerminalLogin />);
82
+ fireEvent.click(await screen.findByRole('button', { name: 'login.terminal.authorizeClient' }));
83
+ await waitFor(() => expect(mocks.exchange).toHaveBeenCalledOnce());
84
+ expect(fetch).not.toHaveBeenCalled();
85
+ });
86
+
87
+ it('uses the shared full-screen branded loader while projects load', () => {
88
+ mocks.projects.mockReturnValue(new Promise(() => {}));
89
+ render(
90
+ <BrandingContext.Provider value={vertesiaBranding}>
91
+ <TerminalLogin />
92
+ </BrandingContext.Provider>,
93
+ );
94
+ const status = screen.getByRole('status');
95
+ expect(status.style.position).toBe('fixed');
96
+ expect(status.style.inset).toBe('0px');
97
+ expect(status.querySelector('.vertesia-loading-motion')).not.toBeNull();
98
+ expect(status.querySelector('img.vertesia-loading-icon')).not.toBeNull();
99
+ });
100
+
101
+ it('uses the shared sign-in page shell for the authorization form', async () => {
102
+ render(
103
+ <BrandingContext.Provider value={vertesiaBranding}>
104
+ <TerminalLogin />
105
+ </BrandingContext.Provider>,
106
+ );
107
+ await screen.findByRole('button', { name: 'login.terminal.authorizeClient' });
108
+ expect(screen.getByText('auth.privacyPolicy')).toBeTruthy();
109
+ expect(screen.getAllByAltText('Vertesia')).toHaveLength(2);
110
+ expect(screen.queryByRole('status')).toBeNull();
111
+ });
@@ -4,22 +4,20 @@ import {
4
4
  Center,
5
5
  ErrorBox,
6
6
  errorMessage,
7
+ FormItem,
7
8
  Input,
8
9
  SelectBox,
9
- Spinner,
10
10
  useFetch,
11
11
  useToast,
12
12
  } from '@vertesia/ui/core';
13
13
  import { Env } from '@vertesia/ui/env';
14
14
  import { useUITranslation } from '@vertesia/ui/i18n';
15
15
  import { useLocation } from '@vertesia/ui/router';
16
- import {
17
- fetchComposableTokenFromFirebaseToken,
18
- fetchComposableTokenFromVertesiaToken,
19
- getCurrentVertesiaToken,
20
- useUserSession,
21
- } from '@vertesia/ui/session';
22
- import { useState } from 'react';
16
+ import { fetchComposableTokenFromVertesiaToken, useUserSession } from '@vertesia/ui/session';
17
+ import { useContext, useState } from 'react';
18
+ import { BrandedAuthLoadingScreen } from '../BrandedAuthScreens';
19
+ import { BrandingContext } from '../BrandedLoadingIndicator';
20
+ import { SignInPageShell } from './SignInPageShell';
23
21
 
24
22
  interface ProfileData {
25
23
  profile?: string;
@@ -31,6 +29,7 @@ interface LoginResult extends Required<ProfileData> {
31
29
  token: string;
32
30
  studio_server_url: string;
33
31
  zeno_server_url: string;
32
+ oauth_server_url: string;
34
33
  }
35
34
 
36
35
  interface ClientInfo extends ProfileData {
@@ -92,10 +91,16 @@ function getClientInfo(location: Location): ClientInfo | null {
92
91
  }
93
92
 
94
93
  export function TerminalLogin() {
94
+ const session = useUserSession();
95
+ const brand = useContext(BrandingContext);
95
96
  const [payload, setPayload] = useState<LoginResult | undefined>();
96
97
  const [error, setError] = useState<Error>();
97
98
  const location = useLocation();
98
99
  const clientInfo = getClientInfo(location);
100
+ const { data: allProjects, error: projectsError } = useFetch(
101
+ () => (session.user ? session.client.projects.list() : Promise.resolve([])),
102
+ { deps: [session.user], condition: () => !!clientInfo },
103
+ );
99
104
  const toast = useToast();
100
105
  const { t } = useUITranslation();
101
106
 
@@ -132,15 +137,20 @@ export function TerminalLogin() {
132
137
  // expire in 1 day
133
138
  let payload: LoginResult | undefined;
134
139
  try {
135
- const vertesiaToken = getCurrentVertesiaToken();
136
- const token = vertesiaToken
137
- ? await fetchComposableTokenFromVertesiaToken(vertesiaToken, data.account, data.project, 24 * 3600)
138
- : await fetchComposableTokenFromFirebaseToken(data.account, data.project, 24 * 3600);
140
+ // Resolve the active session through the shared provider (OAuth, Firebase or central auth).
141
+ // Exchange it server-side for the selected project rather than handing the browser's token to the CLI.
142
+ const token = await fetchComposableTokenFromVertesiaToken(
143
+ await session.rawAuthToken,
144
+ data.account,
145
+ data.project,
146
+ 24 * 3600,
147
+ );
139
148
  if (token) {
140
149
  payload = {
141
150
  ...data,
142
151
  studio_server_url: Env.endpoints.studio,
143
152
  zeno_server_url: Env.endpoints.zeno,
153
+ oauth_server_url: Env.endpoints.sts,
144
154
  token,
145
155
  } as LoginResult;
146
156
  await fetch(clientInfo.redirect, {
@@ -180,31 +190,46 @@ export function TerminalLogin() {
180
190
  );
181
191
  }
182
192
 
193
+ if (projectsError) {
194
+ return <ErrorBox title={t('login.terminal.errorLoadingProjects')}>{errorMessage(projectsError)}</ErrorBox>;
195
+ }
196
+
183
197
  return payload ? (
184
198
  <AuthDoneScreen payload={payload} error={error} />
185
199
  ) : (
186
- <AuthAcceptScreen clientInfo={clientInfo} onAccept={onAccept} />
200
+ <AuthAcceptScreen clientInfo={clientInfo} onAccept={onAccept} allProjects={allProjects ?? []} />
187
201
  );
188
202
  };
189
203
 
204
+ if (clientInfo && !projectsError && (!session.user || !allProjects)) {
205
+ return <BrandedAuthLoadingScreen />;
206
+ }
190
207
  const page = getPageContent();
191
208
 
192
- return <div className="w-full flex flex-col items-center gap-4 mt-24">{page}</div>;
209
+ return (
210
+ <div className="fixed inset-0 overflow-y-auto bg-background text-foreground">
211
+ <SignInPageShell
212
+ lightLogo={brand.logo?.light}
213
+ darkLogo={brand.logo?.dark ?? brand.logo?.light}
214
+ logoAlt={brand.logo?.alt ?? brand.name}
215
+ footer={brand.copy?.footer}
216
+ >
217
+ <div className="w-full max-w-xl space-y-4 rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm">
218
+ {page}
219
+ </div>
220
+ </SignInPageShell>
221
+ </div>
222
+ );
193
223
  }
194
224
 
195
225
  interface AuthAcceptScreenProps {
226
+ allProjects: ProjectRef[];
196
227
  onAccept: (data: ProfileData) => void;
197
228
  clientInfo: ClientInfo;
198
229
  }
199
- function AuthAcceptScreen({ onAccept, clientInfo }: Readonly<AuthAcceptScreenProps>) {
200
- const { client, user } = useUserSession();
201
- const { data: allProjects, error } = useFetch(() => (user ? client.projects.list() : Promise.resolve([])), [user]);
230
+ function AuthAcceptScreen({ onAccept, clientInfo, allProjects }: Readonly<AuthAcceptScreenProps>) {
202
231
  const { t } = useUITranslation();
203
232
 
204
- if (error) {
205
- return <ErrorBox title={t('login.terminal.errorLoadingProjects')}>{errorMessage(error)}</ErrorBox>;
206
- }
207
-
208
233
  const getEnvironmentName = () => {
209
234
  if (Env.isLocalDev) {
210
235
  return t('login.terminal.envLocalDev');
@@ -216,9 +241,9 @@ function AuthAcceptScreen({ onAccept, clientInfo }: Readonly<AuthAcceptScreenPro
216
241
 
217
242
  const envName = getEnvironmentName();
218
243
 
219
- return user && allProjects ? (
244
+ return (
220
245
  <>
221
- <div className="w-1/3">
246
+ <div className="w-full">
222
247
  <div className="mb-4 text-xl font-semibold text-info">Authorizing client on {envName} environment.</div>
223
248
  <div className="mb-2 text-base text-muted">
224
249
  <div>{t('login.terminal.clientWantsAuth')}</div>
@@ -234,8 +259,6 @@ function AuthAcceptScreen({ onAccept, clientInfo }: Readonly<AuthAcceptScreenPro
234
259
  </div>
235
260
  <ProfileForm onAccept={onAccept} allProjects={allProjects} data={clientInfo} />
236
261
  </>
237
- ) : (
238
- <Spinner size="lg" />
239
262
  );
240
263
  }
241
264
 
@@ -305,19 +328,16 @@ function ProfileForm({ allProjects, data, onAccept }: Readonly<ProfileFormProps>
305
328
  const projects = allProjects.filter((p) => p.account === currentData.account);
306
329
 
307
330
  return (
308
- <div className="w-1/3">
309
- <div className="mb-4 flex flex-col gap-2">
310
- <span className="font-semibold text-muted">{t('login.terminal.profileName')}</span>
331
+ <div className="w-full">
332
+ <FormItem className="mb-4" label={t('login.terminal.profileName')}>
311
333
  <Input type="text" value={currentData.profile} onChange={onChangeProfile} />
312
- </div>
313
- <div className="mb-4 flex flex-col gap-2">
314
- <span className="font-semibold text-muted">{t('login.terminal.account')}</span>
334
+ </FormItem>
335
+ <FormItem className="mb-4" label={t('login.terminal.account')}>
315
336
  <SelectAccount value={currentData.account} onChange={onChangeAccount} accounts={accounts || []} />
316
- </div>
317
- <div className="mb-4 flex flex-col gap-2">
318
- <span className="font-semibold text-muted">{t('login.terminal.project')}</span>
337
+ </FormItem>
338
+ <FormItem className="mb-4" label={t('login.terminal.project')}>
319
339
  <SelectProject value={currentData.project} onChange={onChangeProject} projects={projects} />
320
- </div>
340
+ </FormItem>
321
341
  <div className="mb-4 text-sm text-attention">{t('login.terminal.browserPermissionNote')}</div>
322
342
  <div>
323
343
  <Button size="xl" onClick={() => onAccept(currentData)}>
@@ -329,17 +349,19 @@ function ProfileForm({ allProjects, data, onAccept }: Readonly<ProfileFormProps>
329
349
  }
330
350
 
331
351
  interface SelectAccountProps {
352
+ id?: string;
332
353
  value?: string;
333
354
  accounts: AccountRef[];
334
355
  onChange: (value: AccountRef) => void;
335
356
  }
336
- function SelectAccount({ value, accounts, onChange }: Readonly<SelectAccountProps>) {
357
+ function SelectAccount({ id, value, accounts, onChange }: Readonly<SelectAccountProps>) {
337
358
  const { t } = useUITranslation();
338
359
  const _onChange = (value: AccountRef) => {
339
360
  onChange(value);
340
361
  };
341
362
  return (
342
363
  <SelectBox
364
+ id={id}
343
365
  options={accounts}
344
366
  value={accounts?.find((a) => a.id === value)}
345
367
  onChange={_onChange}
@@ -351,17 +373,19 @@ function SelectAccount({ value, accounts, onChange }: Readonly<SelectAccountProp
351
373
  }
352
374
 
353
375
  interface SelectProjectProps {
376
+ id?: string;
354
377
  value?: string;
355
378
  projects: ProjectRef[];
356
379
  onChange: (value: ProjectRef) => void;
357
380
  }
358
- function SelectProject({ value, projects, onChange }: Readonly<SelectProjectProps>) {
381
+ function SelectProject({ id, value, projects, onChange }: Readonly<SelectProjectProps>) {
359
382
  const { t } = useUITranslation();
360
383
  const _onChange = (value: ProjectRef) => {
361
384
  onChange(value);
362
385
  };
363
386
  return (
364
387
  <SelectBox
388
+ id={id}
365
389
  by="id"
366
390
  value={projects.find((p) => p.id === value)}
367
391
  options={projects}
@@ -1,7 +0,0 @@
1
- /**
2
- * Standalone "Start Workflow" button for the content objects toolbar. Renders only when objects are
3
- * selected; opens a modal to pick a workflow rule and runs it on the selected object ids. Gated by
4
- * the caller via the `allowWorkflowRun` prop on {@link SelectionActions}.
5
- */
6
- export declare function StartWorkflowButton(): import("react").JSX.Element | null;
7
- //# sourceMappingURL=StartWorkflowButton.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"StartWorkflowButton.d.ts","sourceRoot":"","sources":["../../../../../../src/features/store/objects/selection/actions/StartWorkflowButton.tsx"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,wBAAgB,mBAAmB,uCAelC"}