Skip to content

Commit

Permalink
Merge pull request #23 from andela/ft-reset-password-160609514
Browse files Browse the repository at this point in the history
#160609514 Reset password by sending an email link
  • Loading branch information
dondrzzy committed Nov 22, 2018
2 parents 1c3190b + d5f471c commit d35b22e
Show file tree
Hide file tree
Showing 15 changed files with 737 additions and 1 deletion.
26 changes: 26 additions & 0 deletions src/actions/actionCreators.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import {
EDIT_ARTICLE_ERROR,
EDIT_ARTICLE_INITIATED,
GET_SPECIFIC_ARTICLE_INITIATED,
SEND_RESET_LINK_INITIATED,
SEND_RESET_LINK_SUCCESS,
SEND_RESET_LINK_ERROR,
RESET_PASSWORD_SUCCESS,
RESET_PASSWORD_ERROR,
} from './types';

export const socialLoginInitiated = () => ({
Expand Down Expand Up @@ -106,3 +111,24 @@ export const editArticleInititated = payload => ({
type: EDIT_ARTICLE_INITIATED,
payload,
});

export const sendResetLinkInitiated = payload => ({
type: SEND_RESET_LINK_INITIATED,
payload,
});
export const sendResetLinkSuccess = payload => ({
type: SEND_RESET_LINK_SUCCESS,
payload,
});
export const sendResetLinkError = payload => ({
type: SEND_RESET_LINK_ERROR,
payload,
});
export const ResetPasswordSuccess = payload => ({
type: RESET_PASSWORD_SUCCESS,
payload,
});
export const ResetPasswordError = payload => ({
type: RESET_PASSWORD_ERROR,
payload,
});
5 changes: 5 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ export const DELETE_ARTICLE_SUCCESS = 'DELETE_ARTICLE_SUCCESS';
export const EDIT_ARTICLE_SUCCESS = 'EDIT_ARTICLE_SUCCESS';
export const EDIT_ARTICLE_ERROR = 'EDIT_ARTICLE_ERROR';
export const EDIT_ARTICLE_INITIATED = 'EDIT_ARTICLE_INITIATED';
export const SEND_RESET_LINK_INITIATED = 'SEND_RESET_LINK_INITIATED';
export const SEND_RESET_LINK_SUCCESS = 'SEND_RESET_LINK_SUCCESS';
export const SEND_RESET_LINK_ERROR = 'SEND_RESET_LINK_ERROR';
export const RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS';
export const RESET_PASSWORD_ERROR = 'RESET_PASSWORD_ERROR';
43 changes: 43 additions & 0 deletions src/actions/userActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import {
import {
socialLoginInitiated,
socialLoginSuccess,
sendResetLinkInitiated,
sendResetLinkSuccess,
sendResetLinkError,
ResetPasswordSuccess,
ResetPasswordError,
} from './actionCreators';

import axiosInstance from '../config/axiosInstance';
Expand Down Expand Up @@ -130,3 +135,41 @@ export const getProfile = () => dispatch => {
dispatch({ type: LOGOUT_USER, payload: false });
});
};

export const sendResetLink = (userDetails) => dispatch => {
toast.dismiss();
dispatch(sendResetLinkInitiated(true));
return axiosInstance
.post('/api/users/password_reset/', userDetails)
.then(response => {
dispatch(sendResetLinkSuccess(true));
toast.success(response.data.user.message, { autoClose: 3500, hideProgressBar: true });
})
.catch(() => {
dispatch(sendResetLinkError('Please enter a valid email'));
toast.error('Please enter a valid email', { autoClose: 3500, hideProgressBar: true });
});
};

export const resetPassword = (passwordDetails) => dispatch => {
toast.dismiss();
const token = localStorage.getItem('reset_password_token');
axios.defaults.headers.common.Authorization = `Token ${token}`;
return axios
.put('https://ah-backend-targaryen-staging.herokuapp.com/api/users/password_update/', passwordDetails)
.then(response => {
dispatch(ResetPasswordSuccess(true));
toast.success(response.data.user.message, { autoClose: 3500, hideProgressBar: true });
localStorage.removeItem('reset_password_token');
})
.catch((error) => {
if (error.response) {
dispatch(ResetPasswordError(error.response.data.errors[0]));
return toast.error(
error.response.data.errors[0],
{ autoClose: 3500, hideProgressBar: true },
);
}
return toast.error('Connection Error', { autoClose: 3500, hideProgressBar: true });
});
};
15 changes: 15 additions & 0 deletions src/assets/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,18 @@ button:focus {
.btn-cancel:hover{
background-color: rgb(107, 117, 126);
}

/* Reset password styles */
.resetPasswordForm {
margin: 0 auto;
width: 400px;
padding: 40px;
background: #f5f5f5;
box-sizing: border-box;
box-shadow: 0 15px 25px rgba(0, 0, 0, .5);
border-radius: 10px;
}

.reset-container {
margin-top: 40px;
}
2 changes: 1 addition & 1 deletion src/components/login/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class Login extends Component {
Login
</button>
<div className="form-group">
<Link className="forgot-password" to="/forgotPassword">Forgot password?</Link>
<Link className="forgot-password" to="/forgot-Password">Forgot password?</Link>
</div>
<div className="row">
<div className="col-md-6">
Expand Down
57 changes: 57 additions & 0 deletions src/components/resetPassword/ForgotPasswordForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import Loader from 'react-loader';
import PropTypes from 'prop-types';

const ForgotPasswordForm = ({
onChange,
onSave,
username,
email,
loading,
}) => (
<div className="reset-container container">
<div className="panel-body">
<div className="text-center resetPasswordForm">
<h3><i className="fa fa-lock fa-4x" /></h3>
<h2 className="text-center">Forgot Password?</h2>
<p>Enter your username and email here to receive a reset link.</p>
<div className="panel-body">
<div role="form" autoComplete="off" className="form">
<div className="form-group">
<div className="input-group">
<div className="input-group-prepend">
<div className="input-group-text"><i className="fas fa-user" /></div>
</div>
<input id="username" name="username" placeholder="username" value={username} className="form-control" type="text" onChange={onChange} />
</div>
</div>
<div className="form-group">
<div className="input-group">
<div className="input-group-prepend">
<div className="input-group-text"><i className="fas fa-envelope" /></div>
</div>
<input id="email" name="email" placeholder="email address" value={email} className="form-control" type="email" onChange={onChange} />
</div>
</div>
<Loader loaded={!loading}>
<div className="form-group">
<input name="recover-submit" className="btn btn-lg btn-primary btn-block" value="Submit" type="submit" onClick={onSave} />
</div>
</Loader>
</div>
</div>
</div>
</div>
</div>
);

ForgotPasswordForm.propTypes = {
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
username: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
loading: PropTypes.bool.isRequired,
};


export default ForgotPasswordForm;
74 changes: 74 additions & 0 deletions src/components/resetPassword/ForgotPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import SendResetLinkForm from './ForgotPasswordForm';
import { sendResetLink } from '../../actions/userActions';

export class ForgotPassword extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
email: '',
};
}

componentWillReceiveProps(nextProps) {
if (nextProps.sendLinkSuccess === true) {
const { history } = this.props;
history.push('/');
}
}

handleChange = event => {
const { name, value } = event.target;
this.setState({ [name]: value });
};

handleSubmit = event => {
event.preventDefault();
const { email, username } = this.state;
const payload = {
user: {
email,
username,
},
};
const { dispatch } = this.props;
dispatch(sendResetLink(payload));
}

render() {
const { username, email } = this.state;
const { resetPasswordloading } = this.props;
return (
<SendResetLinkForm
onChange={this.handleChange}
onSave={this.handleSubmit}
username={username}
email={email}
loading={resetPasswordloading}
/>
);
}
}

