From 26547068faecfdddb3f40a6977083e6e6f5bcecc Mon Sep 17 00:00:00 2001 From: Anton Kuznetsov Date: Mon, 31 Oct 2016 22:02:41 +0300 Subject: [PATCH] Add routes --- index.html | 1 + less/_main.less | 1 - less/awesome-component.less | 6 ---- package.json | 10 ++++-- server.js | 2 +- src/actions/index.js | 34 ++++++++++++++++++ src/common/utils/checkAuthHelper.js | 20 +++++++++++ src/constants/index.js | 7 ++++ src/index.js | 32 +++++++++++++++-- src/pages/Admin.js | 14 ++++++++ src/pages/Login.js | 53 +++++++++++++++++++++++++++++ src/pages/NotFound.js | 7 ++++ src/pages/Posts.js | 30 ++++++++++++++++ src/pages/Wrapper/Wrapper.js | 21 ++++++++++++ src/pages/Wrapper/Wrapper.less | 23 +++++++++++++ src/pages/Wrapper/index.js | 1 + src/reducers/index.js | 17 +++++++++ src/store/index.js | 13 +++++++ webpack.config.js | 32 ++++++++++------- 19 files changed, 299 insertions(+), 25 deletions(-) delete mode 100644 less/awesome-component.less create mode 100644 src/actions/index.js create mode 100644 src/common/utils/checkAuthHelper.js create mode 100644 src/constants/index.js create mode 100644 src/pages/Admin.js create mode 100644 src/pages/Login.js create mode 100644 src/pages/NotFound.js create mode 100644 src/pages/Posts.js create mode 100644 src/pages/Wrapper/Wrapper.js create mode 100644 src/pages/Wrapper/Wrapper.less create mode 100644 src/pages/Wrapper/index.js create mode 100644 src/reducers/index.js create mode 100644 src/store/index.js diff --git a/index.html b/index.html index 17f6e12..91aade9 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ react-hot-env +
diff --git a/less/_main.less b/less/_main.less index c8adc65..c8127a4 100644 --- a/less/_main.less +++ b/less/_main.less @@ -1,3 +1,2 @@ @import '../node_modules/normalize.css/normalize.css'; @import 'global'; -@import 'awesome-component'; diff --git a/less/awesome-component.less b/less/awesome-component.less deleted file mode 100644 index fa9162c..0000000 --- a/less/awesome-component.less +++ /dev/null @@ -1,6 +0,0 @@ -.awesome-component { - color: red; - font-size: 20px; - font-weight: bold; - font-style: italic; -} diff --git a/package.json b/package.json index af92e7e..eb27323 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "babel-eslint": "^7.1.0", "babel-loader": "^6.2.4", "babel-plugin-react-transform": "^2.0.2", - "babel-polyfill": "^6.7.4", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-1": "^6.5.0", @@ -50,6 +49,7 @@ "eslint-plugin-jsx-a11y": "^2.2.3", "eslint-plugin-react": "^6.4.1", "express": "^4.13.4", + "extract-text-webpack-plugin": "^1.0.1", "istanbul": "^0.4.3", "jsdom": "^9.8.3", "less": "^2.6.1", @@ -67,9 +67,15 @@ "webpack-hot-middleware": "^2.10.0" }, "dependencies": { + "babel-polyfill": "^6.16.0", + "isomorphic-fetch": "^2.2.1", + "keymirror": "^0.1.1", "normalize.css": "^5.0.0", "react": "^15.0.2", "react-dom": "^15.0.2", - "react-router": "^3.0.0" + "react-redux": "^4.4.5", + "react-router": "^3.0.0", + "redux": "^3.6.0", + "redux-logger": "^2.7.4" } } diff --git a/server.js b/server.js index 8ae00af..962b6f9 100644 --- a/server.js +++ b/server.js @@ -14,7 +14,7 @@ app.use(require('webpack-dev-middleware')(compiler, { app.use(require('webpack-hot-middleware')(compiler)); app.get('*', function(req, res) { - res.sendFile(path.join(__dirname, 'index.html')); + res.sendFile(path.join(__dirname, 'index.html')); }); app.listen(8181, 'localhost', function (err) { diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 0000000..b5c578a --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,34 @@ +import ActionTypes from '../constants'; + +export function loadPosts() { + return { + type: ActionTypes.LOAD_POSTS, + payload: { + posts: [ + {title: 'Title 1', content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolores perferendis cum nobis, doloribus, ex nisi quibusdam omnis numquam minus illum sint veritatis repellat distinctio accusantium quo fuga quam. Itaque.'}, + {title: 'Title 2', content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolores perferendis cum nobis, doloribus, ex nisi quibusdam omnis numquam minus illum sint veritatis repellat distinctio accusantium quo fuga quam. Itaque.'}, + {title: 'Title 3', content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolores perferendis cum nobis, doloribus, ex nisi quibusdam omnis numquam minus illum sint veritatis repellat distinctio accusantium quo fuga quam. Itaque.'}, + {title: 'Title 4', content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolores perferendis cum nobis, doloribus, ex nisi quibusdam omnis numquam minus illum sint veritatis repellat distinctio accusantium quo fuga quam. Itaque.'}, + {title: 'Title 5', content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolores perferendis cum nobis, doloribus, ex nisi quibusdam omnis numquam minus illum sint veritatis repellat distinctio accusantium quo fuga quam. Itaque.'}, + ] + } + }; +} + +export function loadUserProfile(user, password) { + return { + type: ActionTypes.LOGIN, + payload: { + user: { + id: 42, + name: user, + }, + } + }; +} + +export function badLoadUserProfile() { + return { + type: ActionTypes.UNAUTH_LOGIN, + } +} diff --git a/src/common/utils/checkAuthHelper.js b/src/common/utils/checkAuthHelper.js new file mode 100644 index 0000000..9ce6d2f --- /dev/null +++ b/src/common/utils/checkAuthHelper.js @@ -0,0 +1,20 @@ +import { browserHistory } from 'react-router'; + +export default function checkAuthHelper(store, checker, baseUrl, loginUrl) { + return async ({location: {pathname, search}}, replace, done) => { + const { payload } = await store.dispatch(checker()); + + if (!payload && pathname !== loginUrl) { + replace({ + pathname: loginUrl, + state: { nextPagePathname: `${pathname}${search}` } + }); + } + + if (payload && pathname === loginUrl) { + replace(baseUrl); + } + + done(); + }; +} diff --git a/src/constants/index.js b/src/constants/index.js new file mode 100644 index 0000000..4bd7dbe --- /dev/null +++ b/src/constants/index.js @@ -0,0 +1,7 @@ +import keymirror from 'keymirror'; + +export default keymirror({ + LOGIN: null, + LOAD_POSTS: null, + UNAUTH_LOGIN: null, +}); diff --git a/src/index.js b/src/index.js index 84fa025..ea6e394 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,34 @@ import '../less/_main.less'; import React from 'react'; import { render } from 'react-dom'; -import AwesomeComponent from './components/AwesomeComponent.jsx'; +import { Router, Route, IndexRoute, browserHistory } from 'react-router'; +import { Provider } from 'react-redux'; +import checkAuthHelper from './common/utils/checkAuthHelper'; +import { badLoadUserProfile } from './actions'; +import store from './store'; +import Wrapper from './pages/Wrapper'; +import Admin from './pages/Admin'; +import Login from './pages/Login'; +import Posts from './pages/Posts'; +import NotFound from './pages/NotFound'; -render(React.createElement(AwesomeComponent), document.getElementById('react-root')); +const baseUrl = '/'; +const loginUrl = `${baseUrl}login`; +const checkAuth = checkAuthHelper(store, badLoadUserProfile, baseUrl, loginUrl); + +function MyReactApp () { + return ( + + + + + + + + + + + ); +} + +render(React.createElement(MyReactApp), document.getElementById('react-root')); diff --git a/src/pages/Admin.js b/src/pages/Admin.js new file mode 100644 index 0000000..47b5d4a --- /dev/null +++ b/src/pages/Admin.js @@ -0,0 +1,14 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router'; + +export default class Admin extends Component { + render() { + return ( +
+

