├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .jshintrc ├── README.md ├── config └── index.js ├── package.json ├── server.js ├── server ├── package.json └── server.js ├── source ├── actions │ └── index.js ├── components │ ├── BlogFosterLogo │ │ └── index.js │ ├── HelloWorld │ │ └── index.js │ ├── SignUpWizard │ │ ├── AccountDetails.js │ │ ├── BloggingPreferences.js │ │ ├── SignUp.scss │ │ └── index.js │ └── index.js ├── constants │ └── index.js ├── containers │ ├── App │ │ ├── App.scss │ │ └── index.js │ ├── Root │ │ └── index.js │ ├── SignUp │ │ └── index.js │ └── index.js ├── images │ └── logo.png ├── index.html ├── index.js ├── reducers │ └── index.js ├── routes │ └── index.js └── utils │ └── index.js └── webpack ├── dev.config.js └── prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack 2 | server.js 3 | dist 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | parser: 'babel-eslint' 2 | 3 | plugins: [ 'react' ] 4 | 5 | ecmaFeatures: 6 | jsx: true 7 | 8 | env: 9 | es6: true 10 | browser: true 11 | jasmine: true 12 | node: true 13 | 14 | extends: 'eslint-config-airbnb' 15 | 16 | rules: 17 | comma-dangle: 0 18 | object-curly-spacing: [ 2, 'always' ] 19 | 20 | # React stuff. 21 | react/display-name: 0 22 | react/jsx-boolean-value: 2 23 | react/jsx-no-undef: 2 24 | react/jsx-sort-props: 0 25 | react/jsx-uses-react: 2 26 | react/jsx-uses-vars: 2 27 | react/no-did-mount-set-state: 2 28 | react/no-did-update-set-state: 2 29 | react/no-multi-comp: 0 30 | react/no-unknown-property: 2 31 | react/prop-types: 2 32 | react/react-in-jsx-scope: 2 33 | react/self-closing-comp: 2 34 | react/wrap-multilines: 2 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .DS_Store 3 | node_modules 4 | source/build 5 | .divshot-cache 6 | *.log 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "asi" : false, 4 | "bitwise" : true, 5 | "boss" : false, 6 | "curly" : true, 7 | "debug": false, 8 | "devel": false, 9 | "eqeqeq": true, 10 | "evil": false, 11 | "expr": true, 12 | "forin": false, 13 | "immed": true, 14 | "latedef" : false, 15 | "laxbreak": false, 16 | "multistr": true, 17 | "newcap": true, 18 | "noarg": true, 19 | "node" : true, 20 | "browser": true, 21 | "noempty": false, 22 | "nonew": true, 23 | "onevar": false, 24 | "plusplus": false, 25 | "regexp": false, 26 | "strict": false, 27 | "sub": false, 28 | "trailing" : true, 29 | "undef": true, 30 | "unused": "vars" 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Front Stack Boilerplate 2 | 3 | The boilerplate we are using build *awesome* web native applications. 4 | 5 | There is a client application in [/source](/source) and test server in [/server](/server) folders. 6 | 7 | ## Installation 8 | 9 | Install dependencies, 10 | 11 | ```bash 12 | $ npm install 13 | ``` 14 | 15 | Run build, 16 | 17 | ```bash 18 | $ npm run build 19 | ``` 20 | 21 | Launch application, 22 | 23 | ```bash 24 | $ npm start 25 | ``` 26 | 27 | Open in browser, 28 | 29 | ```bash 30 | $ open http://localhost:3000 31 | ``` 32 | 33 | ## Project Structure 34 | 35 | The sources of application is located in `/source` folder. The distributive version will be placed to `/public` folder after build. Normally, you should always use development configuration and build public version right before the deployment. 36 | 37 | ### [/source/index.html](/source/index.html) 38 | 39 | The `index.html` file, references some CDN resources, fonts, 3rd party CSS files. 40 | 41 | ### [/source/router](/source/router) 42 | 43 | The entry point of application. Initializes `router` and renders the application to `#main` div. 44 | 45 | ### [/source/containers](/source/containers) 46 | 47 | The [smart]() components of application. Setting up the layout of `components` and manage the state. 48 | 49 | ### [/source/components](/source/components) 50 | 51 | The [dump]() components, with own style, initialized via properties, stateless. 52 | 53 | ### [/source/actions](/source/actions) 54 | 55 | The [redux/actions]() related code. 56 | 57 | ### [/source/reducers](/source/reducers) 58 | 59 | The [redux/reducers]() related code. 60 | 61 | ## Materials 62 | 63 | * [Front-end Engineering Stack](https://github.com/blogfoster/blogfoster-engineering/wiki/Frontend-Engineering-Stack) 64 | * [Setting Up Webpack for React and Hot Module Replacement](https://robots.thoughtbot.com/setting-up-webpack-for-react-and-hot-module-replacement) 65 | * [Survive.js](http://survivejs.com/webpack_react/introduction/) 66 | * [Atomic Web Design](http://bradfrost.com/blog/post/atomic-web-design/) 67 | * [Smart and Dumb components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) 68 | 69 | ## Inspired from 70 | 71 | * [emmenko/redux-react-router-async-example](https://github.com/emmenko/redux-react-router-async-example) 72 | * [joshgeller/react-redux-jwt-auth-example](https://github.com/joshgeller/react-redux-jwt-auth-example) 73 | 74 | ## Libraries and Tools 75 | 76 | * [Babel](https://babeljs.io/) 77 | * [React](https://facebook.github.io/react/) 78 | * [Redux](https://github.com/rackt/redux) 79 | * [ReduxRouter](https://github.com/acdlite/redux-router) 80 | * [ReduxThunk](https://github.com/gaearon/redux-thunk) 81 | * [Webpack](https://github.com/webpack/webpack) 82 | * [ESLint](http://eslint.org/) 83 | 84 | ## License 85 | 86 | MIT team@blogfoster.com 87 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | url: 'http://localhost:3010' 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-stack", 3 | "version": "1.0.0", 4 | "description": "The boilerplate we are using build awesome web native applications.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "watch": "npm start", 9 | "build": "webpack --verbose --colors --display-error-details --config webpack/dev.config.js", 10 | "start": "nodemon -w server server/server & webpack-dev-server --config webpack/dev.config.js" 11 | }, 12 | "author": "alexander.beletsky@blogfoster.com", 13 | "license": "ISC", 14 | "dependencies": { 15 | "babel": "^6.3.26", 16 | "babel-polyfill": "^6.3.14", 17 | "history": "^1.17.0", 18 | "isomorphic-fetch": "^2.2.0", 19 | "material-ui": "^0.14.1", 20 | "react": "^0.14.6", 21 | "react-addons-create-fragment": "^0.14.6", 22 | "react-addons-pure-render-mixin": "^0.14.6", 23 | "react-addons-transition-group": "^0.14.6", 24 | "react-addons-update": "^0.14.6", 25 | "react-dom": "^0.14.6", 26 | "react-redux": "^4.0.6", 27 | "react-router": "^1.0.3", 28 | "redux": "^3.0.5", 29 | "redux-router": "^1.0.0-beta7", 30 | "redux-thunk": "^1.0.3" 31 | }, 32 | "devDependencies": { 33 | "babel-core": "^6.4.0", 34 | "babel-eslint": "^5.0.0-beta6", 35 | "babel-loader": "^6.2.1", 36 | "babel-plugin-react-transform": "^2.0.0", 37 | "babel-plugin-transform-runtime": "^6.4.0", 38 | "babel-preset-es2015": "^6.3.13", 39 | "babel-preset-react": "^6.3.13", 40 | "babel-preset-react-hmre": "^1.0.1", 41 | "babel-preset-stage-0": "^6.3.13", 42 | "babel-runtime": "^6.3.19", 43 | "css-loader": "^0.23.1", 44 | "eslint": "^1.10.3", 45 | "eslint-config-airbnb": "^3.1.0", 46 | "eslint-plugin-react": "^3.14.0", 47 | "html-webpack-plugin": "^1.7.0", 48 | "mocha": "^2.3.4", 49 | "node-sass": "^3.4.2", 50 | "sass-loader": "^3.1.2", 51 | "should": "^8.0.2", 52 | "sinon": "^1.17.2", 53 | "style-loader": "^0.13.0", 54 | "webpack": "^1.12.10", 55 | "webpack-dev-middleware": "^1.4.0", 56 | "webpack-dev-server": "^1.14.0", 57 | "webpack-hot-middleware": "^2.6.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack/dev.config'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.get('*', function(req, res) { 17 | res.sendFile(path.join(__dirname, 'source/index.html')); 18 | }); 19 | 20 | app.listen(3000, 'localhost', function(err) { 21 | if (err) { 22 | console.log(err); 23 | return; 24 | } 25 | 26 | console.log('Listening at http://localhost:3000'); 27 | }); 28 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-stack-server", 3 | "version": "1.0.0", 4 | "description": "The boilerplate we are using build awesome web native applications.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack --verbose --colors --display-error-details --config webpack/dev.config.js", 9 | "start": "webpack-dev-server --config webpack/dev.config.js" 10 | }, 11 | "author": "alexander.beletsky@blogfoster.com", 12 | "license": "ISC", 13 | "dependencies": { 14 | "body-parser": "^1.14.1", 15 | "cors": "^2.7.1", 16 | "express": "^4.13.3", 17 | "method-override": "^2.3.5", 18 | "morgan": "^1.6.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const express = require('express'); 3 | const bodyParser = require('body-parser'); 4 | const methodOverride = require('method-override'); 5 | const morgan = require('morgan'); 6 | const cors = require('cors'); 7 | 8 | const app = express(); 9 | const env = process.env.NODE_ENV || 'development'; 10 | const port = process.env.PORT || 3010; 11 | 12 | app.use(morgan()); 13 | app.use(bodyParser.json()); 14 | app.use(methodOverride()); 15 | app.use(cors()); 16 | 17 | const users = []; 18 | 19 | app.get('/api/users', (req, res) => { 20 | res.json(users); 21 | }); 22 | 23 | app.post('/api/users', (req, res) => { 24 | const body = req.body; 25 | const user = _.extend(body, { id: _.uniqueId('user_') }); 26 | 27 | users.push(user); 28 | 29 | res.json(user); 30 | }); 31 | 32 | app.listen(port, () => { 33 | console.log('front-stack server [:' + port + '] ' + env); 34 | }); 35 | -------------------------------------------------------------------------------- /source/actions/index.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import config from '../../config'; 3 | 4 | import { parseJSON, checkHttpStatus } from '../utils'; 5 | 6 | import constants from '../constants'; 7 | 8 | export function signingUp() { 9 | return { 10 | type: constants.SIGNING_UP 11 | }; 12 | } 13 | 14 | export function signedUp(account) { 15 | return { 16 | type: constants.SIGNED_UP, 17 | payload: { 18 | account: account 19 | } 20 | }; 21 | } 22 | 23 | export function signUpError(error) { 24 | return { 25 | type: constants.SIGNUP_ERROR, 26 | payload: { 27 | status: error.response.status, 28 | statusText: error.response.statusText 29 | } 30 | }; 31 | } 32 | 33 | export function signUp(account) { 34 | return (dispatch) => { 35 | const url = config.url + '/api/users'; 36 | 37 | dispatch(signingUp()); 38 | 39 | fetch(url, { method: 'post', body: account }) 40 | .then(checkHttpStatus) 41 | .then(parseJSON) 42 | .then(response => { 43 | setTimeout(() => { 44 | dispatch(signedUp(response)); 45 | }, 1000); 46 | }) 47 | .catch(error => { 48 | dispatch(signUpError(error)); 49 | }); 50 | }; 51 | } 52 | 53 | export function preferenceSelected(preference) { 54 | return { type: 'PREFERENCE_SELECTED', payload: preference }; 55 | } 56 | 57 | export function preferenceSelectedError(error) { 58 | return { type: 'PREFERENCE_SELECTED_ERROR', payload: error }; 59 | } 60 | 61 | export function selectPreference(preference) { 62 | return (dispatch) => { 63 | const url = config.url + '/api/users'; 64 | 65 | fetch(url, { method: 'patch', body: { preference: preference } } ) 66 | .then(checkHttpStatus) 67 | .then(parseJSON) 68 | .then(response => { 69 | // TODO: 70 | dispatch(preferenceSelected(response.data)); 71 | }) 72 | .catch(error => { 73 | // TODO 74 | dispatch(preferenceSelectedError(error.data)); 75 | }); 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /source/components/BlogFosterLogo/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class BlogFosterLogo extends Component { 4 | static propTypes = { 5 | align: PropTypes.string 6 | }; 7 | 8 | render() { 9 | const style = { 10 | width: 50, 11 | height: 50 12 | }; 13 | 14 | return ( 15 |
16 | blogfoster 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default BlogFosterLogo; 23 | -------------------------------------------------------------------------------- /source/components/HelloWorld/index.js: -------------------------------------------------------------------------------- 1 | import './HelloWorld.scss'; 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import { Paper } from 'material-ui'; 6 | 7 | class HelloWorld extends Component { 8 | render() { 9 | return ( 10 |
11 | 12 |
Content
13 |
14 |
15 | ); 16 | } 17 | } 18 | 19 | export default HelloWorld; 20 | -------------------------------------------------------------------------------- /source/components/SignUpWizard/AccountDetails.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | import { TextField } from 'material-ui'; 4 | import { RaisedButton } from 'material-ui'; 5 | 6 | class AccountDetails extends Component { 7 | static propTypes = { 8 | onSignUp: PropTypes.func 9 | }; 10 | 11 | onSubmitted(evt) { 12 | evt.preventDefault(); 13 | 14 | const account = { 15 | firstName: this.refs.firstName.getValue(), 16 | lastName: this.refs.lastName.getValue(), 17 | url: this.refs.url.getValue(), 18 | password: this.refs.password.getValue(), 19 | passwordRepeat: this.refs.passwordRepeat.getValue(), 20 | }; 21 | 22 | this.props.onSignUp(null, account); 23 | } 24 | 25 | render() { 26 | const fieldsStyle = { 27 | width: 50 + '%', 28 | display: 'block', 29 | float: 'right' 30 | }; 31 | 32 | const buttonsStyle = { 33 | marginTop: 30, 34 | textAlign: 'right' 35 | }; 36 | 37 | return ( 38 |
39 |
40 |
41 |
42 |

sdsdfsdsd

43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 |
60 | ); 61 | } 62 | } 63 | 64 | export default AccountDetails; 65 | -------------------------------------------------------------------------------- /source/components/SignUpWizard/BloggingPreferences.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { RaisedButton, Checkbox } from 'material-ui'; 4 | 5 | class BloggingPreferences extends Component { 6 | render() { 7 | const buttonsStyle = { 8 | marginTop: 30, 9 | textAlign: 'right' 10 | }; 11 | 12 | return ( 13 |
14 |
15 |

Blogging Preferences

16 |
17 |
18 | 29 |
30 |
31 | 32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | export default BloggingPreferences; 39 | -------------------------------------------------------------------------------- /source/components/SignUpWizard/SignUp.scss: -------------------------------------------------------------------------------- 1 | .sign-up-wizard { 2 | max-width: 900px; 3 | margin: 0 auto; 4 | padding-top: 90px; 5 | 6 | .content { 7 | display: block; 8 | padding: 30px 60px; 9 | min-height: 600px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/components/SignUpWizard/index.js: -------------------------------------------------------------------------------- 1 | import './SignUp.scss'; 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | 5 | import { Paper, CircularProgress } from 'material-ui'; 6 | import { BlogFosterLogo } from '../'; 7 | 8 | import AccountDetails from './AccountDetails'; 9 | import BloggingPreferences from './BloggingPreferences'; 10 | 11 | class SignUpWizard extends Component { 12 | static propTypes = { 13 | user: PropTypes.object, 14 | progressing: PropTypes.bool, 15 | errors: PropTypes.array, 16 | onSignUp: PropTypes.func, 17 | onPreferenceSelected: PropTypes.func, 18 | onPreferencesCompleted: PropTypes.func 19 | }; 20 | 21 | render() { 22 | const { 23 | progressing, 24 | } = this.props; 25 | 26 | const accountStep = ( 27 | 28 | ); 29 | 30 | const bloggingPreferencesStep = ( 31 | 32 | ); 33 | 34 | const progress = ( 35 |
36 | 37 |
38 | ); 39 | 40 | const stepSelector = this.props.user ? bloggingPreferencesStep : accountStep; 41 | 42 | const content = progressing ? progress : stepSelector; 43 | 44 | return ( 45 |
46 | 47 |
48 | 49 |
50 | {content} 51 |
52 |
53 |
54 | ); 55 | } 56 | } 57 | 58 | export default SignUpWizard; 59 | -------------------------------------------------------------------------------- /source/components/index.js: -------------------------------------------------------------------------------- 1 | export SignUpWizard from './SignUpWizard'; 2 | export BlogFosterLogo from './BlogFosterLogo'; 3 | -------------------------------------------------------------------------------- /source/constants/index.js: -------------------------------------------------------------------------------- 1 | import { createConstants } from '../utils'; 2 | 3 | const constants = createConstants( 4 | 'SIGNING_UP', 5 | 'SIGNED_UP', 6 | 'SIGNUP_ERROR' 7 | ); 8 | 9 | export default constants; 10 | -------------------------------------------------------------------------------- /source/containers/App/App.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | background-color: #ff1744; 5 | } 6 | 7 | hr { 8 | display: block; 9 | height: 1px; 10 | border: 0; 11 | border-top: 1px solid #eaeaea; 12 | margin: 1em 0; 13 | padding: 0 15px; 14 | } 15 | 16 | /** 17 | * For modern browsers 18 | * 1. The space content is one way to avoid an Opera bug when the 19 | * contenteditable attribute is included anywhere else in the document. 20 | * Otherwise it causes space to appear at the top and bottom of elements 21 | * that are clearfixed. 22 | * 2. The use of `table` rather than `block` is only necessary if using 23 | * `:before` to contain the top-margins of child elements. 24 | */ 25 | .cf:before, 26 | .cf:after { 27 | content: " "; /* 1 */ 28 | display: table; /* 2 */ 29 | } 30 | 31 | .cf:after { 32 | clear: both; 33 | } 34 | 35 | /** 36 | * For IE 6/7 only 37 | * Include this rule to trigger hasLayout and contain floats. 38 | */ 39 | .cf { 40 | *zoom: 1; 41 | } 42 | -------------------------------------------------------------------------------- /source/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import './App.scss'; 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | 5 | import { Styles } from 'material-ui'; 6 | 7 | class App extends Component { 8 | static propTypes = { 9 | children: PropTypes.any 10 | }; 11 | 12 | render() { 13 | const style = { 14 | backgroundColor: Styles.Colors.redA400 15 | }; 16 | 17 | return ( 18 |
19 | {this.props.children} 20 |
21 | ); 22 | } 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /source/containers/Root/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class Root extends Component { 4 | static propTypes = { 5 | children: PropTypes.any 6 | } 7 | 8 | render() { 9 | return ( 10 |
11 | {this.props.children} 12 |
13 | ); 14 | } 15 | } 16 | 17 | export default Root; 18 | -------------------------------------------------------------------------------- /source/containers/SignUp/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { signUp } from '../../actions'; 5 | 6 | import { 7 | SignUpWizard 8 | } from '../../components'; 9 | 10 | class SignUp extends Component { 11 | static propTypes = { 12 | dispatch: PropTypes.func, 13 | application: PropTypes.object 14 | }; 15 | 16 | onSignUp(errors, account) { 17 | const { dispatch } = this.props; 18 | 19 | dispatch(signUp(account)); 20 | } 21 | 22 | render() { 23 | return ( 24 |
25 | 26 |
27 | ); 28 | } 29 | } 30 | 31 | const mapStateToProps = (state) => { 32 | return { 33 | application: state.application 34 | }; 35 | }; 36 | 37 | export default connect(mapStateToProps)(SignUp); 38 | -------------------------------------------------------------------------------- /source/containers/index.js: -------------------------------------------------------------------------------- 1 | export App from './App'; 2 | export SignUp from './SignUp'; 3 | -------------------------------------------------------------------------------- /source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blogfoster/front-stack-boilerplate/4c0b14f719e77c05746e18ed5dd93cbc885651ef/source/images/logo.png -------------------------------------------------------------------------------- /source/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Blogfoster Boilerplate 8 | 9 | 10 | 12 | 13 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /source/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import { compose, combineReducers, createStore, applyMiddleware } from 'redux'; 6 | import thunk from 'redux-thunk'; 7 | import { reduxReactRouter, routerStateReducer as router, ReduxRouter } from 'redux-router'; 8 | import createHistory from 'history/lib/createBrowserHistory'; 9 | 10 | import { getRoutes } from './routes'; 11 | import * as reducers from './reducers'; 12 | 13 | const rootReducer = combineReducers({ router, ...reducers }); 14 | 15 | const configureStore = compose( 16 | applyMiddleware(thunk), 17 | reduxReactRouter({ 18 | getRoutes, 19 | createHistory 20 | }) 21 | )(createStore); 22 | 23 | 24 | const store = configureStore(rootReducer); 25 | 26 | const Root = ( 27 | 28 | 29 | 30 | ); 31 | 32 | ReactDom.render(Root, document.getElementById('root-container')); 33 | -------------------------------------------------------------------------------- /source/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { createReducer } from '../utils'; 2 | 3 | import constants from '../constants'; 4 | 5 | const initialState = { 6 | user: null, 7 | progressing: false, 8 | errors: [] 9 | }; 10 | 11 | const actionHandlers = { 12 | [constants.SIGNING_UP]: (state) => { 13 | return { ...state, user: null, progressing: true }; 14 | }, 15 | 16 | [constants.SIGNED_UP]: (state, payload) => { 17 | return { ...state, user: payload.account, progressing: false }; 18 | }, 19 | 20 | [constants.SIGNUP_ERROR]: (state, payload) => { 21 | const errors = [payload.error].concat(state.errors); 22 | 23 | return { ...state, errors }; 24 | } 25 | }; 26 | 27 | export const application = createReducer(initialState, actionHandlers); 28 | -------------------------------------------------------------------------------- /source/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router'; 3 | 4 | import { 5 | App, 6 | SignUp, 7 | Root 8 | } from '../containers'; 9 | 10 | export const getRoutes = (/* { dispatch, getState } */) => { 11 | const routes = ( 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | return routes; 19 | }; 20 | -------------------------------------------------------------------------------- /source/utils/index.js: -------------------------------------------------------------------------------- 1 | export function createConstants(...constants) { 2 | return constants.reduce((acc, constant) => { 3 | acc[constant] = constant; 4 | return acc; 5 | }, {}); 6 | } 7 | 8 | export function createReducer(initialState, reducerMap) { 9 | return (state = initialState, action) => { 10 | const reducer = reducerMap[action.type]; 11 | 12 | return reducer 13 | ? reducer(state, action.payload) 14 | : state; 15 | }; 16 | } 17 | 18 | export function checkHttpStatus(response) { 19 | if (response.status >= 200 && response.status < 300) { 20 | return response; 21 | } 22 | 23 | const error = new Error(response.statusText); 24 | error.response = response; 25 | throw error; 26 | } 27 | 28 | export function parseJSON(response) { 29 | return response.json(); 30 | } 31 | -------------------------------------------------------------------------------- /webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | var port = process.env.NODE_PORT || 3000; 5 | 6 | module.exports = { 7 | devtool: 'cheap-module-eval-source-map', 8 | 9 | entry: [ 10 | // 'webpack-hot-middleware/client', 11 | './source/' 12 | ], 13 | 14 | output: { 15 | path: path.join(__dirname, 'dist'), 16 | filename: 'app.js', 17 | publicPath: '/static/' 18 | }, 19 | 20 | plugins: [ 21 | new webpack.HotModuleReplacementPlugin(), 22 | new webpack.NoErrorsPlugin() 23 | ], 24 | 25 | module: { 26 | loaders: [{ 27 | test: /\.js$/, 28 | loaders: ['babel'], 29 | exclude: /node_modules/ 30 | }, { 31 | test: /\.scss$/, 32 | loader: 'style!css!sass' 33 | }] 34 | }, 35 | 36 | devServer: { 37 | contentBase: './source', 38 | historyApiFallback: true, 39 | hot: true, 40 | inline: true, 41 | progress: true, 42 | port: port 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /webpack/prod.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blogfoster/front-stack-boilerplate/4c0b14f719e77c05746e18ed5dd93cbc885651ef/webpack/prod.config.js --------------------------------------------------------------------------------