Skip to content

Commit

Permalink
feature(authentication): reset password
Browse files Browse the repository at this point in the history
- send a clickable link to email entered into a form
- enter password details to change password
  • Loading branch information
RonKbS committed Nov 20, 2018
1 parent 021f645 commit c38470b
Show file tree
Hide file tree
Showing 15 changed files with 753 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 @@ -14,6 +14,11 @@ import {
GET_ALL_ARTICLES_INITIATED,
LIKE_DISLIKE_SUCCESS,
LIKE_DISLIKE_ERROR,
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 @@ -81,3 +86,24 @@ export const likeDislikeError = payload => ({
type: LIKE_DISLIKE_ERROR,
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 @@ -20,3 +20,8 @@ export const GET_USER_ARTICLES_SUCCESS = 'GET_USER_ARTICLES_SUCCESS';
export const GET_ALL_ARTICLES_INITIATED = 'GET_ALL_ARTICLES_INITIATED';
export const LIKE_DISLIKE_SUCCESS = 'LIKE_DISLIKE_SUCCESS';
export const LIKE_DISLIKE_ERROR = 'LIKE_DISLIKE_ERROR';
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('auth_token');
axios.defaults.headers.common.Authorization = `Token ${token}`;
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('auth_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 @@ -460,3 +460,18 @@ button:focus {
outline: 0;
color: #24292e;
}

/* 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('auth_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 c38470b

Please sign in to comment.