diff --git a/app/controllers/client_api/users/users_controller.rb b/app/controllers/client_api/users/users_controller.rb
index 98c663c40..89d148d76 100644
--- a/app/controllers/client_api/users/users_controller.rb
+++ b/app/controllers/client_api/users/users_controller.rb
@@ -57,19 +57,21 @@ module ClientApi
end
def update
- user_service = ClientApi::UserService.new(
+ service = ClientApi::Users::UpdateService.new(
current_user: current_user,
params: user_params
)
- if user_service.update_user!
+ result = service.execute
+
+ if result[:status] == :success
bypass_sign_in(current_user)
success_response
else
- unsuccess_response(current_user.errors.full_messages,
- :unprocessable_entity)
+ error_response(
+ message: result[:message],
+ details: service.user.errors
+ )
end
- rescue CustomUserError => error
- unsuccess_response(error.to_s)
end
private
@@ -84,22 +86,35 @@ module ClientApi
:system_message_email_notification)
end
- def success_response(template = nil, locals = nil)
+ def success_response(args = {})
+ template = args.fetch(:template) { nil }
+ locals = args.fetch(:locals) { {} }
+ details = args.fetch(:details) { {} }
+
respond_to do |format|
format.json do
- if template && locals
- render template: template, status: :ok, locals: locals
+ if template
+ render template: template,
+ status: :ok,
+ locals: locals
else
- render json: {}, status: :ok
+ render json: { details: details }, status: :ok
end
end
end
end
- def unsuccess_response(message, status = :unprocessable_entity)
+ def error_response(args = {})
+ message = args.fetch(:message) { t('client_api.generic_error_message') }
+ details = args.fetch(:details) { {} }
+ status = args.fetch(:status) { :unprocessable_entity }
+
respond_to do |format|
format.json do
- render json: { message: message },
+ render json: {
+ message: message,
+ details: details
+ },
status: status
end
end
diff --git a/app/javascript/src/components/validation/components/ValidatedErrorHelpBlock.jsx b/app/javascript/src/components/validation/components/ValidatedErrorHelpBlock.jsx
index 1dc37a432..2e9b1afa8 100644
--- a/app/javascript/src/components/validation/components/ValidatedErrorHelpBlock.jsx
+++ b/app/javascript/src/components/validation/components/ValidatedErrorHelpBlock.jsx
@@ -30,7 +30,6 @@ class ValidatedErrorHelpBlock extends Component {
super(props);
this.cleanProps = this.cleanProps.bind(this);
- this.cleanProps = this.cleanProps.bind(this);
}
cleanProps() {
diff --git a/app/javascript/src/components/validation/components/ValidatedForm.jsx b/app/javascript/src/components/validation/components/ValidatedForm.jsx
index 7005decdc..5b15a251a 100644
--- a/app/javascript/src/components/validation/components/ValidatedForm.jsx
+++ b/app/javascript/src/components/validation/components/ValidatedForm.jsx
@@ -4,6 +4,14 @@ import PropTypes from "prop-types";
import _ from "lodash";
class ValidatedForm extends Component {
+ static parseErrors(errors) {
+ // This method is quite smart, in the sense that accepts either
+ // errors in 3 shapes: localized error messages ({}),
+ // unlocalized error messages ({}), or mere strings (unlocalized)
+ const arr = _.isString(errors) ? [errors] : errors;
+ return arr.map((el) => _.isString(el) ? { message: el } : el);
+ }
+
constructor(props) {
super(props);
@@ -34,19 +42,17 @@ class ValidatedForm extends Component {
}
setErrors(errors) {
- // This method is quite smart, in the sense that accepts either
- // errors in 3 shapes: localized error messages ({}),
- // unlocalized error messages ({}), or mere strings (unlocalized)
const newState = {};
_.entries(errors).forEach(([key, value]) => {
- const arr = _.isString(value) ? [value] : value;
- newState[key] = arr.map((el) => _.isString(el) ? { message: el } : el);
+ newState[key] = ValidatedForm.parseErrors(value);
});
this.setState(newState);
}
setErrorsForTag(tag, errors) {
- const newState = update(this.state, { [tag]: { $set: errors } });
+ const newState = update(this.state, {
+ [tag]: { $set: ValidatedForm.parseErrors(errors) }
+ });
this.setState(newState);
}
diff --git a/app/javascript/src/components/validation/components/ValidatedFormControl.jsx b/app/javascript/src/components/validation/components/ValidatedFormControl.jsx
index c6c54dc72..6297b4ce7 100644
--- a/app/javascript/src/components/validation/components/ValidatedFormControl.jsx
+++ b/app/javascript/src/components/validation/components/ValidatedFormControl.jsx
@@ -16,7 +16,7 @@ class ValidatedFormControl extends Component {
const value = e.target.value;
// Pass-through "original" onChange
- if (_.has(this.props, "onChange")) {
+ if (_.has(this.props, "onChange") && this.props.onChange !== undefined) {
this.props.onChange(e);
}
diff --git a/app/javascript/src/components/validation/validators/text_validators.js b/app/javascript/src/components/validation/validators/text_validators.js
index 2a6c61aa1..d2ad5233f 100644
--- a/app/javascript/src/components/validation/validators/text_validators.js
+++ b/app/javascript/src/components/validation/validators/text_validators.js
@@ -2,15 +2,18 @@ import _ from "lodash";
import {
NAME_MIN_LENGTH,
NAME_MAX_LENGTH,
- TEXT_MAX_LENGTH
+ TEXT_MAX_LENGTH,
+ PASSWORD_MIN_LENGTH,
+ PASSWORD_MAX_LENGTH,
+ USER_INITIALS_MAX_LENGTH
} from "../../../config/constants/numeric";
+import { EMAIL_REGEX } from "../../../config/constants/strings";
export const nameMinLengthValidator = (value, messageIds = {}) => {
const messageId = _.has(messageIds, "text_too_short") ?
messageIds.text_too_short :
"validators.text_validators.text_too_short";
-
if (value.length < NAME_MIN_LENGTH) {
return [{
intl: true,
@@ -44,6 +47,20 @@ export const nameLengthValidator = (value, messageIds = {}) => {
return nameMaxLengthValidator(value, messageIds);
};
+export const textBlankValidator = (value, messageIds = {}) => {
+ const messageId = _.has(messageIds, "text_blank") ?
+ messageIds.text_blank :
+ "validators.text_validators.text_blank";
+
+ if (value.length === 0) {
+ return [{
+ intl: true,
+ messageId
+ }];
+ }
+ return [];
+}
+
export const textMaxLengthValidator = (value, messageIds = {}) => {
const messageId = _.has(messageIds, "text_too_long") ?
messageIds.text_too_long :
@@ -57,4 +74,59 @@ export const textMaxLengthValidator = (value, messageIds = {}) => {
}];
}
return [];
+};
+
+export const passwordLengthValidator = (value, messageIds = {}) => {
+ const messageIdTooShort = _.has(messageIds, "text_too_short") ?
+ messageIds.text_too_short :
+ "validators.text_validators.text_too_short";
+ const messageIdTooLong = _.has(messageIds, "text_too_long") ?
+ messageIds.text_too_long :
+ "validators.text_validators.text_too_long";
+
+ if (value.length < PASSWORD_MIN_LENGTH) {
+ return [{
+ intl: true,
+ messageId: messageIdTooShort,
+ values:{ min_length: PASSWORD_MIN_LENGTH }
+ }];
+ } else if (value.length > PASSWORD_MAX_LENGTH) {
+ return [{
+ intl: true,
+ messageId: messageIdTooLong,
+ values:{ max_length: PASSWORD_MAX_LENGTH }
+ }];
+ }
+ return [];
+};
+
+export const userInitialsMaxLengthValidator = (value, messageIds = {}) => {
+ const messageId = _.has(messageIds, "text_too_long") ?
+ messageIds.text_too_long :
+ "validators.text_validators.text_too_long";
+
+ if (value.length > USER_INITIALS_MAX_LENGTH) {
+ return [{
+ intl: true,
+ messageId,
+ values: { max_length: USER_INITIALS_MAX_LENGTH }
+ }];
+ }
+ return [];
+};
+
+export const emailValidator = (value, messageIds = {}) => {
+ const res = textBlankValidator(value, messageIds);
+ if (res.length > 0) {
+ return res;
+ }
+
+ const messageId = _.has(messageIds, "invalid_email") ?
+ messageIds.invalid_email :
+ "validators.text_validators.invalid_email";
+
+ if (!EMAIL_REGEX.test(value)) {
+ return [{ intl: true, messageId }];
+ }
+ return [];
};
\ No newline at end of file
diff --git a/app/javascript/src/config/locales/messages.js b/app/javascript/src/config/locales/messages.js
index fa87b5bf9..3cbbdeb25 100644
--- a/app/javascript/src/config/locales/messages.js
+++ b/app/javascript/src/config/locales/messages.js
@@ -19,7 +19,9 @@ export default {
validators: {
text_validators: {
text_too_short: "is too short (minimum is {min_length} characters)",
- text_too_long: "is too long (maximum is {max_length} characters)"
+ text_too_long: "is too long (maximum is {max_length} characters)",
+ text_blank: "can't be blank",
+ invalid_email: "invalid email"
}
},
error_messages: {
diff --git a/app/javascript/src/scenes/SettingsPage/scenes/profile/components/InputEnabled.jsx b/app/javascript/src/scenes/SettingsPage/scenes/profile/components/InputEnabled.jsx
index 04f087031..7efacc543 100644
--- a/app/javascript/src/scenes/SettingsPage/scenes/profile/components/InputEnabled.jsx
+++ b/app/javascript/src/scenes/SettingsPage/scenes/profile/components/InputEnabled.jsx
@@ -4,29 +4,35 @@ import { string, func } from "prop-types";
import styled from "styled-components";
import { FormattedMessage, FormattedHTMLMessage } from "react-intl";
import {
- FormGroup,
- FormControl,
ControlLabel,
Button,
ButtonToolbar,
- HelpBlock
} from "react-bootstrap";
+import update from "immutability-helper";
import { updateUser } from "../../../../../services/api/users_api";
import { transformName } from "../../../../../services/helpers/string_helper";
import { addAlert } from "../../../../../components/actions/AlertsActions";
import {
BORDER_LIGHT_COLOR,
- COLOR_APPLE_BLOSSOM
} from "../../../../../config/constants/colors";
import {
ENTER_KEY_CODE,
- USER_INITIALS_MAX_LENGTH,
- NAME_MAX_LENGTH,
- PASSWORD_MAX_LENGTH,
- PASSWORD_MIN_LENGTH
} from "../../../../../config/constants/numeric";
-import { EMAIL_REGEX } from "../../../../../config/constants/strings";
+import {
+ ValidatedForm,
+ ValidatedFormGroup,
+ ValidatedFormControl,
+ ValidatedErrorHelpBlock,
+ ValidatedSubmitButton
+} from "../../../../../components/validation";
+import {
+ textBlankValidator,
+ nameMaxLengthValidator,
+ passwordLengthValidator,
+ userInitialsMaxLengthValidator,
+ emailValidator
+} from "../../../../../components/validation/validators/text_validators";
const StyledInputEnabled = styled.div`
border: 1px solid ${BORDER_LIGHT_COLOR};
@@ -38,10 +44,6 @@ const StyledInputEnabled = styled.div`
}
`;
-const StyledHelpBlock = styled(HelpBlock)`
- color: ${COLOR_APPLE_BLOSSOM};
-`;
-
class InputEnabled extends Component {
constructor(props) {
super(props);
@@ -49,30 +51,16 @@ class InputEnabled extends Component {
this.state = {
value: this.props.inputValue === "********" ? "" : this.props.inputValue,
current_password: "",
- password_confirmation: "",
- errorMessage: ""
+ password_confirmation: ""
};
- this.handleChange = this.handleChange.bind(this);
- this.handlePasswordConfirmation = this.handlePasswordConfirmation.bind(
- this
- );
this.handleKeyPress = this.handleKeyPress.bind(this);
- this.confirmationField = this.confirmationField.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.getValidationState = this.getValidationState.bind(this);
- this.handleFullNameValidation = this.handleFullNameValidation.bind(this);
- this.handleEmailValidation = this.handleEmailValidation.bind(this);
- this.handleInitialsValidation = this.handleInitialsValidation.bind(this);
- this.handlePasswordConfirmationValidation = this.handlePasswordConfirmationValidation.bind(
- this
- );
+ this.handleChange = this.handleChange.bind(this);
this.handleCurrentPassword = this.handleCurrentPassword.bind(this);
- this.handleFileChange = this.handleFileChange.bind(this);
- }
-
- getValidationState() {
- return this.state.errorMessage.length > 0 ? "error" : null;
+ this.handlePasswordConfirmation = this.handlePasswordConfirmation.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.inputField = this.inputField.bind(this);
+ this.confirmationField = this.confirmationField.bind(this);
}
handleKeyPress(event) {
@@ -83,170 +71,30 @@ class InputEnabled extends Component {
}
handleChange(event) {
- event.preventDefault();
- switch (this.props.dataField) {
- case "full_name":
- this.handleFullNameValidation(event);
- break;
- case "email":
- this.handleEmailValidation(event);
- break;
- case "initials":
- this.handleInitialsValidation(event);
- break;
- case "password":
- this.handlePasswordValidation(event);
- break;
- case "avatar":
- this.handleFileChange(event);
- break;
- default:
- this.setState({ value: event.target.value, errorMessage: "" });
- }
- }
-
- handleFileChange(event) {
- this.setState({ value: event.currentTarget.files[0], errorMessage: "" });
- }
-
- handlePasswordConfirmation(event) {
- const { value } = event.target;
- if (value.length === 0) {
- this.setState({
- password_confirmation: value,
- errorMessage: