mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-06 05:04:35 +08:00
Update Profile page to use validation
This commit is contained in:
parent
ceb11c143f
commit
c026b60790
13 changed files with 414 additions and 404 deletions
|
@ -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
|
||||
|
|
|
@ -30,7 +30,6 @@ class ValidatedErrorHelpBlock extends Component {
|
|||
super(props);
|
||||
|
||||
this.cleanProps = this.cleanProps.bind(this);
|
||||
this.cleanProps = this.cleanProps.bind(this);
|
||||
}
|
||||
|
||||
cleanProps() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 [];
|
||||
};
|
|
@ -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: {
|
||||
|
|
|
@ -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: <FormattedMessage id="error_messages.cant_be_blank" />
|
||||
});
|
||||
}
|
||||
this.setState({ password_confirmation: value });
|
||||
}
|
||||
|
||||
handleFullNameValidation(event) {
|
||||
const { value } = event.target;
|
||||
if (value.length > NAME_MAX_LENGTH) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: (
|
||||
<FormattedMessage
|
||||
id="error_messages.text_too_long"
|
||||
values={{ max_length: NAME_MAX_LENGTH }}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (value.length === 0) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: <FormattedMessage id="error_messages.cant_be_blank" />
|
||||
});
|
||||
let newVal;
|
||||
if (this.props.dataField === "avatar") {
|
||||
newVal = event.currentTarget.files[0];
|
||||
} else {
|
||||
this.setState({ value, errorMessage: "" });
|
||||
}
|
||||
}
|
||||
|
||||
handleEmailValidation(event) {
|
||||
const { value } = event.target;
|
||||
if (!EMAIL_REGEX.test(value)) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: <FormattedMessage id="error_messages.invalid_email" />
|
||||
});
|
||||
} else if (value.length === 0) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: <FormattedMessage id="error_messages.cant_be_blank" />
|
||||
});
|
||||
} else {
|
||||
this.setState({ value, errorMessage: "" });
|
||||
}
|
||||
}
|
||||
|
||||
handleInitialsValidation(event) {
|
||||
const { value } = event.target;
|
||||
if (value.length > USER_INITIALS_MAX_LENGTH) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: (
|
||||
<FormattedMessage
|
||||
id="error_messages.text_too_long"
|
||||
values={{ max_length: USER_INITIALS_MAX_LENGTH }}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (value.length === 0) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: <FormattedMessage id="error_messages.cant_be_blank" />
|
||||
});
|
||||
} else {
|
||||
this.setState({ value, errorMessage: "" });
|
||||
}
|
||||
}
|
||||
|
||||
handlePasswordValidation(event) {
|
||||
const { value } = event.target;
|
||||
if (value.length > PASSWORD_MAX_LENGTH) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: (
|
||||
<FormattedMessage
|
||||
id="error_messages.text_too_long"
|
||||
values={{ max_length: PASSWORD_MAX_LENGTH }}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (value.length < PASSWORD_MIN_LENGTH) {
|
||||
this.setState({
|
||||
value,
|
||||
errorMessage: (
|
||||
<FormattedMessage
|
||||
id="error_messages.text_too_short"
|
||||
values={{ min_length: PASSWORD_MIN_LENGTH }}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else {
|
||||
this.setState({ value, errorMessage: "" });
|
||||
}
|
||||
}
|
||||
|
||||
handlePasswordConfirmationValidation(event) {
|
||||
const { value } = event.target;
|
||||
if (value !== this.state.value) {
|
||||
this.setState({
|
||||
password_confirmation: value,
|
||||
errorMessage: (
|
||||
<FormattedMessage id="error_messages.passwords_dont_match" />
|
||||
)
|
||||
});
|
||||
} else {
|
||||
this.setState({ password_confirmation: value, errorMessage: "" });
|
||||
newVal = event.target.value;
|
||||
}
|
||||
const newState = update(this.state, {
|
||||
value: { $set: newVal }
|
||||
});
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
handleCurrentPassword(event) {
|
||||
const { value } = event.target;
|
||||
if (value.length > PASSWORD_MAX_LENGTH) {
|
||||
this.setState({
|
||||
current_password: value,
|
||||
errorMessage: (
|
||||
<FormattedMessage
|
||||
id="error_messages.text_too_long"
|
||||
values={{ max_length: PASSWORD_MAX_LENGTH }}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (value.length < PASSWORD_MIN_LENGTH) {
|
||||
this.setState({
|
||||
current_password: value,
|
||||
errorMessage: (
|
||||
<FormattedMessage
|
||||
id="error_messages.text_too_short"
|
||||
values={{ min_length: PASSWORD_MIN_LENGTH }}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else {
|
||||
this.setState({ current_password: value, errorMessage: "" });
|
||||
}
|
||||
const newState = update(this.state, {
|
||||
current_password: { $set: event.target.value }
|
||||
});
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
handlePasswordConfirmation(event) {
|
||||
const newState = update(this.state, {
|
||||
password_confirmation: { $set: event.target.value }
|
||||
});
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
|
@ -292,7 +140,7 @@ class InputEnabled extends Component {
|
|||
}
|
||||
})
|
||||
.catch(({ response }) => {
|
||||
this.setState({ errorMessage: response.data.message.toString() });
|
||||
this.form.setErrors(response.data.details);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -301,108 +149,154 @@ class InputEnabled extends Component {
|
|||
|
||||
if (type === "email") {
|
||||
return (
|
||||
<div>
|
||||
<FormattedHTMLMessage id="settings_page.password_confirmation" />
|
||||
<FormControl
|
||||
<ValidatedFormGroup tag="current_password">
|
||||
<ControlLabel>
|
||||
<FormattedHTMLMessage id="settings_page.password_confirmation" />
|
||||
</ControlLabel>
|
||||
<ValidatedFormControl
|
||||
id="settings_page.current_password"
|
||||
tag="current_password"
|
||||
type="password"
|
||||
value={this.state.current_password}
|
||||
validatorsOnChange={[passwordLengthValidator]}
|
||||
onChange={this.handleCurrentPassword}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
/>
|
||||
</div>
|
||||
<ValidatedErrorHelpBlock tag="current_password" />
|
||||
</ValidatedFormGroup>
|
||||
);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inputField() {
|
||||
const { inputType } = this.props;
|
||||
const { inputType, dataField } = this.props;
|
||||
|
||||
let validatorsOnChange = [];
|
||||
if (dataField === "full_name") {
|
||||
validatorsOnChange = [textBlankValidator, nameMaxLengthValidator];
|
||||
} else if (dataField === "initials") {
|
||||
validatorsOnChange = [textBlankValidator, userInitialsMaxLengthValidator];
|
||||
} else if (dataField === "email") {
|
||||
validatorsOnChange = [emailValidator];
|
||||
}
|
||||
|
||||
if (inputType === "password") {
|
||||
return (
|
||||
<div>
|
||||
<FormattedHTMLMessage id="settings_page.password_confirmation" />
|
||||
<FormControl
|
||||
id="settings_page.current_password"
|
||||
type={inputType}
|
||||
value={this.state.current_password}
|
||||
onChange={this.handleCurrentPassword}
|
||||
autoFocus
|
||||
/>
|
||||
<ControlLabel>
|
||||
<FormattedMessage id="settings_page.new_password" />
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
id="settings_page.new_password"
|
||||
type={inputType}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
autoFocus
|
||||
/>
|
||||
<ControlLabel>
|
||||
<FormattedMessage id="settings_page.new_password_confirmation" />
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
id="settings_page.new_password_confirmation"
|
||||
type={inputType}
|
||||
value={this.state.password_confirmation}
|
||||
onChange={this.handlePasswordConfirmationValidation}
|
||||
/>
|
||||
<ValidatedFormGroup tag="current_password">
|
||||
<ControlLabel>
|
||||
<FormattedHTMLMessage id="settings_page.password_confirmation" />
|
||||
</ControlLabel>
|
||||
<ValidatedFormControl
|
||||
id="settings_page.current_password"
|
||||
type="password"
|
||||
value={this.state.current_password}
|
||||
tag="current_password"
|
||||
validatorsOnChange={[passwordLengthValidator]}
|
||||
onChange={this.handleCurrentPassword}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
autoFocus
|
||||
/>
|
||||
<ValidatedErrorHelpBlock tag="current_password" />
|
||||
</ValidatedFormGroup>
|
||||
<ValidatedFormGroup tag="new_password">
|
||||
<ControlLabel>
|
||||
<FormattedMessage id="settings_page.new_password" />
|
||||
</ControlLabel>
|
||||
<ValidatedFormControl
|
||||
id="settings_page.new_password"
|
||||
type="password"
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
tag="new_password"
|
||||
validatorsOnChange={[passwordLengthValidator]}
|
||||
autoFocus
|
||||
/>
|
||||
<ValidatedErrorHelpBlock tag="new_password" />
|
||||
</ValidatedFormGroup>
|
||||
<ValidatedFormGroup tag="new_password_confirmation">
|
||||
<ControlLabel>
|
||||
<FormattedMessage id="settings_page.new_password_confirmation" />
|
||||
</ControlLabel>
|
||||
<ValidatedFormControl
|
||||
id="settings_page.new_password_confirmation"
|
||||
type="password"
|
||||
value={this.state.password_confirmation}
|
||||
onChange={this.handlePasswordConfirmation}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
tag="new_password_confirmation"
|
||||
validatorsOnChange={[passwordLengthValidator]}
|
||||
/>
|
||||
<ValidatedErrorHelpBlock tag="new_password_confirmation" />
|
||||
</ValidatedFormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (inputType === "file") {
|
||||
return (
|
||||
<FormControl
|
||||
id="user_avatar_input"
|
||||
type={this.props.inputType}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
autoFocus
|
||||
/>
|
||||
<ValidatedFormGroup tag={dataField}>
|
||||
<ValidatedFormControl
|
||||
id="user_avatar_input"
|
||||
tag={dataField}
|
||||
type={this.props.inputType}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
validatorsOnChange={validatorsOnChange}
|
||||
autoFocus
|
||||
/>
|
||||
<ValidatedErrorHelpBlock tag={dataField} />
|
||||
</ValidatedFormGroup>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FormControl
|
||||
type={this.props.inputType}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
autoFocus
|
||||
/>
|
||||
<ValidatedFormGroup tag={dataField}>
|
||||
<ValidatedFormControl
|
||||
tag={dataField}
|
||||
type={this.props.inputType}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
validatorsOnChange={validatorsOnChange}
|
||||
value={this.state.value}
|
||||
autoFocus
|
||||
/>
|
||||
<ValidatedErrorHelpBlock tag={dataField} />
|
||||
</ValidatedFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledInputEnabled id={transformName(this.props.labelTitle)}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<FormGroup validationState={this.getValidationState()}>
|
||||
<h4>
|
||||
<FormattedMessage id="settings_page.change" />
|
||||
<FormattedMessage id={this.props.labelTitle} />
|
||||
</h4>
|
||||
{this.props.labelValue !== "none" && (
|
||||
<ControlLabel>
|
||||
<FormattedMessage id={this.props.labelValue} />
|
||||
</ControlLabel>
|
||||
)}
|
||||
{this.inputField()}
|
||||
{this.confirmationField()}
|
||||
<StyledHelpBlock>{this.state.errorMessage}</StyledHelpBlock>
|
||||
<ButtonToolbar>
|
||||
<Button bsStyle="primary" type="submit">
|
||||
<FormattedMessage
|
||||
id={`general.${this.props.dataField === "avatar"
|
||||
? "upload"
|
||||
: "update"}`}
|
||||
/>
|
||||
</Button>
|
||||
<Button bsStyle="default" onClick={this.props.disableEdit}>
|
||||
<FormattedMessage id="general.cancel" />
|
||||
</Button>
|
||||
</ButtonToolbar>
|
||||
</FormGroup>
|
||||
</form>
|
||||
<StyledInputEnabled>
|
||||
<ValidatedForm
|
||||
onSubmit={this.handleSubmit}
|
||||
ref={(f) => { this.form = f; }}
|
||||
>
|
||||
<h4>
|
||||
<FormattedMessage id="settings_page.change" />
|
||||
<FormattedMessage id={this.props.labelTitle} />
|
||||
</h4>
|
||||
{this.props.labelValue !== "none" && (
|
||||
<ControlLabel>
|
||||
<FormattedMessage id={this.props.labelValue} />
|
||||
</ControlLabel>
|
||||
)}
|
||||
{this.inputField()}
|
||||
{this.confirmationField()}
|
||||
<ButtonToolbar>
|
||||
<ValidatedSubmitButton bsStyle="primary" type="submit">
|
||||
<FormattedMessage
|
||||
id={`general.${this.props.dataField === "avatar"
|
||||
? "upload"
|
||||
: "update"}`}
|
||||
/>
|
||||
</ValidatedSubmitButton>
|
||||
<Button bsStyle="default" onClick={this.props.disableEdit}>
|
||||
<FormattedMessage id="general.cancel" />
|
||||
</Button>
|
||||
</ButtonToolbar>
|
||||
</ValidatedForm>
|
||||
</StyledInputEnabled>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
module ClientApi
|
||||
class UserService < BaseService
|
||||
def update_user!
|
||||
error = I18n.t('client_api.user.password_invalid')
|
||||
raise CustomUserError, error unless check_current_password
|
||||
@params.delete(:current_password) # removes unneeded element
|
||||
@current_user.update(@params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_current_password
|
||||
return true unless @params[:email] || @params[:password]
|
||||
pass_blank_err = I18n.t('client_api.user.blank_password_error')
|
||||
pass_match_err = I18n.t('client_api.user.passwords_dont_match')
|
||||
current_password = @params[:current_password]
|
||||
raise CustomUserError, pass_blank_err unless current_password
|
||||
raise CustomUserError, pass_match_err unless check_password_confirmation
|
||||
@current_user.valid_password? current_password
|
||||
end
|
||||
|
||||
def check_password_confirmation
|
||||
return true if @params[:email]
|
||||
@params[:password] == @params[:password_confirmation]
|
||||
end
|
||||
end
|
||||
CustomUserError = Class.new(StandardError)
|
||||
end
|
53
app/services/client_api/users/update_service.rb
Normal file
53
app/services/client_api/users/update_service.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
module ClientApi
|
||||
module Users
|
||||
class UpdateService < BaseService
|
||||
attr_accessor :user
|
||||
|
||||
def execute
|
||||
@user = @current_user
|
||||
|
||||
if current_password_valid? &&
|
||||
password_confirmation_valid? &&
|
||||
@user.update(@params.except(:current_password))
|
||||
success
|
||||
else
|
||||
error(@user.errors.full_messages.uniq.join('. '))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_password_valid?
|
||||
# Only check for current_password when updating
|
||||
# email or password
|
||||
return true unless @params[:email] || @params[:password]
|
||||
|
||||
if @user.valid_password?(@params[:current_password])
|
||||
return true
|
||||
else
|
||||
@user.errors.add(
|
||||
:current_password,
|
||||
I18n.t('client_api.user.current_password_invalid')
|
||||
)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def password_confirmation_valid?
|
||||
# Only check for password_confirmation when
|
||||
# updating password
|
||||
return true unless @params[:password]
|
||||
|
||||
if @params[:password] == @params[:password_confirmation]
|
||||
return true
|
||||
else
|
||||
@user.errors.add(
|
||||
:password_confirmation,
|
||||
I18n.t('client_api.user.password_confirmation_not_match')
|
||||
)
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1822,9 +1822,7 @@ en:
|
|||
leave_team_error: "An error occured."
|
||||
leave_flash: "Successfuly left team %{team}."
|
||||
user:
|
||||
blank_password_error: "Password can't be blank!"
|
||||
passwords_dont_match: "Passwords don't match"
|
||||
password_invalid: "Password is invalid!"
|
||||
avatar_too_big: "Avatar file size must be less than 0.2 MB"
|
||||
current_password_invalid: "incorrect password"
|
||||
password_confirmation_not_match: "doesn't match"
|
||||
invite_users:
|
||||
permission_error: "You don't have permission to invite additional users to team. Contact its administrator/s."
|
||||
|
|
|
@ -103,7 +103,7 @@ Scenario: Unsuccessful Password Change, current password is invalid
|
|||
And I fill in "mypassword5678" in New password field
|
||||
And I fill in "mypassword5678" in New password confirmation field
|
||||
Then I click "Update" button
|
||||
And I should see "Password is invalid!"
|
||||
And I should see "incorrect password"
|
||||
|
||||
@javascript
|
||||
Scenario: Successful Password Change
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe ClientApi::UserService do
|
||||
let(:user) do
|
||||
create :user,
|
||||
full_name: 'User One',
|
||||
initials: 'UO',
|
||||
email: 'user@happy.com',
|
||||
password: 'asdf1234',
|
||||
password_confirmation: 'asdf1234'
|
||||
end
|
||||
|
||||
describe '#update_user!' do
|
||||
it 'should update user email if the password is correct' do
|
||||
email = 'new_user@happy.com'
|
||||
params = { email: email, current_password: 'asdf1234' }
|
||||
user_service = ClientApi::UserService.new(current_user: user,
|
||||
params: params)
|
||||
user_service.update_user!
|
||||
expect(user.email).to eq(email)
|
||||
end
|
||||
|
||||
it 'should raise CustomUserError error if the password is not correct' do
|
||||
email = 'new_user@happy.com'
|
||||
params = { email: email, current_password: 'banana' }
|
||||
user_service = ClientApi::UserService.new(current_user: user,
|
||||
params: params)
|
||||
expect {
|
||||
user_service.update_user!
|
||||
}.to raise_error(ClientApi::CustomUserError)
|
||||
end
|
||||
|
||||
it 'should update initials and full name without password confirmation' do
|
||||
full_name = 'Happy User'
|
||||
initials = 'HU'
|
||||
user_service = ClientApi::UserService.new(
|
||||
current_user: user,
|
||||
params: { full_name: full_name, initials: initials }
|
||||
)
|
||||
user_service.update_user!
|
||||
expect(user.full_name).to eq(full_name)
|
||||
expect(user.initials).to eq(initials)
|
||||
end
|
||||
|
||||
it 'should raise an error if current password not present' do
|
||||
user_service = ClientApi::UserService.new(
|
||||
current_user: user,
|
||||
params: { password: 'hello1234', password_confirmation: 'hello1234' }
|
||||
)
|
||||
expect {
|
||||
user_service.update_user!
|
||||
}.to raise_error(ClientApi::CustomUserError)
|
||||
end
|
||||
|
||||
it 'should raise an error if password_confirmation don\'t match' do
|
||||
user_service = ClientApi::UserService.new(
|
||||
current_user: user,
|
||||
params: { password: 'hello1234',
|
||||
password_confirmation: 'hello1234567890',
|
||||
current_password: 'asdf1234' }
|
||||
)
|
||||
|
||||
expect {
|
||||
user_service.update_user!
|
||||
}.to raise_error(ClientApi::CustomUserError, 'Passwords don\'t match')
|
||||
end
|
||||
|
||||
it 'should update the password' do
|
||||
new_password = 'hello1234'
|
||||
user_service = ClientApi::UserService.new(
|
||||
current_user: user,
|
||||
params: { password: new_password,
|
||||
password_confirmation: new_password,
|
||||
current_password: 'asdf1234' }
|
||||
)
|
||||
user_service.update_user!
|
||||
expect(user.valid_password?(new_password)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
79
spec/services/client_api/users/update_service_spec.rb
Normal file
79
spec/services/client_api/users/update_service_spec.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
require 'rails_helper'
|
||||
|
||||
include ClientApi::Users
|
||||
|
||||
describe ClientApi::Users::UpdateService do
|
||||
let(:user) do
|
||||
create :user,
|
||||
full_name: 'User One',
|
||||
initials: 'UO',
|
||||
email: 'user@happy.com',
|
||||
password: 'asdf1234',
|
||||
password_confirmation: 'asdf1234'
|
||||
end
|
||||
|
||||
it 'should update user email if the password is correct' do
|
||||
email = 'new_user@happy.com'
|
||||
params = { email: email, current_password: 'asdf1234' }
|
||||
service = UpdateService.new(current_user: user,
|
||||
params: params)
|
||||
result = service.execute
|
||||
expect(result[:status]).to eq :success
|
||||
expect(user.email).to eq(email)
|
||||
end
|
||||
|
||||
it 'should raise CustomUserError error if the password is not correct' do
|
||||
email = 'new_user@happy.com'
|
||||
params = { email: email, current_password: 'banana' }
|
||||
service = UpdateService.new(current_user: user,
|
||||
params: params)
|
||||
result = service.execute
|
||||
expect(result[:status]).to eq :error
|
||||
end
|
||||
|
||||
it 'should update initials and full name without password confirmation' do
|
||||
full_name = 'Happy User'
|
||||
initials = 'HU'
|
||||
service = UpdateService.new(
|
||||
current_user: user,
|
||||
params: { full_name: full_name, initials: initials }
|
||||
)
|
||||
result = service.execute
|
||||
expect(result[:status]).to eq :success
|
||||
expect(user.full_name).to eq(full_name)
|
||||
expect(user.initials).to eq(initials)
|
||||
end
|
||||
|
||||
it 'should raise an error if current password not present' do
|
||||
service = UpdateService.new(
|
||||
current_user: user,
|
||||
params: { password: 'hello1234', password_confirmation: 'hello1234' }
|
||||
)
|
||||
result = service.execute
|
||||
expect(result[:status]).to eq :error
|
||||
end
|
||||
|
||||
it 'should raise an error if password_confirmation don\'t match' do
|
||||
service = UpdateService.new(
|
||||
current_user: user,
|
||||
params: { password: 'hello1234',
|
||||
password_confirmation: 'hello1234567890',
|
||||
current_password: 'asdf1234' }
|
||||
)
|
||||
result = service.execute
|
||||
expect(result[:status]).to eq :error
|
||||
end
|
||||
|
||||
it 'should update the password' do
|
||||
new_password = 'hello1234'
|
||||
service = UpdateService.new(
|
||||
current_user: user,
|
||||
params: { password: new_password,
|
||||
password_confirmation: new_password,
|
||||
current_password: 'asdf1234' }
|
||||
)
|
||||
result = service.execute
|
||||
expect(result[:status]).to eq :success
|
||||
expect(user.valid_password?(new_password)).to be(true)
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue