Skip to content

Commit

Permalink
Reset password
Browse files Browse the repository at this point in the history
  • Loading branch information
Remchi committed Sep 15, 2017
1 parent 09f4e89 commit 10f70b3
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 1 deletion.
14 changes: 14 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import LoginPage from "./components/pages/LoginPage";
import DashboardPage from "./components/pages/DashboardPage";
import SignupPage from "./components/pages/SignupPage";
import ConfirmationPage from "./components/pages/ConfirmationPage";
import ForgotPasswordPage from "./components/pages/ForgotPasswordPage";
import ResetPasswordPage from "./components/pages/ResetPasswordPage";
import UserRoute from "./components/routes/UserRoute";
import GuestRoute from "./components/routes/GuestRoute";

Expand All @@ -25,6 +27,18 @@ const App = ({ location }) => (
exact
component={SignupPage}
/>
<GuestRoute
location={location}
path="/forgot_password"
exact
component={ForgotPasswordPage}
/>
<GuestRoute
location={location}
path="/reset_password/:token"
exact
component={ResetPasswordPage}
/>
<UserRoute
location={location}
path="/dashboard"
Expand Down
7 changes: 7 additions & 0 deletions src/actions/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ export const confirm = token => dispatch =>
localStorage.bookwormJWT = user.token;
dispatch(userLoggedIn(user));
});

export const resetPasswordRequest = ({ email }) => () =>
api.user.resetPasswordRequest(email);

export const validateToken = token => () => api.user.validateToken(token);

export const resetPassword = data => () => api.user.resetPassword(data);
8 changes: 7 additions & 1 deletion src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export default {
signup: user =>
axios.post("/api/users", { user }).then(res => res.data.user),
confirm: token =>
axios.post("/api/auth/confirmation", { token }).then(res => res.data.user)
axios
.post("/api/auth/confirmation", { token })
.then(res => res.data.user),
resetPasswordRequest: email =>
axios.post("/api/auth/reset_password_request", { email }),
validateToken: token => axios.post("/api/auth/validate_token", { token }),
resetPassword: data => axios.post("/api/auth/reset_password", { data })
}
};
70 changes: 70 additions & 0 deletions src/components/forms/ForgotPasswordForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from "react";
import PropTypes from "prop-types";
import { Form, Button, Message } from "semantic-ui-react";
import isEmail from "validator/lib/isEmail";
import InlineError from "../messages/InlineError";

class ForgotPasswordForm extends React.Component {
state = {
data: {
email: ""
},
loading: false,
errors: {}
};

onChange = e =>
this.setState({
...this.state,
data: { ...this.state.data, [e.target.name]: e.target.value }
});

onSubmit = e => {
e.preventDefault();
const errors = this.validate(this.state.data);
this.setState({ errors });
if (Object.keys(errors).length === 0) {
this.setState({ loading: true });
this.props
.submit(this.state.data)
.catch(err =>
this.setState({ errors: err.response.data.errors, loading: false })
);
}
};

validate = data => {
const errors = {};
if (!isEmail(data.email)) errors.email = "Invalid email";
return errors;
};

render() {
const { errors, data, loading } = this.state;

return (
<Form onSubmit={this.onSubmit} loading={loading}>
{!!errors.global && <Message negative>{errors.global}</Message>}
<Form.Field error={!!errors.email}>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="email"
value={data.email}
onChange={this.onChange}
/>
{errors.email && <InlineError text={errors.email} />}
</Form.Field>
<Button primary>ForgotPasswordForm</Button>
</Form>
);
}
}

ForgotPasswordForm.propTypes = {
submit: PropTypes.func.isRequired
};

export default ForgotPasswordForm;
91 changes: 91 additions & 0 deletions src/components/forms/ResetPasswordForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from "react";
import PropTypes from "prop-types";
import { Form, Button } from "semantic-ui-react";
import InlineError from "../messages/InlineError";

