diff --git a/README.md b/README.md index a51cd55..23b4c71 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ git flow feature finish upgrade-mirror ### v1.0 - [x] Travis Testing -- [ ] Asynchronous redux-form validation (detect duplicate username/email) +- [x] Asynchronous redux-form validation (detect duplicate email) - [ ] Pagination Mechanism ### v1.0+ diff --git a/src/common/actions/formActions.js b/src/common/actions/formActions.js new file mode 100644 index 0000000..0e38c5f --- /dev/null +++ b/src/common/actions/formActions.js @@ -0,0 +1,17 @@ +import formAPI from '../api/form'; +import { pushErrors } from '../actions/errorActions'; + +export const validateForm = (formName, fieldName, value) => { + return (dispatch, getState) => { + return formAPI(getState().apiEngine) + .form(formName) + .field(fieldName, value) + .validate() + .catch((err) => { + let validationError = {}; + dispatch(pushErrors(err)); + validationError[fieldName] = 'Unable to validate'; + throw validationError; + }); + }; +}; diff --git a/src/common/api/form.js b/src/common/api/form.js new file mode 100644 index 0000000..52ea619 --- /dev/null +++ b/src/common/api/form.js @@ -0,0 +1,11 @@ +export default (apiEngine) => ({ + form: (formName) => ({ + field: (fieldName, value) => ({ + validate: () => apiEngine.post( + `/api/forms/${formName}/fields/${fieldName}/validation`, { + data: { value }, + } + ), + }), + }), +}); diff --git a/src/common/components/forms/RegisterForm.js b/src/common/components/forms/RegisterForm.js index 33fd3a0..eeab539 100644 --- a/src/common/components/forms/RegisterForm.js +++ b/src/common/components/forms/RegisterForm.js @@ -3,6 +3,7 @@ import { Field, reduxForm } from 'redux-form'; import Button from 'react-bootstrap/lib/Button'; // import validator from 'validator'; import userAPI from '../../api/user'; +import { validateForm } from '../../actions/formActions'; import { pushErrors } from '../../actions/errorActions'; import { Form, FormField, FormFooter } from '../utils/BsForm'; @@ -24,6 +25,17 @@ const validate = (values) => { return errors; }; +let asyncValidate = (values, dispatch) => { + return dispatch(validateForm('register', 'email', values.email)) + .then((json) => { + let validationError = {}; + if (!json.isPassed) { + validationError.email = json.message; + throw validationError; + } + }); +}; + class RegisterForm extends Component { constructor(props) { super(props); @@ -46,6 +58,7 @@ class RegisterForm extends Component { const { handleSubmit, pristine, + asyncValidating, submitting, invalid, } = this.props; @@ -74,7 +87,10 @@ class RegisterForm extends Component { placeholder="Password" /> - @@ -91,4 +107,6 @@ RegisterForm.contextTypes = { export default reduxForm({ form: 'register', validate, + asyncValidate, + asyncBlurFields: ['email'], })(RegisterForm); diff --git a/src/server/controllers/formValidation.js b/src/server/controllers/formValidation.js new file mode 100644 index 0000000..b4f1631 --- /dev/null +++ b/src/server/controllers/formValidation.js @@ -0,0 +1,23 @@ +import { handleDbError } from '../decorators/handleError'; +import User from '../models/User'; + +export default { + register: { + email(req, res) { + User.findOne({ + 'email.value': req.body.value, + }, handleDbError(res)((user) => { + if (user) { + res.json({ + isPassed: false, + message: 'The email is already registered', + }); + } else { + res.json({ + isPassed: true, + }); + } + })); + }, + }, +}; diff --git a/src/server/routes/api.js b/src/server/routes/api.js index 574969d..97740fe 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -3,6 +3,7 @@ import bodyParser from '../middlewares/bodyParser'; import authRequired from '../middlewares/authRequired'; import fileUpload from '../middlewares/fileUpload'; import userController from '../controllers/user'; +import formValidationController from '../controllers/formValidation'; import localeController from '../controllers/locale'; import todoController from '../controllers/todo'; @@ -24,6 +25,10 @@ export default ({ app }) => { filename: 'avatar.jpg', }).single('avatar'), userController.uploadAvatar); + app.post('/api/forms/register/fields/email/validation', + bodyParser.json, + formValidationController.register.email + ); app.get('/api/locale/:locale', localeController.show); app.post('/api/todo', bodyParser.json, todoController.create); app.get('/api/todo', todoController.list);