Skip to content

Commit

Permalink
164047023 Feature(Reset Password) : implement reset password
Browse files Browse the repository at this point in the history
- create requestPasswordReset
- create resetPassword component
[Finishes #164047023]
  • Loading branch information
winniekariuki committed Apr 10, 2019
1 parent ed7fc48 commit 410ee8d
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 17 deletions.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.18.0",
"bootstrap": "^4.3.1",
"coveralls": "^3.0.3",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.9.1",
"enzyme-to-json": "^3.3.5",
"react": "^16.8.6",
"react-bootstrap": "^1.0.0-beta.6",
"react-dom": "^16.8.6",
"watch": "^1.0.2",
"react-redux": "^6.0.1",
"react-scripts": "2.1.8",
"react-router-dom": "^5.0.0",
"react-scripts": "2.1.8",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0"
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"watch": "^1.0.2"
},
"jest": {
"snapshotSerializers": [
Expand Down
8 changes: 8 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<title>Authors Haven</title>
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
}
35 changes: 35 additions & 0 deletions src/actions/requestPasswordReset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { EMAIL_SEND, FAIL_SEND } from "./types";
import urlPath from "../configs/axios";

export const success = message => ({ type: EMAIL_SEND, payload: message });
export const failure = errors => ({ type: FAIL_SEND, payload: errors });

const requestPassword = data => (dispatch) => {
const success = message => ({ type: EMAIL_SEND, payload: message });
const failure = errors => ({ type: FAIL_SEND, payload: errors });

return urlPath
.request({
method: "post",
url: "users/reset_password/",
data: {
email: data.emailData
}
})
.then((res) => {
if (res.data) {
console.log(res)
dispatch(success(res.data.message));
}
})
.catch((err) => {
if (err.request.response) {
console.log(JSON.parse(err.request.response).errors.email[0])
dispatch(failure(JSON.parse(err.request.response).errors.email[0]));
}
dispatch(failure(err.response.data.errors.email));


});
};
export default requestPassword;
32 changes: 32 additions & 0 deletions src/actions/resetPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RESET_SUCCESSFUL, RESET_FAILURE, RESET_REQUEST } from "./types";
import urlPath from "../configs/axios"

export const resetPasswordAction = () => ({ type: RESET_REQUEST });
export const success = message => ({ type: RESET_SUCCESSFUL, payload: message });
export const failure = error => ({ type: RESET_FAILURE, payload: error });


export const resetPassword = (data) => (dispatch) => {
const urlArray = window.location.href.split('/');
const token = urlArray[urlArray.length - 1];
dispatch(resetPasswordAction());

return urlPath
.request({
method: "put",
url: `users/reset_password/${token}`,
data: { password: data.password }
})
.then((res) => {
dispatch(success(res.data.message));
setTimeout(() => {
window.location.href = "/";
}, 2000);
})
.catch((err) => {
console.log(err.request)

dispatch(failure("Error while making the request"));
});
};
export default resetPassword;
9 changes: 9 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const EMAIL_SEND = "EMAIL_SUCCESSFUL";
export const FAIL_SEND = "FAIL_SEND";
export const RESET_REQUEST = "RESET_REQUEST";
export const RESET_SUCCESSFUL = "RESET_SUCCESSFUL";
export const RESET_FAILURE = "RESET_FAILURE";


export const BASE_URL = "https://aholympian.herokuapp.com/api/";

74 changes: 74 additions & 0 deletions src/components/requestReset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { connect } from "react-redux";
import React, { Component } from "react";
import requestPassword from "../actions/requestPasswordReset";
import 'bootstrap/dist/css/bootstrap.css';
import { Form, Container } from 'react-bootstrap'

class RequestResetForm extends Component {
state = { email: "", message: '' };

handleChange = (e) => {
this.setState({
email: e.target.value
});
}

handleSubmit = (e) => {
e.preventDefault();
const emailData = this.state.email;
const data = { emailData };
this.props.requestPassword(data).then(() => {
console.log(this.props.message.email)

this.setState({message: this.props.message})
});
}

render() {
const message = this.props.message;
return (
<div >
<Container>
<div className="m-4" style={{ height: "600px" }}>
<div className="text-center mb-10 mt-50" style={{ height: "20%" }}>
<h2 className="h-100 my-auto mt-25">Reset Password</h2>
</div>
<div className="card text-center my-auto mx-auto border-dark" style={{ height: "70%", backgroundColor: "#27586e" ,width:"50%"}}>
<div className="card-body" >
<Form.Group style={{ height: "20px" }} >
<div><h5 style={{ color: "white" }}> Enter your email address and <br />will send you a link to reset password</h5></div>
<div>
{this.state.message && <p style={{ color: "red" }}>{message}</p>}
</div>
<input className="m-6 my-4 input-normal" style={{ width: "88%" }} type="text" placeholder="Enter your email address" value={this.state.email} name="email" onChange={this.handleChange} required />
<button className="m-6 my-4 input-normal text-white" type="button" style={{ width: "88%", backgroundColor: "#519e8a", color: "white" }} className="submitbtn" onClick={this.handleSubmit}>send email for reset password</button>

</Form.Group >

</div>
</div>
</div>


</Container>
<footer className="bg-dark page-footer font-small blue mb-0px" style={{
width: "100%" ,height: "25% "
}}>
<div class="footer-copyright text-center py-3">
All Rights Reserved<br />
Authors Haven<br />
© 2019<br />

</div>

</footer>
</div>
);
}
}

