Add email not verified

Also add a generic error view.
This commit is contained in:
Hannu Lyytikainen 2017-09-13 18:04:53 +03:00
parent 968673c830
commit ae44704a62
6 changed files with 109 additions and 48 deletions

View file

@ -43,7 +43,7 @@ describe('Application', () => {
'/u/1234': 'Profile page with display name: 1234',
'/login': 'Login',
'/signup': 'Sign up',
'/recover-password': 'Request new password',
'/recover-password': 'Request a new password',
'/this-url-should-not-be-found': 'Page not found',
'/reset-password?t=token&e=email': 'Reset password',
};

View file

@ -29,7 +29,12 @@ export default function reducer(state = initialState, action = {}) {
case RECOVERY_SUCCESS:
return { ...state, submittedEmail: payload.email, recoveryInProgress: false };
case RECOVERY_ERROR:
return { ...state, recoveryInProgress: false, recoveryError: payload };
return {
...state,
recoveryInProgress: false,
recoveryError: payload.error,
submittedEmail: payload.email,
};
case RETYPE_EMAIL:
return { ...state, submittedEmail: null, initialEmail: state.submittedEmail };
case CLEAR_RECOVERY_ERROR:
@ -43,9 +48,9 @@ export default function reducer(state = initialState, action = {}) {
export const recoveryRequest = () => ({ type: RECOVERY_REQUEST });
export const recoverySuccess = email => ({ type: RECOVERY_SUCCESS, payload: { email } });
export const recoveryError = error => ({
export const recoveryError = (error, email) => ({
type: RECOVERY_ERROR,
payload: error,
payload: { error, email },
error: true,
});
export const retypeEmail = () => ({ type: RETYPE_EMAIL });
@ -60,5 +65,5 @@ export const recoverPassword = email =>
return sdk.passwordReset
.request({ email })
.then(() => dispatch(recoverySuccess(email)))
.catch(e => dispatch(recoveryError(e)));
.catch(error => dispatch(recoveryError(error, email)));
};

View file

@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import * as propTypes from '../../util/propTypes';
import { sendVerificationEmail } from '../../ducks/user.duck';
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
@ -13,15 +13,6 @@ import { PasswordRecoveryForm } from '../../containers';
import css from './PasswordRecoveryPage.css';
const recoveryMessage = submittedEmail => {
if (submittedEmail) {
const email = <span className={css.submittedEmail}>{submittedEmail}</span>;
return <FormattedMessage id="PasswordRecoveryPage.emailSubmittedMessage" values={{ email }} />;
} else {
return <FormattedMessage id="PasswordRecoveryPage.forgotPasswordMessage" />;
}
};
export const PasswordRecoveryPageComponent = props => {
const {
authInfoError,
@ -45,13 +36,12 @@ export const PasswordRecoveryPageComponent = props => {
onChange,
onSubmitEmail,
onRetypeEmail,
intl,
} = props;
const title = submittedEmail
? <FormattedMessage id="PasswordRecoveryPage.emailSubmittedTitle" />
: <FormattedMessage id="PasswordRecoveryPage.forgotPasswordTitle" />;
const message = recoveryMessage(submittedEmail);
const title = intl.formatMessage({
id: 'PasswordRecoveryPage.title',
});
const resendEmailLink = (
<InlineTextButton className={css.bottomLink} onClick={() => onSubmitEmail(submittedEmail)}>
@ -65,23 +55,88 @@ export const PasswordRecoveryPageComponent = props => {
</InlineTextButton>
);
const emailSubmittedLinks = (
<div className={css.bottomWrapper}>
<p className={css.bottomText}>
<FormattedMessage id="PasswordRecoveryPage.resendEmailInfo" values={{ resendEmailLink }} />
const submitEmailContent = (
<div className={css.content}>
<KeysIcon />
<h1 className={css.title}>
<FormattedMessage id="PasswordRecoveryPage.forgotPasswordTitle" />
</h1>
<p><FormattedMessage id="PasswordRecoveryPage.forgotPasswordMessage" /></p>
<PasswordRecoveryForm
onChange={onChange}
onSubmit={values => onSubmitEmail(values.email)}
initialValues={{ email: initialEmail }}
recoveryError={recoveryError}
/>
</div>
);
const email = <span className={css.submittedEmail}>{submittedEmail}</span>;
const emailSubmittedContent = (
<div className={css.content}>
<KeysIcon />
<h1 className={css.title}>
<FormattedMessage id="PasswordRecoveryPage.emailSubmittedTitle" />
</h1>
<p>
<FormattedMessage id="PasswordRecoveryPage.emailSubmittedMessage" values={{ email }} />
</p>
<p className={css.bottomText}>
<FormattedMessage id="PasswordRecoveryPage.fixEmailInfo" values={{ fixEmailLink }} />
<div className={css.bottomWrapper}>
<p className={css.bottomText}>
<FormattedMessage
id="PasswordRecoveryPage.resendEmailInfo"
values={{ resendEmailLink }}
/>
</p>
<p className={css.bottomText}>
<FormattedMessage id="PasswordRecoveryPage.fixEmailInfo" values={{ fixEmailLink }} />
</p>
</div>
</div>
);
const emailNotVerifiedContent = (
<div className={css.content}>
<KeysIcon />
<h1 className={css.title}>
<FormattedMessage id="PasswordRecoveryPage.emailNotVerifiedTitle" />
</h1>
<p>
<FormattedMessage id="PasswordRecoveryPage.emailNotVerifiedMessage" values={{ email }} />
</p>
<p>
<FormattedMessage id="PasswordRecoveryPage.emailNotVerifiedContactAdmin" />
</p>
</div>
);
const genericErrorContent = (
<div className={css.content}>
<KeysIcon />
<h1 className={css.title}>
<FormattedMessage id="PasswordRecoveryPage.actionFailedTitle" />
</h1>
<p>
<FormattedMessage id="PasswordRecoveryPage.actionFailedMessage" />
</p>
</div>
);
let content;
if (recoveryError && recoveryError.status === 409) {
content = emailNotVerifiedContent;
} else if (recoveryError && recoveryError.status === 404) {
content = submitEmailContent;
} else if (recoveryError) {
content = genericErrorContent;
} else if (submittedEmail) {
content = emailSubmittedContent;
} else {
content = submitEmailContent;
}
return (
<PageLayout
authInfoError={authInfoError}
logoutError={logoutError}
title="Request new password"
>
<PageLayout authInfoError={authInfoError} logoutError={logoutError} title={title}>
<Topbar
authInProgress={authInProgress}
currentUser={currentUser}
@ -98,19 +153,7 @@ export const PasswordRecoveryPageComponent = props => {
sendVerificationEmailError={sendVerificationEmailError}
/>
<div className={css.root}>
<div className={css.content}>
<KeysIcon />
<h1 className={css.title}>{title}</h1>
<p>{message}</p>
{submittedEmail
? emailSubmittedLinks
: <PasswordRecoveryForm
onChange={onChange}
onSubmit={values => onSubmitEmail(values.email)}
initialValues={{ email: initialEmail }}
recoveryError={recoveryError}
/>}
</div>
{content}
</div>
</PageLayout>
@ -152,6 +195,9 @@ PasswordRecoveryPageComponent.propTypes = {
onSubmitEmail: func.isRequired,
onRetypeEmail: func.isRequired,
// from injectIntl
intl: intlShape.isRequired,
// from withRouter
history: shape({
push: func.isRequired,
@ -207,8 +253,10 @@ const mapDispatchToProps = dispatch => ({
onRetypeEmail: () => dispatch(retypeEmail()),
});
const PasswordRecoveryPage = compose(connect(mapStateToProps, mapDispatchToProps), withRouter)(
PasswordRecoveryPageComponent
);
const PasswordRecoveryPage = compose(
connect(mapStateToProps, mapDispatchToProps),
withRouter,
injectIntl
)(PasswordRecoveryPageComponent);
export default PasswordRecoveryPage;

View file

@ -1,5 +1,6 @@
import React from 'react';
import { renderShallow } from '../../util/test-helpers';
import { fakeIntl } from '../../util/test-data';
import { PasswordRecoveryPageComponent } from './PasswordRecoveryPage';
const noop = () => null;
@ -23,6 +24,7 @@ describe('ContactDetailsPage', () => {
onChange={noop}
onSubmitEmail={noop}
onRetypeEmail={noop}
intl={fakeIntl}
/>
);
expect(tree).toMatchSnapshot();

View file

@ -2,7 +2,7 @@ exports[`ContactDetailsPage matches snapshot 1`] = `
<withRouter(PageLayout)
authInfoError={null}
logoutError={null}
title="Request new password">
title="PasswordRecoveryPage.title">
<Topbar
authInProgress={false}
currentUser={null}

View file

@ -232,6 +232,11 @@
"PasswordRecoveryForm.loginLinkText": "Go log in.",
"PasswordRecoveryForm.loginLinkInfo": "Suddenly remembered your password? {loginLink}",
"PasswordRecoveryForm.sendInstructions": "Send instructions",
"PasswordRecoveryPage.actionFailedTitle": "Whoops!",
"PasswordRecoveryPage.actionFailedMessage": "Something went wrong. Please refresh the page and try again.",
"PasswordRecoveryPage.emailNotVerifiedTitle": "We have bad news",
"PasswordRecoveryPage.emailNotVerifiedMessage": "Unfortunately, we cannot recover your password, because your email {email} hasn't been verified.",
"PasswordRecoveryPage.emailNotVerifiedContactAdmin": "To resolve the issue, please contact Saunatime administrators.",
"PasswordRecoveryPage.emailSubmittedTitle": "Check your inbox",
"PasswordRecoveryPage.emailSubmittedMessage": "We have sent the instructions for resetting your password to {email}.",
"PasswordRecoveryPage.fixEmailLinkText": "Fix it.",
@ -240,6 +245,7 @@
"PasswordRecoveryPage.forgotPasswordMessage": "No worries! Please enter the email address you used when signing up and we'll send you instructions on how to create a new password.",
"PasswordRecoveryPage.resendEmailLinkText": "Send another email.",
"PasswordRecoveryPage.resendEmailInfo": "Didn't get the email? {resendEmailLink}",
"PasswordRecoveryPage.title": "Request a new password",
"PayoutDetailsForm.addressTitle": "Address",
"PayoutDetailsForm.bankDetails": "Bank details",
"PayoutDetailsForm.birthdayDatePlaceholder": "dd",