mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-27 23:40:28 +05:30
Form validation errors integration for ChangePassword
This commit is contained in:
parent
5adfc60783
commit
82416f788f
@ -184,8 +184,8 @@ class PanelTransition extends Component {
|
||||
this.body.onFormSubmit();
|
||||
};
|
||||
|
||||
onFormInvalid = (errorMessage) => {
|
||||
this.props.setError(errorMessage);
|
||||
onFormInvalid = (errors) => {
|
||||
this.props.setError(Object.values(errors).shift());
|
||||
};
|
||||
|
||||
willEnter = (config) => this.getTransitionStyles(config);
|
||||
|
@ -14,16 +14,23 @@ export default class ChangePassword extends Component {
|
||||
static displayName = 'ChangePassword';
|
||||
|
||||
static propTypes = {
|
||||
form: PropTypes.instanceOf(FormModel).isRequired,
|
||||
onSubmit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
form = new FormModel();
|
||||
static get defaultProps() {
|
||||
return {
|
||||
form: new FormModel()
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {form} = this;
|
||||
const {form} = this.props;
|
||||
|
||||
return (
|
||||
<Form onSubmit={this.onFormSubmit}>
|
||||
<Form onSubmit={this.onFormSubmit}
|
||||
form={form}
|
||||
>
|
||||
<div className={styles.contentWithBackButton}>
|
||||
<Link className={styles.backButton} to="/" />
|
||||
|
||||
@ -89,6 +96,6 @@ export default class ChangePassword extends Component {
|
||||
}
|
||||
|
||||
onFormSubmit = () => {
|
||||
this.props.onSubmit(this.form.serialize());
|
||||
this.props.onSubmit(this.props.form);
|
||||
};
|
||||
}
|
||||
|
@ -11,19 +11,22 @@ export default class PasswordRequestForm extends Component {
|
||||
static displayName = 'PasswordRequestForm';
|
||||
|
||||
static propTypes = {
|
||||
form: PropTypes.instanceOf(FormModel).isRequired,
|
||||
onSubmit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
form = new FormModel();
|
||||
|
||||
render() {
|
||||
const {form} = this.props;
|
||||
|
||||
return (
|
||||
<Form onSubmit={this.onSubmit}>
|
||||
<Form onSubmit={this.onSubmit}
|
||||
form={form}
|
||||
>
|
||||
<h2>
|
||||
<Message {...messages.title} />
|
||||
</h2>
|
||||
|
||||
<Input {...this.form.bindField('password')}
|
||||
<Input {...form.bindField('password')}
|
||||
type="password"
|
||||
required
|
||||
autoFocus
|
||||
@ -38,6 +41,6 @@ export default class PasswordRequestForm extends Component {
|
||||
}
|
||||
|
||||
onSubmit = () => {
|
||||
this.props.onSubmit(this.form.value('password'));
|
||||
this.props.onSubmit(this.props.form);
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { intlShape } from 'react-intl';
|
||||
|
||||
import buttons from 'components/ui/buttons.scss';
|
||||
|
||||
export default class Button extends Component {
|
||||
import FormComponent from './FormComponent';
|
||||
|
||||
export default class Button extends FormComponent {
|
||||
static displayName = 'Button';
|
||||
|
||||
static propTypes = {
|
||||
@ -19,10 +20,6 @@ export default class Button extends Component {
|
||||
color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue'])
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { color = 'green', block } = this.props;
|
||||
|
||||
@ -30,9 +27,7 @@ export default class Button extends Component {
|
||||
...this.props
|
||||
};
|
||||
|
||||
if (props.label.id) {
|
||||
props.label = this.context.intl.formatMessage(props.label);
|
||||
}
|
||||
props.label = this.formatMessage(props.label);
|
||||
|
||||
return (
|
||||
<button className={classNames(buttons[color], {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { intlShape } from 'react-intl';
|
||||
|
||||
import styles from './form.scss';
|
||||
import FormInputComponent from './FormInputComponent';
|
||||
|
||||
export default class Checkbox extends Component {
|
||||
export default class Checkbox extends FormInputComponent {
|
||||
static displayName = 'Checkbox';
|
||||
|
||||
static propTypes = {
|
||||
@ -19,16 +19,10 @@ export default class Checkbox extends Component {
|
||||
]).isRequired
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
let { label, color = 'green', skin = 'dark' } = this.props;
|
||||
|
||||
if (label && label.id) {
|
||||
label = this.context.intl.formatMessage(label);
|
||||
}
|
||||
label = this.formatMessage(label);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles[`${color}CheckboxRow`], styles[`${skin}CheckboxRow`])}>
|
||||
@ -37,14 +31,11 @@ export default class Checkbox extends Component {
|
||||
<div className={styles.checkbox} />
|
||||
{label}
|
||||
</label>
|
||||
{this.renderError()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
setEl = (el) => {
|
||||
this.el = el;
|
||||
};
|
||||
|
||||
getValue() {
|
||||
return this.el.checked ? 1 : 0;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import FormModel from 'models/Form';
|
||||
|
||||
import styles from './form.scss';
|
||||
|
||||
export default class Form extends Component {
|
||||
@ -10,6 +12,7 @@ export default class Form extends Component {
|
||||
static propTypes = {
|
||||
id: PropTypes.string, // and id, that uniquely identifies form contents
|
||||
isLoading: PropTypes.bool,
|
||||
form: PropTypes.instanceOf(FormModel),
|
||||
onSubmit: PropTypes.func,
|
||||
onInvalid: PropTypes.func,
|
||||
children: PropTypes.oneOfType([
|
||||
@ -71,17 +74,30 @@ export default class Form extends Component {
|
||||
if (form.checkValidity()) {
|
||||
this.props.onSubmit();
|
||||
} else {
|
||||
const firstError = form.querySelectorAll(':invalid')[0];
|
||||
firstError.focus();
|
||||
const invalidEls = form.querySelectorAll(':invalid');
|
||||
const errors = {};
|
||||
invalidEls[0].focus(); // focus on first error
|
||||
|
||||
let errorMessage = firstError.validationMessage;
|
||||
if (firstError.validity.valueMissing) {
|
||||
errorMessage = `error.${firstError.name}_required`;
|
||||
} else if (firstError.validity.typeMismatch) {
|
||||
errorMessage = `error.${firstError.name}_invalid`;
|
||||
}
|
||||
Array.from(invalidEls).reduce((errors, el) => {
|
||||
if (!el.name) {
|
||||
console.warn('Found an element without name', el);
|
||||
return errors;
|
||||
}
|
||||
|
||||
this.props.onInvalid(errorMessage);
|
||||
let errorMessage = el.validationMessage;
|
||||
if (el.validity.valueMissing) {
|
||||
errorMessage = `error.${el.name}_required`;
|
||||
} else if (el.validity.typeMismatch) {
|
||||
errorMessage = `error.${el.name}_invalid`;
|
||||
}
|
||||
|
||||
errors[el.name] = errorMessage;
|
||||
|
||||
return errors;
|
||||
}, errors);
|
||||
|
||||
this.props.form && this.props.form.setErrors(errors);
|
||||
this.props.onInvalid(errors);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
19
src/components/ui/form/FormComponent.jsx
Normal file
19
src/components/ui/form/FormComponent.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { intlShape } from 'react-intl';
|
||||
|
||||
export default class FormComponent extends Component {
|
||||
static displayName = 'FormComponent';
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
formatMessage(message) {
|
||||
if (message && message.id && this.context && this.context.intl) {
|
||||
message = this.context.intl.formatMessage(message);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
44
src/components/ui/form/FormInputComponent.jsx
Normal file
44
src/components/ui/form/FormInputComponent.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
import errorsDict from 'services/errorsDict';
|
||||
|
||||
import styles from './form.scss';
|
||||
import FormComponent from './FormComponent';
|
||||
|
||||
export default class FormInputComponent extends FormComponent {
|
||||
static displayName = 'FormInputComponent';
|
||||
|
||||
static propTypes = {
|
||||
error: PropTypes.string
|
||||
};
|
||||
|
||||
componentWillReceiveProps() {
|
||||
if (this.state && this.state.error) {
|
||||
Reflect.deleteProperty(this.state, 'error');
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
setEl = (el) => {
|
||||
this.el = el;
|
||||
};
|
||||
|
||||
renderError() {
|
||||
const error = this.state && this.state.error || this.props.error;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={styles.fieldError}>
|
||||
{errorsDict.resolve(error)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
setError(error) {
|
||||
this.setState({error});
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { intlShape } from 'react-intl';
|
||||
|
||||
import { uniqueId } from 'functions';
|
||||
import icons from 'components/ui/icons.scss';
|
||||
|
||||
import styles from './form.scss';
|
||||
import FormInputComponent from './FormInputComponent';
|
||||
|
||||
export default class Input extends Component {
|
||||
export default class Input extends FormInputComponent {
|
||||
static displayName = 'Input';
|
||||
|
||||
static propTypes = {
|
||||
@ -23,19 +23,15 @@ export default class Input extends Component {
|
||||
id: PropTypes.string
|
||||
}),
|
||||
PropTypes.string
|
||||
]).isRequired,
|
||||
]),
|
||||
error: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
skin: PropTypes.oneOf(['dark', 'light']),
|
||||
color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue'])
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
let { icon, color = 'green', skin = 'dark', error, label } = this.props;
|
||||
let { icon, color = 'green', skin = 'dark', label } = this.props;
|
||||
|
||||
const props = {
|
||||
type: 'text',
|
||||
@ -47,9 +43,7 @@ export default class Input extends Component {
|
||||
props.id = uniqueId('input');
|
||||
}
|
||||
|
||||
if (label.id) {
|
||||
label = this.context.intl.formatMessage(label);
|
||||
}
|
||||
label = this.formatMessage(label);
|
||||
|
||||
label = (
|
||||
<label className={styles.textFieldLabel} htmlFor={props.id}>
|
||||
@ -58,9 +52,7 @@ export default class Input extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (props.placeholder && props.placeholder.id) {
|
||||
props.placeholder = this.context.intl.formatMessage(props.placeholder);
|
||||
}
|
||||
props.placeholder = this.formatMessage(props.placeholder);
|
||||
|
||||
let baseClass = styles.formRow;
|
||||
if (icon) {
|
||||
@ -70,33 +62,24 @@ export default class Input extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
error = (
|
||||
<div className={styles.fieldError}>
|
||||
error
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{label}
|
||||
<div className={styles.textFieldContainer}>
|
||||
<input ref={this.setEl} className={classNames(
|
||||
styles[`${skin}TextField`],
|
||||
styles[`${color}TextField`]
|
||||
)} {...props} />
|
||||
<input ref={this.setEl}
|
||||
className={classNames(
|
||||
styles[`${skin}TextField`],
|
||||
styles[`${color}TextField`]
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{icon}
|
||||
</div>
|
||||
{error}
|
||||
{this.renderError()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
setEl = (el) => {
|
||||
this.el = el;
|
||||
};
|
||||
|
||||
getValue() {
|
||||
return this.el.value;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import FormInputComponent from 'components/ui/form/FormInputComponent';
|
||||
|
||||
export default class Form {
|
||||
fields = {};
|
||||
errors = {};
|
||||
|
||||
/**
|
||||
* Connects form with React's component
|
||||
@ -12,13 +15,22 @@ export default class Form {
|
||||
* @return {Object} ref and name props for component
|
||||
*/
|
||||
bindField(name) {
|
||||
return {
|
||||
const props = {
|
||||
name,
|
||||
ref: (el) => {
|
||||
// TODO: validate React component
|
||||
if (!(el instanceof FormInputComponent)) {
|
||||
throw new Error('Expected a component from components/ui/form module');
|
||||
}
|
||||
|
||||
this.fields[name] = el;
|
||||
}
|
||||
};
|
||||
|
||||
if (this.getError(name)) {
|
||||
props.error = this.getError(name);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
focus(fieldId) {
|
||||
@ -37,9 +49,24 @@ export default class Form {
|
||||
return this.fields[fieldId].getValue();
|
||||
}
|
||||
|
||||
setErrors(errors) {
|
||||
const oldErrors = this.errors;
|
||||
this.errors = errors;
|
||||
|
||||
Object.keys(this.fields).forEach((fieldId) => {
|
||||
if (oldErrors[fieldId] || errors[fieldId]) {
|
||||
this.fields[fieldId].setError(errors[fieldId] || null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getError(fieldId) {
|
||||
return this.errors[fieldId] || null;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return Object.keys(this.fields).reduce((acc, key) => {
|
||||
acc[key] = this.fields[key].getValue();
|
||||
return Object.keys(this.fields).reduce((acc, fieldId) => {
|
||||
acc[fieldId] = this.fields[fieldId].getValue();
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import accounts from 'services/api/accounts';
|
||||
import FormModel from 'models/Form';
|
||||
import ChangePassword from 'components/profile/changePassword/ChangePassword';
|
||||
import PasswordRequestForm from 'components/profile/passwordRequestForm/PasswordRequestForm';
|
||||
|
||||
@ -11,14 +12,16 @@ class ChangePasswordPage extends Component {
|
||||
changePassword: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
form = new FormModel();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ChangePassword onSubmit={this.onSubmit} />
|
||||
<ChangePassword onSubmit={this.onSubmit} form={this.form} />
|
||||
);
|
||||
}
|
||||
|
||||
onSubmit = (data) => {
|
||||
this.props.changePassword(data);
|
||||
onSubmit = () => {
|
||||
this.props.changePassword(this.form);
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,16 +35,23 @@ function goToProfile() {
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
changePassword: (data) => {
|
||||
changePassword: (form) => {
|
||||
return (dispatch) => {
|
||||
// TODO: судя по всему registerPopup было явно лишним. Надо еще раз
|
||||
// обдумать API и переписать
|
||||
dispatch(registerPopup('requestPassword', PasswordRequestForm));
|
||||
dispatch(createPopup('requestPassword', (props) => {
|
||||
return {
|
||||
onSubmit: (password) => {
|
||||
form,
|
||||
onSubmit: () => {
|
||||
// TODO: hide this logic in action
|
||||
accounts.changePassword({
|
||||
...data,
|
||||
password
|
||||
accounts.changePassword(form.serialize())
|
||||
.catch((resp) => {
|
||||
if (resp.errors) {
|
||||
form.setErrors(resp.errors);
|
||||
}
|
||||
|
||||
return Promise.reject(resp);
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(updateUser({
|
||||
|
@ -8,13 +8,14 @@ export default {
|
||||
resolve(error) {
|
||||
return errorsMap[error] ? errorsMap[error]() : error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errorsMap = {
|
||||
'error.login_required': () => <Message {...messages.loginRequired} />,
|
||||
'error.login_not_exist': () => <Message {...messages.loginNotExist} />,
|
||||
'error.password_required': () => <Message {...messages.passwordRequired} />,
|
||||
|
||||
'error.password_invalid': () => <Message {...messages.invalidPassword} />,
|
||||
'error.password_incorrect': () => (
|
||||
<span>
|
||||
<Message {...messages.invalidPassword} />
|
||||
|
@ -3,7 +3,7 @@ import { defineMessages } from 'react-intl';
|
||||
export default defineMessages({
|
||||
invalidPassword: {
|
||||
id: 'invalidPassword',
|
||||
defaultMessage: 'You entered wrong account password.'
|
||||
defaultMessage: 'You have entered wrong account password.'
|
||||
},
|
||||
|
||||
suggestResetPassword: {
|
||||
|
Loading…
Reference in New Issue
Block a user