Skip to content

Commit

Permalink
Login page added (promise middleware + axios) + simple API server
Browse files Browse the repository at this point in the history
Promise based HTTP client for the browser and node.js
https://github.com/mzabriskie/axios

promise middleware
reduxjs/redux#99 (comment)
https://medium.com/@bananaoomarang/handcrafting-an-isomorphic-redux-appl
ication-with-love-40ada4468af4
  • Loading branch information
jsdmc committed Oct 7, 2015
1 parent a55c325 commit 87c1aab
Show file tree
Hide file tree
Showing 17 changed files with 442 additions and 41 deletions.
35 changes: 35 additions & 0 deletions api/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
var express = require('express');
var path = require('path');

var app = express();

var isProduction = process.env.NODE_ENV === 'production';
var port = isProduction ? process.env.PORT : 3001;

var bodyParser = require('body-parser');
app.use(bodyParser.json({ type: 'application/json' }))

app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');

next();
});

app.post('/api/login', function(req, res) {
const credentials = req.body;
if(credentials.user==='admin' && credentials.password==='password'){
res.json({'user': credentials.user, 'role': 'ADMIN'});
}else{
res.status('500').send({'message' : 'Invalid user/password'});
}
});

app.post('/api/logout', function(req, res) {
res.json({'user': 'admin', 'role': 'ADMIN'});
});

