├── .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 |