From b365c466015d971aa8c887aa18b59ca14b9c2d5c Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 10 Feb 2019 20:26:03 +0100 Subject: [PATCH] Validate that email is not repeated --- src/components/user/User.js | 155 +++++++++++++++----------- src/data-sources/users/collection.js | 26 +++++ src/data-sources/users/index.js | 8 +- src/modules/users/Layouts.js | 69 ++++++++++-- src/modules/users/views/CreateUser.js | 19 +--- src/modules/users/views/UpdateUser.js | 4 + src/modules/users/views/helpers.js | 23 ++++ 7 files changed, 216 insertions(+), 88 deletions(-) create mode 100644 src/modules/users/views/helpers.js diff --git a/src/components/user/User.js b/src/components/user/User.js index 530e764..b3c5a0c 100644 --- a/src/components/user/User.js +++ b/src/components/user/User.js @@ -18,8 +18,8 @@ export class User extends Component { constructor(props) { super(props); this.state = { - submitSuccess: false, - isNew: props.isNew + submitSuccess: props.fromCreation, + fromCreation: props.fromCreation }; this.handleNameChange = this.handleNameChange.bind(this); @@ -39,7 +39,8 @@ export class User extends Component { ...state, name, submitSuccess: false, - nameValid: this.props.isValidUserName(name) + nameValid: this.props.isValidUserName(name), + nameError: null })); } @@ -49,7 +50,8 @@ export class User extends Component { ...state, email, submitSuccess: false, - emailValid: this.props.isValidUserEmail(email) + emailValid: this.props.isValidUserEmail(email), + emailError: null })); } @@ -98,55 +100,12 @@ export class User extends Component { return false; } - handleSubmit(event) { - event.preventDefault(); - const { name, email, password, role } = this.state; - this.setState(state => ({ - ...state, - submitSuccess: false - })); - - this.props - .onSubmit( - { - name, - email, - password, - role - }, - this.props.user._id // Or this.state.newUserId (if new) - ) - .then(() => { - // TODO, receive here new user ID, set as "newUserId" in state - this.setState(state => ({ - ...state, - name: null, - nameValid: false, - email: null, - emailValid: false, - role: null, - roleValid: null, - passwordsValid: null, - submitSuccess: true, - isNew: false - })); - }) - .catch(() => { - console.error("Error sending user"); - }); - } - - handleCancel() { - event.preventDefault(); - this.props.onCancel(); - } - hasChangedName() { return this.state.name && this.state.name.length; } hasValidName() { - return this.props.isValidUserName(this.state.name); + return this.props.isValidUserName(this.state.name) && !this.state.nameError; } hasChangedEmail() { @@ -154,7 +113,7 @@ export class User extends Component { } hasValidEmail() { - return this.props.isValidUserEmail(this.state.email); + return this.props.isValidUserEmail(this.state.email) && !this.state.emailError; } hasChangedRole() { @@ -170,7 +129,7 @@ export class User extends Component { } submitEnabled() { - if (!this.state.isNew) { + if (!this.props.isNew) { return (this.hasChangedPassword() || this.state.role) && !this.repeatedPasswordError(); } return ( @@ -187,11 +146,21 @@ export class User extends Component { } handleNameBlur() { - if (this.hasChangedName() && !this.hasValidName()) { - this.setState(state => ({ - ...state, - nameError: NAME_NOT_VALID - })); + if (this.hasChangedName()) { + if (!this.hasValidName()) { + this.setState(state => ({ + ...state, + nameError: NAME_NOT_VALID + })); + } else { + this.props.isUserNameRepeated(this.state.name).then(isRepeated => { + this.setState(state => ({ + ...state, + nameValid: !isRepeated, + nameError: isRepeated ? "Name is repeated" : null + })); + }); + } } else { this.setState(state => ({ ...state, @@ -201,11 +170,21 @@ export class User extends Component { } handleEmailBlur() { - if (this.hasChangedEmail() && !this.hasValidEmail()) { - this.setState(state => ({ - ...state, - emailError: EMAIL_NOT_VALID - })); + if (this.hasChangedEmail()) { + if (!this.hasValidEmail()) { + this.setState(state => ({ + ...state, + emailError: EMAIL_NOT_VALID + })); + } else { + this.props.isUserEmailRepeated(this.state.email).then(isRepeated => { + this.setState(state => ({ + ...state, + emailValid: !isRepeated, + emailError: isRepeated ? "Email is repeated" : null + })); + }); + } } else { this.setState(state => ({ ...state, @@ -214,6 +193,50 @@ export class User extends Component { } } + handleSubmit(event) { + event.preventDefault(); + const { name, email, password, role } = this.state; + this.setState(state => ({ + ...state, + submitSuccess: false + })); + + this.props + .onSubmit( + { + name, + email, + password, + role + }, + this.props.user._id + ) + .then(() => { + if (!this.props.isNew) { + this.setState(state => ({ + ...state, + name: null, + nameValid: false, + email: null, + emailValid: false, + role: null, + roleValid: null, + passwordsValid: null, + submitSuccess: true, + fromCreation: false + })); + } + }) + .catch(() => { + console.error("Error sending user"); + }); + } + + handleCancel() { + event.preventDefault(); + this.props.onCancel(); + } + render() { const { user = {}, @@ -222,7 +245,8 @@ export class User extends Component { roles, currentUserIsAdmin, submitLoading, - submitError + submitError, + isNew } = this.props; const { submitSuccess, @@ -232,7 +256,7 @@ export class User extends Component { emailValid, roleValid, passwordsValid, - isNew + fromCreation } = this.state; const repeatedPasswordErrorMessage = passwordsValid ? ( @@ -344,8 +368,8 @@ export class User extends Component { /> {nameField} {emailField} @@ -380,7 +404,10 @@ export class User extends Component { User.propTypes = { currentUserIsAdmin: PropTypes.bool, + fromCreation: PropTypes.bool, isNew: PropTypes.bool, + isUserEmailRepeated: PropTypes.func, + isUserNameRepeated: PropTypes.func, isValidUserEmail: PropTypes.func, isValidUserName: PropTypes.func, onCancel: PropTypes.func, diff --git a/src/data-sources/users/collection.js b/src/data-sources/users/collection.js index 5331c00..bfa8dbe 100644 --- a/src/data-sources/users/collection.js +++ b/src/data-sources/users/collection.js @@ -43,6 +43,32 @@ export const usersCollectionWithExtraData = new Selector( [] ); +const exactSearchBy = (usersResults, { email, name }) => { + return usersResults.filter(user => { + let matchKeys = 0; + let matches = 0; + if (email) { + matchKeys++; + if (user.email === email) { + matches++; + } + } + if (name) { + matchKeys++; + if (user.name === name) { + matches++; + } + } + return matchKeys === matches; + }); +}; + +export const usersCollectionExactFiltered = new Selector( + usersCollectionWithExtraData, + exactSearchBy, + [] +); + const searchBy = (usersResults, { search, showSystem }) => { return usersResults.filter(user => { if (!showSystem && user.isSystemRole) { diff --git a/src/data-sources/users/index.js b/src/data-sources/users/index.js index 1f2c6aa..b71a3a4 100644 --- a/src/data-sources/users/index.js +++ b/src/data-sources/users/index.js @@ -1,5 +1,9 @@ import { userMe, userMeWithAvatar, userMeIsAdmin } from "./me"; -import { usersCollection, usersCollectionFilteredAndSorted } from "./collection"; +import { + usersCollection, + usersCollectionExactFiltered, + usersCollectionFilteredAndSorted +} from "./collection"; import { usersModels, usersModelsWithExtraData, @@ -20,7 +24,7 @@ usersModels.onChangeAny(changeDetails => { }); export { userMe, userMeWithAvatar, userMeIsAdmin }; -export { usersCollection, usersCollectionFilteredAndSorted }; +export { usersCollection, usersCollectionExactFiltered, usersCollectionFilteredAndSorted }; export { usersModels, usersModelsWithExtraData, diff --git a/src/modules/users/Layouts.js b/src/modules/users/Layouts.js index 85a0eb7..b17459a 100644 --- a/src/modules/users/Layouts.js +++ b/src/modules/users/Layouts.js @@ -1,6 +1,8 @@ import React, { Component } from "react"; import { withRouter } from "react-router-dom"; import PropTypes from "prop-types"; +import { plugins } from "reactive-data-source"; +import { pickBy, identity } from "lodash"; import { UsersContainer } from "./views/UsersContainer"; import { UsersList } from "./views/UsersList"; @@ -8,6 +10,7 @@ import { UsersList } from "./views/UsersList"; import { Component as UsersListTogglable } from "src/components/users-list-togglable"; import { UpdateUser } from "./views/UpdateUser"; import { CreateUser } from "./views/CreateUser"; +import { usersCollection, usersCollectionExactFiltered } from "src/data-sources/users"; // LIST USERS @@ -67,15 +70,67 @@ export const UpdateUserLayout = withRouter(UpdateUserLayoutBase); // CREATE USER -export const CreateUserLayoutBase = ({ history }) => { - const onCancel = () => { - history.goBack(); - }; - return ; -}; +export class CreateUserLayoutBase extends Component { + constructor(props) { + super(props); + this.state = { + newUserId: false + }; + this.createUser = this.createUser.bind(this); + this.onCancel = this.onCancel.bind(this); + } + + createUser(userData) { + return usersCollection.create(pickBy(userData, identity)).then(() => { + return usersCollectionExactFiltered + .filter({ + name: userData.name + }) + .read() + .then(results => { + this.setState(state => ({ + ...state, + newUserId: results[0]._id + })); + }); + }); + } + + onCancel() { + this.props.history.goBack(); + } + + render() { + const { newUserId } = this.state; + + return newUserId ? ( + + ) : ( + + ); + } +} CreateUserLayoutBase.propTypes = { + createError: PropTypes.instanceOf(Error), + createLoading: PropTypes.bool, history: PropTypes.any }; -export const CreateUserLayout = withRouter(CreateUserLayoutBase); +export const mapDataSourceToProps = () => { + return { + createLoading: usersCollection.create.getters.loading, + createError: usersCollection.create.getters.error + }; +}; + +export const CreateUserLayout = withRouter( + plugins.connect(mapDataSourceToProps)(CreateUserLayoutBase) +); diff --git a/src/modules/users/views/CreateUser.js b/src/modules/users/views/CreateUser.js index f108a28..543f93c 100644 --- a/src/modules/users/views/CreateUser.js +++ b/src/modules/users/views/CreateUser.js @@ -1,29 +1,18 @@ -import { pickBy, identity } from "lodash"; - import { plugins } from "reactive-data-source"; import { Component as UserComponent } from "src/components/user"; -import { - usersCollection, - userMeIsAdmin, - isValidUserName, - isValidUserEmail -} from "src/data-sources/users"; +import { userMeIsAdmin, isValidUserName, isValidUserEmail } from "src/data-sources/users"; import { nonSystemRoles } from "src/data-sources/roles"; -const createUser = userData => usersCollection.create(pickBy(userData, identity)); +import { isUserNameRepeated, isUserEmailRepeated } from "./helpers"; export const mapDataSourceToProps = () => { - const submitUser = usersCollection.create; return { currentUserIsAdmin: userMeIsAdmin.read.getters.value, roles: nonSystemRoles.read.getters.value, - onSubmit: createUser, - submitLoading: submitUser.getters.loading, - submitError: submitUser.getters.error, - isNew: true, - user: {}, + isUserEmailRepeated, + isUserNameRepeated, isValidUserName, isValidUserEmail }; diff --git a/src/modules/users/views/UpdateUser.js b/src/modules/users/views/UpdateUser.js index e6433ea..3d093da 100644 --- a/src/modules/users/views/UpdateUser.js +++ b/src/modules/users/views/UpdateUser.js @@ -13,6 +13,8 @@ import { isValidUserEmail } from "src/data-sources/users"; +import { isUserNameRepeated, isUserEmailRepeated } from "./helpers"; + const updateUser = (userData, id) => usersModels.byId(id).update(pickBy(userData, identity)); export const mapDataSourceToProps = ({ id }) => { @@ -27,6 +29,8 @@ export const mapDataSourceToProps = ({ id }) => { user: user.getters.value, userLoading: user.getters.loading, userError: user.getters.error, + isUserNameRepeated, + isUserEmailRepeated, isValidUserName, isValidUserEmail }; diff --git a/src/modules/users/views/helpers.js b/src/modules/users/views/helpers.js new file mode 100644 index 0000000..b54ad82 --- /dev/null +++ b/src/modules/users/views/helpers.js @@ -0,0 +1,23 @@ +import { usersCollectionExactFiltered } from "src/data-sources/users"; + +export const isUserNameRepeated = name => { + console.log("VALIDATING!!"); + return usersCollectionExactFiltered + .filter({ + name + }) + .read() + .then(results => { + console.log("RESULTS"); + console.log(results); + return results.length > 0; + }); +}; + +export const isUserEmailRepeated = email => + usersCollectionExactFiltered + .filter({ + email + }) + .read() + .then(results => results.length > 0);