export const mapStateToProps = state => ({
sendLinkSuccess: state.user.sendLinkSuccess,
sendLinkError: state.user.sendLinkError,
resetPasswordloading: state.user.resetPasswordloading,
});

ForgotPassword.propTypes = {
dispatch: PropTypes.func.isRequired,
sendLinkSuccess: PropTypes.bool,
resetPasswordloading: PropTypes.bool,
history: PropTypes.object.isRequired,
};

ForgotPassword.defaultProps = {
sendLinkSuccess: false,
resetPasswordloading: false,
};

export default connect(mapStateToProps)(ForgotPassword);
51 changes: 51 additions & 0 deletions src/components/resetPassword/ResetPasswordForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';

const ResetPasswordForm = ({
onChange,
onSave,
password1,
password2,
}) => (
<div className="container reset-container">
<div className="panel-body">
<div className="text-center resetPasswordForm">
<h3><i className="fa fa-lock-open fa-4x" /></h3>
<h2 className="text-center">Reset Password</h2>
<p>Change your password here and login again</p>
<div className="panel-body">
<div role="form" autoComplete="off" className="form">
<div className="form-group">
<div className="input-group">
<div className="input-group-prepend">
<div className="input-group-text"><i className="fas fa-lock" /></div>
</div>
<input id="password1" name="password1" placeholder="password" value={password1} className="form-control" type="password" onChange={onChange} />
</div>
</div>
<div className="form-group">
<div className="input-group">
<div className="input-group-prepend">
<div className="input-group-text"><i className="fas fa-lock" /></div>
</div>
<input id="password2" name="password2" placeholder="confirm password" value={password2} className="form-control" type="password" onChange={onChange} />
</div>
</div>
<div className="form-group">
<input name="recover-submit" className="btn btn-lg btn-primary btn-block" value="Reset" type="submit" onClick={onSave} />
</div>
</div>
</div>
</div>
</div>
</div>
);

