Add account activation messages (#82)
Show messages based on the query paran recieved during the redirect after account activation link is clicked VAN-304
This commit is contained in:
@@ -58,6 +58,7 @@ initialize({
|
||||
config: () => {
|
||||
mergeConfig({
|
||||
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
|
||||
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
63
src/logistration/AccountActivationMessage.jsx
Normal file
63
src/logistration/AccountActivationMessage.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { ACCOUNT_ACTIVATION_MESSAGE } from './data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const AccountActivationMessage = (props) => {
|
||||
const { intl, messageType } = props;
|
||||
const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType;
|
||||
|
||||
let activationMessage;
|
||||
let heading;
|
||||
|
||||
switch (messageType) {
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: {
|
||||
heading = intl.formatMessage(messages['authn.account.activation.success.message.title']);
|
||||
activationMessage = intl.formatMessage(messages['authn.account.activation.success.message']);
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.INFO: {
|
||||
activationMessage = intl.formatMessage(messages['authn.account.already.activated.message']);
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
||||
const supportLink = (
|
||||
<Alert.Link href={getConfig().ACTIVATION_EMAIL_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['authn.account.activation.support.link'])}
|
||||
</Alert.Link>
|
||||
);
|
||||
|
||||
heading = intl.formatMessage(messages['authn.account.activation.error.message.title']);
|
||||
activationMessage = (
|
||||
<FormattedMessage
|
||||
id="authn.account.activation.error.message"
|
||||
defaultMessage="Something went wrong, please {supportLink} to resolve this issue."
|
||||
description="Account activation error message"
|
||||
values={{ supportLink }}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return activationMessage ? (
|
||||
<Alert id="account-activation-message" variant={variant}>
|
||||
{heading && <Alert.Heading>{heading}</Alert.Heading>}
|
||||
{activationMessage}
|
||||
</Alert>
|
||||
) : null;
|
||||
};
|
||||
|
||||
AccountActivationMessage.propTypes = {
|
||||
messageType: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AccountActivationMessage);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Alert, Hyperlink } from '@edx/paragon';
|
||||
import { Alert } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { processLink } from '../data/utils/dataUtils';
|
||||
@@ -82,7 +82,7 @@ const LoginFailureMessage = (props) => {
|
||||
return (
|
||||
<li key={error}>
|
||||
{beforeLink}
|
||||
<Hyperlink destination={link}>{linkText}</Hyperlink>
|
||||
<Alert.Link href={link}>{linkText}</Alert.Link>
|
||||
{afterLink}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Form, Hyperlink, Input, StatefulButton, ValidationFormGroup,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import AccountActivationMessage from './AccountActivationMessage';
|
||||
import ConfirmationAlert from './ConfirmationAlert';
|
||||
import { getThirdPartyAuthContext, loginRequest } from './data/actions';
|
||||
import { loginErrorSelector, loginRequestSelector, thirdPartyAuthContextSelector } from './data/selectors';
|
||||
@@ -16,10 +17,10 @@ import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionL
|
||||
import LoginHelpLinks from './LoginHelpLinks';
|
||||
import LoginFailureMessage from './LoginFailure';
|
||||
import messages from './messages';
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import SocialAuthProviders from './SocialAuthProviders';
|
||||
import ThirdPartyAuthAlert from './ThirdPartyAuthAlert';
|
||||
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import {
|
||||
DEFAULT_REDIRECT_URL, DEFAULT_STATE, LOGIN_PAGE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, PENDING_STATE,
|
||||
} from '../data/constants';
|
||||
@@ -147,6 +148,9 @@ class LoginPage extends React.Component {
|
||||
} = this.props;
|
||||
const { currentProvider, providers, secondaryProviders } = this.props.thirdPartyAuthContext;
|
||||
|
||||
const params = (new URL(window.location.href)).searchParams;
|
||||
const activationMsgType = params.get('account_activation_status');
|
||||
|
||||
if (this.state.institutionLogin) {
|
||||
return (
|
||||
<InstitutionLogistration
|
||||
@@ -175,6 +179,7 @@ class LoginPage extends React.Component {
|
||||
/>
|
||||
)}
|
||||
{this.props.loginError ? <LoginFailureMessage loginError={this.props.loginError} /> : null}
|
||||
{activationMsgType && <AccountActivationMessage messageType={activationMsgType} />}
|
||||
{this.props.forgotPassword.status === 'complete' ? <ConfirmationAlert email={this.props.forgotPassword.email} /> : null}
|
||||
<div className="d-flex flex-row">
|
||||
<p>
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
export const INACTIVE_USER = 'inactive-user';
|
||||
export const INTERNAL_SERVER_ERROR = 'internal-server-error';
|
||||
export const NON_COMPLIANT_PASSWORD_EXCEPTION = 'NonCompliantPasswordException';
|
||||
|
||||
// Account Activation Message
|
||||
export const ACCOUNT_ACTIVATION_MESSAGE = {
|
||||
INFO: 'info',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
@@ -216,6 +216,32 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Sign in with your company or school',
|
||||
description: 'Company or school login link text.',
|
||||
},
|
||||
// Account Activation Strings
|
||||
'authn.account.activation.success.message.title': {
|
||||
id: 'authn.account.activation.success.message.title',
|
||||
defaultMessage: 'Success! You have activated your account.',
|
||||
description: 'Account Activation success message title',
|
||||
},
|
||||
'authn.account.activation.success.message': {
|
||||
id: 'authn.account.activation.success.message',
|
||||
defaultMessage: 'You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign In to continue.',
|
||||
description: 'Message show to learners when their account has been activated successfully',
|
||||
},
|
||||
'authn.account.already.activated.message': {
|
||||
id: 'authn.account.already.activated.message',
|
||||
defaultMessage: 'This account has already been activated.',
|
||||
description: 'Message shown when learner account has already been activated',
|
||||
},
|
||||
'authn.account.activation.error.message.title': {
|
||||
id: 'authn.account.activation.error.message.title',
|
||||
defaultMessage: 'Your account could not be activated',
|
||||
description: 'Account Activation error message title',
|
||||
},
|
||||
'authn.account.activation.support.link': {
|
||||
id: 'authn.account.activation.support.link',
|
||||
defaultMessage: 'contact support',
|
||||
description: 'Link text used in account activation error message to go to learner help center',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
57
src/logistration/tests/AccountActivationMessage.test.jsx
Normal file
57
src/logistration/tests/AccountActivationMessage.test.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import AccountActivationMessage from '../AccountActivationMessage';
|
||||
import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants';
|
||||
|
||||
const IntlAccountActivationMessage = injectIntl(AccountActivationMessage);
|
||||
|
||||
describe('AccountActivationMessage', () => {
|
||||
it('should match account already activated message', () => {
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'This account has already been activated.';
|
||||
expect(accountActivationMessage.find('#account-activation-message').find('div').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match account activated success message', () => {
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'Success! You have activated your account.'
|
||||
+ 'You will now receive email updates and alerts from us related to '
|
||||
+ 'the courses you are enrolled in. Sign In to continue.';
|
||||
expect(accountActivationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match account activation error message', () => {
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'Your account could not be activated'
|
||||
+ 'Something went wrong, please contact support to resolve this issue.';
|
||||
expect(accountActivationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should not display anything for invalid message type', () => {
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType="invalid-message" />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
expect(accountActivationMessage).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -117,6 +117,16 @@ describe('LoginPage', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should show account activation message', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login?account_activation_status=info') };
|
||||
|
||||
const expectedMessage = 'This account has already been activated.';
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#account-activation-message').find('div').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should display login help button', () => {
|
||||
const root = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(root.find('button.field-link').first().text()).toEqual('Need help signing in?');
|
||||
@@ -154,7 +164,7 @@ describe('LoginPage', () => {
|
||||
},
|
||||
});
|
||||
delete window.location;
|
||||
window.location = { href: '' };
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
renderer.create(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(dasboardUrl);
|
||||
});
|
||||
@@ -177,7 +187,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: '' };
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
renderer.create(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
|
||||
});
|
||||
@@ -199,7 +209,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: '' };
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
|
||||
@@ -36,9 +36,10 @@ exports[`LoginFailureMessage should match error message containing link snapshot
|
||||
<li>
|
||||
To be on the safe side, you can reset your password
|
||||
<a
|
||||
className="alert-link"
|
||||
href="/reset"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
here
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user