app.listen(port, function () {
console.log('Server running on port ' + port);
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js",
"build": "npm run clean && npm run build:webpack",
"start": "npm run clean && node devServer.js",
"api": "node ./api/api.js",
"lint": "eslint src"
},
"repository": {
Expand All @@ -32,6 +33,7 @@
},
"homepage": "https://github.com/jsdmc/react-redux-router-crud-boilerplate",
"dependencies": {
"axios": "^0.7.0",
"classnames": "^2.1.5",
"history": "^1.12.0",
"react": "^0.14.0-rc1",
Expand Down
54 changes: 54 additions & 0 deletions src/components/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { Component } from 'react';
// import { Link } from 'react-router';
import './Header.scss';

export default class Header extends Component {
onLogoutClick() {
event.preventDefault();
// this.props.handleLogout();
}

render() {
return (
<nav className="navbar navbar-inverse navbar-fixed-top">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="#">React-Redux-Router-CRUD-boilerplate</a>
</div>
<div id="navbar" className="navbar-collapse collapse">
<ul className="nav navbar-nav navbar-right">
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span className='fa fa-user header_fa'></span>'Anonymous'<span className="caret"></span>
</a>
<ul className="dropdown-menu">
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li className="logout-link"><a href="#" onClick={e=>this.onLogoutClick(e)}><i className="fa fa-sign-out header_fa"/>Log out</a></li>

<li role="separator" className="divider"></li>
<li>
<a href="https://github.com/cloudmu/react-redux-starter-kit"target="_blank" title="View on Github"><i className="fa fa-github header_fa"/>Github</a>
</li>
</ul>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Help</a></li>
</ul>
<form className="navbar-form navbar-right">
<input type="text" className="form-control" placeholder="Search..." />
</form>
</div>
</div>
</nav>
);
}
}
15 changes: 15 additions & 0 deletions src/components/Header/Header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
:global {
.navbar-brand {
position:relative;
padding-left: 60;
}

.nav .header_fa {
font-size: 1em;
margin-right: 0.5em;
}

.navbar-default .navbar-nav>li>a:focus {
outline: none;
}
}
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*
*/

export Header from './Header/Header';
export AutoCounter from './AutoCounter/AutoCounter';
export Counter from './Counter';
export SmartLink from './SmartLink/SmartLink';
31 changes: 2 additions & 29 deletions src/containers/CoreLayout.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Component, PropTypes } from 'react';
import { SmartLink } from '../components';
import { Header, SmartLink } from '../components';
import '../styles/main.scss';

export default class CoreLayout extends Component {
Expand All @@ -9,36 +9,9 @@ export default class CoreLayout extends Component {
}

render() {
const navTop = () => (
<nav className="navbar navbar-inverse navbar-fixed-top">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="#">React-Redux-Router-CRUD-boilerplate</a>
</div>
<div id="navbar" className="navbar-collapse collapse">
<ul className="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Help</a></li>
</ul>
<form className="navbar-form navbar-right">
<input type="text" className="form-control" placeholder="Search..." />
</form>
</div>
</div>
</nav>
);

return (
<div>
{ navTop() }
<Header />
<div className="container-fluid">
<div className="row">
<div className="col-sm-3 col-md-2 sidebar">
Expand Down
90 changes: 90 additions & 0 deletions src/containers/LoginPage/LoginPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { pushState } from 'redux-router';
import { login } from '../../redux/modules/auth';

import './LoginPage.scss';

class Login extends Component {

componentWillReceiveProps(nextProps) {
console.log('Login: componentWillReceiveProps: '+nextProps);
console.log('user: '+this.props.user+'\tnextUser='+nextProps.user);

if (nextProps.user) {
// logged in, let's show home
this.dispatch(pushState(null, '/counter'));
}
}

handleLogin(event) {
event.preventDefault();
const username = this.refs.username; // need for getDOMNode() call going away in React 0.14
const password = this.refs.password;
this.props.dispatch(login(username.value, password.value));
// username.value = '';
// password.value = '';
}

render(){
const {user, loginError} = this.props;
return(
<div className="container">
<div className="row">
<div className="col-md-4 col-md-offset-4">
<div className="panel panel-default panel-signin">
<div className="panel-heading">
<h3 className="panel-title">Please Log in</h3>
</div>
<form className="form-signin">

<div className="input-group">
<span className="input-group-addon"><i className="fa fa-user"/></span>
<input type="text" ref="username" className="form-control" placeholder="Username" required autofocus/>
</div>

<div className="input-group">
<span className="input-group-addon"><i className="fa fa-lock"/></span>
<input type="password" ref="password" className="form-control" placeholder="Password" required/>
</div>

<div className="checkbox">
<label>
<input type="checkbox" value="remember-me"/> Remember me
</label>
</div>

{
!user && loginError &&
<div className="alert alert-danger">
{loginError.message}. Hint: use admin/password to log in.
</div>
}

<button className="btn btn-primary btn-block" onClick={::this.handleLogin}><i className="fa fa-sign-in"/>{' '}Log in</button>
</form>
</div>
</div>
</div>
</div>
);
}
}

Login.propTypes = {
user: PropTypes.string,
loginError: PropTypes.object,
dispatch: PropTypes.func.isRequired,
routerState: PropTypes.object.isRequired
};

function mapStateToProps(state){
const { auth, router } = state;
if(auth){
return {user: auth.user, loginError: auth.loginError, routerState: router};
}else{
return {user: null, routerState: router};
}
}

export default connect(mapStateToProps)(Login);
29 changes: 29 additions & 0 deletions src/containers/LoginPage/LoginPage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
:global{
.panel-signin {
margin-top: 25%;
}

.form-signin {
max-width: auto;
padding: 15px;
margin: 0 auto;

.checkbox {
margin-bottom: 10px;
}

.form-control:focus {
z-index: 2;
}
input[type="text"] {
margin-bottom: 0px;
}
input[type="password"] {
margin-bottom: 0px;
}
}

.input-group {
margin-bottom: 10px;
}
}
3 changes: 2 additions & 1 deletion src/containers/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export CoreLayout from './CoreLayout'
export MoviesPage from './MoviesPage/MoviesPage'
export MoviesPage from './MoviesPage/MoviesPage'
export LoginPage from './LoginPage/LoginPage'
15 changes: 13 additions & 2 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
<html>
<head>
<title>React Transform Boilerplate</title>
<link href='http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.css'
rel="stylesheet" type="text/css" />
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

<!-- Optional theme -->
<!-- <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> -->


<link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">

<link rel="stylesheet" href="/styles.css">

<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<!-- NO MORE JQUERY - use react bootstrap -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<div id='root'>
Expand Down
27 changes: 27 additions & 0 deletions src/redux/middleware/promiseMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default function promiseMiddleware() {
return next => action => {
const { promise, type, ...rest } = action;

if (!promise) {
return next(action);
}

const [REQUEST, SUCCESS, FAILURE] = types;

next({ ...rest, type: REQUEST });

return promise
.then(res => {
next({ ...rest, res, type: SUCCESS });

return true;
})
.catch(error => {
next({ ...rest, error, type: FAILURE });

// Another benefit is being able to log all failures here
console.log(error);
return false;
});
};
}
Loading

0 comments on commit 87c1aab

Please sign in to comment.