diff --git a/src/data/constants.js b/src/data/constants.js
index 6b58337e..e8b1f5ac 100644
--- a/src/data/constants.js
+++ b/src/data/constants.js
@@ -6,3 +6,7 @@ export const DEFAULT_REDIRECT_URL = '/dashboard';
// Constants
export const SUPPORTED_ICON_CLASSES = ['apple', 'facebook', 'google', 'microsoft'];
+
+// Stateful Submit Button States
+export const DEFAULT_STATE = 'default';
+export const PENDING_STATE = 'pending';
diff --git a/src/forgot-password/ForgotPasswordPage.jsx b/src/forgot-password/ForgotPasswordPage.jsx
index 611add2b..7b4d3980 100644
--- a/src/forgot-password/ForgotPasswordPage.jsx
+++ b/src/forgot-password/ForgotPasswordPage.jsx
@@ -2,13 +2,14 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
-import { Button, Input, ValidationFormGroup } from '@edx/paragon';
+import { Input, StatefulButton, ValidationFormGroup } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { forgotPassword } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import RequestInProgressAlert from './RequestInProgressAlert';
+import { LOGIN_PAGE } from '../data/constants';
import LoginHelpLinks from '../logistration/LoginHelpLinks';
const ForgotPasswordPage = (props) => {
@@ -39,7 +40,7 @@ const ForgotPasswordPage = (props) => {
return (
<>
- {status === 'complete' ? : null}
+ {status === 'complete' ? : null}
+
diff --git a/src/logistration/LoginPage.jsx b/src/logistration/LoginPage.jsx
index 821349a2..e187abe2 100644
--- a/src/logistration/LoginPage.jsx
+++ b/src/logistration/LoginPage.jsx
@@ -1,22 +1,23 @@
import React from 'react';
-import { Button, Input, ValidationFormGroup } from '@edx/paragon';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { Input, StatefulButton, ValidationFormGroup } from '@edx/paragon';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import { forgotPasswordResultSelector } from '../forgot-password';
import ConfirmationAlert from './ConfirmationAlert';
import { getThirdPartyAuthContext, loginRequest } from './data/actions';
-import { DEFAULT_REDIRECT_URL, REGISTER_PAGE } from '../data/constants';
import { loginRequestSelector, thirdPartyAuthContextSelector } from './data/selectors';
+import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionLogistration';
import LoginHelpLinks from './LoginHelpLinks';
import LoginFailureMessage from './LoginFailure';
+import messages from './messages';
import RedirectLogistration from './RedirectLogistration';
import SocialAuthProviders from './SocialAuthProviders';
import ThirdPartyAuthAlert from './ThirdPartyAuthAlert';
-import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionLogistration';
-import messages from './messages';
+
+import { DEFAULT_REDIRECT_URL, DEFAULT_STATE, REGISTER_PAGE } from '../data/constants';
+import { forgotPasswordResultSelector } from '../forgot-password';
class LoginPage extends React.Component {
constructor(props, context) {
@@ -111,13 +112,13 @@ class LoginPage extends React.Component {
}
render() {
- const { intl } = this.props;
- const { currentProvider, finishAuthUrl, providers } = this.props.thirdPartyAuthContext;
+ const { intl, submitState, thirdPartyAuthContext } = this.props;
+
if (this.state.institutionLogin) {
return (
@@ -128,15 +129,15 @@ class LoginPage extends React.Component {
- {currentProvider
+ {thirdPartyAuthContext.currentProvider
&& (
)}
{this.props.loginError ?
: null}
@@ -146,15 +147,21 @@ class LoginPage extends React.Component {
First time here?
Create an Account.
-
{intl.formatMessage(messages['logistration.login.institution.login.sign.in'])}
-
-
-
{intl.formatMessage(messages['logistration.login.institution.login.sign.in.with'])}
-
+
+ {intl.formatMessage(messages['logistration.login.institution.login.sign.in'])}
+
+ {thirdPartyAuthContext.secondaryProviders.length ? (
+ <>
+
+
+
{intl.formatMessage(messages['logistration.login.institution.login.sign.in.with'])}
+
+ >
+ ) : null }
- {providers.length && !currentProvider ? (
+ {thirdPartyAuthContext.providers.length && !thirdPartyAuthContext.currentProvider ? (
<>
or sign in with
-
+
>
) : null}
@@ -221,28 +231,32 @@ class LoginPage extends React.Component {
}
LoginPage.defaultProps = {
- loginResult: null,
forgotPassword: null,
+ loginResult: null,
loginError: null,
+ submitState: DEFAULT_STATE,
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
providers: [],
+ secondaryProviders: [],
},
};
LoginPage.propTypes = {
- intl: intlShape.isRequired,
+ forgotPassword: PropTypes.shape({
+ email: PropTypes.string,
+ status: PropTypes.string,
+ }),
getThirdPartyAuthContext: PropTypes.func.isRequired,
+ intl: intlShape.isRequired,
+ loginError: PropTypes.string,
loginRequest: PropTypes.func.isRequired,
loginResult: PropTypes.shape({
redirectUrl: PropTypes.string,
success: PropTypes.bool,
}),
- forgotPassword: PropTypes.shape({
- email: PropTypes.string,
- status: PropTypes.string,
- }),
+ submitState: PropTypes.string,
thirdPartyAuthContext: PropTypes.shape({
currentProvider: PropTypes.string,
platformName: PropTypes.string,
@@ -250,7 +264,6 @@ LoginPage.propTypes = {
secondaryProviders: PropTypes.array,
finishAuthUrl: PropTypes.string,
}),
- loginError: PropTypes.string,
};
const mapStateToProps = state => {
@@ -258,10 +271,11 @@ const mapStateToProps = state => {
const loginResult = loginRequestSelector(state);
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
return {
+ loginError: state.logistration.loginError,
+ submitState: state.logistration.submitState,
forgotPassword,
loginResult,
thirdPartyAuthContext,
- loginError: state.logistration.loginError,
};
};
diff --git a/src/logistration/RegistrationPage.jsx b/src/logistration/RegistrationPage.jsx
index 37116553..f8a351c1 100644
--- a/src/logistration/RegistrationPage.jsx
+++ b/src/logistration/RegistrationPage.jsx
@@ -1,9 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import {
- Button, Input, ValidationFormGroup,
-} from '@edx/paragon';
+import { Input, StatefulButton, ValidationFormGroup } from '@edx/paragon';
import {
getLocale, getCountryList, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
@@ -12,7 +10,9 @@ import { getThirdPartyAuthContext, registerNewUser } from './data/actions';
import { registrationRequestSelector, thirdPartyAuthContextSelector } from './data/selectors';
import RedirectLogistration from './RedirectLogistration';
import RegistrationFailure from './RegistrationFailure';
-import { DEFAULT_REDIRECT_URL, LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
+import {
+ DEFAULT_REDIRECT_URL, DEFAULT_STATE, LOGIN_PAGE, REGISTER_PAGE,
+} from '../data/constants';
import SocialAuthProviders from './SocialAuthProviders';
import ThirdPartyAuthAlert from './ThirdPartyAuthAlert';
import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionLogistration';
@@ -159,7 +159,7 @@ class RegistrationPage extends React.Component {
}
render() {
- const { intl } = this.props;
+ const { intl, submitState } = this.props;
const {
currentProvider, finishAuthUrl, providers, secondaryProviders,
} = this.props.thirdPartyAuthContext;
@@ -295,12 +295,15 @@ class RegistrationPage extends React.Component {
/>
By creating an account, you agree to the Terms of Service and Honor Code and you acknowledge that edX and each Member process your personal data in accordance with the Privacy Policy.
-
+ />
>
@@ -312,6 +315,7 @@ RegistrationPage.defaultProps = {
registrationResult: null,
registerNewUser: null,
registrationError: null,
+ submitState: DEFAULT_STATE,
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
@@ -333,6 +337,7 @@ RegistrationPage.propTypes = {
email: PropTypes.array,
username: PropTypes.array,
}),
+ submitState: PropTypes.string,
thirdPartyAuthContext: PropTypes.shape({
currentProvider: PropTypes.string,
platformName: PropTypes.string,
@@ -353,8 +358,9 @@ const mapStateToProps = state => {
const registrationResult = registrationRequestSelector(state);
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
return {
- registrationResult,
registrationError: state.logistration.registrationError,
+ submitState: state.logistration.submitState,
+ registrationResult,
thirdPartyAuthContext,
};
};
diff --git a/src/logistration/data/reducers.js b/src/logistration/data/reducers.js
index 7abc5284..013b9dd3 100644
--- a/src/logistration/data/reducers.js
+++ b/src/logistration/data/reducers.js
@@ -4,11 +4,13 @@ import {
THIRD_PARTY_AUTH_CONTEXT,
} from './actions';
+import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
+
export const defaultState = {
- registrationResult: {},
+ loginError: null,
loginResult: {},
registrationError: null,
- loginError: null,
+ registrationResult: {},
};
const reducer = (state = defaultState, action) => {
@@ -16,21 +18,23 @@ const reducer = (state = defaultState, action) => {
case REGISTER_NEW_USER.BEGIN:
return {
...state,
+ submitState: PENDING_STATE,
};
case REGISTER_NEW_USER.SUCCESS:
return {
...state,
-
registrationResult: action.payload,
};
case REGISTER_NEW_USER.FAILURE:
return {
...state,
registrationError: action.payload.error,
+ submitState: DEFAULT_STATE,
};
case LOGIN_REQUEST.BEGIN:
return {
...state,
+ submitState: PENDING_STATE,
};
case LOGIN_REQUEST.SUCCESS:
return {
@@ -41,6 +45,7 @@ const reducer = (state = defaultState, action) => {
return {
...state,
loginError: action.payload.loginError,
+ submitState: DEFAULT_STATE,
};
case THIRD_PARTY_AUTH_CONTEXT.BEGIN:
return {
diff --git a/src/logistration/messages.jsx b/src/logistration/messages.jsx
index eb4bd58f..3d442448 100644
--- a/src/logistration/messages.jsx
+++ b/src/logistration/messages.jsx
@@ -2,6 +2,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
+ 'logistration.sign.in.button': {
+ id: 'logistration.sign.in.button',
+ defaultMessage: 'Sign in',
+ description: 'Button label that appears on login page',
+ },
+ 'logistration.create.account.button': {
+ id: 'ogistration.create.account.button',
+ defaultMessage: 'Create Account',
+ description: 'Button label that appears on register page',
+ },
'logistration.need.help.signing.in.collapsible.menu': {
id: 'logistration.need.help.signing.in.collapsible.menu',
defaultMessage: 'Need help signing in?',
diff --git a/src/logistration/tests/LoginPage.test.jsx b/src/logistration/tests/LoginPage.test.jsx
index 30b26626..1a1c9031 100644
--- a/src/logistration/tests/LoginPage.test.jsx
+++ b/src/logistration/tests/LoginPage.test.jsx
@@ -8,6 +8,7 @@ import { getConfig } from '@edx/frontend-platform';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import LoginPage from '../LoginPage';
import { RenderInstitutionButton } from '../InstitutionLogistration';
+import { PENDING_STATE } from '../../data/constants';
const IntlLoginPage = injectIntl(LoginPage);
const mockStore = configureStore();
@@ -64,6 +65,20 @@ describe('LoginPage', () => {
expect(tree).toMatchSnapshot();
});
+ it('should match pending button state snapshot', () => {
+ store = mockStore({
+ ...initialState,
+ logistration: {
+ ...initialState.logistration,
+ submitState: PENDING_STATE,
+ },
+ });
+
+ const tree = renderer.create(reduxWrapper(
))
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
it('should match forget password alert message snapshot', () => {
props = {
...props,
@@ -79,6 +94,7 @@ describe('LoginPage', () => {
logistration: {
...initialState.logistration,
thirdPartyAuthContext: {
+ ...initialState.logistration.thirdPartyAuthContext,
providers: [appleProvider],
},
},
@@ -184,6 +200,7 @@ describe('LoginPage', () => {
logistration: {
...initialState.logistration,
thirdPartyAuthContext: {
+ ...initialState.logistration.thirdPartyAuthContext,
providers: [{
...appleProvider,
loginUrl,
diff --git a/src/logistration/tests/RegistrationPage.test.jsx b/src/logistration/tests/RegistrationPage.test.jsx
index 40716492..7d0a4c99 100644
--- a/src/logistration/tests/RegistrationPage.test.jsx
+++ b/src/logistration/tests/RegistrationPage.test.jsx
@@ -8,6 +8,7 @@ import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n
import RegistrationPage from '../RegistrationPage';
import { RenderInstitutionButton } from '../InstitutionLogistration';
+import { PENDING_STATE } from '../../data/constants';
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
@@ -78,6 +79,19 @@ describe('./RegistrationPage.js', () => {
expect(tree.toJSON()).toMatchSnapshot();
});
+ it('should match pending button state snapshot', () => {
+ store = mockStore({
+ ...initialState,
+ logistration: {
+ ...initialState.logistration,
+ submitState: PENDING_STATE,
+ },
+ });
+
+ const tree = renderer.create(reduxWrapper(
));
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
it('should match TPA provider snapshot', () => {
store = mockStore({
...initialState,
diff --git a/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap b/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap
index facf4090..0791e67e 100644
--- a/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap
+++ b/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap
@@ -25,18 +25,11 @@ exports[`LoginPage should match TPA provider snapshot 1`] = `
-