Refactor PasswordChangeForm: use Form connector from Final Form

This commit is contained in:
Vesa Luusua 2018-04-16 21:20:26 +03:00
parent 09dea3bdd1
commit c415babb83
3 changed files with 200 additions and 159 deletions

View file

@ -2,13 +2,13 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
import { Form as FinalForm } from 'react-final-form';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import * as validators from '../../util/validators';
import { ensureCurrentUser } from '../../util/data';
import { isChangePasswordWrongPassword } from '../../util/errors';
import { Form, PrimaryButton, TextInputField } from '../../components';
import { Form, PrimaryButton, FieldTextInput } from '../../components';
import css from './PasswordChangeForm.css';
@ -23,158 +23,173 @@ class PasswordChangeFormComponent extends Component {
window.clearTimeout(this.resetTimeoutId);
}
render() {
const {
rootClassName,
className,
changePasswordError,
currentUser,
form,
handleSubmit,
submitting,
inProgress,
intl,
invalid,
pristine,
ready,
reset,
} = this.props;
const user = ensureCurrentUser(currentUser);
if (!user.id) {
return null;
}
// New password
const newPasswordLabel = intl.formatMessage({
id: 'PasswordChangeForm.newPasswordLabel',
});
const newPasswordPlaceholder = intl.formatMessage({
id: 'PasswordChangeForm.newPasswordPlaceholder',
});
const newPasswordRequiredMessage = intl.formatMessage({
id: 'PasswordChangeForm.newPasswordRequired',
});
const newPasswordRequired = validators.requiredStringNoTrim(newPasswordRequiredMessage);
const passwordMinLengthMessage = intl.formatMessage(
{
id: 'PasswordChangeForm.passwordTooShort',
},
{
minLength: validators.PASSWORD_MIN_LENGTH,
}
);
const passwordMaxLengthMessage = intl.formatMessage(
{
id: 'PasswordChangeForm.passwordTooLong',
},
{
maxLength: validators.PASSWORD_MAX_LENGTH,
}
);
const passwordMinLength = validators.minLength(
passwordMinLengthMessage,
validators.PASSWORD_MIN_LENGTH
);
const passwordMaxLength = validators.maxLength(
passwordMaxLengthMessage,
validators.PASSWORD_MAX_LENGTH
);
// password
const passwordLabel = intl.formatMessage({
id: 'PasswordChangeForm.passwordLabel',
});
const passwordPlaceholder = intl.formatMessage({
id: 'PasswordChangeForm.passwordPlaceholder',
});
const passwordRequiredMessage = intl.formatMessage({
id: 'PasswordChangeForm.passwordRequired',
});
const passwordRequired = validators.requiredStringNoTrim(passwordRequiredMessage);
const passwordFailedMessage = intl.formatMessage({
id: 'PasswordChangeForm.passwordFailed',
});
const passwordErrorText = isChangePasswordWrongPassword(changePasswordError)
? passwordFailedMessage
: null;
const confirmClasses = classNames(css.confirmChangesSection, {
[css.confirmChangesSectionVisible]: !pristine,
});
const genericFailure =
changePasswordError && !passwordErrorText ? (
<span className={css.error}>
<FormattedMessage id="PasswordChangeForm.genericFailure" />
</span>
) : null;
const classes = classNames(rootClassName || css.root, className);
const submitDisabled = invalid || submitting || inProgress;
return (
<Form
className={classes}
onSubmit={values => {
handleSubmit(values)
.then(() => {
this.resetTimeoutId = window.setTimeout(reset, RESET_TIMEOUT);
})
.catch(() => {
// Error is handled in duck file already.
});
<FinalForm
{...this.props}
render={fieldRenderProps => {
const {
rootClassName,
className,
formId,
changePasswordError,
currentUser,
handleSubmit,
submitting,
inProgress,
intl,
invalid,
pristine,
ready,
reset,
} = fieldRenderProps;
const user = ensureCurrentUser(currentUser);
if (!user.id) {
return null;
}
// New password
const newPasswordLabel = intl.formatMessage({
id: 'PasswordChangeForm.newPasswordLabel',
});
const newPasswordPlaceholder = intl.formatMessage({
id: 'PasswordChangeForm.newPasswordPlaceholder',
});
const newPasswordRequiredMessage = intl.formatMessage({
id: 'PasswordChangeForm.newPasswordRequired',
});
const newPasswordRequired = validators.requiredStringNoTrim(newPasswordRequiredMessage);
const passwordMinLengthMessage = intl.formatMessage(
{
id: 'PasswordChangeForm.passwordTooShort',
},
{
minLength: validators.PASSWORD_MIN_LENGTH,
}
);
const passwordMaxLengthMessage = intl.formatMessage(
{
id: 'PasswordChangeForm.passwordTooLong',
},
{
maxLength: validators.PASSWORD_MAX_LENGTH,
}
);
const passwordMinLength = validators.minLength(
passwordMinLengthMessage,
validators.PASSWORD_MIN_LENGTH
);
const passwordMaxLength = validators.maxLength(
passwordMaxLengthMessage,
validators.PASSWORD_MAX_LENGTH
);
// password
const passwordLabel = intl.formatMessage({
id: 'PasswordChangeForm.passwordLabel',
});
const passwordPlaceholder = intl.formatMessage({
id: 'PasswordChangeForm.passwordPlaceholder',
});
const passwordRequiredMessage = intl.formatMessage({
id: 'PasswordChangeForm.passwordRequired',
});
const passwordRequired = validators.requiredStringNoTrim(passwordRequiredMessage);
const passwordFailedMessage = intl.formatMessage({
id: 'PasswordChangeForm.passwordFailed',
});
const passwordErrorText = isChangePasswordWrongPassword(changePasswordError)
? passwordFailedMessage
: null;
const confirmClasses = classNames(css.confirmChangesSection, {
[css.confirmChangesSectionVisible]: !pristine,
});
const genericFailure =
changePasswordError && !passwordErrorText ? (
<span className={css.error}>
<FormattedMessage id="PasswordChangeForm.genericFailure" />
</span>
) : null;
const classes = classNames(rootClassName || css.root, className);
const submitDisabled = invalid || submitting || inProgress;
return (
<Form
className={classes}
onSubmit={values => {
handleSubmit(values)
.then(() => {
this.resetTimeoutId = window.setTimeout(reset, RESET_TIMEOUT);
})
.catch(() => {
// Error is handled in duck file already.
});
}}
>
<div className={css.newPasswordSection}>
<FieldTextInput
type="password"
id={formId ? `${formId}.newPassword` : 'newPassword'}
name="newPassword"
autoComplete="new-password"
label={newPasswordLabel}
placeholder={newPasswordPlaceholder}
validate={validators.composeValidators(
newPasswordRequired,
passwordMinLength,
passwordMaxLength
)}
/>
</div>
<div className={confirmClasses}>
<h3 className={css.confirmChangesTitle}>
<FormattedMessage id="PasswordChangeForm.confirmChangesTitle" />
</h3>
<p className={css.confirmChangesInfo}>
<FormattedMessage id="PasswordChangeForm.confirmChangesInfo" />
</p>
<FieldTextInput
className={css.password}
type="password"
id="currentPassword"
name="currentPassword"
autoComplete="current-password"
label={passwordLabel}
placeholder={passwordPlaceholder}
validate={validators.composeValidators(
passwordRequired,
passwordMinLength,
passwordMaxLength
)}
customErrorText={passwordErrorText}
/>
</div>
<div className={css.bottomWrapper}>
{genericFailure}
<PrimaryButton
className={css.submitButton}
type="submit"
inProgress={inProgress}
ready={ready}
disabled={submitDisabled}
>
<FormattedMessage id="PasswordChangeForm.saveChanges" />
</PrimaryButton>
</div>
</Form>
);
}}
>
<div className={css.newPasswordSection}>
<TextInputField
type="password"
name="newPassword"
autoComplete="new-password"
id={`${form}.newPassword`}
label={newPasswordLabel}
placeholder={newPasswordPlaceholder}
validate={[newPasswordRequired, passwordMinLength, passwordMaxLength]}
/>
</div>
<div className={confirmClasses}>
<h3 className={css.confirmChangesTitle}>
<FormattedMessage id="PasswordChangeForm.confirmChangesTitle" />
</h3>
<p className={css.confirmChangesInfo}>
<FormattedMessage id="PasswordChangeForm.confirmChangesInfo" />
</p>
<TextInputField
className={css.password}
type="password"
name="currentPassword"
autoComplete="current-password"
id={`${form}.currentPassword`}
label={passwordLabel}
placeholder={passwordPlaceholder}
validate={[passwordRequired, passwordMinLength, passwordMaxLength]}
customErrorText={passwordErrorText}
/>
</div>
<div className={css.bottomWrapper}>
{genericFailure}
<PrimaryButton
className={css.submitButton}
type="submit"
inProgress={inProgress}
ready={ready}
disabled={submitDisabled}
>
<FormattedMessage id="PasswordChangeForm.saveChanges" />
</PrimaryButton>
</div>
</Form>
/>
);
}
}
@ -184,24 +199,22 @@ PasswordChangeFormComponent.defaultProps = {
className: null,
changePasswordError: null,
inProgress: false,
formId: null,
};
const { bool, string } = PropTypes;
PasswordChangeFormComponent.propTypes = {
...formPropTypes,
rootClassName: string,
className: string,
changePasswordError: propTypes.error,
inProgress: bool,
intl: intlShape.isRequired,
ready: bool.isRequired,
formId: string,
};
const defaultFormName = 'PasswordChangeForm';
const PasswordChangeForm = compose(reduxForm({ form: defaultFormName }), injectIntl)(
PasswordChangeFormComponent
);
const PasswordChangeForm = compose(injectIntl)(PasswordChangeFormComponent);
PasswordChangeForm.displayName = 'PasswordChangeForm';
export default PasswordChangeForm;

View file

@ -1,6 +1,6 @@
import React from 'react';
import { renderShallow } from '../../util/test-helpers';
import { fakeIntl } from '../../util/test-data';
import { fakeIntl, createCurrentUser } from '../../util/test-data';
import { PasswordChangePageComponent } from './PasswordChangePage';
const noop = () => null;
@ -14,6 +14,7 @@ describe('PasswordChangePage', () => {
location={{ search: '' }}
scrollingDisabled={false}
authInProgress={false}
currentUser={createCurrentUser('user1')}
currentUserHasListings={false}
isAuthenticated={false}
onChange={noop}

View file

@ -72,6 +72,33 @@ exports[`PasswordChangePage matches snapshot 1`] = `
values={Object {}}
/>
</h1>
<PasswordChangeForm
changePasswordError={null}
currentUser={
Object {
"attributes": Object {
"banned": false,
"email": "user1@example.com",
"emailVerified": true,
"profile": Object {
"abbreviatedName": "user1 abbreviated name",
"displayName": "user1 display name",
"firstName": "user1 first name",
"lastName": "user1 last name",
},
"stripeConnected": true,
},
"id": UUID {
"uuid": "user1",
},
"type": "currentUser",
}
}
inProgress={false}
onChange={[Function]}
onSubmit={[Function]}
ready={false}
/>
</div>
</LayoutWrapperMain>
<LayoutWrapperFooter