<file_sep>import React from 'react';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import Constants from '../Constants';

class Header extends React.Component {

	logout() {
		this.props.logoutA();
	}

	render() {

		let loggedIn = '';
		if(this.props.authData.username) {
			loggedIn = <li><NavLink to={Constants.ROUTE_THINGS_ALL}><span className="glyphicon glyphicon-user"></span> {this.props.authData.username} </NavLink></li>
		}

		let loginLink = '';
		let logoutLink = '';
		if(this.props.authData.username) {
			logoutLink = <li><NavLink to={Constants.ROUTE_THINGS_ALL} onClick={this.logout.bind(this)}><span className="glyphicon glyphicon-log-out"></span> Logout</NavLink></li>
		} else {
			loginLink = <li><NavLink to={Constants.ROUTE_LOGIN}><span className="glyphicon glyphicon-log-in"></span> Login</NavLink></li>
		}

		let homeClassName = this.props.authData.loadingData ? 'glyphicon glyphicon-grain loading-data' : 'glyphicon glyphicon-grain';

		return (<header>
			<nav className="navbar navbar-default">
				<div className="container-fluid">
					<div className="navbar-header">
						<button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
							<span className="icon-bar"></span>
							<span className="icon-bar"></span>
							<span className="icon-bar"></span>                        
						</button>
						<NavLink className="navbar-brand" to={Constants.ROUTE_THINGS_ALL}>Bozo's playground <span className={homeClassName}></span></NavLink>
					</div>
					<div className="collapse navbar-collapse" id="myNavbar">
						<ul className="nav navbar-nav">
							<li><NavLink exact activeClassName='active' to={Constants.ROUTE_THINGS_ALL}>All things</NavLink></li>
							<li><NavLink exact activeClassName='active' to={Constants.ROUTE_THING_CREATE}>Create new thing</NavLink></li>
						</ul>
						<ul className="nav navbar-nav navbar-right">
							{loggedIn}
							{loginLink}
							{logoutLink}
						</ul>
					</div>
				</div>
			</nav>
		</header>)
	}
};

const logoutA = () => {
	return {type: 'SET_USERNAME', payload: ''};
}

const mapStateToProps = (state) => {
	return {
		authData: state.authR.authData
	};
};

const mapDispatchToProps = (dispatch) => {
	return bindActionCreators({
		logoutA : logoutA
	}, dispatch);
};

export default connect(mapStateToProps, mapDispatchToProps)(Header);<file_sep>import React from 'react';
import { Route, Switch } from 'react-router-dom';

import { Things } from "../components/Things"
import Thing from "../components/Thing"
import { Login } from "../components/Login"
import AuthenticatedRoute from "./AuthenticatedRoute"
import Constants from '../Constants';

const Main = () => {
	return (
		<main>
			<Switch>
				<Route exact path={Constants.ROUTE_THINGS_ALL} component={Things} />
				<Route exact path={Constants.ROUTE_THING_VIEW} component={Thing} />
				<AuthenticatedRoute path={Constants.ROUTE_THING_CREATE} exact component={Thing} />
				<AuthenticatedRoute path={Constants.ROUTE_THING_EDIT} exact component={Thing} />
				<Route exact path={Constants.ROUTE_LOGIN} component={Login} />
				<Route path="*" component={Things} />  
			</Switch>
		</main>
	)
};

export default Main;<file_sep>
# reactjs-boilerplate
Boilerplate code for ReactJS + Redux + Less + Controlled form + AJAX + HTTP request interceptor + Router v4 + Authentication

## Demo app

