@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.
- package/lib/boot/branding.d.ts +13 -0
- package/lib/boot/branding.d.ts.map +1 -1
- package/lib/boot/branding.js +7 -0
- package/lib/boot/branding.js.map +1 -1
- package/lib/boot/index.d.ts +1 -1
- package/lib/boot/index.d.ts.map +1 -1
- package/lib/boot/index.js +5 -23
- package/lib/boot/index.js.map +1 -1
- package/lib/boot/loading.d.ts +4 -0
- package/lib/boot/loading.d.ts.map +1 -0
- package/lib/boot/loading.js +28 -0
- package/lib/boot/loading.js.map +1 -0
- package/lib/core/components/SelectList.d.ts.map +1 -1
- package/lib/core/components/SelectList.js +3 -3
- package/lib/core/components/SelectList.js.map +1 -1
- package/lib/core/components/SidePanel.d.ts.map +1 -1
- package/lib/core/components/SidePanel.js +2 -1
- package/lib/core/components/SidePanel.js.map +1 -1
- package/lib/core/components/shadcn/theme/ThemeProvider.d.ts.map +1 -1
- package/lib/core/components/shadcn/theme/ThemeProvider.js +21 -5
- package/lib/core/components/shadcn/theme/ThemeProvider.js.map +1 -1
- package/lib/env/index.d.ts +11 -0
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +16 -3
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +4 -1
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +5 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +9 -2
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/features/magic-pdf/GroundedExtractionView.js +3 -3
- package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -1
- package/lib/features/media-viewer/AudioPanel.d.ts +10 -2
- package/lib/features/media-viewer/AudioPanel.d.ts.map +1 -1
- package/lib/features/media-viewer/AudioPanel.js +78 -13
- package/lib/features/media-viewer/AudioPanel.js.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.d.ts +2 -2
- package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.js +9 -7
- package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js +7 -3
- package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
- package/lib/features/oauth/useMcpConnections.d.ts +4 -2
- package/lib/features/oauth/useMcpConnections.d.ts.map +1 -1
- package/lib/features/oauth/useMcpConnections.js +15 -9
- package/lib/features/oauth/useMcpConnections.js.map +1 -1
- package/lib/features/store/objects/selection/ObjectsActionContext.d.ts.map +1 -1
- package/lib/features/store/objects/selection/ObjectsActionContext.js +2 -0
- package/lib/features/store/objects/selection/ObjectsActionContext.js.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.d.ts +4 -2
- package/lib/features/store/objects/selection/SelectionActions.d.ts.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js +6 -3
- package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowAction.d.ts +4 -0
- package/lib/features/store/objects/selection/actions/StartWorkflowAction.d.ts.map +1 -0
- package/lib/features/store/objects/selection/actions/StartWorkflowAction.js +81 -0
- package/lib/features/store/objects/selection/actions/StartWorkflowAction.js.map +1 -0
- package/lib/features/store/objects/selection/actions/index.d.ts +1 -0
- package/lib/features/store/objects/selection/actions/index.d.ts.map +1 -1
- package/lib/features/store/objects/selection/actions/index.js +1 -0
- package/lib/features/store/objects/selection/actions/index.js.map +1 -1
- package/lib/features/store/types/TypeRegistry.d.ts +345 -155
- package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +345 -155
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
- package/lib/i18n/locales/ar.json +10 -2
- package/lib/i18n/locales/de.json +6 -2
- package/lib/i18n/locales/en.json +6 -2
- package/lib/i18n/locales/es.json +6 -2
- package/lib/i18n/locales/fr.json +7 -2
- package/lib/i18n/locales/it.json +6 -2
- package/lib/i18n/locales/ja.json +6 -2
- package/lib/i18n/locales/ko.json +6 -2
- package/lib/i18n/locales/pt.json +6 -2
- package/lib/i18n/locales/ru.json +8 -2
- package/lib/i18n/locales/tr.json +6 -2
- package/lib/i18n/locales/zh-TW.json +6 -2
- package/lib/i18n/locales/zh.json +6 -2
- package/lib/session/UserSession.d.ts +3 -0
- package/lib/session/UserSession.d.ts.map +1 -1
- package/lib/session/UserSession.js +18 -8
- package/lib/session/UserSession.js.map +1 -1
- package/lib/session/UserSessionProvider.d.ts.map +1 -1
- package/lib/session/UserSessionProvider.js +34 -24
- package/lib/session/UserSessionProvider.js.map +1 -1
- package/lib/session/auth/authRoundTrip.d.ts +8 -0
- package/lib/session/auth/authRoundTrip.d.ts.map +1 -0
- package/lib/session/auth/authRoundTrip.js +22 -0
- package/lib/session/auth/authRoundTrip.js.map +1 -0
- package/lib/session/auth/composable.d.ts.map +1 -1
- package/lib/session/auth/composable.js +9 -7
- package/lib/session/auth/composable.js.map +1 -1
- package/lib/session/auth/domainRouting.d.ts +13 -6
- package/lib/session/auth/domainRouting.d.ts.map +1 -1
- package/lib/session/auth/domainRouting.js +2 -21
- package/lib/session/auth/domainRouting.js.map +1 -1
- package/lib/session/auth/oauth.d.ts +6 -1
- package/lib/session/auth/oauth.d.ts.map +1 -1
- package/lib/session/auth/oauth.js +145 -8
- package/lib/session/auth/oauth.js.map +1 -1
- package/lib/session/auth/tokenIssuer.d.ts +3 -0
- package/lib/session/auth/tokenIssuer.d.ts.map +1 -0
- package/lib/session/auth/tokenIssuer.js +27 -0
- package/lib/session/auth/tokenIssuer.js.map +1 -0
- package/lib/session/auth/vertesiaHosts.d.ts +47 -0
- package/lib/session/auth/vertesiaHosts.d.ts.map +1 -0
- package/lib/session/auth/vertesiaHosts.js +79 -0
- package/lib/session/auth/vertesiaHosts.js.map +1 -0
- package/lib/session/index.d.ts +1 -0
- package/lib/session/index.d.ts.map +1 -1
- package/lib/session/index.js +1 -0
- package/lib/session/index.js.map +1 -1
- package/lib/shell/AuthScreenPreview.d.ts +1 -1
- package/lib/shell/AuthScreenPreview.d.ts.map +1 -1
- package/lib/shell/AuthScreenPreview.js +2 -5
- package/lib/shell/AuthScreenPreview.js.map +1 -1
- package/lib/shell/BrandedAuthScreens.d.ts +1 -4
- package/lib/shell/BrandedAuthScreens.d.ts.map +1 -1
- package/lib/shell/BrandedAuthScreens.js +5 -26
- package/lib/shell/BrandedAuthScreens.js.map +1 -1
- package/lib/shell/BrandedLoadingIndicator.d.ts +11 -0
- package/lib/shell/BrandedLoadingIndicator.d.ts.map +1 -0
- package/lib/shell/BrandedLoadingIndicator.js +27 -0
- package/lib/shell/BrandedLoadingIndicator.js.map +1 -0
- package/lib/shell/SplashScreen.d.ts.map +1 -1
- package/lib/shell/SplashScreen.js +2 -1
- package/lib/shell/SplashScreen.js.map +1 -1
- package/lib/shell/apps/AppInstallationProvider.d.ts +16 -4
- package/lib/shell/apps/AppInstallationProvider.d.ts.map +1 -1
- package/lib/shell/apps/iframe-auth.d.ts +2 -0
- package/lib/shell/apps/iframe-auth.d.ts.map +1 -1
- package/lib/shell/apps/iframe-auth.js +6 -2
- package/lib/shell/apps/iframe-auth.js.map +1 -1
- package/lib/shell/index.d.ts +1 -0
- package/lib/shell/index.d.ts.map +1 -1
- package/lib/shell/index.js +1 -0
- package/lib/shell/index.js.map +1 -1
- package/lib/shell/login/SignInAuthPending.js +2 -2
- package/lib/shell/login/SignInAuthPending.js.map +1 -1
- package/lib/shell/login/SignInEmailStep.d.ts.map +1 -1
- package/lib/shell/login/SignInEmailStep.js +2 -2
- package/lib/shell/login/SignInEmailStep.js.map +1 -1
- package/lib/shell/login/TerminalLogin.d.ts.map +1 -1
- package/lib/shell/login/TerminalLogin.js +28 -21
- package/lib/shell/login/TerminalLogin.js.map +1 -1
- package/lib/vertesia-ui-boot.js +33 -29
- package/lib/vertesia-ui-boot.js.map +1 -1
- package/lib/vertesia-ui-core.js +34 -34
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +163 -163
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.Bl8KeZUK.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Bl8KeZUK.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BmT06XFI.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BmT06XFI.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Ch-Jjj0L.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Ch-Jjj0L.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CjhloW9s.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CjhloW9s.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CkfHdQzY.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CkfHdQzY.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DFuKfTe1.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DFuKfTe1.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DHqNVekZ.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DHqNVekZ.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DOaKbmaj.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DOaKbmaj.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Dge3Gl-h.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Dge3Gl-h.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.GLt-10Lx.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.GLt-10Lx.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.T-XtAGbw.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.T-XtAGbw.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.VOJyTpMM.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.VOJyTpMM.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.js +3 -3
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-layout.js +15 -15
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +44 -44
- package/lib/vertesia-ui-rich-text.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +72 -48
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +51 -51
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/package.json +19 -19
- package/src/boot/branding.ts +8 -0
- package/src/boot/index.ts +6 -23
- package/src/boot/loading.ts +27 -0
- package/src/core/components/SelectList.tsx +5 -2
- package/src/core/components/SidePanel.tsx +65 -60
- package/src/core/components/shadcn/theme/ThemeProvider.test.tsx +101 -0
- package/src/core/components/shadcn/theme/ThemeProvider.tsx +21 -9
- package/src/env/index.test.ts +21 -0
- package/src/env/index.ts +33 -2
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +81 -0
- package/src/features/agent/chat/ModernAgentConversation.tsx +5 -0
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +17 -1
- package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
- package/src/features/media-viewer/AudioPanel.test.ts +84 -0
- package/src/features/media-viewer/AudioPanel.tsx +97 -14
- package/src/features/oauth/McpConnectionsButton.test.tsx +56 -0
- package/src/features/oauth/McpConnectionsButton.tsx +9 -6
- package/src/features/oauth/McpConnectionsDialog.tsx +14 -5
- package/src/features/oauth/useMcpConnections.test.ts +10 -0
- package/src/features/oauth/useMcpConnections.ts +18 -10
- package/src/features/store/objects/selection/ObjectsActionContext.tsx +2 -0
- package/src/features/store/objects/selection/SelectionActions.tsx +13 -6
- package/src/features/store/objects/selection/actions/StartWorkflowAction.tsx +162 -0
- package/src/features/store/objects/selection/actions/index.ts +1 -0
- package/src/i18n/locales/ar.json +10 -2
- package/src/i18n/locales/de.json +6 -2
- package/src/i18n/locales/en.json +6 -2
- package/src/i18n/locales/es.json +6 -2
- package/src/i18n/locales/fr.json +7 -2
- package/src/i18n/locales/it.json +6 -2
- package/src/i18n/locales/ja.json +6 -2
- package/src/i18n/locales/ko.json +6 -2
- package/src/i18n/locales/pt.json +6 -2
- package/src/i18n/locales/ru.json +8 -2
- package/src/i18n/locales/tr.json +6 -2
- package/src/i18n/locales/zh-TW.json +6 -2
- package/src/i18n/locales/zh.json +6 -2
- package/src/session/UserSession.test.ts +48 -3
- package/src/session/UserSession.ts +20 -8
- package/src/session/UserSessionProvider.handoff.test.tsx +103 -0
- package/src/session/UserSessionProvider.iframe.test.tsx +178 -0
- package/src/session/UserSessionProvider.state-mismatch.test.tsx +43 -0
- package/src/session/UserSessionProvider.tsx +35 -23
- package/src/session/auth/auth-flow.md +18 -5
- package/src/session/auth/authRoundTrip.ts +24 -0
- package/src/session/auth/composable.test.ts +23 -1
- package/src/session/auth/composable.ts +10 -8
- package/src/session/auth/domainRouting.ts +4 -25
- package/src/session/auth/oauth.test.ts +121 -4
- package/src/session/auth/oauth.ts +172 -11
- package/src/session/auth/tokenIssuer.test.ts +25 -0
- package/src/session/auth/tokenIssuer.ts +25 -0
- package/src/session/auth/vertesiaHosts.test.ts +26 -0
- package/src/session/auth/vertesiaHosts.ts +85 -0
- package/src/session/index.ts +1 -0
- package/src/shell/AuthScreenPreview.tsx +2 -6
- package/src/shell/BrandedAuthScreens.test.tsx +26 -0
- package/src/shell/BrandedAuthScreens.tsx +4 -40
- package/src/shell/BrandedLoadingIndicator.tsx +42 -0
- package/src/shell/SplashScreen.tsx +4 -4
- package/src/shell/apps/iframe-auth.test.ts +8 -11
- package/src/shell/apps/iframe-auth.ts +10 -1
- package/src/shell/index.tsx +1 -0
- package/src/shell/login/SignInAuthPending.tsx +2 -2
- package/src/shell/login/SignInEmailStep.tsx +4 -2
- package/src/shell/login/TerminalLogin.test.tsx +111 -0
- package/src/shell/login/TerminalLogin.tsx +61 -37
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts +0 -7
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts.map +0 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +0 -51
- package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +0 -1
- package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js.map +0 -1
- 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 {
|
|
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
|
|
7
|
+
import { type AuthLoadingScreenProps, DefaultAuthLoadingScreen } from './SplashScreen';
|
|
7
8
|
import type { AuthScreens } from './VertesiaShell';
|
|
8
9
|
|
|
9
|
-
|
|
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="
|
|
85
|
-
<
|
|
86
|
-
|
|
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('
|
|
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: {
|
|
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(
|
|
111
|
+
expect(settled).toBe(true);
|
|
108
112
|
|
|
109
|
-
|
|
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
|
|
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 = () => {
|
package/src/shell/index.tsx
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
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
|
|
244
|
+
return (
|
|
220
245
|
<>
|
|
221
|
-
<div className="w-
|
|
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-
|
|
309
|
-
<
|
|
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
|
-
</
|
|
313
|
-
<
|
|
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
|
-
</
|
|
317
|
-
<
|
|
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
|
-
</
|
|
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"}
|