Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Use redux hooks in Authn login page #1188

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/common-components/data/reducers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';

export const storeName = 'commonComponents';

export const defaultState = {
fieldDescriptions: {},
optionalFields: {
Expand Down
28 changes: 0 additions & 28 deletions src/common-components/data/selectors.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/common-components/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export { default as InstitutionLogistration } from './InstitutionLogistration';
export { RenderInstitutionButton } from './InstitutionLogistration';
export { default as reducer } from './data/reducers';
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 PasswordField } from './PasswordField';
export { default as Zendesk } from './Zendesk';
146 changes: 33 additions & 113 deletions src/login/LoginPage.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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 { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form, StatefulButton,
} from '@openedx/paragon';
Expand All @@ -14,7 +14,7 @@ import { Link } from 'react-router-dom';

import AccountActivationMessage from './AccountActivationMessage';
import {
backupLoginFormBegin,
backupLoginFormBegin as backupFormState,
dismissPasswordResetBanner,
loginRequest,
} from './data/actions';
Expand All @@ -28,12 +28,11 @@ import {
RedirectLogistration,
ThirdPartyAuthAlert,
} from '../common-components';
import { getThirdPartyAuthContext } from '../common-components/data/actions';
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
import { getThirdPartyAuthContext as getTPADataFromBackend } from '../common-components/data/actions';
import EnterpriseSSO from '../common-components/EnterpriseSSO';
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
import {
DEFAULT_STATE, PENDING_STATE, RESET_PAGE,
PENDING_STATE, RESET_PAGE,
} from '../data/constants';
import {
getActivationStatus,
Expand All @@ -45,33 +44,31 @@ import {
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';

const LoginPage = (props) => {
const dispatch = useDispatch();
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 {
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 activationMsgType = getActivationStatus();
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);

const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} });
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
Expand All @@ -86,19 +83,20 @@ const LoginPage = (props) => {
if (tpaHint) {
payload.tpa_hint = tpaHint;
}
getTPADataFromBackend(payload);
}, [getTPADataFromBackend, queryParams, tpaHint]);
dispatch(getTPADataFromBackend(payload));
}, [dispatch, queryParams, tpaHint]);

/**
* Backup the login form in redux when login page is toggled.
*/
useEffect(() => {
if (shouldBackupState) {
backupFormState({
dispatch(backupFormState({
formFields: { ...formFields },
errors: { ...errors },
});
}));
}
}, [shouldBackupState, formFields, errors, backupFormState]);
}, [shouldBackupState, formFields, errors, dispatch]);

useEffect(() => {
if (loginErrorCode) {
Expand Down Expand Up @@ -141,7 +139,7 @@ const LoginPage = (props) => {
const handleSubmit = (event) => {
event.preventDefault();
if (showResetPasswordSuccessBanner) {
props.dismissPasswordResetBanner();
dispatch(dismissPasswordResetBanner());
}

const formData = { ...formFields };
Expand All @@ -158,7 +156,7 @@ const LoginPage = (props) => {
password: formData.password,
...queryParams,
};
props.loginRequest(payload);
dispatch(loginRequest(payload));
};

const handleOnChange = (event) => {
Expand Down Expand Up @@ -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 = {
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,
},
)(injectIntl(LoginPage));
export default LoginPage;
10 changes: 10 additions & 0 deletions src/login/tests/LoginPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,18 @@ describe('LoginPage', () => {
</IntlProvider>
);

const loginFormData = {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
};

const initialState = {
login: {
loginFormData,
loginResult: { success: false, redirectUrl: '' },
},
commonComponents: {
Expand Down
47 changes: 10 additions & 37 deletions src/logistration/Logistration.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';

import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
Expand All @@ -16,9 +16,6 @@ import { Navigate, useNavigate } from 'react-router-dom';

import BaseContainer from '../base-container';
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
import {
tpaProvidersSelector,
} from '../common-components/data/selectors';
import messages from '../common-components/messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import {
Expand All @@ -30,18 +27,19 @@ import { RegistrationPage } from '../register';
import { backupRegistrationForm } from '../register/data/actions';

const Logistration = (props) => {
const { selectedPage, tpaProviders } = props;
const { selectedPage } = props;
const tpaHint = getTpaHint();
const {
providers, secondaryProviders,
} = tpaProviders;
const { formatMessage } = useIntl();
const [institutionLogin, setInstitutionLogin] = useState(false);
const [key, setKey] = useState('');
const navigate = useNavigate();
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === 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(() => {
const authService = getAuthService();
if (authService) {
Expand Down Expand Up @@ -71,11 +69,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);
};
Expand Down Expand Up @@ -156,35 +154,10 @@ const Logistration = (props) => {

Logistration.propTypes = {
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 = {
selectedPage: REGISTER_PAGE,
};

const mapStateToProps = state => ({
tpaProviders: tpaProvidersSelector(state),
});

export default connect(
mapStateToProps,
{
backupLoginForm,
backupRegistrationForm,
clearThirdPartyAuthContextErrorMessage,
},
)(Logistration);
export default Logistration;