Skip to content

Commit

Permalink
Merge branch 'feature/forget-password' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
gocreating committed Oct 29, 2016
2 parents 902cdf1 + 0460f7a commit 91f3eb8
Show file tree
Hide file tree
Showing 24 changed files with 553 additions and 88 deletions.
6 changes: 5 additions & 1 deletion configs/project/server.js
Expand Up @@ -18,10 +18,14 @@ if (process.env.TRAVIS) {
secret: '4eO5viHe23',
expiresIn: 60 * 60 * 24 * 3, // in seconds
},
verification: {
verifyEmail: {
secret: 'df5s6sdHdjJdRg56',
expiresIn: 60 * 60, // in seconds
},
resetPassword: {
secret: 'FsgWqLhX0Z6JvJfPYwPZ',
expiresIn: 60 * 60, // in seconds
},
},
mongo: require('./mongo/credential'),
firebase: require('./firebase/credential.json'),
Expand Down
15 changes: 13 additions & 2 deletions src/common/api/user.js
@@ -1,10 +1,21 @@
export default (apiEngine) => ({
list: ({ page }) => apiEngine.get('/api/users', { params: { page } }),
register: (user) => apiEngine.post('/api/users', { data: user }),
verify: ({ token }) => apiEngine.post('/api/users/verification', {
data: { verificationToken: token },
verifyEmail: ({ token }) => apiEngine.post('/api/users/email/verify', {
data: { verifyEmailToken: token },
}),
login: (user) => apiEngine.post('/api/users/login', { data: user }),
requestResetPassword: (form) => (
apiEngine.post('/api/users/password/request-reset', { data: form })
),
resetPassword: ({ token, ...form }) => (
apiEngine.put('/api/users/password', {
data: {
resetPasswordToken: token,
...form,
},
})
),
logout: () => apiEngine.get('/api/users/logout'),
read: () => apiEngine.get('/api/users/me'),
update: (user) => apiEngine.put('/api/users/me', { data: user }),
Expand Down
115 changes: 115 additions & 0 deletions src/common/components/forms/user/ForgetPasswordForm.js
@@ -0,0 +1,115 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { Field, reduxForm } from 'redux-form';
import Alert from 'react-bootstrap/lib/Alert';
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';
import configs from '../../../../../configs/project/client';

export let validate = (values) => {
let errors = {};

// if (values.email && !validator.isEmail(values.email)) {
// errors.email = 'Not an email';
// }

if (!values.email) {
errors.email = 'Required';
}

if (configs.recaptcha && !values.recaptcha) {
errors.recaptcha = 'Required';
}

return errors;
};

let asyncValidate = (values, dispatch) => {
return dispatch(validateForm('userForgetPassword', 'email', values.email))
.then((json) => {
let validationError = {};
if (!json.isPassed) {
validationError.email = json.message;
throw validationError;
}
});
};

class ForgetPasswordForm extends Component {
constructor() {
super();
this.handleSubmit = this._handleSubmit.bind(this);
}

_handleSubmit(formData) {
let { dispatch, apiEngine, initialize } = this.props;

return userAPI(apiEngine)
.requestResetPassword(formData)
.catch((err) => {
dispatch(pushErrors(err));
throw err;
})
.then((json) => {
initialize({
email: '',
});
});
}

render() {
const {
handleSubmit,
submitSucceeded,
submitFailed,
error,
pristine,
submitting,
invalid,
} = this.props;

return (
<Form horizontal onSubmit={handleSubmit(this.handleSubmit)}>
{submitSucceeded && (
<Alert bsStyle="success">A reset link is sent</Alert>
)}
{submitFailed && error && (<Alert bsStyle="danger">{error}</Alert>)}
<Field
label="Email"
name="email"
component={FormField}
type="text"
placeholder="Email"
/>
<Field
label=" "
name="recaptcha"
component={FormField}
type="recaptcha"
/>
<FormFooter>
<Button type="submit" disabled={pristine || submitting || invalid}>
Request An Email to Reset My Password
</Button>
<Link to="/user/login">
<Button bsStyle="link">Cancel</Button>
</Link>
</FormFooter>
</Form>
);
}
};

export default reduxForm({
form: 'userForgetPassword',
validate,
asyncValidate,
asyncBlurFields: ['email'],
})(connect(state => ({
apiEngine: state.apiEngine,
}))(ForgetPasswordForm));
6 changes: 5 additions & 1 deletion src/common/components/forms/user/LoginForm.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { push } from 'react-router-redux';
import { Field, reduxForm, SubmissionError } from 'redux-form';
import Alert from 'react-bootstrap/lib/Alert';
Expand Down Expand Up @@ -100,14 +101,17 @@ class LoginForm extends Component {
<Button type="submit" disabled={pristine || submitting || invalid}>
Login
</Button>
<Link to="/user/password/forget">
<Button bsStyle="link">Forget password?</Button>
</Link>
</FormFooter>
</Form>
);
}
};

export default reduxForm({
form: 'login',
form: 'userLogin',
validate,
})(connect(state => ({
apiEngine: state.apiEngine,
Expand Down
4 changes: 2 additions & 2 deletions src/common/components/forms/user/RegisterForm.js
Expand Up @@ -34,7 +34,7 @@ const validate = (values) => {
};

let asyncValidate = (values, dispatch) => {
return dispatch(validateForm('register', 'email', values.email))
return dispatch(validateForm('userRegister', 'email', values.email))
.then((json) => {
let validationError = {};
if (!json.isPassed) {
Expand Down Expand Up @@ -119,7 +119,7 @@ class RegisterForm extends Component {
};

export default reduxForm({
form: 'register',
form: 'userRegister',
validate,
asyncValidate,
asyncBlurFields: ['email'],
Expand Down
114 changes: 114 additions & 0 deletions src/common/components/forms/user/ResetPasswordForm.js
@@ -0,0 +1,114 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { Field, reduxForm } from 'redux-form';
import Alert from 'react-bootstrap/lib/Alert';
import Button from 'react-bootstrap/lib/Button';
import userAPI from '../../../api/user';
import { pushErrors } from '../../../actions/errorActions';
import { Form, FormField, FormFooter } from '../../utils/BsForm';

export const validate = (values) => {
const errors = {};

if (
values.newPasswordConfirm &&
values.newPassword !== values.newPasswordConfirm
) {
errors.newPassword = errors.newPasswordConfirm = 'Password Not Matched';
}

if (!values.newPassword) {
errors.newPassword = 'Required';
}

if (!values.newPasswordConfirm) {
errors.newPasswordConfirm = 'Required';
}

return errors;
};

class ChangePasswordForm extends Component {
constructor(props) {
super(props);
this.handleSubmit = this._handleSubmit.bind(this);
}

_handleSubmit(formData) {
let { dispatch, apiEngine, routing, initialize } = this.props;
let location = routing.locationBeforeTransitions;

return userAPI(apiEngine)
.resetPassword({
...formData,
token: location.query.token,
})
.catch((err) => {
dispatch(pushErrors(err));
throw err;
})
.then((json) => {
initialize({
newPassword: '',
newPasswordConfirm: '',
});
});
}

render() {
const {
handleSubmit,
submitSucceeded,
submitFailed,
error,
pristine,
submitting,
invalid,
} = this.props;

return (
<Form horizontal onSubmit={handleSubmit(this.handleSubmit)}>
{submitSucceeded && (
<Alert bsStyle="success">
Password Changed.
Go to <Link to="/user/login">login page</Link> now.
</Alert>
)}
{submitFailed && error && (<Alert bsStyle="danger">{error}</Alert>)}
<Field
label="New Password"
name="newPassword"
component={FormField}
type="password"
disabled={submitSucceeded}
placeholder="New Password"
/>
<Field
label="New Password Confirm"
name="newPasswordConfirm"
component={FormField}
type="password"
disabled={submitSucceeded}
placeholder="New Password Confirm"
/>
<FormFooter>
<Button type="submit" disabled={pristine || submitting || invalid}>
Reset
{submitting && (
<i className="fa fa-spinner fa-spin" aria-hidden="true" />
)}
</Button>
</FormFooter>
</Form>
);
}
};

export default reduxForm({
form: 'userResetPassword',
validate,
})(connect(state => ({
apiEngine: state.apiEngine,
routing: state.routing,
}))(ChangePasswordForm));
13 changes: 13 additions & 0 deletions src/common/components/pages/user/ForgetPasswordPage.js
@@ -0,0 +1,13 @@
import React from 'react';
import PageHeader from 'react-bootstrap/lib/PageHeader';
import PageLayout from '../../layouts/PageLayout';
import ForgetPasswordForm from '../../forms/user/ForgetPasswordForm';

let ForgetPasswordPage = () => (
<PageLayout>
<PageHeader>Forget Password</PageHeader>
<ForgetPasswordForm />
</PageLayout>
);

export default ForgetPasswordPage;
13 changes: 13 additions & 0 deletions src/common/components/pages/user/ResetPasswordPage.js
@@ -0,0 +1,13 @@
import React from 'react';
import PageHeader from 'react-bootstrap/lib/PageHeader';
import PageLayout from '../../layouts/PageLayout';
import ResetPasswordForm from '../../forms/user/ResetPasswordForm';

let ResetPasswordPage = (props) => (
<PageLayout>
<PageHeader>Reset Password</PageHeader>
<ResetPasswordForm />
</PageLayout>
);

export default ResetPasswordPage;
Expand Up @@ -19,7 +19,7 @@ class VerificationPage extends React.Component {
let { dispatch, apiEngine, location } = this.props;
if (process.env.BROWSER) {
userAPI(apiEngine)
.verify({ token: location.query.token })
.verifyEmail({ token: location.query.token })
.catch((err) => {
this.setState({
isVerifying: false,
Expand Down
11 changes: 11 additions & 0 deletions src/common/routes/user/forgetPassword.js
@@ -0,0 +1,11 @@
export default (store) => ({
path: 'password/forget',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(
null,
require('../../components/pages/user/ForgetPasswordPage').default
);
});
},
});
4 changes: 3 additions & 1 deletion src/common/routes/user/index.js
Expand Up @@ -4,9 +4,11 @@ export default (store) => ({
require.ensure([], (require) => {
cb(null, [
require('./register').default(store),
require('./verification').default(store),
require('./verifyEmail').default(store),
require('./login').default(store),
require('./edit').default(store),
require('./forgetPassword').default(store),
require('./resetPassword').default(store),
require('./logout').default(store),
require('./me').default(store),
]);
Expand Down
11 changes: 11 additions & 0 deletions src/common/routes/user/resetPassword.js
@@ -0,0 +1,11 @@
export default (store) => ({
path: 'password/reset',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(
null,
require('../../components/pages/user/ResetPasswordPage').default
);
});
},
});
@@ -1,8 +1,8 @@
export default (store) => ({
path: 'verification',
path: 'email/verify',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(null, require('../../components/pages/user/VerificationPage').default);
cb(null, require('../../components/pages/user/VerifyEmailPage').default);
});
},
});

0 comments on commit 91f3eb8

Please sign in to comment.