diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index ebc4dc295..51d8e2d53 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -1,3 +1,4 @@ +import { SignInMode } from '@logto/schemas'; import { useEffect } from 'react'; import { Route, Routes, BrowserRouter, Navigate } from 'react-router-dom'; @@ -50,6 +51,9 @@ const App = () => { return null; } + const isRegisterOnly = experienceSettings.signInMode === SignInMode.Register; + const isSignInOnly = experienceSettings.signInMode === SignInMode.SignIn; + return ( @@ -64,13 +68,19 @@ const App = () => { }> {/* sign-in */} - } /> + : } + /> } /> } /> } /> {/* register */} - } /> + : } + /> } diff --git a/packages/ui/src/hooks/use-sie.ts b/packages/ui/src/hooks/use-sie.ts index fa3a48a13..cc9c75a27 100644 --- a/packages/ui/src/hooks/use-sie.ts +++ b/packages/ui/src/hooks/use-sie.ts @@ -11,5 +11,6 @@ export const useSieMethods = () => { signUpSettings: { password, verify }, signInMethods: experienceSettings?.signIn.methods ?? [], socialConnectors: experienceSettings?.socialConnectors ?? [], + signInMode: experienceSettings?.signInMode, }; }; diff --git a/packages/ui/src/hooks/use-social-sign-in-listener.ts b/packages/ui/src/hooks/use-social-sign-in-listener.ts index 17aa2732f..57cb5f90a 100644 --- a/packages/ui/src/hooks/use-social-sign-in-listener.ts +++ b/packages/ui/src/hooks/use-social-sign-in-listener.ts @@ -1,3 +1,4 @@ +import { SignInMode } from '@logto/schemas'; import { useEffect, useCallback, useContext, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useNavigate } from 'react-router-dom'; @@ -11,7 +12,8 @@ import useApi from './use-api'; import { PageContext } from './use-page-context'; const useSocialSignInListener = () => { - const { setToast } = useContext(PageContext); + const { setToast, experienceSettings } = useContext(PageContext); + const { t } = useTranslation(); const parameters = useParams(); const navigate = useNavigate(); @@ -19,6 +21,13 @@ const useSocialSignInListener = () => { const signInWithSocialErrorHandlers: ErrorHandlers = useMemo( () => ({ 'user.identity_not_exists': (error) => { + // Should not let user register under sign-in only mode + if (experienceSettings?.signInMode === SignInMode.SignIn) { + setToast(error.message); + + return; + } + if (parameters.connector) { navigate(`/social/register/${parameters.connector}`, { replace: true, @@ -27,7 +36,7 @@ const useSocialSignInListener = () => { } }, }), - [navigate, parameters.connector] + [experienceSettings?.signInMode, navigate, parameters.connector, setToast] ); const { result, run: asyncSignInWithSocial } = useApi( diff --git a/packages/ui/src/pages/Register/index.test.tsx b/packages/ui/src/pages/Register/index.test.tsx index 7dcc2a6c0..34be5a739 100644 --- a/packages/ui/src/pages/Register/index.test.tsx +++ b/packages/ui/src/pages/Register/index.test.tsx @@ -1,4 +1,4 @@ -import { SignInIdentifier } from '@logto/schemas'; +import { SignInMode, SignInIdentifier } from '@logto/schemas'; import { MemoryRouter } from 'react-router-dom'; import renderWithPageContext from '@/__mocks__/RenderWithPageContext'; @@ -99,4 +99,18 @@ describe('', () => { mockSignInExperienceSettings.socialConnectors.length ); }); + + test('render with sign-in only mode should return ErrorPage', () => { + const { queryByText } = renderWithPageContext( + + + + + + ); + + expect(queryByText('description.not_found')).not.toBeNull(); + }); }); diff --git a/packages/ui/src/pages/Register/index.tsx b/packages/ui/src/pages/Register/index.tsx index 2c374f50b..b4db9f3b9 100644 --- a/packages/ui/src/pages/Register/index.tsx +++ b/packages/ui/src/pages/Register/index.tsx @@ -1,3 +1,4 @@ +import { SignInMode } from '@logto/schemas'; import { useTranslation } from 'react-i18next'; import Divider from '@/components/Divider'; @@ -8,14 +9,19 @@ import { SocialSignInList } from '@/containers/SocialSignIn'; import { useSieMethods } from '@/hooks/use-sie'; import { UserFlow } from '@/types'; +import ErrorPage from '../ErrorPage'; import Main from './Main'; import * as styles from './index.module.scss'; const Register = () => { - const { signUpMethods, socialConnectors } = useSieMethods(); + const { signUpMethods, socialConnectors, signInMode } = useSieMethods(); const otherMethods = signUpMethods.slice(1); const { t } = useTranslation(); + if (!signInMode || signInMode === SignInMode.SignIn) { + return ; + } + return (
@@ -40,7 +46,7 @@ const Register = () => { } { // SignIn footer - signUpMethods.length > 0 && ( + signInMode === SignInMode.SignInAndRegister && signUpMethods.length > 0 && ( <>
diff --git a/packages/ui/src/pages/SecondaryRegister/index.test.tsx b/packages/ui/src/pages/SecondaryRegister/index.test.tsx index 7cf02d370..5344c6e7d 100644 --- a/packages/ui/src/pages/SecondaryRegister/index.test.tsx +++ b/packages/ui/src/pages/SecondaryRegister/index.test.tsx @@ -1,4 +1,4 @@ -import { SignInIdentifier } from '@logto/schemas'; +import { SignInIdentifier, SignInMode } from '@logto/schemas'; import { Routes, Route, MemoryRouter } from 'react-router-dom'; import renderWithPageContext from '@/__mocks__/RenderWithPageContext'; @@ -86,7 +86,7 @@ describe('', () => { }); test('renders non-supported signUp methods should return error page', () => { - const { queryByText, container } = renderWithPageContext( + const { queryByText } = renderWithPageContext( ', () => { }); test('render non-verified passwordless methods should return error page', () => { - const { queryByText, container } = renderWithPageContext( + const { queryByText } = renderWithPageContext( ', () => { expect(queryByText('action.create_account')).toBeNull(); expect(queryByText('description.not_found')).not.toBeNull(); }); + + test('render with sign-in only mode', () => { + const { queryByText } = renderWithPageContext( + + + + + + } + /> + + + ); + expect(queryByText('action.create_account')).toBeNull(); + expect(queryByText('description.not_found')).not.toBeNull(); + }); }); diff --git a/packages/ui/src/pages/SecondaryRegister/index.tsx b/packages/ui/src/pages/SecondaryRegister/index.tsx index da8791a9e..3d59f749e 100644 --- a/packages/ui/src/pages/SecondaryRegister/index.tsx +++ b/packages/ui/src/pages/SecondaryRegister/index.tsx @@ -1,4 +1,4 @@ -import { SignInIdentifier } from '@logto/schemas'; +import { SignInMode, SignInIdentifier } from '@logto/schemas'; import { useParams } from 'react-router-dom'; import { is } from 'superstruct'; @@ -16,7 +16,11 @@ type Parameters = { const SecondaryRegister = () => { const { method = '' } = useParams(); - const { signUpMethods, signUpSettings } = useSieMethods(); + const { signUpMethods, signUpSettings, signInMode } = useSieMethods(); + + if (!signInMode || signInMode === SignInMode.SignIn) { + return ; + } // Validate the signUp method if (!is(method, SignInMethodGuard) || !signUpMethods.includes(method)) { diff --git a/packages/ui/src/pages/SecondarySignIn/index.test.tsx b/packages/ui/src/pages/SecondarySignIn/index.test.tsx index 8b6752dfe..51be21535 100644 --- a/packages/ui/src/pages/SecondarySignIn/index.test.tsx +++ b/packages/ui/src/pages/SecondarySignIn/index.test.tsx @@ -1,4 +1,4 @@ -import { SignInIdentifier } from '@logto/schemas'; +import { SignInIdentifier, SignInMode } from '@logto/schemas'; import { Routes, Route, MemoryRouter } from 'react-router-dom'; import renderWithPageContext from '@/__mocks__/RenderWithPageContext'; @@ -114,4 +114,28 @@ describe('', () => { expect(queryByText('action.sign_in')).toBeNull(); expect(queryByText('description.not_found')).not.toBeNull(); }); + + test('render with register only mode', async () => { + const { queryByText } = renderWithPageContext( + + + + + + } + /> + + + ); + expect(queryByText('action.sign_in')).toBeNull(); + expect(queryByText('description.not_found')).not.toBeNull(); + }); }); diff --git a/packages/ui/src/pages/SecondarySignIn/index.tsx b/packages/ui/src/pages/SecondarySignIn/index.tsx index 210156119..3c4d3d919 100644 --- a/packages/ui/src/pages/SecondarySignIn/index.tsx +++ b/packages/ui/src/pages/SecondarySignIn/index.tsx @@ -1,4 +1,4 @@ -import { SignInIdentifier } from '@logto/schemas'; +import { SignInMode, SignInIdentifier } from '@logto/schemas'; import { useParams } from 'react-router-dom'; import SecondaryPageWrapper from '@/components/SecondaryPageWrapper'; @@ -14,9 +14,13 @@ type Props = { const SecondarySignIn = () => { const { method = '' } = useParams(); - const { signInMethods } = useSieMethods(); + const { signInMethods, signInMode } = useSieMethods(); const signInMethod = signInMethods.find(({ identifier }) => identifier === method); + if (!signInMode || signInMode === SignInMode.Register) { + return ; + } + if (!signInMethod) { return ; } diff --git a/packages/ui/src/pages/SignIn/index.test.tsx b/packages/ui/src/pages/SignIn/index.test.tsx index 8d45d4d12..e2d518d76 100644 --- a/packages/ui/src/pages/SignIn/index.test.tsx +++ b/packages/ui/src/pages/SignIn/index.test.tsx @@ -1,3 +1,4 @@ +import { SignInMode } from '@logto/schemas'; import { MemoryRouter } from 'react-router-dom'; import renderWithPageContext from '@/__mocks__/RenderWithPageContext'; @@ -130,4 +131,18 @@ describe('', () => { mockSignInExperienceSettings.socialConnectors.length ); }); + + test('render with register only mode should return ErrorPage', () => { + const { queryByText } = renderWithPageContext( + + + + + + ); + + expect(queryByText('description.not_found')).not.toBeNull(); + }); }); diff --git a/packages/ui/src/pages/SignIn/index.tsx b/packages/ui/src/pages/SignIn/index.tsx index 59766a671..30a887ad9 100644 --- a/packages/ui/src/pages/SignIn/index.tsx +++ b/packages/ui/src/pages/SignIn/index.tsx @@ -1,3 +1,4 @@ +import { SignInMode } from '@logto/schemas'; import { useTranslation } from 'react-i18next'; import Divider from '@/components/Divider'; @@ -8,14 +9,19 @@ import { SocialSignInList } from '@/containers/SocialSignIn'; import { useSieMethods } from '@/hooks/use-sie'; import { UserFlow } from '@/types'; +import ErrorPage from '../ErrorPage'; import Main from './Main'; import * as styles from './index.module.scss'; const SignIn = () => { - const { signInMethods, signUpMethods, socialConnectors } = useSieMethods(); + const { signInMethods, signUpMethods, socialConnectors, signInMode } = useSieMethods(); const otherMethods = signInMethods.slice(1).map(({ identifier }) => identifier); const { t } = useTranslation(); + if (!signInMode || signInMode === SignInMode.Register) { + return ; + } + return (
@@ -41,7 +47,7 @@ const SignIn = () => { } { // Create Account footer - signUpMethods.length > 0 && ( + signInMode === SignInMode.SignInAndRegister && signUpMethods.length > 0 && ( <>