Skip to content

Commit

Permalink
feat(login): user should be able to login
Browse files Browse the repository at this point in the history
- add user login functionality
- add login functionality tests
- add login form for inputs
- add error and success messages when logging in
- add a progress loader

[Finishes #164798253]
  • Loading branch information
e-ian committed May 8, 2019
1 parent c5d8871 commit 5174aef
Show file tree
Hide file tree
Showing 28 changed files with 915 additions and 50 deletions.
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"presets": [
"@babel/preset-env", "@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/transform-runtime"
]
}
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ module.exports = {
"\\.(css|jpg|png|scss)$": "<rootDir>/ignore-css.js"
},
// A list of paths to directories that Jest should use to search for files in
roots: ["<rootDir>/tests/"],
roots: ["<rootDir>./src/tests/"],

// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: ["<rootDir>/SetupTests.js"],
setupFiles: ["<rootDir>./src/SetupTests.js"],

// The test environment that will be used for testing
testEnvironment: "jest-environment-jsdom",
Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,26 @@
},
"homepage": "https://github.com/andela/ah-frontend-prime#readme",
"dependencies": {
"@material-ui/core": "^3.9.3",
"axios": "^0.18.0",
"babel-jest": "^24.8.0",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"express": "^4.16.4",
"materialize-css": "^1.0.0-rc.2",
"react-loader": "^2.4.5",
"react-redux": "^7.0.3",
"react-router-dom": "^5.0.0",
"react-test-renderer": "^16.8.6",
"react-toastify": "^5.1.0",
"redux": "^4.0.1",
"redux-mock-store": "^1.5.3",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
Expand All @@ -46,7 +55,8 @@
"eslint": "^5.16.0",
"eslint-plugin-react": "^7.12.4",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.7.1",
"jest": "^24.8.0",
"moxios": "^0.4.0",
"node-sass": "^4.12.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
Expand Down
File renamed without changes.
53 changes: 53 additions & 0 deletions src/actions/LoginAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import axios from 'axios';

import { toast } from "react-toastify";
import { LOGIN_STARTED ,LOGIN_FAIL,SUCCESSFUL} from './types';


export const successLogin = data => {
return({
type: SUCCESSFUL,
payload: data.data.user.token,
});
}

export const userLoginRequest = userData => async (dispatch) => {
dispatch({
type:LOGIN_STARTED
});

setTimeout(() => {

}, 3000);
try {

const response = await axios.post(
'https://ah-backend-prime-staging.herokuapp.com/api/v1/users/login/',
userData,
);
dispatch(successLogin(response));
sessionStorage.setItem("token", response.data.user.token);
toast.success(`Welcome ${response.data.user.username}. Login Successful`, {
position: toast.POSITION.TOP_RIGHT,
autoClose: 2000,
hideProgressBar: false,
});
} catch (error) {

dispatch({
type:LOGIN_FAIL
});
const errors=error.response.data.errors;

errors.error.forEach(err => {
toast.error(` ${err}`, {
position: toast.POSITION.TOP_RIGHT,
autoClose: 2000,
hideProgressBar: false,
})

})

}

};
4 changes: 3 additions & 1 deletion src/actions/types.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
//add your actions types here
// export const LOGIN_USER = 'LOGIN_USER';
export const LOGIN_STARTED="LOGIN_STARTED";
export const LOGIN_FAIL="LOGIN_FAIL";
export const SUCCESSFUL="SUCCESSFUL";
118 changes: 118 additions & 0 deletions src/components/login/LoginModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from "react";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import { withStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import "../../styles/register.scss"

const styles = themes => ({
floatingLabelFocusStyle: {
color: "black",
fontFamily: "Quantico",
fontSize: "15px",
fontWeight: "bold"
}
});
const LoginComponent = props => {
const {
classes,
password,
email,
handleChange,
handleSubmit,
isProcessing
} = props;

let Loader = require('react-loader');

return (
<div>
<Grid container spacing={24}>
<Grid item lg={2} md={2} />
<Grid item lg={8} md={8}>
<Paper className="Paper">
<h3>Join Authors Haven Today</h3>
<p>
A social platform that creates a community of like minded authors to foster
inspiration and
<br />
innovation by leveraging the modern web.
</p>
<Grid container spacing={24}>
<Grid item lg={6} md={6}>
<div className="button-collective">
<Button className="face-book">
<img
src="https://img.icons8.com/color/96/000000/facebook.png"
width="=40px"
height="40px"
alt="facebook"
/>
Login with Facebook
</Button>
<Button className="twitter">
<img
src="https://img.icons8.com/color/96/000000/twitter-circled.png"
width="=40px"
height="40px"
alt="twitter"
/>
Login with Twitter
</Button>
<Button className="google">
<img
src="https://img.icons8.com/color/96/000000/google-logo.png"
width="=40px"
height="40px"
alt="google"
/>
Login with Google
</Button>
</div>
</Grid>
<Grid item lg={6} md={6} className="grid-register">
<form method="post" onSubmit={handleSubmit}>
<TextField
InputLabelProps={{
className: classes.floatingLabelFocusStyle
}}
id="email"
label="enter email"
name="email"
type="text" required
className="textField"
value={email}
onChange={handleChange}
/>

<TextField
InputLabelProps={{
className: classes.floatingLabelFocusStyle
}}
id="password"
label="enter password"
name="password" required minLength={6} maxLength={16}
type="password"
className="textField"
value={password}
onChange={handleChange}
/>

<Loader loaded={!isProcessing}></Loader>
<Button type="submit" className="button-success">
Login
</Button>
</form>
</Grid>
</Grid>
<p>Don't have an account yet, create one now</p>
<p>Forgot your password, reset now</p>
</Paper>
</Grid>
<Grid item lg={2} md={2} />
</Grid>
</div>
);
};
export default withStyles(styles)(LoginComponent);
12 changes: 0 additions & 12 deletions src/components/loginComponent.js

This file was deleted.

7 changes: 2 additions & 5 deletions src/components/routes.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';


import Login from './loginComponent';
import LoginContainer from "../containers/Login/LoginContainer";
import Home from './home';



class Routes extends Component {

render(){
return (
<BrowserRouter>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/login" component={LoginContainer} />
</BrowserRouter>
);
};
}

export default Routes;
60 changes: 60 additions & 0 deletions src/containers/Login/LoginContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';

import LoginModal from "../../components/login/LoginModal";
import {userLoginRequest} from "../../actions/LoginAction";


export class LoginContainer extends Component {
constructor(props){
super(props);
this.state = {
email:'',
password:'',
isLoading:false,
errors: {
email: '',
password: ''
}
};

}

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

handleSubmit = (event) => {
event.preventDefault();
const data = {
user: {
'email': this.state.email,
'password': this.state.password
}
};
this.props.userLoginRequest(data);

}

render() {
return (
<div>
<LoginModal
handleChange={this.handleChange}
email={this.state.email}
isProcessing={this.props.isProcessing}
password={this.state.password}
errors={this.state.errors}
handleSubmit={this.handleSubmit}/>
</div>
)
}
}

export const mapStateToProps=state=>({
isProcessing:state.auth_login.isProcessing
});

export default connect(mapStateToProps, {userLoginRequest})(LoginContainer);
3 changes: 3 additions & 0 deletions src/containers/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from '../store';
import '../styles/app.scss';
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer } from "react-toastify";
import Routes from '../components/routes';

class App extends Component{
render() {
return(
<Provider store={store}>
<ToastContainer />
<Routes/>
</Provider>
)
Expand Down
1 change: 1 addition & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css?family=Quantico" rel="stylesheet">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Authors's haven</title>
</head>
Expand Down
Loading

0 comments on commit 5174aef

Please sign in to comment.