const mapDispatchToProps = dispatch => ({
requestPassword: data => dispatch(requestPassword(data))
});
const mapStateToProps = state => ({ message: state.requestPassword.message });
export default connect(mapStateToProps, mapDispatchToProps)(RequestResetForm);
92 changes: 92 additions & 0 deletions src/components/resetPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { connect } from "react-redux";
import React, { Component } from "react";
import { resetPassword } from "../actions/resetPassword";
import 'bootstrap/dist/css/bootstrap.css';
import { Form, Container } from 'react-bootstrap';


class ResetPassword extends Component {
state = { password: "", confirmPassword: "", message: true, error: true };

handleChange = (e) => {
this.setState({ [e.target.id]: e.target.value });
if (e.target.id === "password") {
if (e.target.value.length === 0) {
this.setState({ error: true });
this.setState({ message: "Password cannot be empty" });
return;
}
if (!this.validatePassword(e.target.value)) {
this.setState({ error: true });
this.setState({ message: "Password should be atleast 8 characters with a capital letter, a small letter, a number and a special character" });
return;
}
}
if (e.target.id === "confirmPassword") {
if (e.target.value !== this.state.password) {

this.setState({ error: true });
this.setState({ message: "Passwords do not match" });
return;
}
else {
this.setState({ error: null, message: null });
}
}
else {
this.setState({ error: null, message: null });
}
}

validatePassword = (password) => {
const valid = password.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$/);
return valid;
}

handleSubmit = (e) => {

e.preventDefault();
if (this.state.password === this.state.confirmPassword) {
const passwordData = this.state.password;
const data = { password: passwordData };
this.props.resetPassword(data);
} else {
this.setState({ message: "Passwords didn't match" });
}
}

render() {
const message = this.props.message;

return (
<div>
<Container>
<div className="m-4" style={{ height: "600px" }}>
<div className="text-center mb-10 mt-50" style={{ height: "20%" }}>
<h2 className="h-100 my-auto mt-25">Reset Password</h2>
</div>
<div className="card text-center my-auto mx-auto border-dark" style={{ height: "70%", backgroundColor: "#27586e", width: "50%" }}>
<div className="card-body" >
<Form.Group style={{ height: "20px" }} >
<div>
{this.state.message && <p style={{ color: "red" }}>{this.state.message}</p>}
</div>
<input className="m-6 my-4 input-normal" style={{ width: "88%" }} type="password" id="password" placeholder="Enter New Password" value={this.state.password} name="password" onChange={this.handleChange} required />
<input className="m-6 my-4 input-normal" style={{ width: "88%" }} type="password" id="confirmPassword" placeholder=" Enter Confirm Password" value={this.state.confirmPassword} name="confirmpassword" onChange={this.handleChange} required />
<button disabled={this.state.error ? "true" : null} className="m-6 my-4 input-normal text-white" type="button" style={{ width: "88%", backgroundColor: "#519e8a", color: "white" }} className="submitbtn" onClick={this.handleSubmit}>Reset Password</button>
<h2>{this.token}</h2>
</Form.Group >
</div>
</div>
</div>


</Container>
</div>

);
}
}

const mapStateToProps = state => ({ passwordReset: state.resetPassword });
export default connect(mapStateToProps, { resetPassword })(ResetPassword);
13 changes: 13 additions & 0 deletions src/configs/axios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import axios from "axios";
import { BASE_URL } from "../actions/types";

const token = localStorage.getItem("token");
const urlPath = axios.create({
baseURL: `${BASE_URL}/`,
headers: {
"Content-Type": "application/json",
Authorization: `${token}`
}
});

export default urlPath;
12 changes: 12 additions & 0 deletions src/css/bootstrap.min.css

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/* Import all your reducers here.
* This will give a centralised point of access for all reducers.
*/
import { combineReducers } from "redux";
import { requestPassword } from "./requestPasswordReset";
import { resetPassword } from "./resetPassword";

const rootReducer = combineReducers({
requestPassword,
resetPassword,

});
export default rootReducer;
12 changes: 12 additions & 0 deletions src/reducers/requestPasswordReset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { EMAIL_SEND, FAIL_SEND } from "../actions/types";

export function requestPassword(state = {}, action) {
switch (action.type) {
case EMAIL_SEND:
return { ...state, message: action.payload };
case FAIL_SEND:
return { ...state, message: action.payload };
default:
return state;
}
}
14 changes: 14 additions & 0 deletions src/reducers/resetPassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RESET_REQUEST, RESET_SUCCESSFUL, RESET_FAILURE } from "../actions/types";

export function resetPassword(state = {}, action) {
switch (action.type) {
case RESET_REQUEST:
return { ...state };
case RESET_SUCCESSFUL:
return { ...state, message: action.payload };
case RESET_FAILURE:
return { ...state, messsage: action.payload };
default:
return state;
}
}
29 changes: 19 additions & 10 deletions src/routers/Approuter.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import React, { Component } from 'react';
import Login from '../components/Login/Login';
import Home from '../components/Home/Home';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import RequestResetForm from "../components/requestReset";
import ResetPassword from "../components/resetPassword";
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import store from "../store/index";
import { Provider } from "react-redux";

const Approuter = () => (
<BrowserRouter>
<div>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/login" component={Login} />
</Switch>
</div>
</BrowserRouter>
);
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route path='/' component={Home} exact />
<Route path='/login' component={Login} />
<Route path="/resetpassword" component={RequestResetForm} />
<Route path="/resetform/:token" component={ResetPassword} />
</Switch>
</div>
</BrowserRouter>
</Provider>
)


export default Approuter;
6 changes: 6 additions & 0 deletions src/static/footer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
footer{
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
Loading

0 comments on commit 410ee8d

Please sign in to comment.