Merged in register-validations (pull request #33)

[Frontend] Register validations
This commit is contained in:
Ivan Diaz 2016-08-07 20:55:06 -03:00
commit c3509f1158
18 changed files with 191 additions and 69 deletions

View File

@ -4,6 +4,7 @@ const UserActions = Reflux.createActions([
'checkLoginStatus',
'login',
'logout',
'signup',
'sendRecoverPassword',
'recoverPassword'
]);

View File

@ -4,10 +4,6 @@
height: 361px;
}
&__input {
margin: 10px 0;
}
&__inputs {
display: inline-block;
margin: 0 auto 20px;

View File

@ -1,14 +1,10 @@
.recover-password {
&__inputs {
display: inline-block;
margin: 0 auto 20px;
margin: 0 auto 10px;
text-align: left;
}
&__input {
margin: 10px 0 0 0;
}
&__submit-button {
margin-bottom: 40px;
}

View File

@ -1,19 +1,22 @@
import React from 'react';
import {ListenerMixin} from 'reflux';
import Reflux from 'reflux';
import ReCAPTCHA from 'react-google-recaptcha';
import CommonActions from 'actions/common-actions';
import UserActions from 'actions/user-actions';
import UserStore from 'stores/user-store';
import i18n from 'lib-app/i18n';
import Button from 'core-components/button';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
import Form from 'core-components/form';
import Input from 'core-components/input';
import Widget from 'core-components/widget';
import WidgetTransition from 'core-components/widget-transition';
const CommonActions = require('actions/common-actions');
let MainSignUpPageWidget = React.createClass({
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
componentDidMount() {
if (UserStore.isLoggedIn()) {
@ -21,38 +24,75 @@ let MainSignUpPageWidget = React.createClass({
}
},
getInitialState() {
return {
loading: false,
email: null
};
},
render() {
return (
<div className="main-signup-page">
<WidgetTransition sideToShow="front" className="main-signup-page--widget-container">
<Widget className="signup-widget" title="Register">
<Form className="signup-widget--form" onSubmit={this.handleLoginFormSubmit}>
<div className="signup-widget--inputs">
<Input {...this.getInputProps()} label="Full Name" name="name"/>
<Input {...this.getInputProps()} label="Email Address" name="email"/>
<Input {...this.getInputProps()} label="Password" name="password" password/>
<Input {...this.getInputProps()} label="Repeat Password" name="repeated-password" password/>
</div>
<div className="signup-widget--captcha">
<ReCAPTCHA sitekey="custom-site-key" onChange={function () {}}/>
</div>
<Button type="primary">SIGN UP</Button>
</Form>
</Widget>
</WidgetTransition>
<Widget className="signup-widget col-md-6 col-md-offset-3" title="Register">
<Form {...this.getFormProps()}>
<div className="signup-widget__inputs">
<Input {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
<Input {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
<Input {...this.getInputProps()} label="Password" name="password" password validation="PASSWORD" required/>
<Input {...this.getInputProps()} label="Repeat Password" name="repeated-password" password validation="REPEAT_PASSWORD" required/>
</div>
<div className="signup-widget__captcha">
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
</div>
<SubmitButton type="primary">SIGN UP</SubmitButton>
</Form>
{this.renderMessage()}
</Widget>
</div>
);
},
renderMessage() {
switch (this.state.message) {
case 'success':
return <Message type="success">{i18n('SIGNUP_SUCCESS')}</Message>;
case 'fail':
return <Message type="error">{i18n('EMAIL_EXISTS')}</Message>;
default:
return null;
}
},
getFormProps() {
return {
loading: this.state.loading,
className: 'signup-widget__form',
onSubmit: this.handleLoginFormSubmit
};
},
getInputProps() {
return {
inputType: 'secondary',
className: 'signup-widget-input'
}
className: 'signup-widget__input'
};
},
handleLoginFormSubmit(formState) {
UserActions.login(formState.email, formState.password);
this.setState({
loading: true
});
UserActions.signup(formState);
},
onUserStoreChanged(event) {
this.setState({
loading: false,
message: (event === 'SIGNUP_FAIL') ? 'fail': 'success'
});
}
});

View File

@ -1,26 +1,21 @@
.main-signup-page {
&--widget-container {
height: 525px;
width: 364px;
}
height: 669px;
.signup-widget {
padding: 30px;
text-align: center;
&--inputs {
&__form {
margin-bottom: 20px;
}
&__inputs {
display: inline-block;
margin: 0 auto;
}
&--input {
margin-bottom: 5px;
}
&--captcha {
margin-top: 30px;
margin-bottom: 20px;
&__captcha {
margin: 10px 84px 20px;
height: 78px;
width: 304px;
}

View File

@ -25,8 +25,8 @@ describe('Form component', function () {
}
function resetStubs() {
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub();
ValidationFactoryMock.validators.customValidatorMock.validate = stub();
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub();
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub();
ValidationFactoryMock.getValidator.reset();
onSubmit.reset();
}
@ -84,8 +84,8 @@ describe('Form component', function () {
});
it('should validate required fields when blurring', function () {
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.validate = stub().returns('MOCK_ERROR_2');
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');
expect(fields[0].props.error).to.equal(undefined);
expect(fields[0].props.error).to.equal(undefined);
expect(fields[0].props.error).to.equal(undefined);
@ -107,8 +107,8 @@ describe('Form component', function () {
beforeEach(function () {
onValidateErrors = stub();
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.validate = stub().returns('MOCK_ERROR_2');
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');
renderForm({
errors: {first: 'MOCK_ERROR_CONTROLLED'},
@ -172,8 +172,8 @@ describe('Form component', function () {
});
it('should validate all fields and not call onSubmit if there are errors', function () {
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.validate = stub().returns('MOCK_ERROR_2');
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');
fields[0].focus = spy(fields[0].focus);
fields[1].focus = spy(fields[1].focus);
@ -186,15 +186,15 @@ describe('Form component', function () {
});
it('should focus the first field with error', function () {
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.validate = stub().returns('MOCK_ERROR_2');
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');
fields[0].focus = spy(fields[0].focus);
fields[1].focus = spy(fields[1].focus);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
expect(fields[0].focus).to.have.been.called;
ValidationFactoryMock.validators.defaultValidatorMock.validate = stub();
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub();
fields[0].focus.reset();
fields[1].focus.reset();

View File

@ -1,5 +1,6 @@
const React = require('react');
const _ = require('lodash');
const classNames = require('classnames');
const {reactDFS, renderChildrenWithProps} = require('lib-core/react-dfs');
const ValidationFactory = require('lib-app/validations/validations-factory');
@ -49,8 +50,9 @@ const Form = React.createClass({
getProps() {
let props = _.clone(this.props);
props.className = this.getClass();
props.onSubmit = this.handleSubmit;
delete props.errors;
delete props.loading;
delete props.onValidateErrors;
@ -58,6 +60,16 @@ const Form = React.createClass({
return props;
},
getClass() {
let classes = {
'form': true
};
classes[this.props.className] = (this.props.className);
return classNames(classes);
},
getFieldProps({props, type}) {
let additionalProps = {};
@ -112,7 +124,7 @@ const Form = React.createClass({
let newErrors = _.clone(errors);
if (this.state.validations[fieldName]) {
newErrors[fieldName] = this.state.validations[fieldName].validate(form[fieldName], form);
newErrors[fieldName] = this.state.validations[fieldName].performValidation(form[fieldName], form);
}
return newErrors;

View File

@ -0,0 +1,5 @@
.form {
.input {
margin-bottom: 20px;
}
}

View File

@ -15,7 +15,8 @@ const Message = React.createClass({
getDefaultProps() {
return {
type: 'info'
type: 'info',
leftAligned: false
};
},

View File

@ -84,5 +84,24 @@ module.exports = [
};
}
}
},
{
path: 'user/signup',
time: 1000,
response: function (data) {
if (data.email.length > 15) {
return {
status: 'success',
data: {}
};
} else {
return {
status: 'fail',
message: 'Email already exists',
data: {}
};
}
}
}
];

View File

@ -7,14 +7,19 @@ export default {
'RECOVER_SENT': 'An email with recover instructions has been sent.',
'NEW_PASSWORD': 'New password',
'REPEAT_NEW_PASSWORD': 'Repeat new password',
'VALID_RECOVER': 'Password recovered successfully',
'INVALID_RECOVER': 'Invalid recover data',
'BACK_LOGIN_FORM': 'Back to login form',
'EMAIL_NOT_EXIST': 'Email does not exist',
//ERRORS
'EMAIL_NOT_EXIST': 'Email does not exist',
'ERROR_EMPTY': 'Invalid value',
'ERROR_PASSWORD': 'Invalid password',
'ERROR_NAME': 'Invalid name',
'ERROR_EMAIL': 'Invalid email',
'PASSWORD_NOT_MATCH': 'Password does not match'
'PASSWORD_NOT_MATCH': 'Password does not match',
'INVALID_RECOVER': 'Invalid recover data',
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
'VALID_RECOVER': 'Password recovered successfully',
'EMAIL_EXISTS': 'Email already exists, please try to log in or recover password'
};

View File

@ -1,5 +1,5 @@
let customValidatorMock = {validate: stub()};
let defaultValidatorMock = {validate: stub()};
let customValidatorMock = {performValidation: stub(), validate: stub()};
let defaultValidatorMock = {performValidation: stub(), validate: stub()};
export default {
getValidator: spy(function (validation) {

View File

@ -0,0 +1,17 @@
import Validator from 'lib-app/validations/validator';
class AlphaNumericValidator extends Validator {
constructor(errorKey = 'INVALID_VALUE', validator = null) {
super(validator);
this.errorKey = errorKey;
}
validate(value, form) {
let alphaMatch = /^[-\sa-zA-Z.]+$/;
if (!alphaMatch.test(value)) return this.getError(this.errorKey);
}
}
export default AlphaNumericValidator;

View File

@ -3,8 +3,9 @@ import Validator from 'lib-app/validations/validator';
class EmailValidator extends Validator {
validate(value, form) {
if (value.length < 6) return this.getError('ERROR_EMAIL');
if (value.indexOf('@') === -1) return this.getError('ERROR_EMAIL');
let emailMatch = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (!emailMatch.test(value)) return this.getError('ERROR_EMAIL');
}
}

View File

@ -1,8 +1,8 @@
import Validator from 'lib-app/validations/validator';
class LengthValidator extends Validator {
constructor(length, errorKey = 'INVALID_VALUE') {
super();
constructor(length, errorKey = 'INVALID_VALUE', validator = null) {
super(validator);
this.minlength = length;
this.errorKey = errorKey;

View File

@ -1,10 +1,12 @@
import Validator from 'lib-app/validations/validator';
import AlphaNumericValidator from 'lib-app/validations/alphanumeric-validator';
import EmailValidator from 'lib-app/validations/email-validator';
import RepeatPasswordValidator from 'lib-app/validations/repeat-password-validator';
import LengthValidator from 'lib-app/validations/length-validator';
let validators = {
'DEFAULT': new Validator(),
'NAME': new AlphaNumericValidator('ERROR_NAME', new LengthValidator(2, 'ERROR_NAME')),
'EMAIL': new EmailValidator(),
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),
'REPEAT_PASSWORD': new RepeatPasswordValidator()

View File

@ -1,6 +1,22 @@
const i18n = require('lib-app/i18n');
class Validator {
constructor(validator = null) {
this.previousValidator = validator;
}
performValidation(value, form) {
let error;
error = this.validate(value, form);
if (this.previousValidator && !error) {
error = this.previousValidator.validate(value, form);
}
return error;
}
validate(value, form) {
if (!value.length) return this.getError('ERROR_EMPTY');
}

View File

@ -12,6 +12,7 @@ const UserStore = Reflux.createStore({
this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus);
this.listenTo(UserActions.login, this.loginUser);
this.listenTo(UserActions.signup, this.signupUser);
this.listenTo(UserActions.logout, this.logoutUser);
this.listenTo(UserActions.recoverPassword, this.recoverPassword);
this.listenTo(UserActions.sendRecoverPassword, this.sendRecoverPassword);
@ -34,6 +35,13 @@ const UserStore = Reflux.createStore({
}
},
signupUser(signupData) {
return API.call({
path: 'user/signup',
data: signupData
}).then(this.handleSignupSuccess, this.handleSignupFail);
},
loginUser(loginData) {
let onSuccessLogin = (loginData.remember) ? this.handleLoginSuccessWithRemember : this.handleLoginSuccess;
let onFailedLogin = (loginData.isAutomatic) ? null : this.handleLoginFail;
@ -109,6 +117,14 @@ const UserStore = Reflux.createStore({
handleLoginFail() {
this.trigger('LOGIN_FAIL');
},
handleSignupSuccess() {
this.trigger('SIGNUP_SUCCESS');
},
handleSignupFail() {
this.trigger('SIGNUP_FAIL');
}
});