[Demo](http://www.srpskibre.com/react)

## Purpose

Boilerplate code for 
- ReactJS + Redux 
- Less -> CSS 
- Controlled form 
- AJAX with Axios + HTTP request interceptor 
- Router v4 + 2 ways to redirect to another route
- User authentication

with the latest versions of all dependencies (March 16, 2018)
<file_sep>const Constants = {
	ROUTE_PUBLIC: '/public',
	ROUTE_PRIVATE: '/private',
	ROUTE_LOGIN: '/login',
	ROUTE_THING_VIEW: '/thing/view/:thingid',
	ROUTE_THING_CREATE: '/thing/create',
	ROUTE_THING_EDIT: '/thing/edit/:thingid',
	ROUTE_THINGS_ALL: '/things',

	API_URL: ''
};

export default Constants;<file_sep>import React, { Component } from 'react';
import './App.css';
import x from './assets/x.jpg';
import Main from './layout/Main';
import Header from './layout/Header';

class App extends Component {
  render() {
    return (
      <div>
        <Header />
        <div className="container">
          <Main />
        </div>
        
        {/* <br /><br /><img src={x} alt='x' width='600px' /> */}
      </div>
    );
  }
}

export default App;
<file_sep>import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import Constants from '../Constants';
import { loadAllThings } from "./Things"

class Thing extends React.Component {

	constructor(props) {
		super(props);
		this.state = {
			thingsData : {
				things : [],
				thingForm : {
					id : null,
					name : '',
					surname : ''
				}
			}
		}
	}

	componentDidMount() {
		if(this.props.thingsData.things.length === 0) {
			this.props.loadAllThings(this.props.history);
		} else {
			this.prepareThingToEdit();
		}
	}

	componentWillReceiveProps(nextProps) {
		this.prepareThingToEdit();
	}

	getThingIdParam() {
		if(this.props.match.params) {
			return parseInt(this.props.match.params.thingid, 10);
		} else {
			return null;
		}
	}

	prepareThingToEdit() {

		let thingToEdit = {
			id : null,
			name : '',
			surname : ''
		}

		const thingId = this.getThingIdParam();

		if(thingId) {
			const allThings = this.props.thingsData.things;
			const thingToEditIndex = allThings.findIndex(thing => thing.id === thingId);
			const tte = allThings[thingToEditIndex];
			if(tte) {
				thingToEdit = tte;
			}
		}

		this.setState({
			thingsData : {
				...this.props.thingsData,
				thingForm : thingToEdit
			}
		});
	}

	onChange(e) {
		this.setState({
			thingsData : {
				...this.state.thingsData,
				thingForm : {
					...this.state.thingsData.thingForm,
					[e.target.name] : e.target.value
				}
			}
		});
	}

	onSubmit(e) {
		e.preventDefault();
		if(this.state.thingsData.thingForm.name.trim() && this.state.thingsData.thingForm.surname.trim()) {
			this.props.submitThing(this.state.thingsData.thingForm);
			this.redirectTo(Constants.ROUTE_THINGS_ALL);
		}
	}

	redirectTo(route) {
		this.props.history.push(route);
	}

	render() {

		if(this.props.match.path === Constants.ROUTE_THING_VIEW) {
			return (<div>
				<h1>View thing</h1>
				<div className="row">
					<div className="col-md-6">
						<table className="table table-hover">
							<tbody>
								<tr>
									<td><b>ID</b></td>
									<td>{this.state.thingsData.thingForm.id}</td>
								</tr>
								<tr>
									<td><b>Name</b></td>
									<td>{this.state.thingsData.thingForm.name}</td>
								</tr>
								<tr>
									<td><b>Surname</b></td>
									<td>{this.state.thingsData.thingForm.surname}</td>
								</tr>
							</tbody>
						</table>
					</div>
				</div>
			</div>)
		}

		return (<div>
			<h1>{this.props.match.path === Constants.ROUTE_THING_CREATE ? 'Create' : 'Edit'} thing</h1>
			<div className="row">
				<div className="col-md-6">
					<form onSubmit={this.onSubmit.bind(this)} >
						<div className="form-group">
							<label htmlFor="name">Name :</label>
							<input type="text" className="form-control" id="name" name="name" value={this.state.thingsData.thingForm.name} onChange={this.onChange.bind(this)} />
						</div>
						<div className="form-group">
							<label htmlFor="name">Surname :</label>
							<input type="text" className="form-control" id="surname" name="surname" value={this.state.thingsData.thingForm.surname} onChange={this.onChange.bind(this)} />
						</div>
						<button type="submit" className="btn btn-primary">Save</button>&nbsp;
					</form>
				</div>
			</div>
		</div>)
	}
};

const submitThing = (thing) => {
	return {type: 'CREATE_EDIT_THING', payload: thing};
};

const mapStateToProps = (state) => {
	return {
		authData: state.authR.authData,
		thingsData: state.thingsR.thingsData
	};
};

const mapDispatchToProps = (dispatch) => {
	return bindActionCreators({
		submitThing: submitThing,
		loadAllThings: loadAllThings
	}, dispatch);
};

export default connect(mapStateToProps, mapDispatchToProps)(Thing);
<file_sep>import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import axios from "axios";
import { Link } from 'react-router-dom';

import Constants from '../Constants';
import { interceptAjaxCalls } from "../Interceptor"

class ThingsC extends React.Component {

	constructor(props) {
		super(props);
		this.state = {

		}
	}

	componentDidMount() {
		if(this.props.thingsData.things.length === 0) {
			this.props.loadAllThings(this.props.history);
		}
	}

	render() {

		const thingsTable = this.props.thingsData.things.map(thing => <tr key={thing.id}>
				<td>{thing.id}</td>
				<td>{thing.name} {thing.surname}</td>
				<td className="right-float">
					<Link to={`/thing/view/${thing.id}`}><span className="glyphicon glyphicon-list" title="View thing" ></span></Link> &nbsp;
					<Link to={`/thing/edit/${thing.id}`}><span className="glyphicon glyphicon-edit" title="Edit thing" ></span></Link> &nbsp;
				</td>
			</tr>);

		return (<div>
			<h1>All things</h1>
			<div className="row">
				<div className="col-md-6">
					<table className="table">
						<thead>
							<tr>
								<th>ID</th>
								<th>Name</th>
								<th>&nbsp;</th>
							</tr>
						</thead>
						<tbody>
							{thingsTable}
						</tbody>
					</table>
				</div>
			</div>
		</div>)
	}
};

const loadAllThingsAction = (hist) => {
	return function(dispatch) {
		dispatch(interceptAjaxCalls(axios, hist));
		dispatch({type: "SET_LOADING", payload: true});
		axios.get(Constants.API_URL + '/things.json', {

		}).then((response) => {
			dispatch({type: "ADD_THINGS", payload: response.data.things});
			dispatch({type: "SET_LOADING", payload: false});
		})
		.catch((err) => {
			console.log(err);
			dispatch({type: "SET_LOADING", payload: false});
		})
	}
}

const mapStateToProps = (state) => {
	return {
		authData: state.authR.authData,
		thingsData: state.thingsR.thingsData
	};
};

const mapDispatchToProps = (dispatch) => {
	return bindActionCreators({
		loadAllThings : loadAllThingsAction
	}, dispatch);
};

export const Things = connect(mapStateToProps, mapDispatchToProps)(ThingsC);
export const loadAllThings = loadAllThingsAction;
<file_sep>
export default function reducer(state=
	{
		authData: {
			username : null,
			redirectedFrom : null,
			loadingData : false
		}
	}, action) {

	switch (action.type) {
		case "SET_USERNAME": {
			return {...state, 
				authData: {
					...state.authData,
					username: action.payload
				}
			}
		}

		case "SET_REDIRECTED_FROM": {
			return {...state, 
				authData: {
					...state.authData,
					redirectedFrom: action.payload
				}
			}
		}

		case "SET_LOADING": {
			return {...state, 
				authData: {
					...state.authData,
					loadingData: action.payload
				}
			}
		}

		default : {
			return {...state};
		}
	}

}<file_sep>import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Redirect } from "react-router-dom";

