Compare commits

...

2 Commits

Author SHA1 Message Date
Syed Sajjad Hussain Shah
35a55acfa9 fix: fix useSelector usage in logistration and login component (#1203) 2024-03-15 12:11:46 +05:00
Attiya Ishaque
f9e4e2d18a fix: Use redux hooks in Authn login page 2024-03-15 12:11:46 +05:00
7 changed files with 69 additions and 182 deletions

View File

@@ -1,6 +1,8 @@
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions'; import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants'; import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';
export const storeName = 'commonComponents';
export const defaultState = { export const defaultState = {
fieldDescriptions: {}, fieldDescriptions: {},
optionalFields: { optionalFields: {

View File

@@ -1,28 +0,0 @@
import { createSelector } from 'reselect';
export const storeName = 'commonComponents';
export const commonComponentsSelector = state => ({ ...state[storeName] });
export const thirdPartyAuthContextSelector = createSelector(
commonComponentsSelector,
commonComponents => commonComponents.thirdPartyAuthContext,
);
export const fieldDescriptionSelector = createSelector(
commonComponentsSelector,
commonComponents => commonComponents.fieldDescriptions,
);
export const optionalFieldsSelector = createSelector(
commonComponentsSelector,
commonComponents => commonComponents.optionalFields,
);
export const tpaProvidersSelector = createSelector(
commonComponentsSelector,
commonComponents => ({
providers: commonComponents.thirdPartyAuthContext.providers,
secondaryProviders: commonComponents.thirdPartyAuthContext.secondaryProviders,
}),
);

View File

@@ -9,7 +9,7 @@ export { default as InstitutionLogistration } from './InstitutionLogistration';
export { RenderInstitutionButton } from './InstitutionLogistration'; export { RenderInstitutionButton } from './InstitutionLogistration';
export { default as reducer } from './data/reducers'; export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas'; export { default as saga } from './data/sagas';
export { storeName } from './data/selectors'; export { storeName } from './data/reducers';
export { default as FormGroup } from './FormGroup'; export { default as FormGroup } from './FormGroup';
export { default as PasswordField } from './PasswordField'; export { default as PasswordField } from './PasswordField';
export { default as Zendesk } from './Zendesk'; export { default as Zendesk } from './Zendesk';

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { connect } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { import {
Form, StatefulButton, Form, StatefulButton,
} from '@openedx/paragon'; } from '@openedx/paragon';
@@ -14,7 +14,7 @@ import { Link } from 'react-router-dom';
import AccountActivationMessage from './AccountActivationMessage'; import AccountActivationMessage from './AccountActivationMessage';
import { import {
backupLoginFormBegin, backupLoginFormBegin as backupFormState,
dismissPasswordResetBanner, dismissPasswordResetBanner,
loginRequest, loginRequest,
} from './data/actions'; } from './data/actions';
@@ -28,12 +28,11 @@ import {
RedirectLogistration, RedirectLogistration,
ThirdPartyAuthAlert, ThirdPartyAuthAlert,
} from '../common-components'; } from '../common-components';
import { getThirdPartyAuthContext } from '../common-components/data/actions'; import { getThirdPartyAuthContext as getTPADataFromBackend } from '../common-components/data/actions';
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
import EnterpriseSSO from '../common-components/EnterpriseSSO'; import EnterpriseSSO from '../common-components/EnterpriseSSO';
import ThirdPartyAuth from '../common-components/ThirdPartyAuth'; import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
import { import {
DEFAULT_STATE, PENDING_STATE, RESET_PAGE, PENDING_STATE, RESET_PAGE,
} from '../data/constants'; } from '../data/constants';
import { import {
getActivationStatus, getActivationStatus,
@@ -45,33 +44,31 @@ import {
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
const LoginPage = (props) => { const LoginPage = (props) => {
const { const dispatch = useDispatch();
backedUpFormData,
loginErrorCode,
loginErrorContext,
loginResult,
shouldBackupState,
thirdPartyAuthContext: {
providers,
currentProvider,
secondaryProviders,
finishAuthUrl,
platformName,
errorMessage: thirdPartyErrorMessage,
},
thirdPartyAuthApiStatus,
institutionLogin,
showResetPasswordSuccessBanner,
submitState,
// Actions
backupFormState,
handleInstitutionLogin,
getTPADataFromBackend,
} = props;
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const backedUpFormData = useSelector(state => state.login.loginFormData);
const loginErrorCode = useSelector(state => state.login.loginErrorCode);
const loginErrorContext = useSelector(state => state.login.loginErrorContext);
const loginResult = useSelector(state => state.login.loginResult);
const shouldBackupState = useSelector(state => state.login.shouldBackupState);
const showResetPasswordSuccessBanner = useSelector(state => state.login.showResetPasswordSuccessBanner);
const submitState = useSelector(state => state.login.submitState);
const thirdPartyAuthApiStatus = useSelector(state => state.commonComponents.thirdPartyAuthApiStatus);
const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers);
const currentProvider = useSelector(state => state.commonComponents.thirdPartyAuthContext.currentProvider);
const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders);
const finishAuthUrl = useSelector(state => state.commonComponents.thirdPartyAuthContext.finishAuthUrl);
const platformName = useSelector(state => state.commonComponents.thirdPartyAuthContext.platformName);
const thirdPartyErrorMessage = useSelector(state => state.commonComponents.thirdPartyAuthContext.errorMessage);
const {
institutionLogin,
handleInstitutionLogin,
} = props;
const activationMsgType = getActivationStatus(); const activationMsgType = getActivationStatus();
const queryParams = useMemo(() => getAllPossibleQueryParams(), []); const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields }); 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 [errors, setErrors] = useState({ ...backedUpFormData.errors });
@@ -86,19 +83,20 @@ const LoginPage = (props) => {
if (tpaHint) { if (tpaHint) {
payload.tpa_hint = tpaHint; payload.tpa_hint = tpaHint;
} }
getTPADataFromBackend(payload); dispatch(getTPADataFromBackend(payload));
}, [getTPADataFromBackend, queryParams, tpaHint]); }, [dispatch, queryParams, tpaHint]);
/** /**
* Backup the login form in redux when login page is toggled. * Backup the login form in redux when login page is toggled.
*/ */
useEffect(() => { useEffect(() => {
if (shouldBackupState) { if (shouldBackupState) {
backupFormState({ dispatch(backupFormState({
formFields: { ...formFields }, formFields: { ...formFields },
errors: { ...errors }, errors: { ...errors },
}); }));
} }
}, [shouldBackupState, formFields, errors, backupFormState]); }, [shouldBackupState, formFields, errors, dispatch]);
useEffect(() => { useEffect(() => {
if (loginErrorCode) { if (loginErrorCode) {
@@ -141,7 +139,7 @@ const LoginPage = (props) => {
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault();
if (showResetPasswordSuccessBanner) { if (showResetPasswordSuccessBanner) {
props.dismissPasswordResetBanner(); dispatch(dismissPasswordResetBanner());
} }
const formData = { ...formFields }; const formData = { ...formFields };
@@ -158,7 +156,7 @@ const LoginPage = (props) => {
password: formData.password, password: formData.password,
...queryParams, ...queryParams,
}; };
props.loginRequest(payload); dispatch(loginRequest(payload));
}; };
const handleOnChange = (event) => { const handleOnChange = (event) => {
@@ -281,88 +279,10 @@ 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 = { 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, 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 // Actions
backupFormState: PropTypes.func.isRequired,
dismissPasswordResetBanner: PropTypes.func.isRequired,
loginRequest: PropTypes.func.isRequired,
getTPADataFromBackend: PropTypes.func.isRequired,
handleInstitutionLogin: PropTypes.func.isRequired, handleInstitutionLogin: PropTypes.func.isRequired,
}; };
LoginPage.defaultProps = { export default LoginPage;
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,
},
)(injectIntl(LoginPage));

View File

@@ -40,8 +40,18 @@ describe('LoginPage', () => {
</IntlProvider> </IntlProvider>
); );
const loginFormData = {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
};
const initialState = { const initialState = {
login: { login: {
loginFormData,
loginResult: { success: false, redirectUrl: '' }, loginResult: { success: false, redirectUrl: '' },
}, },
commonComponents: { commonComponents: {

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
@@ -16,9 +16,6 @@ import { Navigate, useNavigate } from 'react-router-dom';
import BaseContainer from '../base-container'; import BaseContainer from '../base-container';
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions'; import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
import {
tpaProvidersSelector,
} from '../common-components/data/selectors';
import messages from '../common-components/messages'; import messages from '../common-components/messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import { import {
@@ -30,11 +27,8 @@ import { RegistrationPage } from '../register';
import { backupRegistrationForm } from '../register/data/actions'; import { backupRegistrationForm } from '../register/data/actions';
const Logistration = (props) => { const Logistration = (props) => {
const { selectedPage, tpaProviders } = props; const { selectedPage } = props;
const tpaHint = getTpaHint(); const tpaHint = getTpaHint();
const {
providers, secondaryProviders,
} = tpaProviders;
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [institutionLogin, setInstitutionLogin] = useState(false); const [institutionLogin, setInstitutionLogin] = useState(false);
const [key, setKey] = useState(''); const [key, setKey] = useState('');
@@ -42,6 +36,10 @@ const Logistration = (props) => {
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false; const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false;
const hideRegistrationLink = getConfig().SHOW_REGISTRATION_LINKS === false; const hideRegistrationLink = getConfig().SHOW_REGISTRATION_LINKS === false;
const dispatch = useDispatch();
const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers);
const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders);
useEffect(() => { useEffect(() => {
const authService = getAuthService(); const authService = getAuthService();
if (authService) { if (authService) {
@@ -71,11 +69,11 @@ const Logistration = (props) => {
return; return;
} }
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' }); sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
props.clearThirdPartyAuthContextErrorMessage(); dispatch(clearThirdPartyAuthContextErrorMessage());
if (tabKey === LOGIN_PAGE) { if (tabKey === LOGIN_PAGE) {
props.backupRegistrationForm(); dispatch(backupRegistrationForm());
} else if (tabKey === REGISTER_PAGE) { } else if (tabKey === REGISTER_PAGE) {
props.backupLoginForm(); dispatch(backupLoginForm());
} }
setKey(tabKey); setKey(tabKey);
}; };
@@ -156,35 +154,10 @@ const Logistration = (props) => {
Logistration.propTypes = { Logistration.propTypes = {
selectedPage: PropTypes.string, selectedPage: PropTypes.string,
backupLoginForm: PropTypes.func.isRequired,
backupRegistrationForm: PropTypes.func.isRequired,
clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired,
tpaProviders: PropTypes.shape({
providers: PropTypes.arrayOf(PropTypes.shape({})),
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
}),
};
Logistration.defaultProps = {
tpaProviders: {
providers: [],
secondaryProviders: [],
},
}; };
Logistration.defaultProps = { Logistration.defaultProps = {
selectedPage: REGISTER_PAGE, selectedPage: REGISTER_PAGE,
}; };
const mapStateToProps = state => ({ export default Logistration;
tpaProviders: tpaProvidersSelector(state),
});
export default connect(
mapStateToProps,
{
backupLoginForm,
backupRegistrationForm,
clearThirdPartyAuthContextErrorMessage,
},
)(Logistration);

View File

@@ -43,6 +43,15 @@ describe('Logistration', () => {
</IntlProvider> </IntlProvider>
); );
const loginFormData = {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
};
const initialState = { const initialState = {
register: { register: {
registrationFormData: { registrationFormData: {
@@ -71,6 +80,7 @@ describe('Logistration', () => {
}, },
}, },
login: { login: {
loginFormData,
loginResult: { success: false, redirectUrl: '' }, loginResult: { success: false, redirectUrl: '' },
}, },
}; };