Skip to content

Commit 87c1aab

Browse files
committed
Login page added (promise middleware + axios) + simple API server
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
1 parent a55c325 commit 87c1aab

17 files changed

+442
-41
lines changed

api/api.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
var express = require('express');
2+
var path = require('path');
3+
4+
var app = express();
5+
6+
var isProduction = process.env.NODE_ENV === 'production';
7+
var port = isProduction ? process.env.PORT : 3001;
8+
9+
var bodyParser = require('body-parser');
10+
app.use(bodyParser.json({ type: 'application/json' }))
11+
12+
app.use(function(req, res, next) {
13+
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
14+
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
15+
res.header('Access-Control-Allow-Headers', 'Content-Type');
16+
17+
next();
18+
});
19+
20+
app.post('/api/login', function(req, res) {
21+
const credentials = req.body;
22+
if(credentials.user==='admin' && credentials.password==='password'){
23+
res.json({'user': credentials.user, 'role': 'ADMIN'});
24+
}else{
25+
res.status('500').send({'message' : 'Invalid user/password'});
26+
}
27+
});
28+
29+
app.post('/api/logout', function(req, res) {
30+
res.json({'user': 'admin', 'role': 'ADMIN'});
31+
});
32+
33+
app.listen(port, function () {
34+
console.log('Server running on port ' + port);
35+
});

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js",
88
"build": "npm run clean && npm run build:webpack",
99
"start": "npm run clean && node devServer.js",
10+
"api": "node ./api/api.js",
1011
"lint": "eslint src"
1112
},
1213
"repository": {
@@ -32,6 +33,7 @@
3233
},
3334
"homepage": "https://github.com/jsdmc/react-redux-router-crud-boilerplate",
3435
"dependencies": {
36+
"axios": "^0.7.0",
3537
"classnames": "^2.1.5",
3638
"history": "^1.12.0",
3739
"react": "^0.14.0-rc1",

src/components/Header/Header.jsx

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { Component } from 'react';
2+
// import { Link } from 'react-router';
3+
import './Header.scss';
4+
5+
export default class Header extends Component {
6+
onLogoutClick() {
7+
event.preventDefault();
8+
// this.props.handleLogout();
9+
}
10+
11+
render() {
12+
return (
13+
<nav className="navbar navbar-inverse navbar-fixed-top">
14+
<div className="container-fluid">
15+
<div className="navbar-header">
16+
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
17+
<span className="sr-only">Toggle navigation</span>
18+
<span className="icon-bar"></span>
19+
<span className="icon-bar"></span>
20+
<span className="icon-bar"></span>
21+
</button>
22+
<a className="navbar-brand" href="#">React-Redux-Router-CRUD-boilerplate</a>
23+
</div>
24+
<div id="navbar" className="navbar-collapse collapse">
25+
<ul className="nav navbar-nav navbar-right">
26+
<li className="dropdown">
27+
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
28+
<span className='fa fa-user header_fa'></span>'Anonymous'<span className="caret"></span>
29+
</a>
30+
<ul className="dropdown-menu">
31+
<li><a href="#">Settings</a></li>
32+
<li><a href="#">Profile</a></li>
33+
<li className="logout-link"><a href="#" onClick={e=>this.onLogoutClick(e)}><i className="fa fa-sign-out header_fa"/>Log out</a></li>
34+
35+
<li role="separator" className="divider"></li>
36+
<li>
37+
<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>
38+
</li>
39+
</ul>
40+
</li>
41+
</ul>
42+
<ul className="nav navbar-nav navbar-right">
43+
<li><a href="#">Dashboard</a></li>
44+
<li><a href="#">Help</a></li>
45+
</ul>
46+
<form className="navbar-form navbar-right">
47+
<input type="text" className="form-control" placeholder="Search..." />
48+
</form>
49+
</div>
50+
</div>
51+
</nav>
52+
);
53+
}
54+
}

src/components/Header/Header.scss

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
:global {
2+
.navbar-brand {
3+
position:relative;
4+
padding-left: 60;
5+
}
6+
7+
.nav .header_fa {
8+
font-size: 1em;
9+
margin-right: 0.5em;
10+
}
11+
12+
.navbar-default .navbar-nav>li>a:focus {
13+
outline: none;
14+
}
15+
}

src/components/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*
66
*/
77

8+
export Header from './Header/Header';
89
export AutoCounter from './AutoCounter/AutoCounter';
910
export Counter from './Counter';
1011
export SmartLink from './SmartLink/SmartLink';

src/containers/CoreLayout.js

+2-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { Component, PropTypes } from 'react';
2-
import { SmartLink } from '../components';
2+
import { Header, SmartLink } from '../components';
33
import '../styles/main.scss';
44

55
export default class CoreLayout extends Component {
@@ -9,36 +9,9 @@ export default class CoreLayout extends Component {
99
}
1010

1111
render() {
12-
const navTop = () => (
13-
<nav className="navbar navbar-inverse navbar-fixed-top">
14-
<div className="container-fluid">
15-
<div className="navbar-header">
16-
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
17-
<span className="sr-only">Toggle navigation</span>
18-
<span className="icon-bar"></span>
19-
<span className="icon-bar"></span>
20-
<span className="icon-bar"></span>
21-
</button>
22-
<a className="navbar-brand" href="#">React-Redux-Router-CRUD-boilerplate</a>
23-
</div>
24-
<div id="navbar" className="navbar-collapse collapse">
25-
<ul className="nav navbar-nav navbar-right">
26-
<li><a href="#">Dashboard</a></li>
27-
<li><a href="#">Settings</a></li>
28-
<li><a href="#">Profile</a></li>
29-
<li><a href="#">Help</a></li>
30-
</ul>
31-
<form className="navbar-form navbar-right">
32-
<input type="text" className="form-control" placeholder="Search..." />
33-
</form>
34-
</div>
35-
</div>
36-
</nav>
37-
);
38-
3912
return (
4013
<div>
41-
{ navTop() }
14+
<Header />
4215
<div className="container-fluid">
4316
<div className="row">
4417
<div className="col-sm-3 col-md-2 sidebar">
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { connect } from 'react-redux';
3+
import { pushState } from 'redux-router';
4+
import { login } from '../../redux/modules/auth';
5+
6+
import './LoginPage.scss';
7+
8+
class Login extends Component {
9+
10+
componentWillReceiveProps(nextProps) {
11+
console.log('Login: componentWillReceiveProps: '+nextProps);
12+
console.log('user: '+this.props.user+'\tnextUser='+nextProps.user);
13+
14+
if (nextProps.user) {
15+
// logged in, let's show home
16+
this.dispatch(pushState(null, '/counter'));
17+
}
18+
}
19+
20+
handleLogin(event) {
21+
event.preventDefault();
22+
const username = this.refs.username; // need for getDOMNode() call going away in React 0.14
23+
const password = this.refs.password;
24+
this.props.dispatch(login(username.value, password.value));
25+
// username.value = '';
26+
// password.value = '';
27+
}
28+
29+
render(){
30+
const {user, loginError} = this.props;
31+
return(
32+
<div className="container">
33+
<div className="row">
34+
<div className="col-md-4 col-md-offset-4">
35+
<div className="panel panel-default panel-signin">
36+
<div className="panel-heading">
37+
<h3 className="panel-title">Please Log in</h3>
38+
</div>
39+
<form className="form-signin">
40+
41+
<div className="input-group">
42+
<span className="input-group-addon"><i className="fa fa-user"/></span>
43+
<input type="text" ref="username" className="form-control" placeholder="Username" required autofocus/>
44+
</div>
45+
46+
<div className="input-group">
47+
<span className="input-group-addon"><i className="fa fa-lock"/></span>
48+
<input type="password" ref="password" className="form-control" placeholder="Password" required/>
49+
</div>
50+
51+
<div className="checkbox">
52+
<label>
53+
<input type="checkbox" value="remember-me"/> Remember me
54+
</label>
55+
</div>
56+
57+
{
58+
!user && loginError &&
59+
<div className="alert alert-danger">
60+
{loginError.message}. Hint: use admin/password to log in.
61+
</div>
62+
}
63+
64+
<button className="btn btn-primary btn-block" onClick={::this.handleLogin}><i className="fa fa-sign-in"/>{' '}Log in</button>
65+
</form>
66+
</div>
67+
</div>
68+
</div>
69+
</div>
70+
);
71+
}
72+
}
73+
74+
Login.propTypes = {
75+
user: PropTypes.string,
76+
loginError: PropTypes.object,
77+
dispatch: PropTypes.func.isRequired,
78+
routerState: PropTypes.object.isRequired
79+
};
80+
81+
function mapStateToProps(state){
82+
const { auth, router } = state;
83+
if(auth){
84+
return {user: auth.user, loginError: auth.loginError, routerState: router};
85+
}else{
86+
return {user: null, routerState: router};
87+
}
88+
}
89+
90+
export default connect(mapStateToProps)(Login);
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
:global{
2+
.panel-signin {
3+
margin-top: 25%;
4+
}
5+
6+
.form-signin {
7+
max-width: auto;
8+
padding: 15px;
9+
margin: 0 auto;
10+
11+
.checkbox {
12+
margin-bottom: 10px;
13+
}
14+
15+
.form-control:focus {
16+
z-index: 2;
17+
}
18+
input[type="text"] {
19+
margin-bottom: 0px;
20+
}
21+
input[type="password"] {
22+
margin-bottom: 0px;
23+
}
24+
}
25+
26+
.input-group {
27+
margin-bottom: 10px;
28+
}
29+
}

src/containers/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export CoreLayout from './CoreLayout'
2-
export MoviesPage from './MoviesPage/MoviesPage'
2+
export MoviesPage from './MoviesPage/MoviesPage'
3+
export LoginPage from './LoginPage/LoginPage'

src/index.html

+13-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@
22
<html>
33
<head>
44
<title>React Transform Boilerplate</title>
5-
<link href='http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.css'
6-
rel="stylesheet" type="text/css" />
5+
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
6+
7+
<!-- Optional theme -->
8+
<!-- <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> -->
9+
10+
11+
<link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
12+
713
<link rel="stylesheet" href="/styles.css">
14+
15+
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
16+
<!-- NO MORE JQUERY - use react bootstrap -->
17+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
18+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
819
</head>
920
<body>
1021
<div id='root'>
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export default function promiseMiddleware() {
2+
return next => action => {
3+
const { promise, type, ...rest } = action;
4+
5+
if (!promise) {
6+
return next(action);
7+
}
8+
9+
const [REQUEST, SUCCESS, FAILURE] = types;
10+
11+
next({ ...rest, type: REQUEST });
12+
13+
return promise
14+
.then(res => {
15+
next({ ...rest, res, type: SUCCESS });
16+
17+
return true;
18+
})
19+
.catch(error => {
20+
next({ ...rest, error, type: FAILURE });
21+
22+
// Another benefit is being able to log all failures here
23+
console.log(error);
24+
return false;
25+
});
26+
};
27+
}

0 commit comments

Comments
 (0)