@vertesia/ui 1.5.0 → 1.6.0-dev.20260918.061604Z
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/README.md +125 -0
- package/lib/boot/branding.d.ts +58 -0
- package/lib/boot/branding.d.ts.map +1 -0
- package/lib/boot/branding.js +62 -0
- package/lib/boot/branding.js.map +1 -0
- package/lib/boot/index.d.ts +27 -0
- package/lib/boot/index.d.ts.map +1 -0
- package/lib/boot/index.js +325 -0
- package/lib/boot/index.js.map +1 -0
- package/lib/boot/vite.d.ts +63 -0
- package/lib/boot/vite.d.ts.map +1 -0
- package/lib/boot/vite.js +108 -0
- package/lib/boot/vite.js.map +1 -0
- package/lib/core/components/shadcn/button.d.ts.map +1 -1
- package/lib/core/components/shadcn/button.js +1 -2
- package/lib/core/components/shadcn/button.js.map +1 -1
- package/lib/core/components/shadcn/filters/filter-styles.d.ts +1 -1
- package/lib/core/components/shadcn/filters/filter-styles.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/filter-styles.js +4 -4
- package/lib/core/components/shadcn/label.js +1 -1
- package/lib/core/components/shadcn/label.js.map +1 -1
- package/lib/env/index.d.ts +32 -1
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +55 -3
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/PayloadBuilder.d.ts +4 -0
- package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
- package/lib/features/agent/PayloadBuilder.js +6 -1
- package/lib/features/agent/PayloadBuilder.js.map +1 -1
- package/lib/features/agent/chat/AgentRunFeedback.d.ts +66 -0
- package/lib/features/agent/chat/AgentRunFeedback.d.ts.map +1 -0
- package/lib/features/agent/chat/AgentRunFeedback.js +298 -0
- package/lib/features/agent/chat/AgentRunFeedback.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +7 -5
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +11 -10
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +28 -7
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +2 -0
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.d.ts +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.js +5 -5
- package/lib/features/agent/chat/hooks/useAgentPlans.js.map +1 -1
- package/lib/features/agent/chat/index.d.ts +1 -0
- package/lib/features/agent/chat/index.d.ts.map +1 -1
- package/lib/features/agent/chat/index.js +1 -0
- package/lib/features/agent/chat/index.js.map +1 -1
- package/lib/features/facets/AgentRunnerFacetsNav.d.ts +8 -0
- package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/AgentRunnerFacetsNav.js +12 -0
- package/lib/features/facets/AgentRunnerFacetsNav.js.map +1 -1
- package/lib/features/permissions/UserPermissionsProvider.d.ts +15 -1
- package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
- package/lib/features/permissions/UserPermissionsProvider.js +20 -12
- package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
- package/lib/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
- package/lib/features/store/objects/search/DocumentSearchContext.js +6 -1
- package/lib/features/store/objects/search/DocumentSearchContext.js.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
- package/lib/features/store/types/TypeRegistry.d.ts +130 -0
- package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +130 -0
- package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
- package/lib/i18n/index.d.ts +5 -4
- package/lib/i18n/index.d.ts.map +1 -1
- package/lib/i18n/index.js +16 -2
- package/lib/i18n/index.js.map +1 -1
- package/lib/i18n/locales/ar.json +30 -0
- package/lib/i18n/locales/de.json +30 -0
- package/lib/i18n/locales/en.json +30 -0
- package/lib/i18n/locales/es.json +30 -0
- package/lib/i18n/locales/fr.json +30 -0
- package/lib/i18n/locales/it.json +30 -0
- package/lib/i18n/locales/ja.json +30 -0
- package/lib/i18n/locales/ko.json +30 -0
- package/lib/i18n/locales/pt.json +30 -0
- package/lib/i18n/locales/ru.json +30 -0
- package/lib/i18n/locales/tr.json +30 -0
- package/lib/i18n/locales/zh-TW.json +30 -0
- package/lib/i18n/locales/zh.json +30 -0
- package/lib/layout/ErrorPage.js +1 -1
- package/lib/layout/ErrorPage.js.map +1 -1
- package/lib/layout/Navbar.d.ts.map +1 -1
- package/lib/layout/Navbar.js +3 -2
- package/lib/layout/Navbar.js.map +1 -1
- package/lib/session/UserSession.d.ts.map +1 -1
- package/lib/session/UserSession.js +2 -3
- package/lib/session/UserSession.js.map +1 -1
- package/lib/session/UserSessionProvider.d.ts +2 -1
- package/lib/session/UserSessionProvider.d.ts.map +1 -1
- package/lib/session/UserSessionProvider.js +13 -4
- package/lib/session/UserSessionProvider.js.map +1 -1
- package/lib/session/auth/composable.d.ts.map +1 -1
- package/lib/session/auth/composable.js +88 -3
- package/lib/session/auth/composable.js.map +1 -1
- package/lib/session/auth/domainRouting.d.ts +22 -12
- package/lib/session/auth/domainRouting.d.ts.map +1 -1
- package/lib/session/auth/domainRouting.js +35 -21
- package/lib/session/auth/domainRouting.js.map +1 -1
- package/lib/session/auth/firebase.d.ts.map +1 -1
- package/lib/session/auth/firebase.js +10 -0
- package/lib/session/auth/firebase.js.map +1 -1
- package/lib/session/auth/useAuthState.d.ts +9 -4
- package/lib/session/auth/useAuthState.d.ts.map +1 -1
- package/lib/session/auth/useAuthState.js +9 -4
- package/lib/session/auth/useAuthState.js.map +1 -1
- package/lib/session/index.d.ts.map +1 -1
- package/lib/session/index.js +3 -1
- package/lib/session/index.js.map +1 -1
- package/lib/shell/AuthScreenPreview.d.ts +12 -0
- package/lib/shell/AuthScreenPreview.d.ts.map +1 -0
- package/lib/shell/AuthScreenPreview.js +140 -0
- package/lib/shell/AuthScreenPreview.js.map +1 -0
- package/lib/shell/BrandedAuthScreens.d.ts +19 -0
- package/lib/shell/BrandedAuthScreens.d.ts.map +1 -0
- package/lib/shell/BrandedAuthScreens.js +64 -0
- package/lib/shell/BrandedAuthScreens.js.map +1 -0
- package/lib/shell/SplashScreen.d.ts +13 -2
- package/lib/shell/SplashScreen.d.ts.map +1 -1
- package/lib/shell/SplashScreen.js +20 -19
- package/lib/shell/SplashScreen.js.map +1 -1
- package/lib/shell/VertesiaShell.d.ts +15 -4
- package/lib/shell/VertesiaShell.d.ts.map +1 -1
- package/lib/shell/VertesiaShell.js +8 -3
- package/lib/shell/VertesiaShell.js.map +1 -1
- package/lib/shell/apps/StandaloneApp.js +5 -1
- package/lib/shell/apps/StandaloneApp.js.map +1 -1
- package/lib/shell/index.d.ts +6 -0
- package/lib/shell/index.d.ts.map +1 -1
- package/lib/shell/index.js +5 -0
- package/lib/shell/index.js.map +1 -1
- package/lib/shell/login/SignInAuthPending.js +1 -1
- 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/SignInFlow.d.ts +68 -0
- package/lib/shell/login/SignInFlow.d.ts.map +1 -0
- package/lib/shell/login/SignInFlow.js +108 -0
- package/lib/shell/login/SignInFlow.js.map +1 -0
- package/lib/shell/login/SignInIcons.d.ts +1 -1
- package/lib/shell/login/SignInIcons.d.ts.map +1 -1
- package/lib/shell/login/SignInPageShell.d.ts +13 -0
- package/lib/shell/login/SignInPageShell.d.ts.map +1 -0
- package/lib/shell/login/SignInPageShell.js +14 -0
- package/lib/shell/login/SignInPageShell.js.map +1 -0
- package/lib/shell/login/SignInPrimitives.d.ts +3 -3
- package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
- package/lib/shell/login/SignInPrimitives.js +13 -11
- package/lib/shell/login/SignInPrimitives.js.map +1 -1
- package/lib/shell/login/SignInRestrictedEnvStep.d.ts +5 -0
- package/lib/shell/login/SignInRestrictedEnvStep.d.ts.map +1 -1
- package/lib/shell/login/SignInRestrictedEnvStep.js +8 -4
- package/lib/shell/login/SignInRestrictedEnvStep.js.map +1 -1
- package/lib/shell/login/SigninScreen.d.ts +38 -3
- package/lib/shell/login/SigninScreen.d.ts.map +1 -1
- package/lib/shell/login/SigninScreen.js +33 -86
- package/lib/shell/login/SigninScreen.js.map +1 -1
- package/lib/shell/login/SignupForm.d.ts +3 -1
- package/lib/shell/login/SignupForm.d.ts.map +1 -1
- package/lib/shell/login/SignupForm.js +7 -3
- package/lib/shell/login/SignupForm.js.map +1 -1
- package/lib/shell/login/signInUtils.d.ts +12 -1
- package/lib/shell/login/signInUtils.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.js +34 -11
- package/lib/shell/login/signInUtils.js.map +1 -1
- package/lib/vertesia-ui-boot.js +237 -0
- package/lib/vertesia-ui-boot.js.map +1 -0
- package/lib/vertesia-ui-core.js +88 -46
- 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 +350 -317
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.js +2 -2
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js.map +1 -0
- package/lib/vertesia-ui-layout.js +43 -19
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +123 -74
- package/lib/vertesia-ui-rich-text.js.map +1 -1
- package/lib/vertesia-ui-router.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 +87 -33
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +154 -98
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
- package/llms.txt +6 -2
- package/package.json +45 -36
- package/src/__tests__/a11y.test.tsx +25 -0
- package/src/__tests__/theme.test.ts +91 -0
- package/src/boot/branding.test.ts +72 -0
- package/src/boot/branding.ts +108 -0
- package/src/boot/index.test.ts +216 -0
- package/src/boot/index.ts +356 -0
- package/src/boot/vite.test.ts +93 -0
- package/src/boot/vite.ts +118 -0
- package/src/core/components/shadcn/button.tsx +1 -2
- package/src/core/components/shadcn/filters/filter-styles.ts +4 -4
- package/src/core/components/shadcn/label.tsx +1 -1
- package/src/env/index.test.ts +127 -0
- package/src/env/index.ts +73 -3
- package/src/features/agent/PayloadBuilder.tsx +10 -1
- package/src/features/agent/chat/AgentRunFeedback.test.tsx +347 -0
- package/src/features/agent/chat/AgentRunFeedback.tsx +465 -0
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +1 -1
- package/src/features/agent/chat/ModernAgentConversation.tsx +52 -47
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +145 -0
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +42 -24
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +4 -0
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +38 -5
- package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +2 -0
- package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +14 -0
- package/src/features/agent/chat/hooks/useAgentPlans.test.ts +52 -0
- package/src/features/agent/chat/hooks/useAgentPlans.ts +5 -5
- package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +0 -5
- package/src/features/agent/chat/index.ts +6 -0
- package/src/features/facets/AgentRunnerFacetsNav.tsx +26 -0
- package/src/features/permissions/UserPermissionsProvider.test.tsx +50 -1
- package/src/features/permissions/UserPermissionsProvider.tsx +82 -44
- package/src/features/store/objects/search/DocumentSearchContext.ts +8 -3
- package/src/features/store/objects/selection/SelectionActions.tsx +4 -1
- package/src/i18n/index.tsx +25 -2
- package/src/i18n/locales/ar.json +30 -0
- package/src/i18n/locales/de.json +30 -0
- package/src/i18n/locales/en.json +30 -0
- package/src/i18n/locales/es.json +30 -0
- package/src/i18n/locales/fr.json +30 -0
- package/src/i18n/locales/it.json +30 -0
- package/src/i18n/locales/ja.json +30 -0
- package/src/i18n/locales/ko.json +30 -0
- package/src/i18n/locales/pt.json +30 -0
- package/src/i18n/locales/ru.json +30 -0
- package/src/i18n/locales/tr.json +30 -0
- package/src/i18n/locales/zh-TW.json +30 -0
- package/src/i18n/locales/zh.json +30 -0
- package/src/layout/ErrorPage.tsx +1 -1
- package/src/layout/Navbar.test.tsx +58 -0
- package/src/layout/Navbar.tsx +5 -2
- package/src/router/path.test.ts +5 -5
- package/src/session/UserSession.ts +2 -4
- package/src/session/UserSessionProvider.strict-mode.test.tsx +44 -0
- package/src/session/UserSessionProvider.test.ts +62 -1
- package/src/session/UserSessionProvider.tsx +12 -4
- package/src/session/auth/composable.renewal.test.ts +170 -0
- package/src/session/auth/composable.test.ts +23 -1
- package/src/session/auth/composable.ts +101 -2
- package/src/session/auth/domainRouting.test.ts +35 -65
- package/src/session/auth/domainRouting.ts +51 -34
- package/src/session/auth/firebase.test.ts +25 -0
- package/src/session/auth/firebase.ts +10 -0
- package/src/session/auth/useAuthState.ts +11 -4
- package/src/session/index.ts +3 -1
- package/src/shell/AuthScreenPreview.test.tsx +44 -0
- package/src/shell/AuthScreenPreview.tsx +228 -0
- package/src/shell/BrandedAuthScreens.test.tsx +181 -0
- package/src/shell/BrandedAuthScreens.tsx +117 -0
- package/src/shell/SplashScreen.test.tsx +37 -0
- package/src/shell/SplashScreen.tsx +44 -39
- package/src/shell/VertesiaShell.tsx +49 -22
- package/src/shell/apps/StandaloneApp.tsx +5 -1
- package/src/shell/index.tsx +6 -0
- package/src/shell/login/SignInAuthPending.tsx +3 -3
- package/src/shell/login/SignInEmailStep.test.tsx +32 -0
- package/src/shell/login/SignInEmailStep.tsx +13 -9
- package/src/shell/login/SignInFlow.tsx +213 -0
- package/src/shell/login/SignInPageShell.tsx +62 -0
- package/src/shell/login/SignInPrimitives.tsx +14 -11
- package/src/shell/login/SignInRestrictedEnvStep.tsx +19 -7
- package/src/shell/login/SigninScreen.test.tsx +104 -0
- package/src/shell/login/SigninScreen.tsx +115 -145
- package/src/shell/login/SignupForm.tsx +8 -3
- package/src/shell/login/signInUtils.ts +42 -9
- package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +11 -3
- package/src/widgets/markdown/markdown.css +21 -21
- package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DN79y06J.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DN79y06J.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js.map +0 -1
- package/src/session/auth/authState.test.ts +0 -35
|
@@ -55,10 +55,11 @@ const SIGNIN_STEP_BUTTON_BASE = 'cursor-pointer inline-flex items-center justify
|
|
|
55
55
|
const SIGNIN_STEP_BUTTON_VARIANTS = {
|
|
56
56
|
// Filled CTA; greys out when disabled.
|
|
57
57
|
primary:
|
|
58
|
-
'h-[42px] gap-2.5 rounded-md bg-foreground text-background hover:opacity-90 ' +
|
|
58
|
+
'h-[42px] gap-2.5 rounded-md bg-[var(--auth-button,var(--foreground))] text-[color:var(--auth-button-text,var(--background))] hover:opacity-90 ' +
|
|
59
59
|
'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
60
60
|
// Non-interactive primary kept at full opacity (spinner reads as active).
|
|
61
|
-
loading:
|
|
61
|
+
loading:
|
|
62
|
+
'h-[42px] gap-2.5 rounded-md bg-[var(--auth-button,var(--foreground))] text-[color:var(--auth-button-text,var(--background))] opacity-90',
|
|
62
63
|
// Flat text link.
|
|
63
64
|
ghost: 'h-9 text-muted hover:text-foreground',
|
|
64
65
|
} as const;
|
|
@@ -135,7 +136,7 @@ interface SignInInitialsBadgeProps {
|
|
|
135
136
|
export function SignInInitialsBadge({ initials, shape = 'circle' }: SignInInitialsBadgeProps) {
|
|
136
137
|
return (
|
|
137
138
|
<span
|
|
138
|
-
className={`bg-info text-info
|
|
139
|
+
className={`bg-info text-info grid place-items-center font-semibold shrink-0 ${INITIALS_BADGE_SHAPES[shape]}`}
|
|
139
140
|
>
|
|
140
141
|
{initials}
|
|
141
142
|
</span>
|
|
@@ -145,7 +146,7 @@ export function SignInInitialsBadge({ initials, shape = 'circle' }: SignInInitia
|
|
|
145
146
|
/** Rounded tile framing a provider/status icon. */
|
|
146
147
|
export function SignInIconBadge({ children }: { children: ReactNode }) {
|
|
147
148
|
return (
|
|
148
|
-
<div className="inline-grid place-items-center size-14 rounded-xl bg-info
|
|
149
|
+
<div className="inline-grid place-items-center size-14 rounded-xl bg-info border border-info/15 mb-3.5">
|
|
149
150
|
{children}
|
|
150
151
|
</div>
|
|
151
152
|
);
|
|
@@ -177,7 +178,7 @@ const ACCOUNT_CARD_VARIANTS = {
|
|
|
177
178
|
topRow: 'flex items-center gap-2.5 px-3 py-2.5',
|
|
178
179
|
title: 'text-[13.5px] font-semibold text-foreground leading-tight',
|
|
179
180
|
subtitle: 'text-[11.5px] text-muted leading-tight mt-0.5',
|
|
180
|
-
bottomRow: 'flex items-center gap-2.5 px-3 py-1.5 border-t border-border bg-muted
|
|
181
|
+
bottomRow: 'flex items-center gap-2.5 px-3 py-1.5 border-t border-border bg-muted',
|
|
181
182
|
mailBox: 'size-[30px] grid place-items-center shrink-0',
|
|
182
183
|
mailIcon: 'size-4 text-muted',
|
|
183
184
|
email: 'text-sm text-foreground/80 flex-1 truncate',
|
|
@@ -187,7 +188,7 @@ const ACCOUNT_CARD_VARIANTS = {
|
|
|
187
188
|
topRow: 'flex items-center gap-3 px-3.5 py-2.5',
|
|
188
189
|
title: 'text-sm font-semibold text-foreground truncate',
|
|
189
190
|
subtitle: 'text-xs text-foreground/80 truncate',
|
|
190
|
-
bottomRow: 'flex items-center gap-3 px-3.5 py-1 border-t border-border bg-muted
|
|
191
|
+
bottomRow: 'flex items-center gap-3 px-3.5 py-1 border-t border-border bg-muted',
|
|
191
192
|
mailBox: 'w-9 h-6 grid place-items-center shrink-0',
|
|
192
193
|
mailIcon: 'size-3.5 text-muted',
|
|
193
194
|
email: 'text-xs text-foreground/80 flex-1 truncate',
|
|
@@ -253,7 +254,7 @@ interface SignInAccountRowProps {
|
|
|
253
254
|
/** Single-row identity card: badge + title/subtitle + a trailing action link. */
|
|
254
255
|
export function SignInAccountRow({ badge, title, subtitle, actionLabel, onAction }: SignInAccountRowProps) {
|
|
255
256
|
return (
|
|
256
|
-
<div className="flex items-center gap-3 px-3.5 py-2.5 rounded-md border border-border bg-muted
|
|
257
|
+
<div className="flex items-center gap-3 px-3.5 py-2.5 rounded-md border border-border bg-muted">
|
|
257
258
|
{badge}
|
|
258
259
|
<SignInIdentityLines
|
|
259
260
|
title={title}
|
|
@@ -275,7 +276,7 @@ interface SignInEmailRowProps {
|
|
|
275
276
|
/** Standalone bordered pill: mail icon + email + a trailing action link. */
|
|
276
277
|
export function SignInEmailRow({ email, actionLabel, onAction }: SignInEmailRowProps) {
|
|
277
278
|
return (
|
|
278
|
-
<div className="flex items-center gap-2 px-3 py-2 rounded-md border border-border bg-muted
|
|
279
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-md border border-border bg-muted">
|
|
279
280
|
<Mail className="size-4 text-muted shrink-0" />
|
|
280
281
|
<span className="text-sm text-foreground/80 flex-1 truncate">{email}</span>
|
|
281
282
|
<SignInInlineLinkButton onClick={onAction}>{actionLabel}</SignInInlineLinkButton>
|
|
@@ -307,7 +308,7 @@ export function SignInProviderButton({ provider, label, onClick, variant = 'outl
|
|
|
307
308
|
variant="unstyled"
|
|
308
309
|
size="none"
|
|
309
310
|
onClick={onClick}
|
|
310
|
-
className="cursor-pointer group h-[42px] w-full inline-flex items-center gap-3 ps-3.5 pe-3 rounded-md border border-border bg-background text-sm font-medium text-foreground transition hover:bg-muted
|
|
311
|
+
className="cursor-pointer group h-[42px] w-full inline-flex items-center gap-3 ps-3.5 pe-3 rounded-md border border-border bg-background text-sm font-medium text-foreground transition hover:bg-muted"
|
|
311
312
|
>
|
|
312
313
|
<Icon className="!size-[18px] shrink-0" />
|
|
313
314
|
<span className="flex-1 text-start">{label}</span>
|
|
@@ -317,7 +318,9 @@ export function SignInProviderButton({ provider, label, onClick, variant = 'outl
|
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
const variantClass =
|
|
320
|
-
variant === 'filled'
|
|
321
|
+
variant === 'filled'
|
|
322
|
+
? '!bg-[var(--auth-button,var(--foreground))] text-[color:var(--auth-button-text,var(--background))] hover:!bg-[var(--auth-button,var(--foreground))]/90'
|
|
323
|
+
: 'hover:shadow-sm';
|
|
321
324
|
return (
|
|
322
325
|
<Button
|
|
323
326
|
variant="outline"
|
|
@@ -342,7 +345,7 @@ interface SignInCalloutProps {
|
|
|
342
345
|
/** Destructive notice: icon + bold title over a muted meta line. */
|
|
343
346
|
export function SignInCallout({ icon: Icon, title, meta }: SignInCalloutProps) {
|
|
344
347
|
return (
|
|
345
|
-
<div className="flex items-center gap-2.5 px-3 py-2.5 rounded-md bg-destructive
|
|
348
|
+
<div className="flex items-center gap-2.5 px-3 py-2.5 rounded-md bg-destructive border border-destructive/20">
|
|
346
349
|
<Icon className="size-5 text-destructive shrink-0" />
|
|
347
350
|
<div className="flex-1 min-w-0 text-sm">
|
|
348
351
|
<div className="font-semibold text-destructive">{title}</div>
|
|
@@ -22,7 +22,6 @@ const AUTO_REDIRECT_SECONDS = 15;
|
|
|
22
22
|
* docs/restrict-access-to-non-production-envs.md.
|
|
23
23
|
*/
|
|
24
24
|
export default function SignInRestrictedEnvStep({ onUseDifferentEmail }: SignInRestrictedEnvStepProps) {
|
|
25
|
-
const { t } = useUITranslation();
|
|
26
25
|
// Region-aware: send the user to their own region's production site, never another region's (an
|
|
27
26
|
// EU user must not be redirected to the US site). Falls back to the canonical site when unknown.
|
|
28
27
|
const productionUrl = getProductionAppUrl(Env.region);
|
|
@@ -52,6 +51,24 @@ export default function SignInRestrictedEnvStep({ onUseDifferentEmail }: SignInR
|
|
|
52
51
|
};
|
|
53
52
|
}, [productionUrl]);
|
|
54
53
|
|
|
54
|
+
return (
|
|
55
|
+
<SignInRestrictedEnvView
|
|
56
|
+
secondsLeft={secondsLeft}
|
|
57
|
+
onUseDifferentEmail={onUseDifferentEmail}
|
|
58
|
+
onRedirect={() => {
|
|
59
|
+
window.location.href = productionUrl;
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Shared presentation; the host owns session cleanup and automatic redirects. */
|
|
66
|
+
export function SignInRestrictedEnvView({
|
|
67
|
+
secondsLeft = AUTO_REDIRECT_SECONDS,
|
|
68
|
+
onUseDifferentEmail,
|
|
69
|
+
onRedirect,
|
|
70
|
+
}: SignInRestrictedEnvStepProps & { secondsLeft?: number; onRedirect: () => void }) {
|
|
71
|
+
const { t } = useUITranslation();
|
|
55
72
|
return (
|
|
56
73
|
<SignInStepLayout>
|
|
57
74
|
<SignInStepHeader
|
|
@@ -67,12 +84,7 @@ export default function SignInRestrictedEnvStep({ onUseDifferentEmail }: SignInR
|
|
|
67
84
|
title={t('auth.restricted.calloutTitle')}
|
|
68
85
|
meta={t('auth.restricted.calloutMeta')}
|
|
69
86
|
/>
|
|
70
|
-
<SignInStepButton
|
|
71
|
-
className="w-full"
|
|
72
|
-
onClick={() => {
|
|
73
|
-
window.location.href = productionUrl;
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
87
|
+
<SignInStepButton className="w-full" onClick={onRedirect}>
|
|
76
88
|
{t('auth.restricted.redirect')}
|
|
77
89
|
</SignInStepButton>
|
|
78
90
|
<SignInStepButton variant="ghost" onClick={onUseDifferentEmail}>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { Button } from '@vertesia/ui/core';
|
|
3
|
+
import { Env } from '@vertesia/ui/env';
|
|
4
|
+
import { CredentialError } from '@vertesia/ui/session';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { type SignInScreenViewProps, SigninScreen } from './SigninScreen';
|
|
7
|
+
|
|
8
|
+
const session = {
|
|
9
|
+
isLoading: false,
|
|
10
|
+
user: undefined as { name: string } | undefined,
|
|
11
|
+
authError: undefined as Error | undefined,
|
|
12
|
+
signOut: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
vi.mock('@vertesia/ui/session', async (importOriginal) => ({
|
|
15
|
+
...(await importOriginal<typeof import('@vertesia/ui/session')>()),
|
|
16
|
+
useUserSession: () => session,
|
|
17
|
+
useUXTracking: () => ({ trackEvent: vi.fn() }),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('./signInUtils', async (importOriginal) => ({
|
|
20
|
+
...(await importOriginal<typeof import('./signInUtils')>()),
|
|
21
|
+
resetSignInState: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
function CustomSignIn({ flow, onUseDifferentAccount, notice }: SignInScreenViewProps) {
|
|
25
|
+
return (
|
|
26
|
+
<main>
|
|
27
|
+
<h1>My application login</h1>
|
|
28
|
+
<p>Mode: {flow.mode}</p>
|
|
29
|
+
<Button onClick={() => flow.onProceedFromEmail('person@example.com')}>Continue with email</Button>
|
|
30
|
+
<Button onClick={onUseDifferentAccount}>Change identity</Button>
|
|
31
|
+
{notice}
|
|
32
|
+
</main>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('custom sign-in presentation', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
Env.init({
|
|
39
|
+
version: '1.0.0',
|
|
40
|
+
isLocalDev: true,
|
|
41
|
+
isDocker: false,
|
|
42
|
+
type: 'development',
|
|
43
|
+
name: 'Test application',
|
|
44
|
+
endpoints: {
|
|
45
|
+
studio: 'https://studio.example.test',
|
|
46
|
+
zeno: 'https://store.example.test',
|
|
47
|
+
sts: 'https://sts.example.test',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
session.isLoading = false;
|
|
51
|
+
session.user = undefined;
|
|
52
|
+
session.authError = undefined;
|
|
53
|
+
session.signOut.mockClear();
|
|
54
|
+
localStorage.clear();
|
|
55
|
+
sessionStorage.clear();
|
|
56
|
+
history.replaceState({}, '', '/app/deep-link');
|
|
57
|
+
});
|
|
58
|
+
afterEach(cleanup);
|
|
59
|
+
|
|
60
|
+
it('replaces the complete page and drives the existing flow while preserving the return path', () => {
|
|
61
|
+
const view = render(<SigninScreen preservePath View={CustomSignIn} />);
|
|
62
|
+
expect(view.container.firstElementChild?.tagName).toBe('MAIN');
|
|
63
|
+
expect(screen.queryByText('Privacy Policy')).toBeNull();
|
|
64
|
+
expect(screen.getByText('Mode: email')).toBeTruthy();
|
|
65
|
+
fireEvent.click(screen.getByRole('button', { name: 'Continue with email' }));
|
|
66
|
+
expect(screen.getByText('Mode: providers')).toBeTruthy();
|
|
67
|
+
expect(window.location.pathname).toBe('/app/deep-link');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('keeps session gating in shared code for custom views', () => {
|
|
71
|
+
session.isLoading = true;
|
|
72
|
+
const view = render(<SigninScreen preservePath View={CustomSignIn} />);
|
|
73
|
+
expect(screen.queryByText('My application login')).toBeNull();
|
|
74
|
+
session.isLoading = false;
|
|
75
|
+
view.rerender(<SigninScreen preservePath View={CustomSignIn} />);
|
|
76
|
+
expect(screen.getByText('My application login')).toBeTruthy();
|
|
77
|
+
session.user = { name: 'Signed-in user' };
|
|
78
|
+
view.rerender(<SigninScreen preservePath View={CustomSignIn} />);
|
|
79
|
+
expect(screen.queryByText('My application login')).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('retains allowed-path and transient-error suppression for custom views', () => {
|
|
83
|
+
const view = render(<SigninScreen allowedPrefix="/app/" View={CustomSignIn} />);
|
|
84
|
+
expect(screen.queryByText('My application login')).toBeNull();
|
|
85
|
+
session.authError = new Error('Transient failure');
|
|
86
|
+
view.rerender(<SigninScreen suppressAuthErrorPrefix="/app/" View={CustomSignIn} />);
|
|
87
|
+
expect(screen.queryByText('My application login')).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('exposes dedicated recovery modes and working identity reset even on a suppressed path', async () => {
|
|
91
|
+
session.authError = new CredentialError('Expired credential', 'https://sts.example.test');
|
|
92
|
+
render(<SigninScreen preservePath suppressAuthErrorPrefix="/app/" View={CustomSignIn} />);
|
|
93
|
+
expect(await screen.findByText('Mode: credentialFailure')).toBeTruthy();
|
|
94
|
+
fireEvent.click(screen.getByRole('button', { name: 'Change identity' }));
|
|
95
|
+
await waitFor(() => expect(session.signOut).toHaveBeenCalledOnce());
|
|
96
|
+
expect(screen.getByText('Mode: email')).toBeTruthy();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('continues to render the default page when no override is supplied', () => {
|
|
100
|
+
render(<SigninScreen preservePath />);
|
|
101
|
+
expect(screen.queryByText('My application login')).toBeNull();
|
|
102
|
+
expect(screen.getByRole('link', { name: /privacy policy/i })).toBeTruthy();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE, type SignupData, type SignupPayload } from '@vertesia/common';
|
|
2
2
|
import { Env } from '@vertesia/ui/env';
|
|
3
3
|
import { useUITranslation } from '@vertesia/ui/i18n';
|
|
4
|
-
import { RegionTag } from '@vertesia/ui/layout';
|
|
5
4
|
import {
|
|
6
5
|
AuthenticationServiceError,
|
|
7
6
|
CredentialError,
|
|
@@ -12,29 +11,33 @@ import {
|
|
|
12
11
|
useUserSession,
|
|
13
12
|
useUXTracking,
|
|
14
13
|
} from '@vertesia/ui/session';
|
|
15
|
-
import { useCallback, useEffect
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import SignInProvidersStep from './SignInProvidersStep';
|
|
14
|
+
import { type ComponentType, type ReactNode, useCallback, useEffect } from 'react';
|
|
15
|
+
import { type SignInFlowController, SignInFlowSteps, useSignInFlow } from './SignInFlow';
|
|
16
|
+
import { SignInPageShell } from './SignInPageShell';
|
|
19
17
|
import SignInRecoveryStep, { type SignInRecoveryKind } from './SignInRecoveryStep';
|
|
20
18
|
import SignInRestrictedEnvStep from './SignInRestrictedEnvStep';
|
|
21
|
-
import SignInReturningStep from './SignInReturningStep';
|
|
22
19
|
import SignInTenantBlockedStep from './SignInTenantBlockedStep';
|
|
23
|
-
import SignInTenantStep from './SignInTenantStep';
|
|
24
20
|
import SignupForm from './SignupForm';
|
|
25
|
-
import {
|
|
26
|
-
clearLastSuccessfulLogin,
|
|
27
|
-
clearPendingSignin,
|
|
28
|
-
isInviteRequiredError,
|
|
29
|
-
type LastSuccessfulLogin,
|
|
30
|
-
type ProviderId,
|
|
31
|
-
readLastSuccessfulLogin,
|
|
32
|
-
readPendingSignin,
|
|
33
|
-
resetSignInState,
|
|
34
|
-
writeLastSuccessfulLogin,
|
|
35
|
-
} from './signInUtils';
|
|
21
|
+
import { isInviteRequiredError, readPendingSignin, resetSignInState } from './signInUtils';
|
|
36
22
|
|
|
37
|
-
|
|
23
|
+
/** Presentation only: the shared screen still owns session gating and recovery transitions. */
|
|
24
|
+
export interface SignInScreenViewProps {
|
|
25
|
+
flow: SignInFlowController<SignInRecoveryMode>;
|
|
26
|
+
authError?: Error;
|
|
27
|
+
/** Retain these shared forms for tenant resolution and provider sign-in; flow alone does not initiate redirects. */
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
notice: ReactNode;
|
|
30
|
+
isNested: boolean;
|
|
31
|
+
lightLogo?: string;
|
|
32
|
+
darkLogo?: string;
|
|
33
|
+
onUseDifferentAccount: () => void;
|
|
34
|
+
onContinueWithSanitizedScope: () => void;
|
|
35
|
+
onRetry: () => void;
|
|
36
|
+
onSignup: (data: SignupData, firebaseToken: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SigninScreenProps {
|
|
40
|
+
View?: ComponentType<SignInScreenViewProps>;
|
|
38
41
|
isNested?: boolean;
|
|
39
42
|
allowedPrefix?: string | string[];
|
|
40
43
|
lightLogo?: string;
|
|
@@ -50,6 +53,7 @@ export function SigninScreen({
|
|
|
50
53
|
darkLogo,
|
|
51
54
|
preservePath,
|
|
52
55
|
suppressAuthErrorPrefix,
|
|
56
|
+
View,
|
|
53
57
|
}: SigninScreenProps) {
|
|
54
58
|
const pathname = typeof window === 'undefined' ? '' : window.location.pathname;
|
|
55
59
|
const allow = matchesPathPrefix(pathname, allowedPrefix);
|
|
@@ -61,6 +65,7 @@ export function SigninScreen({
|
|
|
61
65
|
darkLogo={darkLogo}
|
|
62
66
|
preservePath={preservePath}
|
|
63
67
|
suppressAuthError={suppressAuthError}
|
|
68
|
+
View={View}
|
|
64
69
|
/>
|
|
65
70
|
);
|
|
66
71
|
}
|
|
@@ -75,13 +80,12 @@ function matchesPathPrefix(pathname: string, prefix?: string | string[]) {
|
|
|
75
80
|
});
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The modes this screen adds on top of {@link SignInCoreMode}: every one is entered from an
|
|
85
|
+
* `authError` the session surfaced, which is why they stay here rather than in the shared flow.
|
|
86
|
+
*/
|
|
87
|
+
export type SignInRecoveryMode =
|
|
82
88
|
| 'blocked'
|
|
83
|
-
| 'returning'
|
|
84
|
-
| 'pending'
|
|
85
89
|
| 'signup'
|
|
86
90
|
| 'restricted'
|
|
87
91
|
| 'scopeUnavailable'
|
|
@@ -106,19 +110,17 @@ function SigninScreenImpl({
|
|
|
106
110
|
darkLogo,
|
|
107
111
|
preservePath,
|
|
108
112
|
suppressAuthError,
|
|
113
|
+
View,
|
|
109
114
|
}: SigninScreenProps & { suppressAuthError?: boolean }) {
|
|
110
|
-
const { t } = useUITranslation();
|
|
111
115
|
const { isLoading, user, authError, signOut } = useUserSession();
|
|
112
116
|
const { trackEvent } = useUXTracking();
|
|
113
117
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
const flow = useSignInFlow<SignInRecoveryMode>({
|
|
119
|
+
signOut,
|
|
120
|
+
trackEvent,
|
|
121
|
+
user,
|
|
118
122
|
});
|
|
119
|
-
const
|
|
120
|
-
const [tenant, setTenant] = useState<TenantInfo | undefined>(undefined);
|
|
121
|
-
const [pendingProvider, setPendingProvider] = useState<ProviderId | null>(null);
|
|
123
|
+
const { mode, setMode, email, setEmail, tenant, setTenant, storedSession, setStoredSession } = flow;
|
|
122
124
|
|
|
123
125
|
const recoveryIdentity =
|
|
124
126
|
authError instanceof RequestedScopeUnavailableError || authError instanceof NoAccessibleAccountError
|
|
@@ -166,68 +168,14 @@ function SigninScreenImpl({
|
|
|
166
168
|
if (pending) setEmail(pending.email);
|
|
167
169
|
setMode('blocked');
|
|
168
170
|
}
|
|
169
|
-
}, [authError]);
|
|
170
|
-
|
|
171
|
-
// On successful login, finalize the last-successful-login entry with the user's name.
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (!user) return;
|
|
174
|
-
const pending = readPendingSignin();
|
|
175
|
-
if (!pending) return;
|
|
176
|
-
writeLastSuccessfulLogin({
|
|
177
|
-
email: pending.email,
|
|
178
|
-
lastProvider: pending.provider,
|
|
179
|
-
tenantName: pending.tenantName,
|
|
180
|
-
name: user.name || undefined,
|
|
181
|
-
});
|
|
182
|
-
clearPendingSignin();
|
|
183
|
-
}, [user]);
|
|
184
|
-
|
|
185
|
-
const onProceedFromEmail = useCallback((e: string, t: TenantInfo | undefined) => {
|
|
186
|
-
setEmail(e);
|
|
187
|
-
setTenant(t);
|
|
188
|
-
setMode(t ? 'tenant' : 'providers');
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
|
-
const onBack = useCallback(() => {
|
|
192
|
-
setMode('email');
|
|
193
|
-
setTenant(undefined);
|
|
194
|
-
}, []);
|
|
195
|
-
|
|
196
|
-
const onNotYou = useCallback(() => {
|
|
197
|
-
clearLastSuccessfulLogin();
|
|
198
|
-
clearPendingSignin();
|
|
199
|
-
setStoredSession(null);
|
|
200
|
-
setEmail('');
|
|
201
|
-
setTenant(undefined);
|
|
202
|
-
setMode('email');
|
|
203
|
-
void signOut();
|
|
204
|
-
}, [signOut]);
|
|
205
|
-
|
|
206
|
-
const onProviderClicked = useCallback(
|
|
207
|
-
(provider: ProviderId) => {
|
|
208
|
-
// Tenant context comes from a resolved tenant or stored tenantName, not the provider.
|
|
209
|
-
const hasTenant = !!tenant || !!storedSession?.tenantName;
|
|
210
|
-
// Only the pre-existing enterprise_signin event; non-tenant sign-ins emit nothing.
|
|
211
|
-
if (hasTenant) trackEvent('enterprise_signin', { provider });
|
|
212
|
-
setPendingProvider(provider);
|
|
213
|
-
setMode('pending');
|
|
214
|
-
// The redirect itself happens in the calling step's startSignIn(); this just shows the pending screen.
|
|
215
|
-
},
|
|
216
|
-
[trackEvent, storedSession?.tenantName, tenant],
|
|
217
|
-
);
|
|
171
|
+
}, [authError, setEmail, setMode]);
|
|
218
172
|
|
|
219
173
|
// "Use a different email" out of the blocked/signup screen. The user reached it
|
|
220
174
|
// as a valid Firebase user with no Vertesia account, so a partial reset isn't
|
|
221
175
|
// enough: unless we also clear the persisted records and sign out of Firebase,
|
|
222
176
|
// the leftover session re-runs the invite check on the next auth change or
|
|
223
177
|
// reload and lands them back on blocked.
|
|
224
|
-
const startOver =
|
|
225
|
-
setStoredSession(null); // drop the in-memory mirror too — resetSignInState only clears storage
|
|
226
|
-
setEmail('');
|
|
227
|
-
setTenant(undefined);
|
|
228
|
-
setMode('email');
|
|
229
|
-
void resetSignInState();
|
|
230
|
-
}, []);
|
|
178
|
+
const { startOver } = flow;
|
|
231
179
|
|
|
232
180
|
const useDifferentAccount = useCallback(() => {
|
|
233
181
|
Env.logger.info('Authentication recovery action selected', {
|
|
@@ -238,7 +186,7 @@ function SigninScreenImpl({
|
|
|
238
186
|
setTenant(undefined);
|
|
239
187
|
setMode('email');
|
|
240
188
|
void resetSignInState().finally(() => signOut());
|
|
241
|
-
}, [signOut]);
|
|
189
|
+
}, [setStoredSession, setEmail, setTenant, setMode, signOut]);
|
|
242
190
|
|
|
243
191
|
const continueWithSanitizedScope = useCallback(() => {
|
|
244
192
|
if (!(authError instanceof RequestedScopeUnavailableError)) return;
|
|
@@ -279,9 +227,7 @@ function SigninScreenImpl({
|
|
|
279
227
|
if (isLoading || user || shouldHideTransientAuthError) return null;
|
|
280
228
|
|
|
281
229
|
let content: React.ReactNode = null;
|
|
282
|
-
if (mode === '
|
|
283
|
-
content = <SignInAuthPending provider={pendingProvider} />;
|
|
284
|
-
} else if (mode === 'blocked') {
|
|
230
|
+
if (mode === 'blocked') {
|
|
285
231
|
content = (
|
|
286
232
|
<SignInTenantBlockedStep
|
|
287
233
|
email={email || storedSession?.email || ''}
|
|
@@ -336,67 +282,91 @@ function SigninScreenImpl({
|
|
|
336
282
|
);
|
|
337
283
|
} else if (mode === 'signup' && !localStorage.getItem('tenantName')) {
|
|
338
284
|
content = <SignupForm onSignup={onSignup} goBack={startOver} />;
|
|
339
|
-
} else if (mode === 'tenant' && tenant) {
|
|
340
|
-
content = (
|
|
341
|
-
<SignInTenantStep
|
|
342
|
-
email={email}
|
|
343
|
-
tenant={tenant}
|
|
344
|
-
onBack={onBack}
|
|
345
|
-
onProviderClicked={() => onProviderClicked((tenant.provider ?? 'oidc') as ProviderId)}
|
|
346
|
-
/>
|
|
347
|
-
);
|
|
348
|
-
} else if (mode === 'providers') {
|
|
349
|
-
content = <SignInProvidersStep email={email} onBack={onBack} onProviderClicked={onProviderClicked} />;
|
|
350
|
-
} else if (mode === 'returning' && storedSession) {
|
|
351
|
-
content = (
|
|
352
|
-
<SignInReturningStep session={storedSession} onNotYou={onNotYou} onProviderClicked={onProviderClicked} />
|
|
353
|
-
);
|
|
354
285
|
} else {
|
|
355
|
-
|
|
286
|
+
// Every remaining mode is a core one; an unmatched recovery mode falls through to the
|
|
287
|
+
// email step, exactly as it did when this chain owned all twelve branches.
|
|
288
|
+
content = <SignInFlowSteps flow={flow} />;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const notice = authError && !isDedicatedAuthError(authError) && !isInviteRequiredError(authError) && (
|
|
292
|
+
<SignInErrorNotice />
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if (View) {
|
|
296
|
+
return (
|
|
297
|
+
<View
|
|
298
|
+
flow={flow}
|
|
299
|
+
authError={authError}
|
|
300
|
+
notice={notice}
|
|
301
|
+
isNested={isNested}
|
|
302
|
+
lightLogo={lightLogo}
|
|
303
|
+
darkLogo={darkLogo}
|
|
304
|
+
onUseDifferentAccount={useDifferentAccount}
|
|
305
|
+
onContinueWithSanitizedScope={continueWithSanitizedScope}
|
|
306
|
+
onRetry={retryAuthentication}
|
|
307
|
+
onSignup={onSignup}
|
|
308
|
+
>
|
|
309
|
+
{content}
|
|
310
|
+
</View>
|
|
311
|
+
);
|
|
356
312
|
}
|
|
357
313
|
|
|
314
|
+
return (
|
|
315
|
+
<DefaultSignInScreen isNested={isNested} lightLogo={lightLogo} darkLogo={darkLogo} notice={notice}>
|
|
316
|
+
{content}
|
|
317
|
+
</DefaultSignInScreen>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** The shared Studio sign-in layout; branding only supplies its content and assets. */
|
|
322
|
+
export function DefaultSignInScreen({
|
|
323
|
+
isNested,
|
|
324
|
+
lightLogo,
|
|
325
|
+
darkLogo,
|
|
326
|
+
notice,
|
|
327
|
+
children,
|
|
328
|
+
logoAlt,
|
|
329
|
+
footer,
|
|
330
|
+
}: {
|
|
331
|
+
isNested?: boolean;
|
|
332
|
+
lightLogo?: string;
|
|
333
|
+
darkLogo?: string;
|
|
334
|
+
notice?: ReactNode;
|
|
335
|
+
children: ReactNode;
|
|
336
|
+
logoAlt?: string;
|
|
337
|
+
footer?: string;
|
|
338
|
+
}) {
|
|
358
339
|
return (
|
|
359
340
|
<div
|
|
360
341
|
style={{ zIndex: 999998 }}
|
|
361
342
|
className={`${isNested ? 'absolute' : 'fixed'} inset-0 overflow-y-auto bg-background`}
|
|
362
343
|
>
|
|
363
|
-
<
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
<div className="mt-6 max-w-[420px] text-center text-sm text-muted">
|
|
376
|
-
<div>
|
|
377
|
-
{t('auth.signInError')}
|
|
378
|
-
<br />
|
|
379
|
-
{t('auth.signInErrorContact')}
|
|
380
|
-
<a className="text-info mx-1" href="mailto:support@vertesiahq.com">
|
|
381
|
-
support@vertesiahq.com
|
|
382
|
-
</a>
|
|
383
|
-
{t('auth.signInErrorPersists')}
|
|
384
|
-
</div>
|
|
385
|
-
</div>
|
|
386
|
-
)}
|
|
344
|
+
<SignInPageShell
|
|
345
|
+
lightLogo={lightLogo}
|
|
346
|
+
darkLogo={darkLogo}
|
|
347
|
+
logoAlt={logoAlt}
|
|
348
|
+
footer={footer}
|
|
349
|
+
notice={notice}
|
|
350
|
+
>
|
|
351
|
+
{children}
|
|
352
|
+
</SignInPageShell>
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
387
356
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
</
|
|
357
|
+
/** Shared generic authentication error notice. */
|
|
358
|
+
export function SignInErrorNotice() {
|
|
359
|
+
const { t } = useUITranslation();
|
|
360
|
+
return (
|
|
361
|
+
<div className="mt-6 max-w-[420px] text-center text-sm text-muted">
|
|
362
|
+
<div>
|
|
363
|
+
{t('auth.signInError')}
|
|
364
|
+
<br />
|
|
365
|
+
{t('auth.signInErrorContact')}
|
|
366
|
+
<a className="text-info mx-1" href="mailto:support@vertesiahq.com">
|
|
367
|
+
support@vertesiahq.com
|
|
368
|
+
</a>
|
|
369
|
+
{t('auth.signInErrorPersists')}
|
|
400
370
|
</div>
|
|
401
371
|
</div>
|
|
402
372
|
);
|
|
@@ -11,18 +11,19 @@ interface CompanySizeOption {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
interface SignupFormProps {
|
|
14
|
+
identity?: Pick<User, 'displayName' | 'email'>;
|
|
14
15
|
onSignup: (data: SignupData, fbToken: string) => void;
|
|
15
16
|
goBack: () => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export default function SignupForm({ onSignup, goBack }: SignupFormProps) {
|
|
19
|
+
export default function SignupForm({ onSignup, goBack, identity }: SignupFormProps) {
|
|
19
20
|
const { t } = useUITranslation();
|
|
20
21
|
const [accountType, setAccountType] = useState<string | undefined>(undefined);
|
|
21
22
|
const [companySize, setCompanySize] = useState<CompanySizeOption | undefined>(undefined);
|
|
22
23
|
const [companyName, setCompanyName] = useState<string | undefined>(undefined);
|
|
23
24
|
const [companyWebsite, setCompanyWebsite] = useState<string | undefined>(undefined);
|
|
24
25
|
const [projectMaturity, setProjectMaturity] = useState<string | undefined>(undefined);
|
|
25
|
-
const [fbUser, setFbUser] = useState<User | undefined>(
|
|
26
|
+
const [fbUser, setFbUser] = useState<Pick<User, 'displayName' | 'email'> | undefined>(identity);
|
|
26
27
|
|
|
27
28
|
const [error, setError] = useState<string | undefined>(undefined);
|
|
28
29
|
const isCompany = accountType === 'company';
|
|
@@ -57,13 +58,17 @@ export default function SignupForm({ onSignup, goBack }: SignupFormProps) {
|
|
|
57
58
|
];
|
|
58
59
|
|
|
59
60
|
useEffect(() => {
|
|
61
|
+
if (identity) {
|
|
62
|
+
setFbUser(identity);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
60
65
|
const user = getFirebaseAuth().currentUser;
|
|
61
66
|
if (!user) {
|
|
62
67
|
console.error('No user found');
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
65
70
|
setFbUser(user);
|
|
66
|
-
}, []);
|
|
71
|
+
}, [identity]);
|
|
67
72
|
|
|
68
73
|
const isValid = () => {
|
|
69
74
|
if (!accountType) {
|