├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── README.md
├── bin
└── webpack-dev-server.js
├── build
├── webpack-dev-server.js
└── webpack
│ ├── _base.js
│ ├── development.js
│ ├── development_hot.js
│ └── production.js
├── config
└── index.js
├── db.json
├── package.json
├── src
├── actions
│ └── actions.js
├── components
│ └── Athlete.js
├── containers
│ ├── AthleteApp.js
│ ├── DevTools.js
│ ├── DevToolsWindow.js
│ └── Root.js
├── helpers
│ └── auth.js
├── index.html
├── index.js
├── layouts
│ └── CoreLayout.js
├── reducers
│ └── index.js
├── routes
│ └── index.js
├── store
│ └── configureStore.js
├── styles
│ ├── _base.scss
│ ├── core.scss
│ └── vendor
│ │ └── _normalize.scss
├── utils
│ └── index.js
└── views
│ ├── AthleteView.js
│ └── HomeView.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage" : 0
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | # A special property that should be specified at the top of the file outside of
4 | # any sections. Set to true to stop .editor config file search on current file
5 | root = true
6 |
7 | # Indentation style
8 | # Possible values - tab, space
9 | indent_style = space
10 |
11 | # Indentation size in single-spaced characters
12 | # Possible values - an integer, tab
13 | indent_size = 2
14 |
15 | # Line ending file format
16 | # Possible values - lf, crlf, cr
17 | end_of_line = lf
18 |
19 | # File character encoding
20 | # Possible values - latin1, utf-8, utf-16be, utf-16le
21 | charset = utf-8
22 |
23 | # Denotes whether to trim whitespace at the end of lines
24 | # Possible values - true, false
25 | trim_trailing_whitespace = true
26 |
27 | # Denotes whether file should end with a newline
28 | # Possible values - true, false
29 | insert_final_newline = true
30 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/**
2 | *.spec.js
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends" : "airbnb",
3 | "env" : {
4 | "browser" : true
5 | },
6 | "globals" : {
7 | "__DEV__" : false,
8 | "__PROD__" : false,
9 | "__DEBUG__" : false,
10 | "__DEBUG_NW__" : false
11 | },
12 | "rules": {
13 | "comma-dangle" : [1, "never"],
14 | "func-names" : 0,
15 | "key-spacing" : 0,
16 | "space-before-function-paren" : [2, "always"],
17 | "no-else-return" : 0,
18 | "no-multi-spaces" : 0,
19 | "quotes" : [2, "single"],
20 | "jsx-quotes" : [2, "prefer-single"],
21 | "one-var" : 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | <<<<<<< HEAD
2 | .DS_STORE
3 | *.log
4 |
5 | node_modules
6 |
7 | dist
8 | src/credentials.js
9 | =======
10 | # Logs
11 | logs
12 | *.log
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # node-waf configuration
29 | .lock-wscript
30 |
31 | # Compiled binary addons (http://nodejs.org/api/addons.html)
32 | build/Release
33 |
34 | # Dependency directory
35 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
36 | node_modules
37 | >>>>>>> 54e3d496f4fb7beb4ae4f506001cf3b0acb1f87e
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ~~Loosely based~~ Built upon [@davezuko's kit](https://github.com/davezuko/react-redux-starter-kit) in combination with the official Redux docs.
2 | ## Mock API
3 | For development purposes, a mock REST API server can be run using json-server.
4 |
5 | Install it with `npm install -g json-server`
6 |
7 | In a new terminal, run it with `json-server -p 4000 db.json` where `db.json` is the file with mock data.
8 | You can view the json-server page at http://localhost:4000, but it isn't really necessary.
9 |
10 |
11 |
12 | ## Getting Started
13 | ### `npm start`
14 | Runs the webpack build system with webpack-dev-server (http://localhost:3000)
15 |
16 | ### `npm run dev:nw`
17 | Same as `npm run start` but opens the debug tools in a new window.
18 |
19 | _Note:_ You need to enable popups in Chrome or you will get an error.
20 |
21 | ### `npm run compile`
22 | Runs the webpack build system with the current NODE_ENV and compiles the application to the `./dist` directory. Production builds will fail on eslint errors (but not on warnings).
23 |
24 |
25 | ## Structure
26 |
27 |
28 | ```
29 | .
30 | ├── bin # Build/Start scripts
31 | ├── build # All build-related configuration
32 | │ ├── webpack # Environment-specific configuration files for Webpack
33 | ├── config # Project configuration settings
34 | ├── src # Application source code
35 | ├── components # Generic React Components (generally Dumb components)
36 | ├── containers # Components that provide context (e.g. Redux Providers)
37 | ├── layouts # Components that dictate major page structure
38 | ├── reducers # Redux reducers
39 | ├── routes # Application route definitions
40 | ├── stores # Redux store configuration
41 | ├── utils # Generic utilities
42 | ├── views # Components that live at a route
43 | └── index.js # Application bootstrap and rendering
44 | ```
45 |
--------------------------------------------------------------------------------
/bin/webpack-dev-server.js:
--------------------------------------------------------------------------------
1 | require('babel/register');
2 |
3 | const devServer = require('../build/webpack-dev-server');
4 | const config = require('../config');
5 |
6 | const host = config.get('webpack_host');
7 | const port = config.get('webpack_port');
8 | devServer.listen(port, host, function () {
9 | console.log(`Webpack dev server running at ${host}: ${port}`); // eslint-disable-line
10 | });
11 |
--------------------------------------------------------------------------------
/build/webpack-dev-server.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import WebpackDevServer from 'webpack-dev-server';
3 | import config from '../config';
4 | import webpackConfig from './webpack/development_hot';
5 |
6 | const paths = config.get('utils_paths');
7 |
8 | const server = new WebpackDevServer(webpack(webpackConfig), {
9 | contentBase : paths.project(config.get('dir_src')),
10 | hot : true,
11 | quiet : false,
12 | noInfo : false,
13 | lazy : false,
14 | stats : {
15 | colors : true
16 | },
17 | historyApiFallback : true
18 | });
19 |
20 | export default server;
21 |
--------------------------------------------------------------------------------
/build/webpack/_base.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import config from '../../config';
3 | import HtmlWebpackPlugin from 'html-webpack-plugin';
4 |
5 | const paths = config.get('utils_paths');
6 |
7 | const webpackConfig = {
8 | name : 'client',
9 | target : 'web',
10 | entry : {
11 | app : [
12 | paths.project(config.get('dir_src'))
13 | ],
14 | vendor : config.get('vendor_dependencies')
15 | },
16 | output : {
17 | filename : '[name].[hash].js',
18 | path : paths.project(config.get('dir_dist')),
19 | publicPath : '/'
20 | },
21 | plugins : [
22 | new webpack.DefinePlugin(config.get('globals')),
23 | new webpack.optimize.OccurrenceOrderPlugin(),
24 | new webpack.optimize.DedupePlugin(),
25 | new HtmlWebpackPlugin({
26 | template : paths.src('index.html'),
27 | hash : true,
28 | filename : 'index.html',
29 | inject : 'body'
30 | })
31 | ],
32 | resolve : {
33 | extensions : ['', '.js', '.jsx'],
34 | alias : config.get('utils_aliases')
35 | },
36 | module : {
37 | loaders : [
38 | {
39 | test : /\.(js|jsx)$/,
40 | exclude : /node_modules/,
41 | loader : 'babel',
42 | query : {
43 | stage : 0,
44 | optional : ['runtime'],
45 | env : {
46 | development : {
47 | plugins : ['react-transform'],
48 | extra : {
49 | 'react-transform' : {
50 | transforms : [{
51 | transform : 'react-transform-catch-errors',
52 | imports : ['react', 'redbox-react']
53 | }]
54 | }
55 | }
56 | }
57 | }
58 | }
59 | },
60 | {
61 | test : /\.scss$/,
62 | loaders : [
63 | 'style-loader',
64 | 'css-loader',
65 | 'autoprefixer?browsers=last 2 version',
66 | 'sass-loader'
67 | ]
68 | },
69 | /* eslint-disable */
70 | { test: /\.woff(\?.*)?$/, loader: "url-loader?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff" },
71 | { test: /\.woff2(\?.*)?$/, loader: "url-loader?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2" },
72 | { test: /\.ttf(\?.*)?$/, loader: "url-loader?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream" },
73 | { test: /\.eot(\?.*)?$/, loader: "file-loader?prefix=fonts/&name=[path][name].[ext]" },
74 | { test: /\.svg(\?.*)?$/, loader: "url-loader?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml" },
75 | /* eslint-enable */
76 | { test: /\.json$/, loader: 'json-loader'}
77 | ]
78 | },
79 | sassLoader : {
80 | includePaths : paths.src('styles')
81 | },
82 | /* add polyfills */
83 | node : {
84 | net: 'empty',
85 | fs: 'empty',
86 | tls: 'empty'
87 |
88 | }
89 | };
90 |
91 | const commonChunkPlugin = new webpack.optimize.CommonsChunkPlugin(
92 | 'vendor', '[name].[hash].js'
93 | );
94 |
95 | webpackConfig.plugins.push(commonChunkPlugin);
96 |
97 | export default webpackConfig;
98 |
--------------------------------------------------------------------------------
/build/webpack/development.js:
--------------------------------------------------------------------------------
1 | import webpackConfig from './_base';
2 |
3 | webpackConfig.devtool = 'source-map';
4 |
5 | export default webpackConfig;
6 |
--------------------------------------------------------------------------------
/build/webpack/development_hot.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import config from '../../config';
3 | import webpackConfig from './development';
4 |
5 | webpackConfig.entry.app.push(
6 | `webpack-dev-server/client?${config.get('webpack_public_path')}`,
7 | `webpack/hot/dev-server`
8 | );
9 |
10 | webpackConfig.plugins.push(
11 | new webpack.HotModuleReplacementPlugin(),
12 | new webpack.NoErrorsPlugin()
13 | );
14 |
15 | // Apply the react-transform HMR plugin to Babel only when it's enabled
16 | webpackConfig.module.loaders = webpackConfig.module.loaders.map(loader => {
17 | if (/js(?!on)/.test(loader.test)) {
18 | loader.query.env.development.extra['react-transform'].transforms.push({
19 | transform : 'react-transform-hmr',
20 | imports : ['react'],
21 | locals : ['module']
22 | });
23 | }
24 |
25 | return loader;
26 | });
27 |
28 | export default webpackConfig;
29 |
--------------------------------------------------------------------------------
/build/webpack/production.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
3 | import webpackConfig from './_base';
4 |
5 | webpackConfig.module.loaders = webpackConfig.module.loaders.map(loader => {
6 | if (/css/.test(loader.test)) {
7 | const [first, ...rest] = loader.loaders;
8 |
9 | loader.loader = ExtractTextPlugin.extract(first, rest.join('!'));
10 | delete loader.loaders;
11 | }
12 |
13 | return loader;
14 | });
15 |
16 | webpackConfig.plugins.push(
17 | new ExtractTextPlugin('[name].[contenthash].css'),
18 | new webpack.optimize.UglifyJsPlugin({
19 | compress : {
20 | 'unused' : true,
21 | 'dead_code' : true
22 | }
23 | })
24 | );
25 |
26 | export default webpackConfig;
27 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { argv } from 'yargs';
3 | import dotenv from 'dotenv';
4 |
5 | dotenv.load();
6 | const config = new Map();
7 |
8 | //----------------------------
9 | // User Configuration
10 | //----------------------------
11 | config.set('dir_src', 'src');
12 | config.set('dir_dist', 'dist');
13 |
14 | config.set('webpack_host', 'localhost');
15 | config.set('webpack_port', process.env.PORT || 3000); // eslint-disable-true
16 |
17 | config.set('vendor_dependencies', [
18 | 'history',
19 | 'react',
20 | 'react-redux',
21 | 'react-router',
22 | 'redux',
23 | 'redux-router'
24 | ]);
25 |
26 | //---------------------------
27 | // Internal Configuration
28 | //---------------------------
29 |
30 | // Environment
31 |
32 | config.set('env', process.env.NODE_ENV);
33 | config.set('globals', {
34 | 'process.env' : {
35 | 'NODE_ENV' : JSON.stringify(config.get('env'))
36 | },
37 | 'NODE_ENV' : config.get('env'),
38 | '__DEV__' : config.get('env') === 'development',
39 | '__PROD__' : config.get('env') === 'production',
40 | '__DEBUG__' : config.get('env') === 'development' && !argv.no_debug,
41 | '__DEBUG_NW__' : !!argv.nw
42 | });
43 |
44 | // Webpack
45 |
46 | config.set('webpack_public_path',
47 | `http://${config.get('webpack_host')}:${config.get('webpack_port')}/`
48 | );
49 |
50 | // Project
51 |
52 | config.set('path_project', path.resolve(__dirname, '../'));
53 |
54 | // Utilities
55 | const paths = (() => {
56 | const base = [config.get('path_project')];
57 | const resolve = path.resolve;
58 |
59 | const project = (...args) => resolve.apply(resolve, [...base, ...args]);
60 |
61 | return {
62 | project : project,
63 | src : project.bind(null, config.get('dir_src')),
64 | dist : project.bind(null, config.get('dir_dist'))
65 | };
66 | })();
67 |
68 | config.set('utils_paths', paths);
69 | config.set('utils_aliases', [
70 | 'actions',
71 | 'components',
72 | 'constants',
73 | 'containers',
74 | 'layouts',
75 | 'reducers',
76 | 'routes',
77 | 'services',
78 | 'styles',
79 | 'utils',
80 | 'views'
81 | ].reduce((acc, dir) => ((acc[dir] = paths.src(dir)) && acc), {}));
82 |
83 | export default config;
84 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "athletes": [
3 | {
4 | "id": 0,
5 | "first_name": "Bo",
6 | "last_name": "Jackson",
7 | "city": "Oakland",
8 | "state": "California",
9 | "weapon": "bat"
10 | },
11 | {
12 | "id": 1,
13 | "first_name": "Michael",
14 | "last_name": "Jordan",
15 | "city": "Chicago",
16 | "state": "Illinois",
17 | "weapon": "balls"
18 | },
19 | {
20 | "id": 2,
21 | "first_name": "Wayne",
22 | "last_name": "Gretzky",
23 | "city": "Los Angeles",
24 | "state": "California",
25 | "weapon": "hockey stick"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "version": "1.0.0",
4 | "description": "Based on https://github.com/davezuko/react-redux-starter-kit and the Redux docs",
5 | "main": "index.js",
6 | "dependencies": {
7 | "babel": "^5.8.23",
8 | "dotenv": "^1.2.0",
9 | "history": "^1.9.0",
10 | "isomorphic-fetch": "^2.2.0",
11 | "react": "^0.14.0",
12 | "react-dom": "^0.14.0",
13 | "react-redux": "^4.0.0",
14 | "react-router": "1.0.0-rc1",
15 | "redux": "^3.0.0",
16 | "redux-router": "^1.0.0-beta3",
17 | "redux-thunk": "^1.0.0",
18 | "request-promise": "^1.0.2",
19 | "yargs": "^3.18.0"
20 | },
21 | "devDependencies": {
22 | "autoprefixer": "^6.0.3",
23 | "autoprefixer-loader": "^3.1.0",
24 | "babel-eslint": "^4.1.1",
25 | "babel-loader": "^5.0.0",
26 | "babel-plugin-react-transform": "^1.1.0",
27 | "babel-runtime": "^5.8.20",
28 | "css-loader": "^0.21.0",
29 | "eslint": "^1.0.0",
30 | "eslint-config-airbnb": "0.1.0",
31 | "eslint-plugin-react": "^3.3.1",
32 | "extract-text-webpack-plugin": "^0.8.0",
33 | "file-loader": "^0.8.4",
34 | "html-webpack-plugin": "^1.6.1",
35 | "json-loader": "^0.5.3",
36 | "node-sass": "^3.3.3",
37 | "react-addons-test-utils": "^0.14.0",
38 | "react-transform-catch-errors": "^1.0.0",
39 | "react-transform-hmr": "^1.0.0",
40 | "redbox-react": "^1.0.4",
41 | "redux-devtools": "3.0.0-beta-3",
42 | "redux-devtools-dock-monitor": "^1.0.0-beta-3",
43 | "redux-devtools-log-monitor": "^1.0.0-beta-3",
44 | "sass-loader": "^3.0.0",
45 | "style-loader": "^0.13.0",
46 | "url-loader": "^0.5.6",
47 | "webpack": "^1.11.0",
48 | "webpack-dev-server": "^1.10.1"
49 | },
50 | "scripts": {
51 | "clean": "rm -rf dist",
52 | "lint": "eslint ./src/**/*.js",
53 | "compile": "webpack",
54 | "start": "npm run dev",
55 | "dev": "NODE_ENV=development node --harmony bin/webpack-dev-server",
56 | "dev:nw": "npm run dev -- --nw",
57 | "dev:no-debug": "npm run dev -- --no_debug",
58 | "test": "echo \"Error: no test specified\" && exit 1",
59 | "deploy": "npm run lint && npm run compile"
60 | },
61 | "repository": {
62 | "type": "git",
63 | "url": ""
64 | },
65 | "author": "",
66 | "license": "ISC",
67 | "bugs": {
68 | "url": ""
69 | },
70 | "homepage": ""
71 | }
72 |
--------------------------------------------------------------------------------
/src/actions/actions.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch'
2 |
3 | export const REQUEST_ATHLETES = 'REQUEST_ATHLETES'
4 | export const RECEIVE_ATHLETES = 'RECEIVE_ATHLETES'
5 |
6 |
7 | function requestAthletes(){
8 | return {
9 | type: REQUEST_ATHLETES
10 | }
11 | }
12 |
13 | function receiveAthletes(json) {
14 | return {
15 | type: RECEIVE_ATHLETES,
16 | athletes: json,
17 | receivedAt: Date.now()
18 | }
19 | }
20 |
21 | export function fetchAthletes() {
22 |
23 | return dispatch => {
24 | dispatch(requestAthletes())
25 | return fetch(`http://localhost:4000/athletes`)
26 | .then(req => req.json())
27 | .then(json => dispatch(receiveAthletes(json)))
28 | }
29 | }
30 |
31 | function shouldFetchAthletes(state) {
32 | console.log('shouldFetchAthletes called')
33 | console.trace()
34 |
35 | const athletes = state.athletes.items
36 |
37 | return !athletes.length
38 | }
39 |
40 | export function fetchAthletesIfNeeded() {
41 | return (dispatch, getState) => {
42 | if (shouldFetchAthletes(getState())) {
43 | return dispatch(fetchAthletes())
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Athlete.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | export default class Athlete extends Component {
5 |
6 | render() {
7 | const { items } = this.props
8 | var props = this.props
9 | console.log('Ath component this.props:' + JSON.stringify(this.props))
10 |
11 | return (
12 |
13 |
Hi from the AthleteComponent
14 |
15 | {JSON.stringify(props)}
16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | Athlete.propTypes = {
23 | items: PropTypes.object
24 | }
25 |
--------------------------------------------------------------------------------
/src/containers/AthleteApp.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { connect } from 'react-redux'
3 | import { Router, Route, Link } from 'react-router'
4 | import { pushState } from 'redux-router'
5 | import { requestAthletes, receiveAthletes, fetchAthletes, fetchAthletesIfNeeded } from '../actions/actions'
6 |
7 | class AthleteApp extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | componentDidMount() {
13 | const { dispatch } = this.props
14 | dispatch(fetchAthletes())
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | if (nextProps !== this.props) {
19 | const { dispatch } = nextProps
20 | dispatch(fetchAthletesIfNeeded())
21 | }
22 | }
23 |
24 | renderItems(items) {
25 | return items.map((item) => {
26 | return (
27 | {item.first_name}
28 | )
29 | })
30 | }
31 |
32 | render() {
33 | const { items, isFetching, lastUpdated } = this.props
34 | return (
35 |
36 | {items.length > 0 &&
37 |
{this.renderItems(items)}
38 | }
39 |
40 | {lastUpdated &&
41 |
42 | Last updated at {new Date(lastUpdated).toLocaleTimeString()}.
43 |
44 | }
45 |
46 | {!isFetching && items.length === 0 &&
47 |
No data to display!
48 | }
49 | {isFetching &&
50 |
51 |
Fetching Items
52 |
53 | }
54 |
55 | )
56 | }
57 | }
58 |
59 | AthleteApp.propTypes = {
60 | items: PropTypes.array.isRequired,
61 | isFetching: PropTypes.bool.isRequired,
62 | lastUpdated: PropTypes.number,
63 | dispatch: PropTypes.func.isRequired
64 | }
65 |
66 |
67 |
68 | function mapStateToProps({router, athletes}) {
69 |
70 | var isFetching = athletes.isFetching
71 | var items = athletes.items
72 | var lastUpdated = athletes.lastUpdated
73 |
74 | return {
75 | isFetching,
76 | items,
77 | lastUpdated
78 | }
79 |
80 | }
81 |
82 | export default connect(mapStateToProps)(AthleteApp)
83 |
--------------------------------------------------------------------------------
/src/containers/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDevTools } from 'redux-devtools';
3 | import LogMonitor from 'redux-devtools-log-monitor';
4 | import DockMonitor from 'redux-devtools-dock-monitor';
5 |
6 | export default createDevTools(
7 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/src/containers/DevToolsWindow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDevTools } from 'redux-devtools';
3 | import LogMonitor from 'redux-devtools-log-monitor';
4 |
5 | export default createDevTools(
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/src/containers/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import routes from '../routes';
4 | import { ReduxRouter } from 'redux-router';
5 | import DevTools from './DevTools';
6 | import { createDevToolsWindow } from '../utils';
7 | import configureStore from '../store/configureStore';
8 | import AthleteApp from './AthleteApp';
9 |
10 | const store = configureStore()
11 |
12 | export default class Root extends React.Component {
13 | static propTypes = {
14 | store : React.PropTypes.object.isRequired,
15 | debug : React.PropTypes.bool,
16 | debugExternal : React.PropTypes.bool
17 | }
18 |
19 | static defaultProps = {
20 | debug : false,
21 | debugExternal : false
22 | }
23 |
24 | renderDevTools () {
25 | if (!this.props.debug) {
26 | return null;
27 | }
28 |
29 | return this.props.debugExternal ?
30 | createDevToolsWindow(this.props.store) : ;
31 | }
32 |
33 | render () {
34 | return (
35 |
36 |
37 |
38 |
39 | {routes}
40 |
41 | {this.renderDevTools()}
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/helpers/auth.js:
--------------------------------------------------------------------------------
1 | import { AUTH_URL } from '../constants/auth';
2 | import { email, password, token } from '../credentials';
3 |
4 |
5 | let rp = require('request-promise');
6 |
7 |
8 | function getAthletes() {
9 | let reqOptions = {
10 | url: 'http://localhost:4000/athletes',
11 | json: true
12 | };
13 |
14 | return new Promise(function(resolve, reject){
15 | rp(reqOptions)
16 | .then(function(response) {
17 | console.log(response)
18 | resolve(response);
19 | })
20 | .catch(function(err) {
21 | reject(err);
22 | })
23 | })
24 |
25 | }
26 |
27 |
28 | class Auth {
29 | getToken () {
30 | return (console.log('maybe this works' + getAthletes()));
31 | }
32 | }
33 |
34 | export default new Auth();
35 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React + Redux
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from './containers/Root';
4 | import configureStore from './store/configureStore';
5 |
6 | const target = document.getElementById('root');
7 | const store = configureStore(window.__INITIAL_STATE__, __DEBUG__);
8 |
9 | const node = (
10 |
13 | );
14 |
15 | ReactDOM.render(node, target);
16 |
--------------------------------------------------------------------------------
/src/layouts/CoreLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import 'styles/core.scss';
3 |
4 | export default class CoreLayout extends React.Component {
5 | static propTypes = {
6 | children : React.PropTypes.element
7 | }
8 |
9 | render () {
10 | return (
11 |
12 |
13 | {this.props.children}
14 |
15 |
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerStateReducer } from 'redux-router';
3 | import { REQUEST_ATHLETES, RECEIVE_ATHLETES } from '../actions/actions'
4 |
5 | function athletes(state = {
6 | isFetching: false,
7 | items: []
8 | }, action) {
9 | switch (action.type) {
10 | case REQUEST_ATHLETES:
11 | return Object.assign({}, state, {
12 | isFetching: true
13 | })
14 | case RECEIVE_ATHLETES:
15 | return Object.assign({}, state, {
16 | isFetching: false,
17 | items: action.athletes,
18 | lastUpdated: action.receivedAt
19 | })
20 | default:
21 | return state
22 | }
23 | }
24 |
25 |
26 |
27 | // export default combineReducers({
28 | // router: routerStateReducer,
29 | // athletes: athletes
30 | //
31 | // });
32 |
33 | let router = routerStateReducer;
34 | const rootReducer = combineReducers({
35 | router,
36 | athletes
37 | })
38 |
39 | export default rootReducer
40 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, IndexRoute } from 'react-router';
3 | import CoreLayout from 'layouts/CoreLayout';
4 | import HomeView from 'views/HomeView';
5 | import AthleteView from 'views/AthleteView';
6 |
7 | export default (
8 |
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import rootReducer from '../reducers';
2 | import thunk from 'redux-thunk';
3 | import routes from '../routes';
4 | import { reduxReactRouter } from 'redux-router';
5 | import createHistory from 'history/lib/createBrowserHistory';
6 | import DevTools from 'containers/DevTools';
7 | import {
8 | applyMiddleware,
9 | compose,
10 | createStore
11 | } from 'redux';
12 |
13 | export default function configureStore (initialState, debug = false) {
14 | let createStoreWithMiddleware;
15 |
16 | const middleware = applyMiddleware(thunk);
17 |
18 | if (debug) {
19 | createStoreWithMiddleware = compose(
20 | middleware,
21 | reduxReactRouter({ routes, createHistory }),
22 | DevTools.instrument()
23 | );
24 | } else {
25 | createStoreWithMiddleware = compose(
26 | middleware,
27 | reduxReactRouter({ routes, createHistory })
28 | );
29 | }
30 |
31 | const store = createStoreWithMiddleware(createStore)(
32 | rootReducer, initialState
33 | );
34 |
35 | if (module.hot) {
36 | module.hot.accept('../reducers', () => {
37 | const nextRootReducer = require('../reducers/index');
38 |
39 | store.replaceReducer(nextRootReducer);
40 | });
41 | }
42 | return store;
43 | }
44 |
--------------------------------------------------------------------------------
/src/styles/_base.scss:
--------------------------------------------------------------------------------
1 | // Settings go here
2 |
--------------------------------------------------------------------------------
/src/styles/core.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 | @import 'vendor/normalize';
3 |
--------------------------------------------------------------------------------
/src/styles/vendor/_normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | -moz-box-sizing: content-box;
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
354 | * (include `-moz` to future-proof).
355 | */
356 |
357 | input[type="search"] {
358 | -webkit-appearance: textfield; /* 1 */
359 | -moz-box-sizing: content-box;
360 | -webkit-box-sizing: content-box; /* 2 */
361 | box-sizing: content-box;
362 | }
363 |
364 | /**
365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
366 | * Safari (but not Chrome) clips the cancel button when the search input has
367 | * padding (and `textfield` appearance).
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Define consistent border, margin, and padding.
377 | */
378 |
379 | fieldset {
380 | border: 1px solid #c0c0c0;
381 | margin: 0 2px;
382 | padding: 0.35em 0.625em 0.75em;
383 | }
384 |
385 | /**
386 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
388 | */
389 |
390 | legend {
391 | border: 0; /* 1 */
392 | padding: 0; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove default vertical scrollbar in IE 8/9/10/11.
397 | */
398 |
399 | textarea {
400 | overflow: auto;
401 | }
402 |
403 | /**
404 | * Don't inherit the `font-weight` (applied by a rule above).
405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
406 | */
407 |
408 | optgroup {
409 | font-weight: bold;
410 | }
411 |
412 | /* Tables
413 | ========================================================================== */
414 |
415 | /**
416 | * Remove most spacing between table cells.
417 | */
418 |
419 | table {
420 | border-collapse: collapse;
421 | border-spacing: 0;
422 | }
423 |
424 | td,
425 | th {
426 | padding: 0;
427 | }
428 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import DevTools from 'containers/DevToolsWindow';
5 |
6 | export function createConstants (...constants) {
7 | return constants.reduce((acc, constant) => {
8 | acc[constant] = constant;
9 | return acc;
10 | }, {});
11 | }
12 |
13 | export function createReducer (initialState, reducerMap) {
14 | return (state = initialState, action) => {
15 | const reducer = reducerMap[action.type];
16 |
17 | return reducer ? reducer(state, action.payload) : state;
18 | };
19 | }
20 |
21 | export function createDevToolsWindow (store) {
22 | const win = window.open(
23 | null,
24 | 'redux-devtools', // give it a name so it reuses the same window
25 | `width=400,height=${window.outerHeight},menubar=no,location=no,resizable=yes,scrollbars=no,status=no`
26 | );
27 |
28 | // reload in case it's reusing the same window with the old content
29 | win.location.reload();
30 |
31 | // wait a little bit for it to reload, then render
32 | setTimeout(() => {
33 | // Wait for the reload to prevent:
34 | // "Uncaught Error: Invariant Violation: _registerComponent(...): Target container is not a DOM element."
35 | win.document.write('');
36 | win.document.body.style.margin = '0';
37 |
38 | ReactDOM.render(
39 |
40 |
41 |
42 | , win.document.getElementById('react-devtools-root')
43 | );
44 | }, 10);
45 | }
46 |
--------------------------------------------------------------------------------
/src/views/AthleteView.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes }from 'react'
2 | import { bindActionCreators } from 'redux'
3 | import { connect } from 'react-redux'
4 | import { Router, Route, Link } from 'react-router'
5 | import { pushState } from 'redux-router'
6 | import * as athleteActions from '../actions/actions'
7 | import Athlete from '../components/Athlete'
8 |
9 | const mapStateToProps = (state) => ({
10 | athletes : state.athletes.items,
11 | routerState : state.router
12 | })
13 |
14 | const mapDispatchToProps = (dispatch) => ({
15 | actions : bindActionCreators(athleteActions, dispatch)
16 | })
17 |
18 | export class AthleteView extends Component {
19 | constructor(props){
20 | super(props)
21 | }
22 |
23 | componentDidMount() {
24 | console.log('Mounted an AthleteView')
25 | console.log('size of athlete array after component mount:' + this.props.athletes.length)
26 |
27 | }
28 |
29 | // This method doesn't seem to be called ever...
30 | componentWillReceiveProps(nextProps) {
31 | console.log('Ath View ComponentWillReceiveProps')
32 | if (nextProps !== this.props) {
33 | this.props.actions.fetchAthletesIfNeeded()
34 | }
35 | }
36 |
37 | componentWillMount() {
38 | console.log('Component Will Mount')
39 | if (this.props.athletes.length === 0) {
40 | console.log('Atheletes array is empty, we should fetch data')
41 | this.props.actions.fetchAthletesIfNeeded()
42 | }
43 | //console.log('Component will mount props:' + JSON.stringify(this.props, null, 2))
44 | }
45 |
46 |
47 | static propTypes = {
48 | athlete: React.PropTypes.object
49 | }
50 |
51 | render () {
52 | const { id } = this.props.params;
53 | return (
54 |
55 |
Athlete page!
56 |
57 |
58 | )
59 | }
60 | }
61 |
62 | export default connect(mapStateToProps, mapDispatchToProps)(AthleteView)
63 |
--------------------------------------------------------------------------------
/src/views/HomeView.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import AthleteApp from '../containers/AthleteApp';
5 | import fetchAthletes from '../actions/actions'
6 |
7 | const actionCreators = {
8 | fetchAthletes : () => ({ type : 'REQUEST_ATHLETES' })
9 | };
10 |
11 | const mapStateToProps = (state) => ({
12 | athletes : state.athletes.items,
13 | routerState : state.router
14 | });
15 | const mapDispatchToProps = (dispatch) => ({
16 | actions : bindActionCreators(actionCreators, dispatch)
17 | });
18 |
19 | export class HomeView extends React.Component {
20 |
21 | static propTypes = {
22 | actions : React.PropTypes.object,
23 | athletes : React.PropTypes.array
24 | }
25 |
26 | render () {
27 | return (
28 |
31 | );
32 | }
33 | }
34 |
35 | export default connect(mapStateToProps, mapDispatchToProps)(HomeView);
36 |
37 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | require('babel/register');
2 |
3 | const config = require('./config');
4 | module.exports = require('./build/webpack/' + config.get('env'));
5 |
6 |
--------------------------------------------------------------------------------