Skip to content

Commit 10f70b3

Browse files
committed
Reset password
1 parent 09f4e89 commit 10f70b3

8 files changed

Lines changed: 283 additions & 1 deletion

File tree

src/App.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import LoginPage from "./components/pages/LoginPage";
66
import DashboardPage from "./components/pages/DashboardPage";
77
import SignupPage from "./components/pages/SignupPage";
88
import ConfirmationPage from "./components/pages/ConfirmationPage";
9+
import ForgotPasswordPage from "./components/pages/ForgotPasswordPage";
10+
import ResetPasswordPage from "./components/pages/ResetPasswordPage";
911
import UserRoute from "./components/routes/UserRoute";
1012
import GuestRoute from "./components/routes/GuestRoute";
1113

@@ -25,6 +27,18 @@ const App = ({ location }) => (
2527
exact
2628
component={SignupPage}
2729
/>
30+
<GuestRoute
31+
location={location}
32+
path="/forgot_password"
33+
exact
34+
component={ForgotPasswordPage}
35+
/>
36+
<GuestRoute
37+
location={location}
38+
path="/reset_password/:token"
39+
exact
40+
component={ResetPasswordPage}
41+
/>
2842
<UserRoute
2943
location={location}
3044
path="/dashboard"

src/actions/auth.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@ export const confirm = token => dispatch =>
2626
localStorage.bookwormJWT = user.token;
2727
dispatch(userLoggedIn(user));
2828
});
29+
30+
export const resetPasswordRequest = ({ email }) => () =>
31+
api.user.resetPasswordRequest(email);
32+
33+
export const validateToken = token => () => api.user.validateToken(token);
34+
35+
export const resetPassword = data => () => api.user.resetPassword(data);

src/api.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ export default {
77
signup: user =>
88
axios.post("/api/users", { user }).then(res => res.data.user),
99
confirm: token =>
10-
axios.post("/api/auth/confirmation", { token }).then(res => res.data.user)
10+
axios
11+
.post("/api/auth/confirmation", { token })
12+
.then(res => res.data.user),
13+
resetPasswordRequest: email =>
14+
axios.post("/api/auth/reset_password_request", { email }),
15+
validateToken: token => axios.post("/api/auth/validate_token", { token }),
16+
resetPassword: data => axios.post("/api/auth/reset_password", { data })
1117
}
1218
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import { Form, Button, Message } from "semantic-ui-react";
4+
import isEmail from "validator/lib/isEmail";
5+
import InlineError from "../messages/InlineError";
6+
7+
class ForgotPasswordForm extends React.Component {
8+
state = {
9+
data: {
10+
email: ""
11+
},
12+
loading: false,
13+
errors: {}
14+
};
15+
16+
onChange = e =>
17+
this.setState({
18+
...this.state,
19+
data: { ...this.state.data, [e.target.name]: e.target.value }
20+
});
21+
22+
onSubmit = e => {
23+
e.preventDefault();
24+
const errors = this.validate(this.state.data);
25+
this.setState({ errors });
26+
if (Object.keys(errors).length === 0) {
27+
this.setState({ loading: true });
28+
this.props
29+
.submit(this.state.data)
30+
.catch(err =>
31+
this.setState({ errors: err.response.data.errors, loading: false })
32+
);
33+
}
34+
};
35+
36+
validate = data => {
37+
const errors = {};
38+
if (!isEmail(data.email)) errors.email = "Invalid email";
39+
return errors;
40+
};
41+
42+
render() {
43+
const { errors, data, loading } = this.state;
44+
45+
return (
46+
<Form onSubmit={this.onSubmit} loading={loading}>
47+
{!!errors.global && <Message negative>{errors.global}</Message>}
48+
<Form.Field error={!!errors.email}>
49+
<label htmlFor="email">Email</label>
50+
<input
51+
type="email"
52+
id="email"
53+
name="email"
54+
placeholder="email"
55+
value={data.email}
56+
onChange={this.onChange}
57+
/>
58+
{errors.email && <InlineError text={errors.email} />}
59+
</Form.Field>
60+
<Button primary>ForgotPasswordForm</Button>
61+
</Form>
62+
);
63+
}
64+
}
65+
66+
ForgotPasswordForm.propTypes = {
67+
submit: PropTypes.func.isRequired
68+
};
69+
70+
export default ForgotPasswordForm;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import { Form, Button } from "semantic-ui-react";
4+
import InlineError from "../messages/InlineError";
5+
6+
class ResetPasswordForm extends React.Component {
7+
state = {
8+
data: {
9+
token: this.props.token,
10+
password: "",
11+
passwordConfirmation: ""
12+
},
13+
loading: false,
14+
errors: {}
15+
};
16+
17+
onChange = e =>
18+
this.setState({
19+
...this.state,
20+
data: { ...this.state.data, [e.target.name]: e.target.value }
21+
});
22+
23+
onSubmit = e => {
24+
e.preventDefault();
25+
const errors = this.validate(this.state.data);
26+
this.setState({ errors });
27+
if (Object.keys(errors).length === 0) {
28+
this.setState({ loading: true });
29+
this.props
30+
.submit(this.state.data)
31+
.catch(err =>
32+
this.setState({ errors: err.response.data.errors, loading: false })
33+
);
34+
}
35+
};
36+
37+
validate = data => {
38+
const errors = {};
39+
if (!data.password) errors.password = "Can't be blank";
40+
if (data.password !== data.passwordConfirmation)
41+
errors.password = "Passwords must match";
42+
return errors;
43+
};
44+
45+
render() {
46+
const { errors, data, loading } = this.state;
47+
48+
return (
49+
<Form onSubmit={this.onSubmit} loading={loading}>
50+
<Form.Field error={!!errors.password}>
51+
<label htmlFor="password">New Password</label>
52+
<input
53+
type="password"
54+
id="password"
55+
name="password"
56+
placeholder="your new password"
57+
value={data.password}
58+
onChange={this.onChange}
59+
/>
60+
{errors.password && <InlineError text={errors.password} />}
61+
</Form.Field>
62+
63+
<Form.Field error={!!errors.passwordConfirmation}>
64+
<label htmlFor="passwordConfirmation">
65+
Confirm your new password
66+
</label>
67+
<input
68+
type="password"
69+
id="passwordConfirmation"
70+
name="passwordConfirmation"
71+
placeholder="type it again, please"
72+
value={data.passwordConfirmation}
73+
onChange={this.onChange}
74+
/>
75+
{errors.passwordConfirmation && (
76+
<InlineError text={errors.passwordConfirmation} />
77+
)}
78+
</Form.Field>
79+
80+
<Button primary>Reset</Button>
81+
</Form>
82+
);
83+
}
84+
}
85+
86+
ResetPasswordForm.propTypes = {
87+
submit: PropTypes.func.isRequired,
88+
token: PropTypes.string.isRequired
89+
};
90+
91+
export default ResetPasswordForm;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import { connect } from "react-redux";
4+
import { Message } from "semantic-ui-react";
5+
import ForgotPasswordForm from "../forms/ForgotPasswordForm";
6+
import { resetPasswordRequest } from "../../actions/auth";
7+
8+
class ForgotPasswordPage extends React.Component {
9+
state = {
10+
success: false
11+
};
12+
13+
submit = data =>
14+
this.props
15+
.resetPasswordRequest(data)
16+
.then(() => this.setState({ success: true }));
17+
18+
render() {
19+
return (
20+
<div>
21+
{this.state.success ? (
22+
<Message>Email has been sent.</Message>
23+
) : (
24+
<ForgotPasswordForm submit={this.submit} />
25+
)}
26+
</div>
27+
);
28+
}
29+
}
30+
31+
ForgotPasswordPage.propTypes = {
32+
resetPasswordRequest: PropTypes.func.isRequired
33+
};
34+
35+
export default connect(null, { resetPasswordRequest })(ForgotPasswordPage);

