Skip to content

Commit 91f3eb8

Browse files
committed
Merge branch 'feature/forget-password' into dev
2 parents 902cdf1 + 0460f7a commit 91f3eb8

24 files changed

+553
-88
lines changed

configs/project/server.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ if (process.env.TRAVIS) {
1818
secret: '4eO5viHe23',
1919
expiresIn: 60 * 60 * 24 * 3, // in seconds
2020
},
21-
verification: {
21+
verifyEmail: {
2222
secret: 'df5s6sdHdjJdRg56',
2323
expiresIn: 60 * 60, // in seconds
2424
},
25+
resetPassword: {
26+
secret: 'FsgWqLhX0Z6JvJfPYwPZ',
27+
expiresIn: 60 * 60, // in seconds
28+
},
2529
},
2630
mongo: require('./mongo/credential'),
2731
firebase: require('./firebase/credential.json'),

src/common/api/user.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
export default (apiEngine) => ({
22
list: ({ page }) => apiEngine.get('/api/users', { params: { page } }),
33
register: (user) => apiEngine.post('/api/users', { data: user }),
4-
verify: ({ token }) => apiEngine.post('/api/users/verification', {
5-
data: { verificationToken: token },
4+
verifyEmail: ({ token }) => apiEngine.post('/api/users/email/verify', {
5+
data: { verifyEmailToken: token },
66
}),
77
login: (user) => apiEngine.post('/api/users/login', { data: user }),
8+
requestResetPassword: (form) => (
9+
apiEngine.post('/api/users/password/request-reset', { data: form })
10+
),
11+
resetPassword: ({ token, ...form }) => (
12+
apiEngine.put('/api/users/password', {
13+
data: {
14+
resetPasswordToken: token,
15+
...form,
16+
},
17+
})
18+
),
819
logout: () => apiEngine.get('/api/users/logout'),
920
read: () => apiEngine.get('/api/users/me'),
1021
update: (user) => apiEngine.put('/api/users/me', { data: user }),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { Component } from 'react';
2+
import { connect } from 'react-redux';
3+
import { Link } from 'react-router';
4+
import { Field, reduxForm } from 'redux-form';
5+
import Alert from 'react-bootstrap/lib/Alert';
6+
import Button from 'react-bootstrap/lib/Button';
7+
// import validator from 'validator';
8+
import userAPI from '../../../api/user';
9+
import { validateForm } from '../../../actions/formActions';
10+
import { pushErrors } from '../../../actions/errorActions';
11+
import { Form, FormField, FormFooter } from '../../utils/BsForm';
12+
import configs from '../../../../../configs/project/client';
13+
14+
export let validate = (values) => {
15+
let errors = {};
16+
17+
// if (values.email && !validator.isEmail(values.email)) {
18+
// errors.email = 'Not an email';
19+
// }
20+
21+
if (!values.email) {
22+
errors.email = 'Required';
23+
}
24+
25+
if (configs.recaptcha && !values.recaptcha) {
26+
errors.recaptcha = 'Required';
27+
}
28+
29+
return errors;
30+
};
31+
32+
let asyncValidate = (values, dispatch) => {
33+
return dispatch(validateForm('userForgetPassword', 'email', values.email))
34+
.then((json) => {
35+
let validationError = {};
36+
if (!json.isPassed) {
37+
validationError.email = json.message;
38+
throw validationError;
39+
}
40+
});
41+
};
42+
43+
class ForgetPasswordForm extends Component {
44+
constructor() {
45+
super();
46+
this.handleSubmit = this._handleSubmit.bind(this);
47+
}
48+
49+
_handleSubmit(formData) {
50+
let { dispatch, apiEngine, initialize } = this.props;
51+
52+
return userAPI(apiEngine)
53+
.requestResetPassword(formData)
54+
.catch((err) => {
55+
dispatch(pushErrors(err));
56+
throw err;
57+
})
58+
.then((json) => {
59+
initialize({
60+
email: '',
61+
});
62+
});
63+
}
64+
65+
render() {
66+
const {
67+
handleSubmit,
68+
submitSucceeded,
69+
submitFailed,
70+
error,
71+
pristine,
72+
submitting,
73+
invalid,
74+
} = this.props;
75+
76+
return (
77+
<Form horizontal onSubmit={handleSubmit(this.handleSubmit)}>
78+
{submitSucceeded && (
79+
<Alert bsStyle="success">A reset link is sent</Alert>
80+
)}
81+
{submitFailed && error && (<Alert bsStyle="danger">{error}</Alert>)}
82+
<Field
83+
label="Email"
84+
name="email"
85+
component={FormField}
86+
type="text"
87+
placeholder="Email"
88+
/>
89+
<Field
90+
label=" "
91+
name="recaptcha"
92+
component={FormField}
93+
type="recaptcha"
94+
/>
95+
<FormFooter>
96+
<Button type="submit" disabled={pristine || submitting || invalid}>
97+
Request An Email to Reset My Password
98+
</Button>
99+
<Link to="/user/login">
100+
<Button bsStyle="link">Cancel</Button>
101+
</Link>
102+
</FormFooter>
103+
</Form>
104+
);
105+
}
106+
};
107+
108+
export default reduxForm({
109+
form: 'userForgetPassword',
110+
validate,
111+
asyncValidate,
112+
asyncBlurFields: ['email'],
113+
})(connect(state => ({
114+
apiEngine: state.apiEngine,
115+
}))(ForgetPasswordForm));

src/common/components/forms/user/LoginForm.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { Component } from 'react';
22
import { connect } from 'react-redux';
3+
import { Link } from 'react-router';
34
import { push } from 'react-router-redux';
45
import { Field, reduxForm, SubmissionError } from 'redux-form';
56
import Alert from 'react-bootstrap/lib/Alert';
@@ -100,14 +101,17 @@ class LoginForm extends Component {
100101
<Button type="submit" disabled={pristine || submitting || invalid}>
101102
Login
102103
</Button>
104+
<Link to="/user/password/forget">
105+
<Button bsStyle="link">Forget password?</Button>
106+
</Link>
103107
</FormFooter>
104108
</Form>
105109
);
106110
}
107111
};
108112

109113
export default reduxForm({
110-
form: 'login',
114+
form: 'userLogin',
111115
validate,
112116
})(connect(state => ({
113117
apiEngine: state.apiEngine,

src/common/components/forms/user/RegisterForm.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const validate = (values) => {
3434
};
3535

3636
let asyncValidate = (values, dispatch) => {
37-
return dispatch(validateForm('register', 'email', values.email))
37+
return dispatch(validateForm('userRegister', 'email', values.email))
3838
.then((json) => {
3939
let validationError = {};
4040
if (!json.isPassed) {
@@ -119,7 +119,7 @@ class RegisterForm extends Component {
119119
};
120120

121121
export default reduxForm({
122-
form: 'register',
122+
form: 'userRegister',
123123
validate,
124124
asyncValidate,
125125
asyncBlurFields: ['email'],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React, { Component } from 'react';
2+
import { connect } from 'react-redux';
3+
import { Link } from 'react-router';
4+
import { Field, reduxForm } from 'redux-form';
5+
import Alert from 'react-bootstrap/lib/Alert';
6+
import Button from 'react-bootstrap/lib/Button';
7+
import userAPI from '../../../api/user';
8+
import { pushErrors } from '../../../actions/errorActions';
9+
import { Form, FormField, FormFooter } from '../../utils/BsForm';
10+
11+
export const validate = (values) => {
12+
const errors = {};
13+
14+
if (
15+
values.newPasswordConfirm &&
16+
values.newPassword !== values.newPasswordConfirm
17+
) {
18+
errors.newPassword = errors.newPasswordConfirm = 'Password Not Matched';
19+
}
20+
21+
if (!values.newPassword) {
22+
errors.newPassword = 'Required';
23+
}
24+
25+
if (!values.newPasswordConfirm) {
26+
errors.newPasswordConfirm = 'Required';
27+
}
28+
29+
return errors;
30+
};
31+
32+
class ChangePasswordForm extends Component {
33+
constructor(props) {
34+
super(props);
35+
this.handleSubmit = this._handleSubmit.bind(this);
36+
}
37+
38+
_handleSubmit(formData) {
39+
let { dispatch, apiEngine, routing, initialize } = this.props;
40+
let location = routing.locationBeforeTransitions;
41+
42+
return userAPI(apiEngine)
43+
.resetPassword({
44+
...formData,
45+
token: location.query.token,
46+
})
47+
.catch((err) => {
48+
dispatch(pushErrors(err));
49+
throw err;
50+
})
51+
.then((json) => {
52+
initialize({
53+
newPassword: '',
54+
newPasswordConfirm: '',
55+
});
56+
});
57+
}
58+
59+
render() {
60+
const {
61+
handleSubmit,
62+
submitSucceeded,
63+
submitFailed,
64+
error,
65+
pristine,
66+
submitting,
67+
invalid,
68+
} = this.props;
69+
70+
return (
71+
<Form horizontal onSubmit={handleSubmit(this.handleSubmit)}>
72+
{submitSucceeded && (
73+
<Alert bsStyle="success">
74+
Password Changed.
75+
Go to <Link to="/user/login">login page</Link> now.
76+
</Alert>
77+
)}
78+
{submitFailed && error && (<Alert bsStyle="danger">{error}</Alert>)}
79+
<Field
80+
label="New Password"
81+
name="newPassword"
82+
component={FormField}
83+
type="password"
84+
disabled={submitSucceeded}
85+
placeholder="New Password"
86+
/>
87+
<Field
88+
label="New Password Confirm"
89+
name="newPasswordConfirm"
90+
component={FormField}
91+
type="password"
92+
disabled={submitSucceeded}
93+
placeholder="New Password Confirm"
94+
/>
95+
<FormFooter>
96+
<Button type="submit" disabled={pristine || submitting || invalid}>
97+
Reset
98+
{submitting && (
99+
<i className="fa fa-spinner fa-spin" aria-hidden="true" />
100+
)}
101+
</Button>
102+
</FormFooter>
103+
</Form>
104+
);
105+
}
106+
};
107+
108+
export default reduxForm({
109+
form: 'userResetPassword',
110+
validate,
111+
})(connect(state => ({
112+
apiEngine: state.apiEngine,
113+
routing: state.routing,
114+
}))(ChangePasswordForm));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import PageHeader from 'react-bootstrap/lib/PageHeader';
3+
import PageLayout from '../../layouts/PageLayout';
4+
import ForgetPasswordForm from '../../forms/user/ForgetPasswordForm';
5+
6+
let ForgetPasswordPage = () => (
7+
<PageLayout>
8+
<PageHeader>Forget Password</PageHeader>
9+
<ForgetPasswordForm />
10+
</PageLayout>
11+
);
12+
13+
export default ForgetPasswordPage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import PageHeader from 'react-bootstrap/lib/PageHeader';
3+
import PageLayout from '../../layouts/PageLayout';
4+
import ResetPasswordForm from '../../forms/user/ResetPasswordForm';
5+
6+
let ResetPasswordPage = (props) => (
7+
<PageLayout>
8+
<PageHeader>Reset Password</PageHeader>
9+
<ResetPasswordForm />
10+
</PageLayout>
11+
);
12+
13+
export default ResetPasswordPage;

src/common/components/pages/user/VerificationPage.js renamed to src/common/components/pages/user/VerifyEmailPage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class VerificationPage extends React.Component {
1919
let { dispatch, apiEngine, location } = this.props;
2020
if (process.env.BROWSER) {
2121
userAPI(apiEngine)
22-
.verify({ token: location.query.token })
22+
.verifyEmail({ token: location.query.token })
2323
.catch((err) => {
2424
this.setState({
2525
isVerifying: false,
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default (store) => ({
2+
path: 'password/forget',
3+
getComponent(nextState, cb) {
4+
require.ensure([], (require) => {
5+
cb(
6+
null,
7+
require('../../components/pages/user/ForgetPasswordPage').default
8+
);
9+
});
10+
},
11+
});

src/common/routes/user/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ export default (store) => ({
44
require.ensure([], (require) => {
55
cb(null, [
66
require('./register').default(store),
7-
require('./verification').default(store),
7+
require('./verifyEmail').default(store),
88
require('./login').default(store),
99
require('./edit').default(store),
10+
require('./forgetPassword').default(store),
11+
require('./resetPassword').default(store),
1012
require('./logout').default(store),
1113
require('./me').default(store),
1214
]);
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default (store) => ({
2+
path: 'password/reset',
3+
getComponent(nextState, cb) {
4+
require.ensure([], (require) => {
5+
cb(
6+
null,
7+
require('../../components/pages/user/ResetPasswordPage').default
8+
);
9+
});
10+
},
11+
});
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export default (store) => ({
2-
path: 'verification',
2+
path: 'email/verify',
33
getComponent(nextState, cb) {
44
require.ensure([], (require) => {
5-
cb(null, require('../../components/pages/user/VerificationPage').default);
5+
cb(null, require('../../components/pages/user/VerifyEmailPage').default);
66
});
77
},
88
});

0 commit comments

Comments
 (0)