class ResetPasswordForm extends React.Component {
state = {
data: {
token: this.props.token,
password: "",
passwordConfirmation: ""
},
loading: false,
errors: {}
};

onChange = e =>
this.setState({
...this.state,
data: { ...this.state.data, [e.target.name]: e.target.value }
});

onSubmit = e => {
e.preventDefault();
const errors = this.validate(this.state.data);
this.setState({ errors });
if (Object.keys(errors).length === 0) {
this.setState({ loading: true });
this.props
.submit(this.state.data)
.catch(err =>
this.setState({ errors: err.response.data.errors, loading: false })
);
}
};

validate = data => {
const errors = {};
if (!data.password) errors.password = "Can't be blank";
if (data.password !== data.passwordConfirmation)
errors.password = "Passwords must match";
return errors;
};

render() {
const { errors, data, loading } = this.state;

return (
<Form onSubmit={this.onSubmit} loading={loading}>
<Form.Field error={!!errors.password}>
<label htmlFor="password">New Password</label>
<input
type="password"
id="password"
name="password"
placeholder="your new password"
value={data.password}
onChange={this.onChange}
/>
{errors.password && <InlineError text={errors.password} />}
</Form.Field>

<Form.Field error={!!errors.passwordConfirmation}>
<label htmlFor="passwordConfirmation">
Confirm your new password
</label>
<input
type="password"
id="passwordConfirmation"
name="passwordConfirmation"
placeholder="type it again, please"
value={data.passwordConfirmation}
onChange={this.onChange}
/>
{errors.passwordConfirmation && (
<InlineError text={errors.passwordConfirmation} />
)}
</Form.Field>

<Button primary>Reset</Button>
</Form>
);
}
}

ResetPasswordForm.propTypes = {
submit: PropTypes.func.isRequired,
token: PropTypes.string.isRequired
};

export default ResetPasswordForm;
35 changes: 35 additions & 0 deletions src/components/pages/ForgotPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Message } from "semantic-ui-react";
import ForgotPasswordForm from "../forms/ForgotPasswordForm";
import { resetPasswordRequest } from "../../actions/auth";

class ForgotPasswordPage extends React.Component {
state = {
success: false
};

submit = data =>
this.props
.resetPasswordRequest(data)
.then(() => this.setState({ success: true }));

render() {
return (
<div>
{this.state.success ? (
<Message>Email has been sent.</Message>
) : (
<ForgotPasswordForm submit={this.submit} />
)}
</div>
);
}
}

ForgotPasswordPage.propTypes = {
resetPasswordRequest: PropTypes.func.isRequired
};

export default connect(null, { resetPasswordRequest })(ForgotPasswordPage);
3 changes: 3 additions & 0 deletions src/components/pages/LoginPage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import LoginForm from "../forms/LoginForm";
import { login } from "../../actions/auth";

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

<LoginForm submit={this.submit} />

<Link to="/forgot_password">Forgot Password?</Link>
</div>
);
}
Expand Down
56 changes: 56 additions & 0 deletions src/components/pages/ResetPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Message } from "semantic-ui-react";
import ResetPasswordForm from "../forms/ResetPasswordForm";
import { validateToken, resetPassword } from "../../actions/auth";

class ResetPasswordPage extends React.Component {
state = {
loading: true,
success: false
};

componentDidMount() {
this.props
.validateToken(this.props.match.params.token)
.then(() => this.setState({ loading: false, success: true }))
.catch(() => this.setState({ loading: false, success: false }));
}

submit = data =>
this.props
.resetPassword(data)
.then(() => this.props.history.push("/login"));

render() {
const { loading, success } = this.state;
const token = this.props.match.params.token;

return (
<div>
{loading && <Message>Loading</Message>}
{!loading &&
success && <ResetPasswordForm submit={this.submit} token={token} />}
{!loading && !success && <Message>Invalid Token</Message>}
</div>
);
}
}

ResetPasswordPage.propTypes = {
validateToken: PropTypes.func.isRequired,
resetPassword: PropTypes.func.isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
token: PropTypes.string.isRequired
}).isRequired
}).isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired
}).isRequired
};

export default connect(null, { validateToken, resetPassword })(
ResetPasswordPage
);

0 comments on commit 10f70b3

Please sign in to comment.