Skip to content

Commit

Permalink
Merge pull request #20 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 authored Nov 12, 2018
2 parents b6a5e20 + 7c045ef commit 2f7f95c
Show file tree
Hide file tree
Showing 16 changed files with 635 additions and 5 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react-toastify": "^4.4.0",
"redux": "^4.0.1",
"redux-mock-store": "^1.5.3",
"jest-localstorage-mock": "^2.3.0",
"redux-thunk": "^2.3.0"
},
"scripts": {
Expand Down Expand Up @@ -55,6 +56,7 @@
"eslint-plugin-react": "^7.11.1",
"jest": "^23.6.0",
"jest-cli": "^20.0.4",
"jest-localstorage-mock": "^2.3.0",
"redux-mock-store": "^1.5.3"
}
}
4 changes: 4 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ export const GET_ALL_ARTICLES_SUCCESS = 'GET_ALL_ARTICLES_SUCCESS';
export const GET_PROFILE_PAYLOAD = 'GET_PROFILE_PAYLOAD';
export const GET_PROFILE_ERROR = 'GET_PROFILE_ERROR';
export const GET_PROFILE_INITIATED = 'GET_PROFILE_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';
31 changes: 31 additions & 0 deletions src/actions/userActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
GET_PROFILE_PAYLOAD,
GET_PROFILE_ERROR,
GET_PROFILE_INITIATED,
SEND_RESET_LINK_SUCCESS,
SEND_RESET_LINK_ERROR,
RESET_PASSWORD_SUCCESS,
RESET_PASSWORD_ERROR,
} from './types';
import {
socialLoginInitiated,
Expand Down Expand Up @@ -115,3 +119,30 @@ export const getProfile = () => dispatch => {
dispatch({ type: GET_PROFILE_ERROR, payload: 'This profile does not exist' });
});
};

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


export const resetPassword = (passwordDetails) => dispatch => axiosInstance
.put('/api/users/password_update/', passwordDetails)
.then(response => {
dispatch({ type: RESET_PASSWORD_SUCCESS, payload: true });
toast.dismiss();
toast.success(response.data.user.message, { autoClose: 3500, hideProgressBar: true });
})
.catch(() => {
dispatch({ type: RESET_PASSWORD_ERROR, payload: 'Please enter a valid password' });
toast.dismiss();
toast.error('Please enter a valid password', { autoClose: 3500, hideProgressBar: true });
});
21 changes: 20 additions & 1 deletion src/assets/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ img {
font-family: cursive;
}
.footer-bg {
position: fixed;
background-color: #24292e;
color: white;
text-align: center;
padding: 10px 20px 40px 20px;
padding: 5px 20px 5px 20px;
width: 100%;
bottom: 0;
}
header.uvp {
position: relative;
Expand Down Expand Up @@ -322,3 +325,19 @@ header.uvp {
font-weight: bold;
font-size: 30px;
}

/* 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;
}

4 changes: 2 additions & 2 deletions src/components/landingPage/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ export class Navbar extends Component {
: (
<React.Fragment>
<li className="nav-item active">
<NavLink className="btn ah-btn ah-btn-nav js-scroll-trigger nav-link nav-text" to="signup">
<NavLink className="btn ah-btn ah-btn-nav js-scroll-trigger nav-link nav-text" exact to="/signup">
Signup
</NavLink>
</li>
<li className="nav-item">
<NavLink className="btn ah-btn ah-btn-nav js-scroll-trigger nav-link nav-text" to="login">Login</NavLink>
<NavLink className="btn ah-btn ah-btn-nav js-scroll-trigger nav-link nav-text" exact to="/login">Login</NavLink>
</li>
</React.Fragment>
)
Expand Down
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
53 changes: 53 additions & 0 deletions src/components/resetPassword/ForgotPasswordForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';

const ForgotPasswordForm = ({
onChange,
onSave,
username,
email,
}) => (
<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>
<div className="form-group">
<input name="recover-submit" className="btn btn-lg btn-primary btn-block" value="Submit" type="submit" onClick={onSave} />
</div>
</div>

</div>
</div>
</div>
</div>
);

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


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

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

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 formDetails = this.state;
return (
<SendResetLinkForm
onChange={this.handleChange}
onSave={this.handleSubmit}
username={formDetails.username}
email={formDetails.email}
/>
);
}
}

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

ForgotPasswordPage.propTypes = {
dispatch: PropTypes.func.isRequired,
};

export default connect(mapStateToProps)(ForgotPasswordPage);
53 changes: 53 additions & 0 deletions src/components/resetPassword/ResetPasswordForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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;
78 changes: 78 additions & 0 deletions src/components/resetPassword/ResetPasswordPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import 'react-toastify/dist/ReactToastify.css';
import ResetPasswordForm from './ResetPasswordForm';
import { resetPassword } from '../../actions/userActions';

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

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 } = this.state;
const payload = {
user: {
password: password2,
},
};
const { dispatch } = this.props;
dispatch(resetPassword(payload));
}

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,
});


ResetPasswordPage.defaultProps = {
updatePasswordSuccess: false,
};


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

export default connect(mapStateToProps)(ResetPasswordPage);
4 changes: 4 additions & 0 deletions src/components/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import NotFound from '../notFound/NotFound';
import RegisterUser from '../register/RegisterUser';
import Dashboard from '../dashboard/Dashboard';
import Profile from '../profiles/Profile';
import ResetPasswordPage from '../resetPassword/ResetPasswordPage';
import ForgotPasswordPage from '../resetPassword/ForgotPasswordPage';

const Routes = () => (
<Switch>
Expand All @@ -17,6 +19,8 @@ const Routes = () => (
<Route exact path="/signup" component={RegisterUser} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/profiles/:username" component={Profile} />
<Route exact path="/forgot-password" component={ForgotPasswordPage} />
<Route exact path="/reset-password/:token" component={ResetPasswordPage} />
<Route exact component={NotFound} />
</Switch>
);
Expand Down
Loading

0 comments on commit 2f7f95c

Please sign in to comment.