@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
|
@@ -1,51 +1,45 @@
|
|
|
1
1
|
import { useUserSession } from '@vertesia/ui/session';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface AuthLoadingScreenProps {
|
|
5
|
+
loadingIcon?: ReactNode;
|
|
6
|
+
}
|
|
4
7
|
|
|
5
8
|
interface SplashScreenProps {
|
|
9
|
+
Presentation?: ComponentType<AuthLoadingScreenProps>;
|
|
10
|
+
Screen?: ComponentType<AuthLoadingScreenProps>;
|
|
6
11
|
icon?: ReactNode;
|
|
7
12
|
}
|
|
8
|
-
export function SplashScreen({ icon: Icon }: SplashScreenProps) {
|
|
13
|
+
export function SplashScreen({ icon: Icon, Screen, Presentation = DefaultAuthLoadingScreen }: SplashScreenProps) {
|
|
9
14
|
const { isLoading, authToken } = useUserSession();
|
|
10
|
-
|
|
15
|
+
// Hand off synchronously: an exiting splash must never cover the next view.
|
|
16
|
+
if (authToken || !isLoading) return null;
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}, [isLoading]);
|
|
17
|
-
|
|
18
|
-
// The permission gate owns the loading UI once a token is available.
|
|
19
|
-
// Skip the exit animation too, so two loading indicators never overlap.
|
|
20
|
-
if (authToken) return null;
|
|
18
|
+
// Custom screens own the entire page, including their animation and positioning.
|
|
19
|
+
const LoadingScreen = Screen ?? Presentation;
|
|
20
|
+
return <LoadingScreen loadingIcon={Icon} />;
|
|
21
|
+
}
|
|
21
22
|
|
|
23
|
+
/** Presentation shared by the default shell and configured branding. */
|
|
24
|
+
export function DefaultAuthLoadingScreen({
|
|
25
|
+
loadingIcon,
|
|
26
|
+
loadingLabel = 'Loading',
|
|
27
|
+
}: AuthLoadingScreenProps & { loadingLabel?: string }) {
|
|
22
28
|
return (
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
alignItems: 'center',
|
|
38
|
-
justifyContent: 'center',
|
|
39
|
-
}}
|
|
40
|
-
className="flex w-full h-full items-center justify-center"
|
|
41
|
-
>
|
|
42
|
-
<div className="animate-[spin_4s_linear_infinite]">
|
|
43
|
-
<div className="animate-pulse rounded-full bg-transparent">{Icon || <LoadingIcon />}</div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</motion.div>
|
|
47
|
-
)}
|
|
48
|
-
</AnimatePresence>
|
|
29
|
+
<div style={{ position: 'fixed', inset: 0, zIndex: 999999 }} role="status" aria-label={loadingLabel}>
|
|
30
|
+
<div
|
|
31
|
+
style={{
|
|
32
|
+
display: 'flex',
|
|
33
|
+
width: '100%',
|
|
34
|
+
height: '100%',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
justifyContent: 'center',
|
|
37
|
+
}}
|
|
38
|
+
className="flex w-full h-full items-center justify-center"
|
|
39
|
+
>
|
|
40
|
+
<LoadingAnimation loadingIcon={loadingIcon} />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
49
43
|
);
|
|
50
44
|
}
|
|
51
45
|
|
|
@@ -58,7 +52,7 @@ function LoadingIcon() {
|
|
|
58
52
|
<svg
|
|
59
53
|
width="32"
|
|
60
54
|
height="32"
|
|
61
|
-
className="w-8 h-8 text-
|
|
55
|
+
className="w-8 h-8 text-info"
|
|
62
56
|
viewBox="0 0 50 50"
|
|
63
57
|
xmlns="http://www.w3.org/2000/svg"
|
|
64
58
|
role="img"
|
|
@@ -83,3 +77,14 @@ function LoadingIcon() {
|
|
|
83
77
|
</svg>
|
|
84
78
|
);
|
|
85
79
|
}
|
|
80
|
+
|
|
81
|
+
/** Shared logo motion for full-screen and in-content loading states. */
|
|
82
|
+
export function LoadingAnimation({ loadingIcon }: AuthLoadingScreenProps) {
|
|
83
|
+
return (
|
|
84
|
+
<div className="animate-[var(--vertesia-loading-animation,spin_4s_linear_infinite)] motion-reduce:animate-none">
|
|
85
|
+
<div className="animate-[var(--vertesia-loading-pulse-animation,pulse_2s_cubic-bezier(0.4,0,0.6,1)_infinite)] motion-reduce:animate-none rounded-full bg-transparent">
|
|
86
|
+
{loadingIcon || <LoadingIcon />}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { ThemeProvider, ToastProvider } from '@vertesia/ui/core';
|
|
2
|
-
import { TypeRegistryProvider, UserPermissionProvider } from '@vertesia/ui/features';
|
|
2
|
+
import { type PermissionLoadingScreenProps, TypeRegistryProvider, UserPermissionProvider } from '@vertesia/ui/features';
|
|
3
3
|
import { LanguageBoundI18nProvider, LanguageProvider, type SupportedLanguage } from '@vertesia/ui/i18n';
|
|
4
4
|
import { DevSessionProvider, UserSessionProvider } from '@vertesia/ui/session';
|
|
5
|
-
import type { ReactNode } from 'react';
|
|
5
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
6
|
+
import type { AppBranding } from '../boot/branding.js';
|
|
6
7
|
import { IframeAppContextSync } from './apps/IframeAppContextSync.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import { AppBrandingProvider, BrandedAuthLoadingScreen, brandedAuthScreens } from './BrandedAuthScreens';
|
|
9
|
+
import { type SignInScreenViewProps, SigninScreen } from './login/SigninScreen';
|
|
10
|
+
import { type AuthLoadingScreenProps, SplashScreen } from './SplashScreen';
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
/** Optional full-page presentations. Session and permission gating remain owned by the shell. */
|
|
13
|
+
export interface AuthScreens {
|
|
14
|
+
SignIn?: ComponentType<SignInScreenViewProps>;
|
|
15
|
+
Loading?: ComponentType<AuthLoadingScreenProps>;
|
|
16
|
+
Permissions?: ComponentType<PermissionLoadingScreenProps>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface VertesiaShellProps {
|
|
20
|
+
authScreens?: AuthScreens;
|
|
21
|
+
branding?: AppBranding;
|
|
11
22
|
children: React.ReactNode;
|
|
12
23
|
lightLogo?: string;
|
|
13
24
|
darkLogo?: string;
|
|
@@ -30,26 +41,38 @@ export function VertesiaShell({
|
|
|
30
41
|
suppressSignInErrorPrefixes,
|
|
31
42
|
authToken,
|
|
32
43
|
defaultLanguage,
|
|
44
|
+
authScreens,
|
|
45
|
+
branding,
|
|
33
46
|
}: VertesiaShellProps) {
|
|
34
|
-
const
|
|
47
|
+
const screens = { ...(branding ? brandedAuthScreens : {}), ...authScreens };
|
|
48
|
+
const brandedContent = (
|
|
35
49
|
<TypeRegistryProvider>
|
|
36
50
|
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
|
37
51
|
<LanguageProvider defaultLanguage={defaultLanguage}>
|
|
38
52
|
<LanguageBoundI18nProvider>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
<AppBrandingBoundary branding={branding}>
|
|
54
|
+
<IframeAppContextSync />
|
|
55
|
+
{!authToken && (
|
|
56
|
+
<>
|
|
57
|
+
<SplashScreen
|
|
58
|
+
icon={loadingIcon}
|
|
59
|
+
Screen={authScreens?.Loading}
|
|
60
|
+
Presentation={branding ? BrandedAuthLoadingScreen : undefined}
|
|
61
|
+
/>
|
|
62
|
+
<SigninScreen
|
|
63
|
+
View={screens.SignIn}
|
|
64
|
+
allowedPrefix="/shared/"
|
|
65
|
+
darkLogo={darkLogo}
|
|
66
|
+
lightLogo={lightLogo}
|
|
67
|
+
preservePath={preserveSignInPath}
|
|
68
|
+
suppressAuthErrorPrefix={suppressSignInErrorPrefixes}
|
|
69
|
+
/>
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
<UserPermissionProvider loadingIcon={loadingIcon} LoadingScreen={screens.Permissions}>
|
|
73
|
+
{children}
|
|
74
|
+
</UserPermissionProvider>
|
|
75
|
+
</AppBrandingBoundary>
|
|
53
76
|
</LanguageBoundI18nProvider>
|
|
54
77
|
</LanguageProvider>
|
|
55
78
|
</ThemeProvider>
|
|
@@ -59,10 +82,14 @@ export function VertesiaShell({
|
|
|
59
82
|
return (
|
|
60
83
|
<ToastProvider>
|
|
61
84
|
{authToken ? (
|
|
62
|
-
<DevSessionProvider token={authToken}>{
|
|
85
|
+
<DevSessionProvider token={authToken}>{brandedContent}</DevSessionProvider>
|
|
63
86
|
) : (
|
|
64
|
-
<UserSessionProvider loadOnboardingStatus={loadOnboardingStatus}>{
|
|
87
|
+
<UserSessionProvider loadOnboardingStatus={loadOnboardingStatus}>{brandedContent}</UserSessionProvider>
|
|
65
88
|
)}
|
|
66
89
|
</ToastProvider>
|
|
67
90
|
);
|
|
68
91
|
}
|
|
92
|
+
|
|
93
|
+
function AppBrandingBoundary({ branding, children }: { branding?: AppBranding; children: ReactNode }) {
|
|
94
|
+
return branding ? <AppBrandingProvider branding={branding}>{children}</AppBrandingProvider> : children;
|
|
95
|
+
}
|
|
@@ -109,7 +109,11 @@ function AccessDeniedMessage({ name }: AccessDeniedMessageProps) {
|
|
|
109
109
|
const onProjectChange = (selected: ProjectRef) => {
|
|
110
110
|
localStorage.setItem(LastSelectedAccountId_KEY, selected.account);
|
|
111
111
|
localStorage.setItem(`${LastSelectedProjectId_KEY}-${selected.account}`, selected.id);
|
|
112
|
-
|
|
112
|
+
// An explicit selection must override configured workspace defaults on the next load.
|
|
113
|
+
const url = new URL(window.location.href);
|
|
114
|
+
url.searchParams.set('a', selected.account);
|
|
115
|
+
url.searchParams.set('p', selected.id);
|
|
116
|
+
window.location.assign(url.toString());
|
|
113
117
|
};
|
|
114
118
|
|
|
115
119
|
const filteredProjects = selectedAccountId ? projectsByOrg[selectedAccountId] || [] : [];
|
package/src/shell/index.tsx
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
export * from './AuthScreenPreview';
|
|
1
2
|
export * from './apps/index';
|
|
3
|
+
export * from './BrandedAuthScreens';
|
|
2
4
|
export * from './login/InviteAcceptModal';
|
|
5
|
+
export * from './login/SignInFlow';
|
|
6
|
+
export * from './login/SignInPageShell';
|
|
3
7
|
export * from './login/SigninScreen';
|
|
8
|
+
export * from './login/signInUtils';
|
|
4
9
|
export * from './login/TerminalLogin';
|
|
5
10
|
export * from './login/UserSessionMenu';
|
|
11
|
+
export type { AuthLoadingScreenProps } from './SplashScreen';
|
|
6
12
|
export * from './utils';
|
|
7
13
|
export * from './VertesiaShell';
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in state machine, extracted from SigninScreen so more than one host can drive it.
|
|
3
|
+
*
|
|
4
|
+
* Two hosts run it today: the Studio shell, which layers its own error-recovery modes on top
|
|
5
|
+
* (`SigninScreen`), and the Central Auth broker, which has no Vertesia session and renders only the
|
|
6
|
+
* modes below. The interesting behaviour is not the markup — it is the ordering: remembered
|
|
7
|
+
* identity first, then email, then tenant resolution deciding which providers are even offered.
|
|
8
|
+
* Re-implementing that per host is how the two pages drift apart.
|
|
9
|
+
*
|
|
10
|
+
* The hook takes no session dependency of its own: `signOut` and `trackEvent` come in as options
|
|
11
|
+
* because a host without a `UserSessionProvider` cannot call those hooks.
|
|
12
|
+
*/
|
|
13
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
14
|
+
import SignInAuthPending from './SignInAuthPending';
|
|
15
|
+
import SignInEmailStep, { type TenantInfo } from './SignInEmailStep';
|
|
16
|
+
import SignInProvidersStep from './SignInProvidersStep';
|
|
17
|
+
import SignInReturningStep from './SignInReturningStep';
|
|
18
|
+
import SignInTenantStep from './SignInTenantStep';
|
|
19
|
+
import {
|
|
20
|
+
clearLastSuccessfulLogin,
|
|
21
|
+
clearPendingSignin,
|
|
22
|
+
finalizeSuccessfulLogin,
|
|
23
|
+
type LastSuccessfulLogin,
|
|
24
|
+
type ProviderId,
|
|
25
|
+
readLastSuccessfulLogin,
|
|
26
|
+
resetSignInState,
|
|
27
|
+
signOutOfFirebase,
|
|
28
|
+
} from './signInUtils';
|
|
29
|
+
|
|
30
|
+
/** The modes every host shares. Hosts may add their own; see the `M` parameter below. */
|
|
31
|
+
export type SignInCoreMode = 'email' | 'providers' | 'tenant' | 'returning' | 'pending';
|
|
32
|
+
|
|
33
|
+
export interface SignInFlowOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Host sign-out, run when the user abandons the remembered identity. Defaults to a plain
|
|
36
|
+
* Firebase sign-out; a host with a Vertesia session passes its own so the session goes too.
|
|
37
|
+
*/
|
|
38
|
+
signOut?: () => void | Promise<void>;
|
|
39
|
+
/** Analytics sink. Only `enterprise_signin` is emitted, and only for a tenant-resolved sign-in. */
|
|
40
|
+
trackEvent?: (eventName: string, eventProperties?: Record<string, unknown>) => void;
|
|
41
|
+
/**
|
|
42
|
+
* The authenticated user, once there is one. Supplying it lets the flow stamp the display name
|
|
43
|
+
* onto the remembered-login record; a host that unmounts the flow on login should call
|
|
44
|
+
* {@link finalizeSuccessfulLogin} itself instead.
|
|
45
|
+
*/
|
|
46
|
+
user?: { name?: string | null } | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Flow state plus the handlers the steps are wired to.
|
|
51
|
+
*
|
|
52
|
+
* `M` is the host's own extra modes: `SigninScreen` passes its error-recovery union so `mode` and
|
|
53
|
+
* `setMode` stay exhaustively typed there, while a host with no extra modes gets `SignInCoreMode`.
|
|
54
|
+
*/
|
|
55
|
+
export interface SignInFlowController<M extends string = never> {
|
|
56
|
+
mode: SignInCoreMode | M;
|
|
57
|
+
setMode: (mode: SignInCoreMode | M) => void;
|
|
58
|
+
email: string;
|
|
59
|
+
setEmail: (email: string) => void;
|
|
60
|
+
tenant: TenantInfo | undefined;
|
|
61
|
+
setTenant: (tenant: TenantInfo | undefined) => void;
|
|
62
|
+
storedSession: LastSuccessfulLogin | null;
|
|
63
|
+
setStoredSession: (session: LastSuccessfulLogin | null) => void;
|
|
64
|
+
pendingProvider: ProviderId | null;
|
|
65
|
+
/** Email accepted: go to the tenant step when one resolved, otherwise the provider list. */
|
|
66
|
+
onProceedFromEmail: (email: string, tenant?: TenantInfo) => void;
|
|
67
|
+
/** Back to the email step, dropping the resolved tenant. */
|
|
68
|
+
onBack: () => void;
|
|
69
|
+
/** "Not you?" — forget the remembered identity and sign out. */
|
|
70
|
+
onNotYou: () => void;
|
|
71
|
+
/** A provider was picked; show the pending screen while the IdP redirect happens. */
|
|
72
|
+
onProviderClicked: (provider: ProviderId) => void;
|
|
73
|
+
/** Full reset: clear the persisted records and the Firebase session, back to the email step. */
|
|
74
|
+
startOver: () => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function useSignInFlow<M extends string = never>(options: SignInFlowOptions = {}): SignInFlowController<M> {
|
|
78
|
+
const { signOut, trackEvent, user } = options;
|
|
79
|
+
|
|
80
|
+
const [storedSession, setStoredSession] = useState<LastSuccessfulLogin | null>(() => readLastSuccessfulLogin());
|
|
81
|
+
const [mode, setMode] = useState<SignInCoreMode | M>(() => (readLastSuccessfulLogin() ? 'returning' : 'email'));
|
|
82
|
+
const [email, setEmail] = useState('');
|
|
83
|
+
const [tenant, setTenant] = useState<TenantInfo | undefined>(undefined);
|
|
84
|
+
const [pendingProvider, setPendingProvider] = useState<ProviderId | null>(null);
|
|
85
|
+
|
|
86
|
+
// On successful login, finalize the last-successful-login entry with the user's name.
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!user) return;
|
|
89
|
+
finalizeSuccessfulLogin(user.name ?? undefined);
|
|
90
|
+
}, [user]);
|
|
91
|
+
|
|
92
|
+
const onProceedFromEmail = useCallback((e: string, t: TenantInfo | undefined) => {
|
|
93
|
+
setEmail(e);
|
|
94
|
+
setTenant(t);
|
|
95
|
+
setMode(t ? 'tenant' : 'providers');
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const onBack = useCallback(() => {
|
|
99
|
+
setMode('email');
|
|
100
|
+
setTenant(undefined);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const onNotYou = useCallback(() => {
|
|
104
|
+
clearLastSuccessfulLogin();
|
|
105
|
+
clearPendingSignin();
|
|
106
|
+
setStoredSession(null);
|
|
107
|
+
setEmail('');
|
|
108
|
+
setTenant(undefined);
|
|
109
|
+
setMode('email');
|
|
110
|
+
// Without a host sign-out, drop the Firebase session anyway: leaving it behind means the
|
|
111
|
+
// next auth-state change re-authenticates the identity the user just said isn't theirs.
|
|
112
|
+
void (signOut ? signOut() : signOutOfFirebase());
|
|
113
|
+
}, [signOut]);
|
|
114
|
+
|
|
115
|
+
const onProviderClicked = useCallback(
|
|
116
|
+
(provider: ProviderId) => {
|
|
117
|
+
// Tenant context comes from a resolved tenant or stored tenantName, not the provider.
|
|
118
|
+
const hasTenant = !!tenant || !!storedSession?.tenantName;
|
|
119
|
+
// Only the pre-existing enterprise_signin event; non-tenant sign-ins emit nothing.
|
|
120
|
+
if (hasTenant) trackEvent?.('enterprise_signin', { provider });
|
|
121
|
+
setPendingProvider(provider);
|
|
122
|
+
setMode('pending');
|
|
123
|
+
// The redirect itself happens in the calling step's startSignIn(); this just shows the pending screen.
|
|
124
|
+
},
|
|
125
|
+
[trackEvent, storedSession?.tenantName, tenant],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const startOver = useCallback(() => {
|
|
129
|
+
setStoredSession(null); // drop the in-memory mirror too — resetSignInState only clears storage
|
|
130
|
+
setEmail('');
|
|
131
|
+
setTenant(undefined);
|
|
132
|
+
setMode('email');
|
|
133
|
+
void resetSignInState();
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
mode,
|
|
138
|
+
setMode,
|
|
139
|
+
email,
|
|
140
|
+
setEmail,
|
|
141
|
+
tenant,
|
|
142
|
+
setTenant,
|
|
143
|
+
storedSession,
|
|
144
|
+
setStoredSession,
|
|
145
|
+
pendingProvider,
|
|
146
|
+
onProceedFromEmail,
|
|
147
|
+
onBack,
|
|
148
|
+
onNotYou,
|
|
149
|
+
onProviderClicked,
|
|
150
|
+
startOver,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The part of the controller the shared steps read.
|
|
156
|
+
*
|
|
157
|
+
* Deliberately excludes the setters: their parameter types mention the host's extra modes, and a
|
|
158
|
+
* contravariant position would stop `SignInFlowController<never>` from being passed here.
|
|
159
|
+
*/
|
|
160
|
+
export type SignInStepsController = Omit<
|
|
161
|
+
SignInFlowController<string>,
|
|
162
|
+
'setMode' | 'setEmail' | 'setTenant' | 'setStoredSession' | 'startOver'
|
|
163
|
+
>;
|
|
164
|
+
|
|
165
|
+
interface SignInFlowStepsProps {
|
|
166
|
+
flow: SignInStepsController;
|
|
167
|
+
/** Path to return to after the IdP round-trip. Defaults to the current pathname. */
|
|
168
|
+
redirectTo?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Renders the step for the current core mode. A host mode this doesn't know falls through to the
|
|
173
|
+
* email step, which is where the original screen sent an unrecognized state too.
|
|
174
|
+
*/
|
|
175
|
+
export function SignInFlowSteps({ flow, redirectTo }: SignInFlowStepsProps) {
|
|
176
|
+
const { mode, email, tenant, storedSession, pendingProvider } = flow;
|
|
177
|
+
|
|
178
|
+
if (mode === 'pending' && pendingProvider) {
|
|
179
|
+
return <SignInAuthPending provider={pendingProvider} />;
|
|
180
|
+
}
|
|
181
|
+
if (mode === 'tenant' && tenant) {
|
|
182
|
+
return (
|
|
183
|
+
<SignInTenantStep
|
|
184
|
+
email={email}
|
|
185
|
+
tenant={tenant}
|
|
186
|
+
onBack={flow.onBack}
|
|
187
|
+
onProviderClicked={() => flow.onProviderClicked((tenant.provider ?? 'oidc') as ProviderId)}
|
|
188
|
+
redirectTo={redirectTo}
|
|
189
|
+
/>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (mode === 'providers') {
|
|
193
|
+
return (
|
|
194
|
+
<SignInProvidersStep
|
|
195
|
+
email={email}
|
|
196
|
+
onBack={flow.onBack}
|
|
197
|
+
onProviderClicked={flow.onProviderClicked}
|
|
198
|
+
redirectTo={redirectTo}
|
|
199
|
+
/>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (mode === 'returning' && storedSession) {
|
|
203
|
+
return (
|
|
204
|
+
<SignInReturningStep
|
|
205
|
+
session={storedSession}
|
|
206
|
+
onNotYou={flow.onNotYou}
|
|
207
|
+
onProviderClicked={flow.onProviderClicked}
|
|
208
|
+
redirectTo={redirectTo}
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return <SignInEmailStep initialEmail={email} onProceed={flow.onProceedFromEmail} />;
|
|
213
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chrome around a sign-in step: product logo, the step itself, an optional notice, and the
|
|
3
|
+
* legal footer. Hosts supply their own outer container — the Studio shell overlays the app, the
|
|
4
|
+
* Central Auth broker owns the whole document — so this component deliberately does not position
|
|
5
|
+
* itself.
|
|
6
|
+
*/
|
|
7
|
+
import { useUITranslation } from '@vertesia/ui/i18n';
|
|
8
|
+
import { RegionTag } from '@vertesia/ui/layout';
|
|
9
|
+
import type { ReactNode } from 'react';
|
|
10
|
+
|
|
11
|
+
interface SignInPageShellProps {
|
|
12
|
+
logoAlt?: string;
|
|
13
|
+
footer?: string;
|
|
14
|
+
lightLogo?: string;
|
|
15
|
+
darkLogo?: string;
|
|
16
|
+
/** Rendered between the step and the footer — e.g. a generic authentication error. */
|
|
17
|
+
notice?: ReactNode;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function SignInPageShell({
|
|
22
|
+
lightLogo,
|
|
23
|
+
darkLogo,
|
|
24
|
+
notice,
|
|
25
|
+
children,
|
|
26
|
+
logoAlt = 'Vertesia',
|
|
27
|
+
footer,
|
|
28
|
+
}: SignInPageShellProps) {
|
|
29
|
+
const { t } = useUITranslation();
|
|
30
|
+
return (
|
|
31
|
+
<div className="min-h-full flex flex-col items-center justify-center py-12 px-4">
|
|
32
|
+
<div className="flex flex-col items-center w-full">
|
|
33
|
+
{(lightLogo || darkLogo) && (
|
|
34
|
+
<div className="mb-7">
|
|
35
|
+
{lightLogo && <img src={lightLogo} alt={logoAlt} className="h-10 block dark:hidden" />}
|
|
36
|
+
{darkLogo && <img src={darkLogo} alt={logoAlt} className="h-10 hidden dark:block" />}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
{children}
|
|
41
|
+
|
|
42
|
+
{notice}
|
|
43
|
+
|
|
44
|
+
{footer ? (
|
|
45
|
+
<p className="mt-10 text-xs text-muted">{footer}</p>
|
|
46
|
+
) : (
|
|
47
|
+
<div className="flex items-center gap-5 mt-10 text-xs text-muted">
|
|
48
|
+
<a href="https://vertesiahq.com/privacy" className="hover:text-foreground transition">
|
|
49
|
+
{t('auth.privacyPolicy')}
|
|
50
|
+
</a>
|
|
51
|
+
<span className="text-border">·</span>
|
|
52
|
+
<a href="https://vertesiahq.com/terms" className="hover:text-foreground transition">
|
|
53
|
+
{t('auth.termsOfService')}
|
|
54
|
+
</a>
|
|
55
|
+
<span className="text-border">·</span>
|
|
56
|
+
<RegionTag className="cursor-default" />
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -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}>
|