Admin Page

+ + Posts +
+ ); + } +} diff --git a/src/pages/Login.js b/src/pages/Login.js new file mode 100644 index 0000000..98756b7 --- /dev/null +++ b/src/pages/Login.js @@ -0,0 +1,53 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { loadUserProfile } from '../actions'; + +class Login extends Component { + state = { + user: '', + password: '', + } + + onChange = ({target: {value, name}}) => this.setState({[name]: value}) + + onSubmit = async event => { + event.preventDefault(); + + await Promise.resolve() + .then(() => this.props.loadUserProfile(this.state.user, this.state.password)) + .then(() => { + console.log(this.props) + if (this.props.user && this.props.user.name) { + this.props.router.replace(this.props.location.state.nextPagePathname); + } + }); + } + + render() { + return ( +
+
+
+ +
+
+ +
+ +
+
+ ); + } +} + +export default connect(({user}) => ({user}), { loadUserProfile })(Login); diff --git a/src/pages/NotFound.js b/src/pages/NotFound.js new file mode 100644 index 0000000..1a61139 --- /dev/null +++ b/src/pages/NotFound.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function NotFound() { + return ( +

There is no handler for that page

+ ); +} diff --git a/src/pages/Posts.js b/src/pages/Posts.js new file mode 100644 index 0000000..3ae1cf3 --- /dev/null +++ b/src/pages/Posts.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { loadPosts } from '../actions'; + +class Posts extends Component { + componentDidMount() { + this.props.loadPosts(); + } + + render() { + return ( +
+

Posts Page

+ +
+ {this.props.posts.map((post, i) => { + return ( +
+
{post.title}
+
{post.content}
+
+ ); + })} +
+
+ ); + } +} + +export default connect(({posts}) => ({posts}), {loadPosts})(Posts); diff --git a/src/pages/Wrapper/Wrapper.js b/src/pages/Wrapper/Wrapper.js new file mode 100644 index 0000000..0a2df53 --- /dev/null +++ b/src/pages/Wrapper/Wrapper.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Link } from 'react-router'; +import style from './Wrapper.less'; + +export default function Wrapper({children}) { + return ( +
+
+

+ Header +

+
+
+ {children} +
+
+

Footer

+
+
+ ); +} diff --git a/src/pages/Wrapper/Wrapper.less b/src/pages/Wrapper/Wrapper.less new file mode 100644 index 0000000..bfec4d0 --- /dev/null +++ b/src/pages/Wrapper/Wrapper.less @@ -0,0 +1,23 @@ +:local { + .wrapper { + max-width: 1080px; + } + + .header { + margin: 0 auto; + width: 100%; + } + + .headerLink { + color: red; + font-size: 20px; + } + + .content { + margin: 0 auto; + } + + .footer { + margin: 0 auto; + } +} diff --git a/src/pages/Wrapper/index.js b/src/pages/Wrapper/index.js new file mode 100644 index 0000000..5b9663f --- /dev/null +++ b/src/pages/Wrapper/index.js @@ -0,0 +1 @@ +export default from './Wrapper'; diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 0000000..fc6d711 --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,17 @@ +import ActionTypes from '../constants'; + +export function user(state = {}, {type, payload}) { + if (type === ActionTypes.LOGIN) { + return payload.user; + } + + return state; +} + +export function posts(state = [], {type, payload}) { + if (type === ActionTypes.LOAD_POSTS) { + return payload.posts; + } + + return state; +} diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..a1bbf2f --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,13 @@ +import { createStore, applyMiddleware, combineReducers } from 'redux'; +import logger from 'redux-logger'; +import { user, posts } from '../reducers'; + +const { NODE_ENV } = process.env; +const common = []; +const middleware = NODE_ENV === 'production' ? [...common] : [...common, logger()]; +const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore); + +export default createStoreWithMiddleware(combineReducers({ + user, + posts, +})); diff --git a/webpack.config.js b/webpack.config.js index c50c8f2..cedc733 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,19 +2,23 @@ var path = require('path'); var webpack = require('webpack'); var autoprefixer = require('autoprefixer'); - var ENV = process.env; var PRODUCTION = ENV.NODE_ENV == 'production'; var devtool = PRODUCTION ? false : 'eval'; var entry, plugins; +const commonModules = ['babel-polyfill', 'isomorphic-fetch']; +const commonPlugins = [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(ENV.NODE_ENV) || 'development' + }, + }), +]; if (PRODUCTION){ - entry = ['./src/index']; + entry = [...commonModules, './src/index']; plugins = [ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify(ENV.NODE_ENV) - }}), + ...commonPlugins, new webpack.optimize.OccurenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin({ sourceMap: false, @@ -28,16 +32,13 @@ if (PRODUCTION){ } else { entry = [ 'webpack-hot-middleware/client', + ...commonModules, './src/index' ]; plugins = [ + ...commonPlugins, new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify(ENV.NODE_ENV) || 'development' - }, - }) ]; } @@ -53,8 +54,13 @@ module.exports = { module: { loaders: [ {test: /\.jsx?$/, loaders: ['babel'], include: path.join(__dirname, 'src')}, - {test: /\.less$/, loaders: ['style', 'css', 'postcss', 'less'], include: path.join(__dirname, 'less')}, - {test: /\.css$/, loaders: ['style', 'css', 'postcss']}, + { + test: /\.(less|css)$/, + loaders: [ + 'style', + 'css?importLoaders=2&localIdentName=[name]_[local]_[hash:base64:5]!postcss!less' + ], + }, ] }, resolve: ['', 'js', 'jsx', 'less'],