ResetPasswordForm.propTypes = {
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
password1: PropTypes.string.isRequired,
password2: PropTypes.string.isRequired,
};

export default ResetPasswordForm;
81 changes: 81 additions & 0 deletions src/components/resetPassword/ResetPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toast } from 'react-toastify';
import PropTypes from 'prop-types';
import ResetPasswordForm from './ResetPasswordForm';
import { resetPassword } from '../../actions/userActions';

export class ResetPassword extends Component {
constructor(props) {
super(props);
this.state = {
password1: '',
password2: '',
};
}

componentDidMount() {
const { match: { params: { token } } } = this.props;
localStorage.setItem('reset_password_token', token);
}

componentWillReceiveProps(nextProps) {
if (nextProps.updatePasswordSuccess === true) {
const { history } = this.props;
history.push('/login');
}
}

handleChange = event => {
const { name, value } = event.target;
this.setState({ [name]: value });
};

handleSubmit = event => {
event.preventDefault();
const { password2, password1 } = this.state;
if (password2 === password1) {
const payload = {
user: {
password: password2,
},
};
const { dispatch } = this.props;
return dispatch(resetPassword(payload));
}
return toast.error('Make sure the password entered is the same', { autoClose: 3500, hideProgressBar: true });
};


render() {
const formDetails = this.state;
return (
<ResetPasswordForm
onChange={this.handleChange}
onSave={this.handleSubmit}
password1={formDetails.password1}
password2={formDetails.password2}
/>
);
}
}

const mapStateToProps = state => ({
updatePasswordSuccess: state.user.resetPasswordSuccess,
updatePasswordError: state.user.resetPasswordError,
});


ResetPassword.defaultProps = {
updatePasswordSuccess: false,
};


ResetPassword.propTypes = {
dispatch: PropTypes.func.isRequired,
match: PropTypes.object.isRequired,
updatePasswordSuccess: PropTypes.bool,
history: PropTypes.object.isRequired,
};

export default connect(mapStateToProps)(ResetPassword);
Loading

0 comments on commit d35b22e

Please sign in to comment.