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:
Zainab Amir
2021-01-22 13:17:03 +05:00
committed by GitHub
parent f50f7c89c0
commit b894e3ce0c
9 changed files with 178 additions and 8 deletions

View File

@@ -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,
});
},
},

View 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);

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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',
};

View File

@@ -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;

View 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({});
});
});

View File

@@ -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} />));

View File

@@ -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>