src/components/pages/LoginPage.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import PropTypes from "prop-types";
33
import { connect } from "react-redux";
4+
import { Link } from "react-router-dom";
45
import LoginForm from "../forms/LoginForm";
56
import { login } from "../../actions/auth";
67

@@ -14,6 +15,8 @@ class LoginPage extends React.Component {
1415
<h1>Login page</h1>
1516

1617
<LoginForm submit={this.submit} />
18+
19+
<Link to="/forgot_password">Forgot Password?</Link>
1720
</div>
1821
);
1922
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import { connect } from "react-redux";
4+
import { Message } from "semantic-ui-react";
5+
import ResetPasswordForm from "../forms/ResetPasswordForm";
6+
import { validateToken, resetPassword } from "../../actions/auth";
7+
8+
class ResetPasswordPage extends React.Component {
9+
state = {
10+
loading: true,
11+
success: false
12+
};
13+
14+
componentDidMount() {
15+
this.props
16+
.validateToken(this.props.match.params.token)
17+
.then(() => this.setState({ loading: false, success: true }))
18+
.catch(() => this.setState({ loading: false, success: false }));
19+
}
20+
21+
submit = data =>
22+
this.props
23+
.resetPassword(data)
24+
.then(() => this.props.history.push("/login"));
25+
26+
render() {
27+
const { loading, success } = this.state;
28+
const token = this.props.match.params.token;
29+
30+
return (
31+
<div>
32+
{loading && <Message>Loading</Message>}
33+
{!loading &&
34+
success && <ResetPasswordForm submit={this.submit} token={token} />}
35+
{!loading && !success && <Message>Invalid Token</Message>}
36+
</div>
37+
);
38+
}
39+
}
40+
41+
ResetPasswordPage.propTypes = {
42+
validateToken: PropTypes.func.isRequired,
43+
resetPassword: PropTypes.func.isRequired,
44+
match: PropTypes.shape({
45+
params: PropTypes.shape({
46+
token: PropTypes.string.isRequired
47+
}).isRequired
48+
}).isRequired,
49+
history: PropTypes.shape({
50+
push: PropTypes.func.isRequired
51+
}).isRequired
52+
};
53+
54+
export default connect(null, { validateToken, resetPassword })(
55+
ResetPasswordPage
56+
);

0 commit comments

Comments
 (0)