@vertesia/ui 1.5.0-dev.20260910.052912Z → 1.6.0-dev.20260912.110338Z
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/Link.js +1 -1
- package/lib/core/components/Link.js.map +1 -1
- package/lib/core/components/Spinner.js +1 -1
- package/lib/core/components/Spinner.js.map +1 -1
- package/lib/core/components/TagsInput.js +7 -7
- package/lib/core/components/TagsInput.js.map +1 -1
- package/lib/core/components/shadcn/badge.js +1 -1
- package/lib/core/components/shadcn/badge.js.map +1 -1
- package/lib/core/components/shadcn/button.d.ts.map +1 -1
- package/lib/core/components/shadcn/button.js +3 -4
- package/lib/core/components/shadcn/button.js.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js +2 -2
- package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js +1 -1
- package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js +1 -1
- package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js.map +1 -1
- package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js +1 -1
- package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js.map +1 -1
- package/lib/core/components/shadcn/filters/filter/dateFilter.js +1 -1
- package/lib/core/components/shadcn/filters/filter/dateFilter.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/filters/filters.js +1 -1
- package/lib/core/components/shadcn/filters/filters.js.map +1 -1
- package/lib/core/components/shadcn/label.js +1 -1
- package/lib/core/components/shadcn/label.js.map +1 -1
- package/lib/core/components/shadcn/overflowTabs.js +2 -2
- package/lib/core/components/shadcn/overflowTabs.js.map +1 -1
- package/lib/core/components/shadcn/tabs.js +1 -1
- package/lib/core/components/shadcn/tabs.js.map +1 -1
- package/lib/env/index.d.ts +28 -1
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +51 -3
- package/lib/env/index.js.map +1 -1
- package/lib/features/activity-doc/ActivityDoc.js +1 -1
- package/lib/features/activity-doc/ActivityDoc.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.map +1 -1
- package/lib/features/agent/chat/AgentRunFeedback.d.ts +59 -0
- package/lib/features/agent/chat/AgentRunFeedback.d.ts.map +1 -0
- package/lib/features/agent/chat/AgentRunFeedback.js +286 -0
- package/lib/features/agent/chat/AgentRunFeedback.js.map +1 -0
- package/lib/features/agent/chat/AskUserWidget.d.ts +1 -0
- package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.js +15 -4
- package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +3 -2
- 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 +16 -13
- 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 +30 -8
- 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/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/layout/GenericPageNavHeader.js +1 -1
- package/lib/features/layout/GenericPageNavHeader.js.map +1 -1
- package/lib/features/magic-pdf/AnnotatedImageSlider.js +1 -1
- package/lib/features/magic-pdf/AnnotatedImageSlider.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/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/DocumentPreviewPanel.js +5 -5
- package/lib/features/store/objects/DocumentPreviewPanel.js.map +1 -1
- package/lib/features/store/objects/components/DocumentInput.js +1 -1
- package/lib/features/store/objects/components/DocumentInput.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/objects/upload/DocumentUploadModal.js +1 -1
- package/lib/features/store/objects/upload/DocumentUploadModal.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/features/user/UserInfo.js +1 -1
- package/lib/features/user/UserInfo.js.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/router/RouteComponent.d.ts.map +1 -1
- package/lib/router/RouteComponent.js +15 -12
- package/lib/router/RouteComponent.js.map +1 -1
- package/lib/session/UserSession.d.ts.map +1 -1
- package/lib/session/UserSession.js +6 -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 +10 -1
- 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/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 +21 -20
- 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/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/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 +11 -0
- package/lib/shell/login/signInUtils.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.js +29 -5
- 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 +46 -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 +222 -208
- 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 +13 -13
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +68 -68
- package/lib/vertesia-ui-rich-text.js.map +1 -1
- package/lib/vertesia-ui-router.js +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 +58 -33
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +94 -94
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/json-view/JSONCode.js +1 -1
- package/lib/widgets/json-view/JSONCode.js.map +1 -1
- package/lib/widgets/upload/DropZone.d.ts.map +1 -1
- package/lib/widgets/upload/DropZone.js +1 -1
- package/lib/widgets/upload/DropZone.js.map +1 -1
- package/llms.txt +6 -2
- package/package.json +44 -35
- 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/Link.tsx +1 -1
- package/src/core/components/Spinner.tsx +1 -1
- package/src/core/components/TagsInput.tsx +7 -7
- package/src/core/components/shadcn/badge.tsx +1 -1
- package/src/core/components/shadcn/button.tsx +3 -5
- package/src/core/components/shadcn/filters/comboBox/DateCombobox.tsx +2 -2
- package/src/core/components/shadcn/filters/comboBox/SelectCombobox.tsx +1 -1
- package/src/core/components/shadcn/filters/comboBox/StringListCombobox.tsx +1 -1
- package/src/core/components/shadcn/filters/comboBox/TextCombobox.tsx +1 -1
- package/src/core/components/shadcn/filters/filter/dateFilter.tsx +1 -1
- package/src/core/components/shadcn/filters/filter-styles.ts +4 -4
- package/src/core/components/shadcn/filters/filters.tsx +1 -1
- package/src/core/components/shadcn/label.tsx +1 -1
- package/src/core/components/shadcn/overflowTabs.tsx +2 -2
- package/src/core/components/shadcn/tabs.tsx +1 -1
- package/src/css/utilities.css +7 -0
- package/src/env/index.test.ts +100 -0
- package/src/env/index.ts +66 -3
- package/src/features/activity-doc/ActivityDoc.tsx +1 -1
- package/src/features/agent/PayloadBuilder.tsx +4 -0
- package/src/features/agent/chat/AgentRunFeedback.test.tsx +330 -0
- package/src/features/agent/chat/AgentRunFeedback.tsx +452 -0
- package/src/features/agent/chat/AskUserWidget.test.tsx +24 -2
- package/src/features/agent/chat/AskUserWidget.tsx +19 -4
- package/src/features/agent/chat/ModernAgentConversation.tsx +46 -42
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +207 -0
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +50 -31
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +4 -0
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +42 -8
- 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/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/layout/GenericPageNavHeader.test.tsx +36 -0
- package/src/features/layout/GenericPageNavHeader.tsx +1 -1
- package/src/features/magic-pdf/AnnotatedImageSlider.tsx +1 -1
- package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
- package/src/features/permissions/UserPermissionsProvider.test.tsx +50 -1
- package/src/features/permissions/UserPermissionsProvider.tsx +82 -44
- package/src/features/store/objects/DocumentPreviewPanel.tsx +10 -10
- package/src/features/store/objects/components/DocumentInput.tsx +1 -1
- package/src/features/store/objects/selection/SelectionActions.tsx +4 -1
- package/src/features/store/objects/upload/DocumentUploadModal.tsx +1 -1
- package/src/features/user/UserInfo.tsx +1 -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/RouteComponent.test.tsx +65 -1
- package/src/router/RouteComponent.tsx +18 -15
- package/src/session/UserSession.test.ts +65 -0
- package/src/session/UserSession.ts +6 -4
- package/src/session/UserSessionProvider.strict-mode.test.tsx +44 -0
- package/src/session/UserSessionProvider.test.ts +62 -1
- package/src/session/UserSessionProvider.tsx +9 -1
- 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/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 +45 -40
- 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/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 +30 -5
- package/src/widgets/json-view/JSONCode.test.tsx +2 -2
- package/src/widgets/json-view/JSONCode.tsx +1 -1
- package/src/widgets/markdown/markdown.css +21 -21
- package/src/widgets/upload/DropZone.tsx +2 -4
- 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
|
@@ -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) {
|
|
@@ -82,13 +82,27 @@ export function clearPendingSignin(): void {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
85
|
+
* Promotes the pending sign-in record to the last-successful-login entry, stamping the
|
|
86
|
+
* authenticated user's display name.
|
|
87
|
+
*
|
|
88
|
+
* The returning-user step reads the record this writes, so a host that never calls it never
|
|
89
|
+
* remembers anyone. No-op when no sign-in is pending, which is the normal case for a page load
|
|
90
|
+
* that did not come back from an identity provider.
|
|
88
91
|
*/
|
|
89
|
-
export
|
|
90
|
-
|
|
92
|
+
export function finalizeSuccessfulLogin(name?: string): void {
|
|
93
|
+
const pending = readPendingSignin();
|
|
94
|
+
if (!pending) return;
|
|
95
|
+
writeLastSuccessfulLogin({
|
|
96
|
+
email: pending.email,
|
|
97
|
+
lastProvider: pending.provider,
|
|
98
|
+
tenantName: pending.tenantName,
|
|
99
|
+
name: name || undefined,
|
|
100
|
+
});
|
|
91
101
|
clearPendingSignin();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Drops the Firebase session. Best-effort: no active session, or auth not initialized. */
|
|
105
|
+
export async function signOutOfFirebase(): Promise<void> {
|
|
92
106
|
try {
|
|
93
107
|
await firebaseSignOut(getFirebaseAuth());
|
|
94
108
|
} catch {
|
|
@@ -96,6 +110,17 @@ export async function resetSignInState(): Promise<void> {
|
|
|
96
110
|
}
|
|
97
111
|
}
|
|
98
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Clears the persisted sign-in records (last-successful-login and pending) and
|
|
115
|
+
* signs out of Firebase. Best-effort: sign-out errors (e.g. no active session)
|
|
116
|
+
* are swallowed.
|
|
117
|
+
*/
|
|
118
|
+
export async function resetSignInState(): Promise<void> {
|
|
119
|
+
clearLastSuccessfulLogin();
|
|
120
|
+
clearPendingSignin();
|
|
121
|
+
await signOutOfFirebase();
|
|
122
|
+
}
|
|
123
|
+
|
|
99
124
|
function buildRedirectPath(redirectTo?: string): string {
|
|
100
125
|
let path = redirectTo || window.location.pathname || '/';
|
|
101
126
|
if (path[0] !== '/') path = `/${path}`;
|
|
@@ -17,8 +17,8 @@ describe('JSONCode', () => {
|
|
|
17
17
|
// number
|
|
18
18
|
expect(html).toContain('<span class="text-attention">42</span>');
|
|
19
19
|
// literals
|
|
20
|
-
expect(html).toContain('<span class="text-
|
|
21
|
-
expect(html).toContain('<span class="text-
|
|
20
|
+
expect(html).toContain('<span class="text-info">true</span>');
|
|
21
|
+
expect(html).toContain('<span class="text-info">null</span>');
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it('treats a string with escaped quotes as a single token', () => {
|