Skip to content

Commit

Permalink
Merge pull request #13 from andela/ft-users-login-#164046210
Browse files Browse the repository at this point in the history
#164046210: users can log in
  • Loading branch information
abulojoshua1 committed Apr 17, 2019
2 parents 85c3a8b + a3c9b00 commit 188fb19
Show file tree
Hide file tree
Showing 22 changed files with 779 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"react"
],
"rules": {
}
},
"parser": "babel-eslint"
}

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
Expand All @@ -24,4 +25,4 @@ yarn-error.log*

package-lock.json

.env
/.vscode
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"axios": "^0.18.0",
"bootstrap": "^4.3.1",
"dotenv": "^7.0.0",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"i": "^0.3.6",
"jsonwebtoken": "^8.5.1",
"node-sass": "^4.11.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
Expand All @@ -20,7 +23,8 @@
"reactstrap": "^7.1.0",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0",
"validator": "^10.11.0"
"validator": "^10.11.0",
"sinon": "^7.3.1"
},
"scripts": {
"start": "react-scripts start",
Expand All @@ -42,8 +46,6 @@
"devDependencies": {
"coverage": "^0.1.0",
"coveralls": "^3.0.3",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.11.2",
"enzyme-to-json": "^3.3.5",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.16.0",
Expand Down
52 changes: 36 additions & 16 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,56 @@
/* eslint-disable react/prefer-stateless-function */
import React from 'react';
import { Navbar, Nav } from 'react-bootstrap';
import { NavLink } from 'react-router-dom';
import AuthenticationModal from './AuthenticationModal';
import store from '../store/store';
import { LOGIN, REGISTER } from '../store/actions/actionTypes';
import AlertModal from './AlertModal';

import { isLoggedIn } from '../utils/tokenValidator';

class Header extends React.Component {
state = {
LoggedIn: isLoggedIn,
};

dispatchLogin = () => {
store.dispatch({ type: LOGIN });
};

dispatchRegister = () => {
store.dispatch({ type: REGISTER });
};

render() {
const { LoggedIn } = this.state;
return (
<Navbar expand="lg" className="navbar-custom">
<NavLink exact to="/" className="brand">
Author&apos;s Haven
</NavLink>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Item
onClick={() => store.dispatch({ type: LOGIN })}
className="nav-link auth-btn "
>
Login
</Nav.Item>
<Nav.Item
onClick={() => store.dispatch({ type: REGISTER })}
className="nav-link auth-btn get-started"
>
Get Started
</Nav.Item>
</Nav>
{LoggedIn ? (
<Nav className="ml-auto">
<Nav.Item className="nav-link profile-dropdown">
Your Profile
</Nav.Item>
</Nav>
) : (
<Nav className="ml-auto">
<Nav.Item
onClick={this.dispatchLogin}
className="nav-link auth-btn login"
>
Login
</Nav.Item>
<Nav.Item
onClick={this.dispatchRegister}
className="nav-link auth-btn get-started"
>
Get Started
</Nav.Item>
</Nav>
)}
</Navbar.Collapse>
<AuthenticationModal />
<AlertModal />
Expand Down
3 changes: 2 additions & 1 deletion src/components/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import LoginForm from '../containers/LoginForm';

const Login = () => <div className="main-container">Login</div>;
const Login = () => <div className="main-container"><LoginForm /></div>;
export default Login;
162 changes: 162 additions & 0 deletions src/containers/LoginForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from 'react';
import {
Form, Button, Alert,
} from 'react-bootstrap';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import loginAction from '../store/actions/authActions/LoginAction';
import validateLoginForm from '../utils/validation';
import store from '../store/store';
import { REGISTER, IS_LOADING } from '../store/actions/actionTypes';
import ButtonSpinner from '../components/ButtonSpinner';

export class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
formErrors: {},
touched: false,
show: true,
};
}

onAlertClose = () => {
this.setState({
show: false,
});
}

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

handleSubmit = (event) => {
const { email, password } = this.state;
const { LoginUser } = this.props;
event.preventDefault();
const formErrors = validateLoginForm(email, password);
if (formErrors.length === 0) {
this.setState({
formErrors: [],
});
const data = { email, password };
store.dispatch({ type: IS_LOADING, payload: true });
LoginUser(data);
} else {
this.setState({
formErrors,
touched: false,
});
}
};

swapModal = () => {
store.dispatch({ type: REGISTER });
};

render() {
const { loginUser } = this.props;
const { errors, isLoading } = loginUser;
const { formErrors, touched, show } = this.state;
return (
<div>
<Form className="main-container" onSubmit={this.handleSubmit}>
<Form.Group name="email">
{errors && (
<Alert
className="close-login-alert"
dismissible
variant="danger"
show={show}
onClick={this.onAlertClose}
>
<p>
<small>{errors}</small>
</p>
</Alert>
)}
<Form.Label htmlFor="email">Email address</Form.Label>
<div className="text-danger email-warning">
{formErrors[0] === 'email' && !touched && formErrors[1]}
</div>
<p className="text-danger email-warning">{errors.email}</p>
<Form.Control
className={
formErrors[0] === 'email' ? 'red-form-border' : 'email-input'
}
name="email"
type="email"
placeholder="Enter your email"
onChange={this.handleChange}
/>
</Form.Group>
<Form.Group name="password">
<Form.Label htmlFor="password">Password</Form.Label>
<div className="text-danger password-warning">
{formErrors[0] === 'password' && !touched && formErrors[1]}
</div>
<p className="text-danger password-warning">{errors.password}</p>
<Form.Control
className={
formErrors[0] === 'password'
? 'red-form-border'
: 'password-input'
}
name="password"
type="password"
placeholder="Password"
onChange={this.handleChange}
/>
</Form.Group>
<Button className="btn-one" type="submit" disabled={isLoading}>
{
isLoading && <ButtonSpinner className="btn-disabled btn-one" />
}
{ isLoading ? 'Loading...' : 'Sign In' }
</Button>
<p className="swap-modal-text">
Don&#39;t have an account?
&nbsp;
<span
role="button"
tabIndex={0}
className="swap-modal-span"
onClick={this.swapModal}
onKeyPress={this.swapModal}
>
Click here
</span>
&nbsp;
to register.
</p>
</Form>
</div>
);
}
}

LoginForm.propTypes = {
loginUser: PropTypes.shape({
logged_in: PropTypes.bool,
errors: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
isLoading: PropTypes.bool,
}).isRequired,
LoginUser: PropTypes.func.isRequired,
};

export function mapDispatchToProps(dispatch) {
return {
LoginUser: payload => dispatch(loginAction(payload)),
};
}

export const mapStateToProps = (state) => {
const { loginUser } = state;
return {
loginUser,
};
};

export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
72 changes: 72 additions & 0 deletions src/css/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,75 @@ color: #000000;
.title-link:hover{
color: #000000;
}

.swap-modal-span {
cursor: pointer;
text-decoration: chocolate;
color: chocolate;
}

.swap-modal-span:hover {
opacity: 0.8;
text-decoration: underline;
}

.swap-modal-text {
font-size: 14px;
}

.modal-dialog {
height: 100%;
}
.red-form-border {
border: 1px solid red;
}

.password-input {
margin-top: 0;
}

.btn-one{
background: #792525;
border: none;
color: #fcc810;
}
.btn-one:hover{
opacity: 0.7;
background: #792525;
border: none;
color: #fcc810;
}

.btn-one .btn .btn-primary {
background: #792525;
}

.btn-disabled{
opacity: 0.8;
}

.alert-dismissible .close {
position: absolute;
top: 0;
right: 0;
color: inherit;
}

button.close {
background-color: transparent;
border: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}

.alert {
text-align: center;
padding: 0.5%;

}

small, .small {
font-size: 80%;
font-weight: 400;
}
1 change: 0 additions & 1 deletion src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Routes from './routes';
import * as serviceWorker from './serviceWorker';
import store from './store/store';


require('dotenv').config();

ReactDOM.render(
Expand Down
5 changes: 1 addition & 4 deletions src/routes/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import { BrowserRouter, Route } from 'react-router-dom';
import Header from '../components/Header';
import Footer from '../components/Footer';
import HomeView from '../containers/HomeView';
import Login from '../components/Login';
import RegisterForm from '../components/RegisterForm';


const Routes = () => (
<BrowserRouter>
<div className="App">
<Header />
<Route exact path="/" component={HomeView} />
<Route path="/login" component={Login} />
<Route path="/register" component={RegisterForm} />
<Footer />
</div>
</BrowserRouter>
Expand Down
Loading

0 comments on commit 188fb19

Please sign in to comment.