Add email not found handling

This commit is contained in:
Hannu Lyytikainen 2017-09-13 14:54:42 +03:00
parent 82fe1ce490
commit 13f4d81648
7 changed files with 40 additions and 6 deletions

View file

@ -17,6 +17,7 @@ class TextInputFieldComponent extends Component {
rootClassName,
className,
clearOnUnmount,
customErrorText,
id,
label,
type,
@ -33,9 +34,13 @@ class TextInputFieldComponent extends Component {
const { valid, invalid, touched, error } = meta;
const isTextarea = type === 'textarea';
const errorText = customErrorText || error;
// Error message and input error styles are only shown if the
// field has been touched and the validation has failed.
const hasError = touched && invalid && error;
const hasError = touched && invalid && errorText;
const fieldMeta = { touched, error: errorText };
const inputClasses = classNames(css.input, {
[css.inputSuccess]: valid,
@ -50,7 +55,7 @@ class TextInputFieldComponent extends Component {
<div className={classes}>
{label ? <label htmlFor={id}>{label}</label> : null}
{isTextarea ? <ExpandingTextarea {...inputProps} /> : <input {...inputProps} />}
<ValidationError fieldMeta={meta} />
<ValidationError fieldMeta={fieldMeta} />
</div>
);
}
@ -60,6 +65,7 @@ TextInputFieldComponent.defaultProps = {
rootClassName: null,
className: null,
clearOnUnmount: false,
customErrorText: null,
id: null,
label: null,
};
@ -72,6 +78,10 @@ TextInputFieldComponent.propTypes = {
clearOnUnmount: bool,
// Error message that can be manually passed to input field,
// overrides default validation message
customErrorText: string,
// Label is optional, but if it is given, an id is also required so
// the label can reference the input in the `for` attribute
id: string,

View file

@ -8,6 +8,8 @@ import * as validators from '../../util/validators';
import css from './PasswordRecoveryForm.css';
const isNotFoundError = (error) => error && error.status === 404;
const PasswordRecoveryFormComponent = props => {
const {
rootClassName,
@ -18,6 +20,7 @@ const PasswordRecoveryFormComponent = props => {
form,
initialValues,
intl,
recoveryError,
} = props;
// email
@ -30,8 +33,12 @@ const PasswordRecoveryFormComponent = props => {
const emailRequiredMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailRequired',
});
const emailRequired = validators.required(emailRequiredMessage);
const emailNotFoundMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailNotFound',
});
const emailRequired = validators.required(emailRequiredMessage);
const customErrorText = isNotFoundError(recoveryError) ? emailNotFoundMessage : null;
const initialEmail = initialValues ? initialValues.email : null;
const buttonDisabled = (pristine && !initialEmail) || submitting;
const classes = classNames(rootClassName || css.root, className);
@ -41,6 +48,7 @@ const PasswordRecoveryFormComponent = props => {
<FormattedMessage id="PasswordRecoveryForm.goToLoginHelp" />
</span>
);
return (
<form className={classes} onSubmit={handleSubmit}>
<TextInputField
@ -51,6 +59,7 @@ const PasswordRecoveryFormComponent = props => {
label={emailLabel}
placeholder={emailPlaceholder}
validate={emailRequired}
customErrorText={customErrorText}
/>
<p className={css.bottomWrapper}>
<NamedLink name="LoginPage" className={css.goToLoginLink}>
@ -67,14 +76,16 @@ const PasswordRecoveryFormComponent = props => {
PasswordRecoveryFormComponent.defaultProps = {
rootClassName: null,
className: null,
recoveryError: null,
};
const { string } = PropTypes;
const { instanceOf, string } = PropTypes;
PasswordRecoveryFormComponent.propTypes = {
...formPropTypes,
rootClassName: string,
className: string,
recoveryError: instanceOf(Error),
intl: intlShape.isRequired,
};

View file

@ -36,7 +36,7 @@
}
.title {
@apply --marketplaceH1FontStyles;
@apply --marketplaceModalTitle;
}
.submittedEmail {

View file

@ -4,6 +4,7 @@ export const RECOVERY_REQUEST = 'app/PasswordRecoveryPage/RECOVERY_REQUEST';
export const RECOVERY_SUCCESS = 'app/PasswordRecoveryPage/RECOVERY_SUCCESS';
export const RECOVERY_ERROR = 'app/PasswordRecoveryPage/RECOVERY_ERROR';
export const RETYPE_EMAIL = 'app/PasswordRecoveryPage/RETYPE_EMAIL';
export const CLEAR_RECOVERY_ERROR = 'app/PasswordRecoveryPage/CLEAR_RECOVERY_ERROR';
// ================ Reducer ================ //
@ -31,6 +32,8 @@ export default function reducer(state = initialState, action = {}) {
return { ...state, recoveryInProgress: false, recoveryError: payload };
case RETYPE_EMAIL:
return { ...state, submittedEmail: null, initialEmail: state.submittedEmail };
case CLEAR_RECOVERY_ERROR:
return { ...state, recoveryError: null };
default:
return state;
}
@ -46,6 +49,7 @@ export const recoveryError = error => ({
error: true,
});
export const retypeEmail = () => ({ type: RETYPE_EMAIL });
export const clearRecoveryError = () => ({ type: CLEAR_RECOVERY_ERROR });
// ================ Thunks ================ //

View file

@ -7,7 +7,7 @@ import * as propTypes from '../../util/propTypes';
import { sendVerificationEmail } from '../../ducks/user.duck';
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
import { recoverPassword, retypeEmail } from './PasswordRecoveryPage.duck';
import { recoverPassword, retypeEmail, clearRecoveryError } from './PasswordRecoveryPage.duck';
import { PageLayout, Topbar, InlineTextButton, KeysIcon } from '../../components';
import { PasswordRecoveryForm } from '../../containers';
@ -41,6 +41,8 @@ export const PasswordRecoveryPageComponent = props => {
onResendVerificationEmail,
initialEmail,
submittedEmail,
recoveryError,
onChange,
onSubmitEmail,
onRetypeEmail,
} = props;
@ -109,8 +111,10 @@ export const PasswordRecoveryPageComponent = props => {
{submittedEmail
? emailSubmittedLinks
: <PasswordRecoveryForm
onChange={onChange}
onSubmit={values => onSubmitEmail(values.email)}
initialValues={{ email: initialEmail }}
recoveryError={recoveryError}
/>}
</div>
</div>
@ -128,6 +132,7 @@ PasswordRecoveryPageComponent.defaultProps = {
sendVerificationEmailError: null,
initialEmail: null,
submittedEmail: null,
recoveryError: null,
};
const { bool, func, instanceOf, number, object, shape, string } = PropTypes;
@ -148,6 +153,8 @@ PasswordRecoveryPageComponent.propTypes = {
onResendVerificationEmail: func.isRequired,
initialEmail: string,
submittedEmail: string,
recoveryError: instanceOf(Error),
onChange: func.isRequired,
onSubmitEmail: func.isRequired,
onRetypeEmail: func.isRequired,

View file

@ -20,6 +20,7 @@ describe('ContactDetailsPage', () => {
sendVerificationEmailInProgress={false}
onResendVerificationEmail={noop}
recoveryInProgress={false}
onChange={noop}
onSubmitEmail={noop}
onRetypeEmail={noop}
/>

View file

@ -226,6 +226,7 @@
"PasswordResetPage.resetFailed": "Reset failed. Please try again.",
"PasswordResetPage.title": "Reset password",
"PasswordRecoveryForm.emailLabel": "Email",
"PasswordRecoveryForm.emailNotFound": "Hmm. We didn't find an account with that email. Please double-check your email and try again.",
"PasswordRecoveryForm.emailPlaceholder": "john.doe@example.com",
"PasswordRecoveryForm.emailRequired": "This field is required",
"PasswordRecoveryForm.goToLogin": "{goToLoginHelp} Go to log in.",