diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 2873f660..011afabf 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -1,12 +1,12 @@ -import { useEffect, useMemo, useState } from 'react'; -import { connect } from 'react-redux'; +import { + useCallback, useEffect, useMemo, useState, +} from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { getSiteConfig, sendPageEvent, sendTrackEvent, useIntl } from '@openedx/frontend-base'; -import { - Form, StatefulButton, -} from '@openedx/paragon'; +import { Form, StatefulButton } from '@openedx/paragon'; import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; import Skeleton from 'react-loading-skeleton'; @@ -23,9 +23,7 @@ import { getThirdPartyAuthContext } from '../common-components/data/actions'; import { thirdPartyAuthContextSelector } from '../common-components/data/selectors'; import EnterpriseSSO from '../common-components/EnterpriseSSO'; import ThirdPartyAuth from '../common-components/ThirdPartyAuth'; -import { - DEFAULT_STATE, PENDING_STATE, RESET_PAGE, -} from '../data/constants'; +import { PENDING_STATE, RESET_PAGE } from '../data/constants'; import { getActivationStatus, getAllPossibleQueryParams, @@ -35,45 +33,57 @@ import { } from '../data/utils'; import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; import AccountActivationMessage from './AccountActivationMessage'; -import { - backupLoginFormBegin, - dismissPasswordResetBanner, - loginRequest, -} from './data/actions'; +import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from './data/actions'; import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants'; import LoginFailureMessage from './LoginFailure'; import messages from './messages'; -const LoginPage = (props) => { +const LoginPage = ({ + institutionLogin, + handleInstitutionLogin, +}) => { + const dispatch = useDispatch(); + const backupFormState = useCallback((data) => dispatch(backupLoginFormBegin(data)), [dispatch]); + const getTPADataFromBackend = useCallback(() => dispatch(getThirdPartyAuthContext()), [dispatch]); const { backedUpFormData, loginErrorCode, loginErrorContext, loginResult, shouldBackupState, - thirdPartyAuthContext: { - providers, - currentProvider, - secondaryProviders, - finishAuthUrl, - platformName, - errorMessage: thirdPartyErrorMessage, - }, - thirdPartyAuthApiStatus, - institutionLogin, showResetPasswordSuccessBanner, submitState, - // Actions - backupFormState, - handleInstitutionLogin, - getTPADataFromBackend, - } = props; + thirdPartyAuthContext, + thirdPartyAuthApiStatus, + } = useSelector((state) => ({ + backedUpFormData: state.login.loginFormData, + loginErrorCode: state.login.loginErrorCode, + loginErrorContext: state.login.loginErrorContext, + loginResult: state.login.loginResult, + shouldBackupState: state.login.shouldBackupState, + showResetPasswordSuccessBanner: state.login.showResetPasswordSuccessBanner, + submitState: state.login.submitState, + thirdPartyAuthContext: thirdPartyAuthContextSelector(state), + thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, + })); + const { + providers, + currentProvider, + secondaryProviders, + finishAuthUrl, + platformName, + errorMessage: thirdPartyErrorMessage, + } = thirdPartyAuthContext; const { formatMessage } = useIntl(); const activationMsgType = getActivationStatus(); const queryParams = useMemo(() => getAllPossibleQueryParams(), []); const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields }); - const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} }); + const [errorCode, setErrorCode] = useState({ + type: '', + count: 0, + context: {}, + }); const [errors, setErrors] = useState({ ...backedUpFormData.errors }); const tpaHint = getTpaHint(); @@ -87,7 +97,7 @@ const LoginPage = (props) => { payload.tpa_hint = tpaHint; } getTPADataFromBackend(payload); - }, [getTPADataFromBackend, queryParams, tpaHint]); + }, [queryParams, tpaHint, getTPADataFromBackend]); /** * Backup the login form in redux when login page is toggled. */ @@ -98,7 +108,7 @@ const LoginPage = (props) => { errors: { ...errors }, }); } - }, [shouldBackupState, formFields, errors, backupFormState]); + }, [backupFormState, shouldBackupState, formFields, errors]); useEffect(() => { if (loginErrorCode) { @@ -123,7 +133,10 @@ const LoginPage = (props) => { }, [thirdPartyErrorMessage]); const validateFormFields = (payload) => { - const { emailOrUsername, password } = payload; + const { + emailOrUsername, + password, + } = payload; const fieldErrors = { ...errors }; if (emailOrUsername === '') { @@ -141,14 +154,18 @@ const LoginPage = (props) => { const handleSubmit = (event) => { event.preventDefault(); if (showResetPasswordSuccessBanner) { - props.dismissPasswordResetBanner(); + dispatch(dismissPasswordResetBanner()); } const formData = { ...formFields }; const validationErrors = validateFormFields(formData); if (validationErrors.emailOrUsername || validationErrors.password) { setErrors({ ...validationErrors }); - setErrorCode(prevState => ({ type: INVALID_FORM, count: prevState.count + 1, context: {} })); + setErrorCode(prevState => ({ + type: INVALID_FORM, + count: prevState.count + 1, + context: {}, + })); return; } @@ -158,23 +175,35 @@ const LoginPage = (props) => { password: formData.password, ...queryParams, }; - props.loginRequest(payload); + dispatch(loginRequest(payload)); }; const handleOnChange = (event) => { - const { name, value } = event.target; - setFormFields(prevState => ({ ...prevState, [name]: value })); + const { + name, + value, + } = event.target; + setFormFields(prevState => ({ + ...prevState, + [name]: value, + })); }; const handleOnFocus = (event) => { const { name } = event.target; - setErrors(prevErrors => ({ ...prevErrors, [name]: '' })); + setErrors(prevErrors => ({ + ...prevErrors, + [name]: '', + })); }; const trackForgotPasswordLinkClick = () => { sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' }); }; - const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders); + const { + provider, + skipHintedLogin, + } = getTpaProvider(tpaHint, providers, secondaryProviders); if (tpaHint) { if (thirdPartyAuthApiStatus === PENDING_STATE) { @@ -281,88 +310,9 @@ const LoginPage = (props) => { ); }; -const mapStateToProps = state => { - const loginPageState = state.login; - return { - backedUpFormData: loginPageState.loginFormData, - loginErrorCode: loginPageState.loginErrorCode, - loginErrorContext: loginPageState.loginErrorContext, - loginResult: loginPageState.loginResult, - shouldBackupState: loginPageState.shouldBackupState, - showResetPasswordSuccessBanner: loginPageState.showResetPasswordSuccessBanner, - submitState: loginPageState.submitState, - thirdPartyAuthContext: thirdPartyAuthContextSelector(state), - thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, - }; -}; - LoginPage.propTypes = { - backedUpFormData: PropTypes.shape({ - formFields: PropTypes.shape({}), - errors: PropTypes.shape({}), - }), - loginErrorCode: PropTypes.string, - loginErrorContext: PropTypes.shape({ - email: PropTypes.string, - redirectUrl: PropTypes.string, - context: PropTypes.shape({}), - }), - loginResult: PropTypes.shape({ - redirectUrl: PropTypes.string, - success: PropTypes.bool, - }), - shouldBackupState: PropTypes.bool, - showResetPasswordSuccessBanner: PropTypes.bool, - submitState: PropTypes.string, - thirdPartyAuthApiStatus: PropTypes.string, institutionLogin: PropTypes.bool.isRequired, - thirdPartyAuthContext: PropTypes.shape({ - currentProvider: PropTypes.string, - errorMessage: PropTypes.string, - platformName: PropTypes.string, - providers: PropTypes.arrayOf(PropTypes.shape({})), - secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})), - finishAuthUrl: PropTypes.string, - }), - // Actions - backupFormState: PropTypes.func.isRequired, - dismissPasswordResetBanner: PropTypes.func.isRequired, - loginRequest: PropTypes.func.isRequired, - getTPADataFromBackend: PropTypes.func.isRequired, handleInstitutionLogin: PropTypes.func.isRequired, }; -LoginPage.defaultProps = { - backedUpFormData: { - formFields: { - emailOrUsername: '', password: '', - }, - errors: { - emailOrUsername: '', password: '', - }, - }, - loginErrorCode: null, - loginErrorContext: {}, - loginResult: {}, - shouldBackupState: false, - showResetPasswordSuccessBanner: false, - submitState: DEFAULT_STATE, - thirdPartyAuthApiStatus: PENDING_STATE, - thirdPartyAuthContext: { - currentProvider: null, - errorMessage: null, - finishAuthUrl: null, - providers: [], - secondaryProviders: [], - }, -}; - -export default connect( - mapStateToProps, - { - backupFormState: backupLoginFormBegin, - dismissPasswordResetBanner, - loginRequest, - getTPADataFromBackend: getThirdPartyAuthContext, - }, -)(LoginPage); +export default LoginPage; diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index 94ff754b..d87fe896 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -40,6 +40,14 @@ describe('LoginPage', () => { const initialState = { login: { loginResult: { success: false, redirectUrl: '' }, + loginFormData: { + formFields: { + emailOrUsername: '', password: '', + }, + errors: { + emailOrUsername: '', password: '', + }, + }, }, commonComponents: { thirdPartyAuthApiStatus: null, diff --git a/src/logistration/Logistration.jsx b/src/logistration/Logistration.jsx index 25fb805f..fc0dff5c 100644 --- a/src/logistration/Logistration.jsx +++ b/src/logistration/Logistration.jsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useAppConfig, getAuthService, getSiteConfig, sendPageEvent, sendTrackEvent, useIntl @@ -23,16 +23,20 @@ import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; import { getTpaHint, getTpaProvider, updatePathWithQueryParams, } from '../data/utils'; -import { LoginPage } from '../login'; import { backupLoginForm } from '../login/data/actions'; import { RegistrationPage } from '../register'; import { backupRegistrationForm } from '../register/data/actions'; +import LoginComponentSlot from '../slots/LoginComponentSlot'; -const Logistration = (props) => { - const { selectedPage, tpaProviders } = props; +const Logistration = ({ + selectedPage, +}) => { const tpaHint = getTpaHint(); + const tpaProviders = useSelector(tpaProvidersSelector); + const dispatch = useDispatch(); const { - providers, secondaryProviders, + providers, + secondaryProviders, } = tpaProviders; const { formatMessage } = useIntl(); const [institutionLogin, setInstitutionLogin] = useState(false); @@ -44,7 +48,8 @@ const Logistration = (props) => { useEffect(() => { const authService = getAuthService(); if (authService) { - authService.getCsrfTokenService().getCsrfToken(getSiteConfig().lmsBaseUrl); + authService.getCsrfTokenService() + .getCsrfToken(getSiteConfig().lmsBaseUrl); } }); @@ -70,11 +75,11 @@ const Logistration = (props) => { return; } sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' }); - props.clearThirdPartyAuthContextErrorMessage(); + dispatch(clearThirdPartyAuthContextErrorMessage()); if (tabKey === LOGIN_PAGE) { - props.backupRegistrationForm(); + dispatch(backupRegistrationForm()); } else if (tabKey === REGISTER_PAGE) { - props.backupLoginForm(); + dispatch(backupLoginForm()); } setKey(tabKey); }; @@ -110,7 +115,10 @@ const Logistration = (props) => { {!institutionLogin && (