mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(experience): rename SingleSignOnContext
to UserInteractionContext
(#6163)
This commit is contained in:
parent
61781062c1
commit
8635f1b774
10 changed files with 62 additions and 52 deletions
|
@ -6,7 +6,7 @@ import AppBoundary from './Providers/AppBoundary';
|
|||
import LoadingLayerProvider from './Providers/LoadingLayerProvider';
|
||||
import PageContextProvider from './Providers/PageContextProvider';
|
||||
import SettingsProvider from './Providers/SettingsProvider';
|
||||
import SingleSignOnContextProvider from './Providers/SingleSignOnContextProvider';
|
||||
import UserInteractionContextProvider from './Providers/UserInteractionContextProvider';
|
||||
import Callback from './pages/Callback';
|
||||
import Consent from './pages/Consent';
|
||||
import Continue from './pages/Continue';
|
||||
|
@ -45,7 +45,7 @@ const App = () => {
|
|||
<BrowserRouter>
|
||||
<PageContextProvider>
|
||||
<SettingsProvider>
|
||||
<SingleSignOnContextProvider>
|
||||
<UserInteractionContextProvider>
|
||||
<AppBoundary>
|
||||
<Routes>
|
||||
<Route element={<LoadingLayerProvider />}>
|
||||
|
@ -125,7 +125,7 @@ const App = () => {
|
|||
</Route>
|
||||
</Routes>
|
||||
</AppBoundary>
|
||||
</SingleSignOnContextProvider>
|
||||
</UserInteractionContextProvider>
|
||||
</SettingsProvider>
|
||||
</PageContextProvider>
|
||||
</BrowserRouter>
|
||||
|
|
|
@ -2,20 +2,20 @@ import { type SsoConnectorMetadata } from '@logto/schemas';
|
|||
import { noop } from '@silverhand/essentials';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type SingleSignOnContextType = {
|
||||
export type UserInteractionContextType = {
|
||||
// All the enabled sso connectors
|
||||
availableSsoConnectorsMap: Map<string, SsoConnectorMetadata>;
|
||||
email?: string;
|
||||
setEmail: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
ssoEmail?: string;
|
||||
setSsoEmail: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
// The sso connectors that are enabled for the current domain
|
||||
ssoConnectors: SsoConnectorMetadata[];
|
||||
setSsoConnectors: React.Dispatch<React.SetStateAction<SsoConnectorMetadata[]>>;
|
||||
};
|
||||
|
||||
export default createContext<SingleSignOnContextType>({
|
||||
email: undefined,
|
||||
export default createContext<UserInteractionContextType>({
|
||||
ssoEmail: undefined,
|
||||
availableSsoConnectorsMap: new Map(),
|
||||
ssoConnectors: [],
|
||||
setEmail: noop,
|
||||
setSsoEmail: noop,
|
||||
setSsoConnectors: noop,
|
||||
});
|
|
@ -4,28 +4,37 @@ import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
|||
import useSessionStorage, { StorageKeys } from '@/hooks/use-session-storages';
|
||||
import { useSieMethods } from '@/hooks/use-sie';
|
||||
|
||||
import SingleSignOnContext, { type SingleSignOnContextType } from './SingleSignOnContext';
|
||||
import UserInteractionContext, { type UserInteractionContextType } from './UserInteractionContext';
|
||||
|
||||
type Props = {
|
||||
readonly children: ReactNode;
|
||||
};
|
||||
|
||||
const SingleSignOnContextProvider = ({ children }: Props) => {
|
||||
/**
|
||||
* UserInteractionContextProvider
|
||||
*
|
||||
* This component manages user interaction data during the sign-in process,
|
||||
* combining React's Context API with session storage to enable cross-page
|
||||
* data persistence and access.
|
||||
*
|
||||
* The cached data provided by this provider primarily helps improve the sign-in experience for end users.
|
||||
*/
|
||||
const UserInteractionContextProvider = ({ children }: Props) => {
|
||||
const { ssoConnectors } = useSieMethods();
|
||||
const { get, set, remove } = useSessionStorage();
|
||||
const [email, setEmail] = useState<string | undefined>(get(StorageKeys.SsoEmail));
|
||||
const [ssoEmail, setSsoEmail] = useState<string | undefined>(get(StorageKeys.SsoEmail));
|
||||
const [domainFilteredConnectors, setDomainFilteredConnectors] = useState<SsoConnectorMetadata[]>(
|
||||
get(StorageKeys.SsoConnectors) ?? []
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!email) {
|
||||
if (!ssoEmail) {
|
||||
remove(StorageKeys.SsoEmail);
|
||||
return;
|
||||
}
|
||||
|
||||
set(StorageKeys.SsoEmail, email);
|
||||
}, [email, remove, set]);
|
||||
set(StorageKeys.SsoEmail, ssoEmail);
|
||||
}, [ssoEmail, remove, set]);
|
||||
|
||||
useEffect(() => {
|
||||
if (domainFilteredConnectors.length === 0) {
|
||||
|
@ -41,22 +50,22 @@ const SingleSignOnContextProvider = ({ children }: Props) => {
|
|||
[ssoConnectors]
|
||||
);
|
||||
|
||||
const singleSignOnContext = useMemo<SingleSignOnContextType>(
|
||||
const userInteractionContext = useMemo<UserInteractionContextType>(
|
||||
() => ({
|
||||
email,
|
||||
setEmail,
|
||||
ssoEmail,
|
||||
setSsoEmail,
|
||||
availableSsoConnectorsMap: ssoConnectorsMap,
|
||||
ssoConnectors: domainFilteredConnectors,
|
||||
setSsoConnectors: setDomainFilteredConnectors,
|
||||
}),
|
||||
[domainFilteredConnectors, email, ssoConnectorsMap]
|
||||
[ssoEmail, ssoConnectorsMap, domainFilteredConnectors]
|
||||
);
|
||||
|
||||
return (
|
||||
<SingleSignOnContext.Provider value={singleSignOnContext}>
|
||||
<UserInteractionContext.Provider value={userInteractionContext}>
|
||||
{children}
|
||||
</SingleSignOnContext.Provider>
|
||||
</UserInteractionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SingleSignOnContextProvider;
|
||||
export default UserInteractionContextProvider;
|
|
@ -3,7 +3,7 @@ import { useCallback, useState, useContext } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import SingleSignOnContext from '@/Providers/SingleSignOnContextProvider/SingleSignOnContext';
|
||||
import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext';
|
||||
import { getSingleSignOnConnectors } from '@/apis/single-sign-on';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useErrorHandler from '@/hooks/use-error-handler';
|
||||
|
@ -15,7 +15,8 @@ const useCheckSingleSignOn = () => {
|
|||
const navigate = useNavigate();
|
||||
const request = useApi(getSingleSignOnConnectors);
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>();
|
||||
const { setEmail, setSsoConnectors, availableSsoConnectorsMap } = useContext(SingleSignOnContext);
|
||||
const { setSsoEmail, setSsoConnectors, availableSsoConnectorsMap } =
|
||||
useContext(UserInteractionContext);
|
||||
const singleSignOn = useSingleSignOn();
|
||||
|
||||
const handleError = useErrorHandler();
|
||||
|
@ -26,9 +27,9 @@ const useCheckSingleSignOn = () => {
|
|||
|
||||
// Should clear the context and storage if the user trying to resubmit the form
|
||||
const clearContext = useCallback(() => {
|
||||
setEmail(undefined);
|
||||
setSsoEmail(undefined);
|
||||
setSsoConnectors([]);
|
||||
}, [setEmail, setSsoConnectors]);
|
||||
}, [setSsoEmail, setSsoConnectors]);
|
||||
|
||||
/**
|
||||
* Check if the email is registered with any SSO connectors
|
||||
|
@ -66,7 +67,7 @@ const useCheckSingleSignOn = () => {
|
|||
}
|
||||
|
||||
setSsoConnectors(connectors);
|
||||
setEmail(email);
|
||||
setSsoEmail(email);
|
||||
|
||||
if (!continueSignIn) {
|
||||
return true;
|
||||
|
@ -87,7 +88,7 @@ const useCheckSingleSignOn = () => {
|
|||
handleError,
|
||||
navigate,
|
||||
request,
|
||||
setEmail,
|
||||
setSsoEmail,
|
||||
setSsoConnectors,
|
||||
singleSignOn,
|
||||
t,
|
||||
|
|
|
@ -7,8 +7,8 @@ import {
|
|||
import { useEffect, useCallback, useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import SingleSignOnContext from '@/Providers/SingleSignOnContextProvider/SingleSignOnContext';
|
||||
import SingleSignOnFormModeContext from '@/Providers/SingleSignOnFormModeContextProvider/SingleSignOnFormModeContext';
|
||||
import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext';
|
||||
import { getSingleSignOnConnectors } from '@/apis/single-sign-on';
|
||||
import type { IdentifierInputValue } from '@/components/InputFields/SmartInputField';
|
||||
import useApi from '@/hooks/use-api';
|
||||
|
@ -23,8 +23,8 @@ const useSingleSignOnWatch = (identifierInput?: IdentifierInputValue) => {
|
|||
|
||||
const { singleSignOnEnabled } = useSieMethods();
|
||||
|
||||
const { setEmail, setSsoConnectors, ssoConnectors, availableSsoConnectorsMap } =
|
||||
useContext(SingleSignOnContext);
|
||||
const { setSsoEmail, setSsoConnectors, ssoConnectors, availableSsoConnectorsMap } =
|
||||
useContext(UserInteractionContext);
|
||||
|
||||
const { showSingleSignOnForm, setShowSingleSignOnForm } = useContext(SingleSignOnFormModeContext);
|
||||
|
||||
|
@ -53,10 +53,10 @@ const useSingleSignOnWatch = (identifierInput?: IdentifierInputValue) => {
|
|||
}
|
||||
|
||||
setSsoConnectors(connectors);
|
||||
setEmail(email);
|
||||
setSsoEmail(email);
|
||||
return true;
|
||||
},
|
||||
[availableSsoConnectorsMap, request, setEmail, setSsoConnectors]
|
||||
[availableSsoConnectorsMap, request, setSsoEmail, setSsoConnectors]
|
||||
);
|
||||
|
||||
// Reset the ssoContext
|
||||
|
@ -64,9 +64,9 @@ const useSingleSignOnWatch = (identifierInput?: IdentifierInputValue) => {
|
|||
if (!showSingleSignOnForm) {
|
||||
setSsoConnectors([]);
|
||||
|
||||
setEmail(undefined);
|
||||
setSsoEmail(undefined);
|
||||
}
|
||||
}, [setEmail, setSsoConnectors, showSingleSignOnForm]);
|
||||
}, [setSsoEmail, setSsoConnectors, showSingleSignOnForm]);
|
||||
|
||||
const navigateToSingleSignOn = useCallback(async () => {
|
||||
if (!showSingleSignOnForm) {
|
||||
|
|
|
@ -3,8 +3,8 @@ import { assert } from '@silverhand/essentials';
|
|||
import { fireEvent, act, waitFor } from '@testing-library/react';
|
||||
|
||||
import ConfirmModalProvider from '@/Providers/ConfirmModalProvider';
|
||||
import SingleSignOnContextProvider from '@/Providers/SingleSignOnContextProvider';
|
||||
import SingleSignOnFormModeContextProvider from '@/Providers/SingleSignOnFormModeContextProvider';
|
||||
import UserInteractionContextProvider from '@/Providers/UserInteractionContextProvider';
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||
import { mockSignInExperienceSettings, mockSsoConnectors } from '@/__mocks__/logto';
|
||||
|
@ -53,11 +53,11 @@ const renderForm = (
|
|||
}}
|
||||
>
|
||||
<ConfirmModalProvider>
|
||||
<SingleSignOnContextProvider>
|
||||
<UserInteractionContextProvider>
|
||||
<SingleSignOnFormModeContextProvider>
|
||||
<IdentifierRegisterForm signUpMethods={signUpMethods} />
|
||||
</SingleSignOnFormModeContextProvider>
|
||||
</SingleSignOnContextProvider>
|
||||
</UserInteractionContextProvider>
|
||||
</ConfirmModalProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
|
|
|
@ -3,8 +3,8 @@ import { SignInIdentifier, experience } from '@logto/schemas';
|
|||
import { assert } from '@silverhand/essentials';
|
||||
import { fireEvent, act, waitFor } from '@testing-library/react';
|
||||
|
||||
import SingleSignOnContextProvider from '@/Providers/SingleSignOnContextProvider';
|
||||
import SingleSignOnFormModeContextProvider from '@/Providers/SingleSignOnFormModeContextProvider';
|
||||
import UserInteractionContextProvider from '@/Providers/UserInteractionContextProvider';
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||
import {
|
||||
|
@ -52,11 +52,11 @@ const renderForm = (signInMethods: SignIn['methods'], ssoConnectors: SsoConnecto
|
|||
ssoConnectors,
|
||||
}}
|
||||
>
|
||||
<SingleSignOnContextProvider>
|
||||
<UserInteractionContextProvider>
|
||||
<SingleSignOnFormModeContextProvider>
|
||||
<IdentifierSignInForm signInMethods={signInMethods} />
|
||||
</SingleSignOnFormModeContextProvider>
|
||||
</SingleSignOnContextProvider>
|
||||
</UserInteractionContextProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import { assert } from '@silverhand/essentials';
|
|||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import SingleSignOnContextProvider from '@/Providers/SingleSignOnContextProvider';
|
||||
import SingleSignOnFormModeContextProvider from '@/Providers/SingleSignOnFormModeContextProvider';
|
||||
import UserInteractionContextProvider from '@/Providers/UserInteractionContextProvider';
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||
import { mockSignInExperienceSettings, mockSsoConnectors } from '@/__mocks__/logto';
|
||||
|
@ -50,11 +50,11 @@ describe('UsernamePasswordSignInForm', () => {
|
|||
) =>
|
||||
renderWithPageContext(
|
||||
<SettingsProvider settings={{ ...mockSignInExperienceSettings, ...settings }}>
|
||||
<SingleSignOnContextProvider>
|
||||
<UserInteractionContextProvider>
|
||||
<SingleSignOnFormModeContextProvider>
|
||||
<PasswordSignInForm signInMethods={signInMethods} />
|
||||
</SingleSignOnFormModeContextProvider>
|
||||
</SingleSignOnContextProvider>
|
||||
</UserInteractionContextProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
|
||||
import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';
|
||||
import PageContext from '@/Providers/PageContextProvider/PageContext';
|
||||
import SingleSignOnContext from '@/Providers/SingleSignOnContextProvider/SingleSignOnContext';
|
||||
import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext';
|
||||
import SocialLinkButton from '@/components/Button/SocialLinkButton';
|
||||
import useNativeMessageListener from '@/hooks/use-native-message-listener';
|
||||
import useSingleSignOn from '@/hooks/use-single-sign-on';
|
||||
|
@ -13,7 +13,7 @@ import * as styles from './index.module.scss';
|
|||
|
||||
const SingleSignOnConnectors = () => {
|
||||
const { theme } = useContext(PageContext);
|
||||
const { email, ssoConnectors } = useContext(SingleSignOnContext);
|
||||
const { ssoEmail, ssoConnectors } = useContext(UserInteractionContext);
|
||||
const navigate = useNavigate();
|
||||
const onSubmit = useSingleSignOn();
|
||||
|
||||
|
@ -22,18 +22,18 @@ const SingleSignOnConnectors = () => {
|
|||
|
||||
useEffect(() => {
|
||||
// Return to the previous page if no email and no connectors are available in the context
|
||||
if (!email || ssoConnectors.length === 0) {
|
||||
if (!ssoEmail || ssoConnectors.length === 0) {
|
||||
navigate('../email', {
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
}, [email, navigate, ssoConnectors.length]);
|
||||
}, [ssoEmail, navigate, ssoConnectors.length]);
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout
|
||||
title="action.single_sign_on"
|
||||
description="description.single_sign_on_connectors_list"
|
||||
descriptionProps={{ email }}
|
||||
descriptionProps={{ email: ssoEmail }}
|
||||
>
|
||||
<div className={styles.ssoLinkList}>
|
||||
{ssoConnectors.map((connector) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useCallback, useContext, useEffect } from 'react';
|
|||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
||||
import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';
|
||||
import SingleSignOnContext from '@/Providers/SingleSignOnContextProvider/SingleSignOnContext';
|
||||
import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext';
|
||||
import LockIcon from '@/assets/icons/lock.svg';
|
||||
import Button from '@/components/Button';
|
||||
import ErrorMessage from '@/components/ErrorMessage';
|
||||
|
@ -21,7 +21,7 @@ type FormState = {
|
|||
|
||||
const SingleSignOnEmail = () => {
|
||||
const { errorMessage, clearErrorMessage, onSubmit } = useOnSubmit();
|
||||
const { email } = useContext(SingleSignOnContext);
|
||||
const { ssoEmail } = useContext(UserInteractionContext);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
|
@ -73,7 +73,7 @@ const SingleSignOnEmail = () => {
|
|||
className={styles.inputField}
|
||||
{...field}
|
||||
isDanger={!!errors.identifier}
|
||||
defaultValue={email}
|
||||
defaultValue={ssoEmail}
|
||||
errorMessage={errors.identifier?.message}
|
||||
enabledTypes={[SignInIdentifier.Email]}
|
||||
/>
|
||||
|
|
Loading…
Reference in a new issue