class LoginC extends React.Component {
	
	constructor(props) {
		super(props);
		this.state = {
			username: ''
		}
	}
	
	onChange(e) {
		this.setState({ [e.target.name] : e.target.value });
	}
	
	onSubmit(e) {
		e.preventDefault();
		if(this.state.username) {
			this.props.saveUsername(this.state);
		}
	}

	componentWillUnmount() {
		if(this.props.authData.username && this.props.authData.redirectedFrom) {
			this.props.setRedirectedFrom(null);
		}
	}
	
	render() {

		if(this.props.authData.username) {
			if(this.props.authData.redirectedFrom) {
				return <Redirect to={this.props.authData.redirectedFrom} />;
			} else {
				return <Redirect to={'/'}/>;
			}
		}

		return (<div className="row">
			<div className="col-sm-4 col-md-4 offset-sm-4 offset-md-4">
				<form onSubmit={this.onSubmit.bind(this)}>
					<p>Please login!</p>
					<div className="form-group">
						<label htmlFor="username">Your name : </label>
						<input type="username" className="form-control" id="username" name="username" value={this.state.username} onChange={this.onChange.bind(this)} placeholder="Just enter some name" />
					</div>
					<button type="submit" className="btn btn-default">Login</button>
				</form>
			</div>
		</div>)
	}
}

const saveUsername = (st) => {
	return {type: 'SET_USERNAME', payload: st.username};
}

const setRedirectedFromAction = (redirectedFrom) => {
	return {type: 'SET_REDIRECTED_FROM', payload: redirectedFrom};
}

const mapStateToProps = (state) => {
	return {
		authData: state.authR.authData
	};
};

const mapDispatchToProps = (dispatch) => {
	return bindActionCreators({
		saveUsername : saveUsername,
		setRedirectedFrom : setRedirectedFromAction,
	}, dispatch);
};

export const Login = connect(mapStateToProps, mapDispatchToProps)(LoginC);
export const setRedirectedFrom = setRedirectedFromAction;
<file_sep>import { combineReducers } from "redux"

import authR from "./AuthReducer"
import thingsR from "./ThingsReducer"

export default combineReducers({
    authR,
    thingsR
})
