mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
#191: pass errors through FormModel in AuthPanels
This commit is contained in:
@@ -23,19 +23,14 @@ export default class BaseAuthBody extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
// TODO: we must not access Form#fields. This is a temporary
|
if (nextContext.auth.error !== this.context.auth.error) {
|
||||||
// solution to reset Captcha, when the form does not handle errors
|
this.form.setErrors(nextContext.auth.error || {});
|
||||||
if (nextContext.auth.error
|
|
||||||
&& this.form.fields.captcha
|
|
||||||
&& nextContext.auth.error !== this.context.auth.error
|
|
||||||
) {
|
|
||||||
this.form.fields.captcha.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderErrors() {
|
renderErrors() {
|
||||||
return this.context.auth.error
|
return this.form.hasErrors()
|
||||||
? <AuthError error={this.context.auth.error} onClose={this.onClearErrors} />
|
? <AuthError error={this.form.getFirstError()} onClose={this.onClearErrors} />
|
||||||
: null
|
: null
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
@@ -46,7 +41,9 @@ export default class BaseAuthBody extends Component {
|
|||||||
|
|
||||||
onClearErrors = this.context.clearErrors;
|
onClearErrors = this.context.clearErrors;
|
||||||
|
|
||||||
form = new FormModel();
|
form = new FormModel({
|
||||||
|
renderErrors: false
|
||||||
|
});
|
||||||
|
|
||||||
bindField = this.form.bindField.bind(this.form);
|
bindField = this.form.bindField.bind(this.form);
|
||||||
serialize = this.form.serialize.bind(this.form);
|
serialize = this.form.serialize.bind(this.form);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class PanelTransition extends Component {
|
|||||||
})
|
})
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
user: userShape.isRequired,
|
user: userShape.isRequired,
|
||||||
setError: PropTypes.func.isRequired,
|
setErrors: PropTypes.func.isRequired,
|
||||||
clearErrors: PropTypes.func.isRequired,
|
clearErrors: PropTypes.func.isRequired,
|
||||||
resolve: PropTypes.func.isRequired,
|
resolve: PropTypes.func.isRequired,
|
||||||
reject: PropTypes.func.isRequired,
|
reject: PropTypes.func.isRequired,
|
||||||
@@ -233,9 +233,7 @@ class PanelTransition extends Component {
|
|||||||
this.body.onFormSubmit();
|
this.body.onFormSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
onFormInvalid = (errors) => {
|
onFormInvalid = (errors) => this.props.setErrors(errors);
|
||||||
this.props.setError(Object.values(errors).shift());
|
|
||||||
};
|
|
||||||
|
|
||||||
willEnter = (config) => this.getTransitionStyles(config);
|
willEnter = (config) => this.getTransitionStyles(config);
|
||||||
willLeave = (config) => this.getTransitionStyles(config, {isLeave: true});
|
willLeave = (config) => this.getTransitionStyles(config, {isLeave: true});
|
||||||
@@ -316,7 +314,7 @@ class PanelTransition extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldMeasureHeight() {
|
shouldMeasureHeight() {
|
||||||
return '' + this.props.auth.error + this.state.isHeightDirty + this.props.user.lang;
|
return [this.props.auth.error, this.state.isHeightDirty, this.props.user.lang].join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeader({key, style, data}) {
|
getHeader({key, style, data}) {
|
||||||
@@ -455,5 +453,5 @@ export default connect((state) => ({
|
|||||||
reject: authFlow.reject.bind(authFlow)
|
reject: authFlow.reject.bind(authFlow)
|
||||||
}), {
|
}), {
|
||||||
clearErrors: actions.clearErrors,
|
clearErrors: actions.clearErrors,
|
||||||
setError: actions.setError
|
setErrors: actions.setErrors
|
||||||
})(PanelTransition);
|
})(PanelTransition);
|
||||||
|
|||||||
@@ -117,16 +117,16 @@ export function resendActivation({email = '', captcha}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ERROR = 'error';
|
export const ERROR = 'error';
|
||||||
export function setError(error) {
|
export function setErrors(errors) {
|
||||||
return {
|
return {
|
||||||
type: ERROR,
|
type: ERROR,
|
||||||
payload: error,
|
payload: errors,
|
||||||
error: true
|
error: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearErrors() {
|
export function clearErrors() {
|
||||||
return setError(null);
|
return setErrors(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
@@ -284,32 +284,31 @@ function authHandler(dispatch) {
|
|||||||
function validationErrorsHandler(dispatch, repeatUrl) {
|
function validationErrorsHandler(dispatch, repeatUrl) {
|
||||||
return (resp) => {
|
return (resp) => {
|
||||||
if (resp.errors) {
|
if (resp.errors) {
|
||||||
|
const firstError = Object.keys(resp.errors)[0];
|
||||||
const error = {
|
const error = {
|
||||||
type: resp.errors[Object.keys(resp.errors)[0]],
|
type: resp.errors[firstError],
|
||||||
payload: {
|
payload: {
|
||||||
isGuest: true
|
isGuest: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (resp.data) {
|
if (resp.data) {
|
||||||
|
// TODO: this should be formatted on backend
|
||||||
Object.assign(error.payload, resp.data);
|
Object.assign(error.payload, resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['error.key_not_exists', 'error.key_expire'].includes(error.type) && repeatUrl) {
|
if (['error.key_not_exists', 'error.key_expire'].includes(error.type) && repeatUrl) {
|
||||||
|
// TODO: this should be formatted on backend
|
||||||
Object.assign(error.payload, {
|
Object.assign(error.payload, {
|
||||||
repeatUrl
|
repeatUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setError(error));
|
resp.errors[firstError] = error;
|
||||||
|
|
||||||
|
dispatch(setErrors(resp.errors));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(resp);
|
return Promise.reject(resp);
|
||||||
|
|
||||||
// TODO: log unexpected errors
|
|
||||||
// We can get here something like:
|
|
||||||
// code: 500
|
|
||||||
// {"name":"Invalid Configuration","message":"","code":0,"type":"yii\\base\\InvalidConfigException","file":"/home/sleepwalker/www/account/api/components/ReCaptcha/Component.php","line":12,"stack-trace":["#0 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Object.php(107): api\\components\\ReCaptcha\\Component->init()","#1 [internal function]: yii\\base\\Object->__construct(Array)","#2 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(368): ReflectionClass->newInstanceArgs(Array)","#3 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(153): yii\\di\\Container->build('api\\\\components\\\\...', Array, Array)","#4 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/BaseYii.php(344): yii\\di\\Container->get('api\\\\components\\\\...', Array, Array)","#5 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/ServiceLocator.php(133): yii\\BaseYii::createObject(Array)","#6 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/ServiceLocator.php(71): yii\\di\\ServiceLocator->get('reCaptcha')","#7 /home/sleepwalker/www/account/api/components/ReCaptcha/Validator.php(20): yii\\di\\ServiceLocator->__get('reCaptcha')","#8 /home/sleepwalker/www/account/api/components/ReCaptcha/Validator.php(25): api\\components\\ReCaptcha\\Validator->getComponent()","#9 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Object.php(107): api\\components\\ReCaptcha\\Validator->init()","#10 [internal function]: yii\\base\\Object->__construct(Array)","#11 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(374): ReflectionClass->newInstanceArgs(Array)","#12 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/di/Container.php(153): yii\\di\\Container->build('api\\\\components\\\\...', Array, Array)","#13 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/BaseYii.php(344): yii\\di\\Container->get('api\\\\components\\\\...', Array, Array)","#14 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/validators/Validator.php(209): yii\\BaseYii::createObject(Array)","#15 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(445): yii\\validators\\Validator::createValidator('api\\\\components\\\\...', Object(api\\models\\RegistrationForm), Array, Array)","#16 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(409): yii\\base\\Model->createValidators()","#17 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(185): yii\\base\\Model->getValidators()","#18 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(751): yii\\base\\Model->scenarios()","#19 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(695): yii\\base\\Model->safeAttributes()","#20 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Model.php(823): yii\\base\\Model->setAttributes(Array)","#21 /home/sleepwalker/www/account/api/controllers/SignupController.php(41): yii\\base\\Model->load(Array)","#22 [internal function]: api\\controllers\\SignupController->actionIndex()","#23 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/InlineAction.php(55): call_user_func_array(Array, Array)","#24 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Controller.php(154): yii\\base\\InlineAction->runWithParams(Array)","#25 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Module.php(454): yii\\base\\Controller->runAction('', Array)","#26 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/web/Application.php(84): yii\\base\\Module->runAction('signup', Array)","#27 /home/sleepwalker/www/ely/vendor/yiisoft/yii2/base/Application.php(375): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))","#28 /home/sleepwalker/www/account/api/web/index.php(18): yii\\base\\Application->run()","#29 {main}"]}
|
|
||||||
// We need here status code. Probably `request` module should add _request field en each resp
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,17 +52,15 @@ export default class Captcha extends FormInputComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue() {
|
|
||||||
return this.state && this.state.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
captcha.reset(this.captchaId);
|
captcha.reset(this.captchaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(error) {
|
getValue() {
|
||||||
super.setError(error);
|
return this.state && this.state.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormInvalid() {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,4 +29,11 @@ export default class FormComponent extends Component {
|
|||||||
*/
|
*/
|
||||||
focus() {
|
focus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook, that called, when the form was submitted with invalid data
|
||||||
|
* This is usefull for the cases, when some field needs to be refreshed e.g. captcha
|
||||||
|
*/
|
||||||
|
onFormInvalid() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ export default class FormModel {
|
|||||||
errors = {};
|
errors = {};
|
||||||
handlers = [];
|
handlers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} options
|
||||||
|
* @param {bool} [options.renderErrors=true] - whether the bound filed should
|
||||||
|
* render their errors
|
||||||
|
*/
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.renderErrors = options.renderErrors !== false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects form with React's component
|
* Connects form with React's component
|
||||||
*
|
*
|
||||||
@@ -29,7 +38,7 @@ export default class FormModel {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.getError(name)) {
|
if (this.renderErrors && this.getError(name)) {
|
||||||
props.error = this.getError(name);
|
props.error = this.getError(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,25 +82,43 @@ export default class FormModel {
|
|||||||
/**
|
/**
|
||||||
* Add errors to form fields
|
* Add errors to form fields
|
||||||
*
|
*
|
||||||
* @param {object} errors - object maping {fieldId: errorMessage}
|
* errorType may be string or object {type: string, payload: object}, where
|
||||||
|
* payload is additional data for errorType
|
||||||
|
*
|
||||||
|
* @param {object} errors - object maping {fieldId: errorType}
|
||||||
*/
|
*/
|
||||||
setErrors(errors) {
|
setErrors(errors) {
|
||||||
|
if (typeof errors !== 'object' || errors === null) {
|
||||||
|
throw new Error('Errors must be an object');
|
||||||
|
}
|
||||||
|
|
||||||
const oldErrors = this.errors;
|
const oldErrors = this.errors;
|
||||||
this.errors = errors;
|
this.errors = errors;
|
||||||
|
|
||||||
Object.keys(this.fields).forEach((fieldId) => {
|
Object.keys(this.fields).forEach((fieldId) => {
|
||||||
|
if (this.renderErrors) {
|
||||||
if (oldErrors[fieldId] || errors[fieldId]) {
|
if (oldErrors[fieldId] || errors[fieldId]) {
|
||||||
this.fields[fieldId].setError(errors[fieldId] || null);
|
this.fields[fieldId].setError(errors[fieldId] || null);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fields[fieldId].onFormInvalid();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {object|string|null}
|
||||||
|
*/
|
||||||
|
getFirstError() {
|
||||||
|
return this.errors ? Object.values(this.errors).shift() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get error by id
|
* Get error by id
|
||||||
*
|
*
|
||||||
* @param {string} fieldId - an id of field to get error for
|
* @param {string} fieldId - an id of field to get error for
|
||||||
*
|
*
|
||||||
* @return {string|null}
|
* @return {string|object|null}
|
||||||
*/
|
*/
|
||||||
getError(fieldId) {
|
getError(fieldId) {
|
||||||
return this.errors[fieldId] || null;
|
return this.errors[fieldId] || null;
|
||||||
|
|||||||
Reference in New Issue
Block a user