master 076e24bcd9b3 cached
97 files
159.3 KB
44.1k tokens
106 symbols
1 requests
Download .txt
Repository: erikras/react-redux-universal-hot-example
Branch: master
Commit: 076e24bcd9b3
Files: 97
Total size: 159.3 KB

Directory structure:
gitextract_bh9t0pcb/

├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── api/
│   ├── __tests__/
│   │   └── api-test.js
│   ├── actions/
│   │   ├── __tests__/
│   │   │   ├── loadInfo-test.js
│   │   │   ├── widget-load-test.js
│   │   │   └── widget-update-test.js
│   │   ├── index.js
│   │   ├── loadAuth.js
│   │   ├── loadInfo.js
│   │   ├── login.js
│   │   ├── logout.js
│   │   ├── survey/
│   │   │   ├── index.js
│   │   │   └── isValid.js
│   │   └── widget/
│   │       ├── index.js
│   │       ├── load.js
│   │       └── update.js
│   ├── api.js
│   └── utils/
│       └── url.js
├── app.json
├── bin/
│   ├── api.js
│   └── server.js
├── circle.yml
├── docs/
│   ├── AddingAPage/
│   │   └── AddingAPage.md
│   ├── AddingToHomePage/
│   │   └── AddingToHomePage.md
│   ├── ApiConfig.md
│   ├── Ducks.md
│   ├── ExploringTheDemoApp/
│   │   └── ExploringTheDemoApp.md
│   ├── InlineStyles.md
│   └── InstallingTheKit/
│       └── InstallingTheKit.md
├── karma.conf.js
├── package.json
├── server.babel.js
├── src/
│   ├── client.js
│   ├── components/
│   │   ├── CounterButton/
│   │   │   └── CounterButton.js
│   │   ├── GithubButton/
│   │   │   └── GithubButton.js
│   │   ├── InfoBar/
│   │   │   ├── InfoBar.js
│   │   │   └── InfoBar.scss
│   │   ├── MiniInfoBar/
│   │   │   └── MiniInfoBar.js
│   │   ├── SurveyForm/
│   │   │   ├── SurveyForm.js
│   │   │   ├── SurveyForm.scss
│   │   │   └── surveyValidation.js
│   │   ├── WidgetForm/
│   │   │   ├── WidgetForm.js
│   │   │   └── widgetValidation.js
│   │   ├── __tests__/
│   │   │   └── InfoBar-test.js
│   │   └── index.js
│   ├── config.js
│   ├── containers/
│   │   ├── About/
│   │   │   └── About.js
│   │   ├── App/
│   │   │   ├── App.js
│   │   │   └── App.scss
│   │   ├── Chat/
│   │   │   ├── Chat.js
│   │   │   └── Chat.scss
│   │   ├── DevTools/
│   │   │   └── DevTools.js
│   │   ├── Home/
│   │   │   ├── Home.js
│   │   │   └── Home.scss
│   │   ├── Login/
│   │   │   ├── Login.js
│   │   │   └── Login.scss
│   │   ├── LoginSuccess/
│   │   │   └── LoginSuccess.js
│   │   ├── NotFound/
│   │   │   └── NotFound.js
│   │   ├── Pagination/
│   │   │   ├── Pagination.jsx
│   │   │   ├── Pagination.scss
│   │   │   └── violet.min.scss
│   │   ├── Survey/
│   │   │   └── Survey.js
│   │   ├── Widgets/
│   │   │   ├── Widgets.js
│   │   │   └── Widgets.scss
│   │   └── index.js
│   ├── helpers/
│   │   ├── ApiClient.js
│   │   └── Html.js
│   ├── redux/
│   │   ├── create.js
│   │   ├── middleware/
│   │   │   └── clientMiddleware.js
│   │   └── modules/
│   │       ├── auth.js
│   │       ├── counter.js
│   │       ├── info.js
│   │       ├── reducer.js
│   │       ├── survey.js
│   │       └── widgets.js
│   ├── routes.js
│   ├── server.js
│   ├── theme/
│   │   ├── bootstrap.config.js
│   │   ├── bootstrap.config.prod.js
│   │   ├── bootstrap.overrides.scss
│   │   ├── font-awesome.config.js
│   │   ├── font-awesome.config.less
│   │   ├── font-awesome.config.prod.js
│   │   └── variables.scss
│   └── utils/
│       └── validation.js
├── tests.webpack.js
└── webpack/
    ├── dev.config.js
    ├── prod.config.js
    ├── webpack-dev-server.js
    └── webpack-isomorphic-tools.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["react", "es2015", "stage-0"],

  "plugins": [
    "transform-runtime",
    "add-module-exports",
    "transform-decorators-legacy",
    "transform-react-display-name"
  ],

  "env": {
    "development": {
      "plugins": [
        "typecheck",
        ["react-transform", {
            "transforms": [{
                "transform": "react-transform-catch-errors",
                "imports": ["react", "redbox-react"]
              }
            ]
        }]
      ]
    }
  }
}


================================================
FILE: .editorconfig
================================================
[*]
indent_style = space
end_of_line = lf
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true

[*.md]
max_line_length = 0
trim_trailing_whitespace = false


================================================
FILE: .eslintignore
================================================
webpack/*
karma.conf.js
tests.webpack.js


================================================
FILE: .eslintrc
================================================
{ "extends": "eslint-config-airbnb",
  "env": {
    "browser": true,
    "node": true,
    "mocha": true
  },
  "rules": {
    "new-cap": [2, { "capIsNewExceptions": ["List", "Map", "Set"] }],
    "react/no-multi-comp": 0,
    "import/default": 0,
    "import/no-duplicates": 0,
    "import/named": 0,
    "import/namespace": 0,
    "import/no-unresolved": 0,
    "import/no-named-as-default": 2,
    "comma-dangle": 0,  // not sure why airbnb turned this on. gross!
    "indent": [2, 2, {"SwitchCase": 1}],
    "no-console": 0,
    "no-alert": 0
  },
  "plugins": [
    "react", "import"
  ],
  "settings": {
    "import/parser": "babel-eslint",
    "import/resolve": {
      "moduleDirectory": ["node_modules", "src"]
    }
  },
  "globals": {
    "__DEVELOPMENT__": true,
    "__CLIENT__": true,
    "__SERVER__": true,
    "__DISABLE_SSR__": true,
    "__DEVTOOLS__": true,
    "socket": true,
    "webpackIsomorphicTools": true
  }
}


================================================
FILE: .gitignore
================================================
.idea/
node_modules/
dist/
*.iml
webpack-assets.json
webpack-stats.json
npm-debug.log
*.swp


================================================
FILE: .travis.yml
================================================
language: node_js

node_js:
  - "0.12"
  - "4.0"
  - "4"
  - "5"
  - "stable"

sudo: false

before_script:
  - export DISPLAY=:99.0
  - sh -e /etc/init.d/xvfb start

script:
  - npm run lint
  - npm test
  - npm run test-node


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines

Some basic conventions for contributing to this project.

### General

Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork.

* Non-trivial changes should be discussed in an issue first
* Develop in a topic branch, not master
* Squash your commits

### Linting

Please check your code using `npm run lint` before submitting your pull requests, as the CI build will fail if `eslint` fails.

### Commit Message Format

Each commit message should include a **type**, a **scope** and a **subject**:

```
 <type>(<scope>): <subject>
```

Lines should not exceed 100 characters. This allows the message to be easier to read on github as well as in various git tools and produces a nice, neat commit log ie:

```
 #459  refactor(utils): create url mapper utility function
 #463  chore(webpack): update to isomorphic tools v2
 #494  fix(babel): correct dependencies and polyfills
 #510  feat(app): add react-bootstrap responsive navbar
``` 

#### Type

Must be one of the following:

* **feat**: A new feature
* **fix**: A bug fix
* **docs**: Documentation only changes
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
  semi-colons, etc)
* **refactor**: A code change that neither fixes a bug or adds a feature
* **test**: Adding missing tests
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
  generation

#### Scope

The scope could be anything specifying place of the commit change. For example `webpack`,
`helpers`, `api` etc...

#### Subject

The subject contains succinct description of the change:

* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize first letter
* no dot (.) at the end


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 Erik Rasmussen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# ⚠️ DO NOT USE THIS!! ⚠️

Once upon a time, this repo helped a lot of people, but it's _waaaay_ out of date. It's now more of a historical artifact of what React development looked like in 2015.

# React Redux Universal Hot Example

[![build status](https://img.shields.io/travis/erikras/react-redux-universal-hot-example/master.svg?style=flat-square)](https://travis-ci.org/erikras/react-redux-universal-hot-example)
[![Dependency Status](https://david-dm.org/erikras/react-redux-universal-hot-example.svg?style=flat-square)](https://david-dm.org/erikras/react-redux-universal-hot-example)
[![devDependency Status](https://david-dm.org/erikras/react-redux-universal-hot-example/dev-status.svg?style=flat-square)](https://david-dm.org/erikras/react-redux-universal-hot-example#info=devDependencies)
[![react-redux-universal channel on discord](https://img.shields.io/badge/discord-react--redux--universal%40reactiflux-brightgreen.svg?style=flat-square)](https://discord.gg/0ZcbPKXt5bZZb1Ko)
[![Demo on Heroku](https://img.shields.io/badge/demo-heroku-brightgreen.svg?style=flat-square)](https://react-redux.herokuapp.com)
[![PayPal donate button](https://img.shields.io/badge/donate-paypal-brightgreen.svg?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E2LK57ZQ9YRMN)

---

## About

This is a starter boilerplate app I've put together using the following technologies:

* ~~Isomorphic~~ [Universal](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) rendering
* Both client and server make calls to load data from separate API server
* [React](https://github.com/facebook/react)
* [React Router](https://github.com/rackt/react-router)
* [Express](http://expressjs.com)
* [Babel](http://babeljs.io) for ES6 and ES7 magic
* [Webpack](http://webpack.github.io) for bundling
* [Webpack Dev Middleware](http://webpack.github.io/docs/webpack-dev-middleware.html)
* [Webpack Hot Middleware](https://github.com/glenjamin/webpack-hot-middleware)
* [Redux](https://github.com/rackt/redux)'s futuristic [Flux](https://facebook.github.io/react/blog/2014/05/06/flux.html) implementation
* [Redux Dev Tools](https://github.com/gaearon/redux-devtools) for next generation DX (developer experience). Watch [Dan Abramov's talk](https://www.youtube.com/watch?v=xsSnOQynTHs).
* [React Router Redux](https://github.com/reactjs/react-router-redux) Redux/React Router bindings.
* [ESLint](http://eslint.org) to maintain a consistent code style
* [redux-form](https://github.com/erikras/redux-form) to manage form state in Redux
* [lru-memoize](https://github.com/erikras/lru-memoize) to speed up form validation
* [multireducer](https://github.com/erikras/multireducer) to combine single reducers into one key-based reducer
* [style-loader](https://github.com/webpack/style-loader), [sass-loader](https://github.com/jtangelder/sass-loader) and [less-loader](https://github.com/webpack/less-loader) to allow import of stylesheets in plain css, sass and less,
* [bootstrap-sass-loader](https://github.com/shakacode/bootstrap-sass-loader) and [font-awesome-webpack](https://github.com/gowravshekar/font-awesome-webpack) to customize Bootstrap and FontAwesome
* [react-helmet](https://github.com/nfl/react-helmet) to manage title and meta tag information on both server and client
* [webpack-isomorphic-tools](https://github.com/halt-hammerzeit/webpack-isomorphic-tools) to allow require() work for statics both on client and server
* [mocha](https://mochajs.org/) to allow writing unit tests for the project.

I cobbled this together from a wide variety of similar "starter" repositories. As I post this in June 2015, all of these libraries are right at the bleeding edge of web development. They may fall out of fashion as quickly as they have come into it, but I personally believe that this stack is the future of web development and will survive for several years. I'm building my new projects like this, and I recommend that you do, too.

## Installation

```bash
npm install
```

## Running Dev Server

```bash
npm run dev
```

The first time it may take a little while to generate the first `webpack-assets.json` and complain with a few dozen `[webpack-isomorphic-tools] (waiting for the first Webpack build to finish)` printouts, but be patient. Give it 30 seconds.

### Using Redux DevTools

[Redux Devtools](https://github.com/gaearon/redux-devtools) are enabled by default in development.

- <kbd>CTRL</kbd>+<kbd>H</kbd> Toggle DevTools Dock
- <kbd>CTRL</kbd>+<kbd>Q</kbd> Move DevTools Dock Position
- see [redux-devtools-dock-monitor](https://github.com/gaearon/redux-devtools-dock-monitor) for more detailed information.

If you have the 
[Redux DevTools chrome extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) installed it will automatically be used on the client-side instead.

If you want to disable the dev tools during development, set `__DEVTOOLS__` to `false` in `/webpack/dev.config.js`.  
DevTools are not enabled during production.

## Building and Running Production Server

```bash
npm run build
npm run start
```

## Demo

A demonstration of this app can be seen [running on heroku](https://react-redux.herokuapp.com), which is a deployment of the [heroku branch](https://github.com/erikras/react-redux-universal-hot-example/tree/heroku).

## Documentation

* [Exploring the Demo App](docs/ExploringTheDemoApp/ExploringTheDemoApp.md) is a guide that can be used before you install the kit.
* [Installing the Kit](docs/InstallingTheKit/InstallingTheKit.md) guides you through installation and running the development server locally.
* [Adding Text to the Home Page](docs/AddingToHomePage/AddingToHomePage.md) guides you through adding "Hello, World!" to the home page.
* [Adding A Page](docs/AddingAPage/AddingAPage.md) guides you through adding a new page.
* [React Tutorial - Converting Reflux to Redux](http://engineering.wework.com/process/2015/10/01/react-reflux-to-redux/), by Matt Star
   If you are the kind of person that learns best by following along a tutorial, I can recommend Matt Star's overview and examples.


## Explanation

What initially gets run is `bin/server.js`, which does little more than enable ES6 and ES7 awesomeness in the
server-side node code. It then initiates `server.js`. In `server.js` we proxy any requests to `/api/*` to the
[API server](#api-server), running at `localhost:3030`. All the data fetching calls from the client go to `/api/*`.
Aside from serving the favicon and static content from `/static`, the only thing `server.js` does is initiate delegate
rendering to `react-router`. At the bottom of `server.js`, we listen to port `3000` and initiate the API server.

#### Routing and HTML return

The primary section of `server.js` generates an HTML page with the contents returned by `react-router`. First we instantiate an `ApiClient`, a facade that both server and client code use to talk to the API server. On the server side, `ApiClient` is given the request object so that it can pass along the session cookie to the API server to maintain session state. We pass this API client facade to the `redux` middleware so that the action creators have access to it.

Then we perform [server-side data fetching](#server-side-data-fetching), wait for the data to be loaded, and render the page with the now-fully-loaded `redux` state.

The last interesting bit of the main routing section of `server.js` is that we swap in the hashed script and css from the `webpack-assets.json` that the Webpack Dev Server – or the Webpack build process on production – has spit out on its last run. You won't have to deal with `webpack-assets.json` manually because [webpack-isomorphic-tools](https://github.com/halt-hammerzeit/webpack-isomorphic-tools) take care of that.

We also spit out the `redux` state into a global `window.__data` variable in the webpage to be loaded by the client-side `redux` code.

#### Server-side Data Fetching

The [redux-async-connect](https://www.npmjs.com/package/redux-async-connect) package exposes an API to return promises that need to be fulfilled before a route is rendered. It exposes a `<ReduxAsyncConnect />` container, which wraps our render tree on both [server](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/server.js) and [client](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/client.js). More documentation is available on the [redux-async-connect](https://www.npmjs.com/package/redux-async-connect) page.

#### Client Side

The client side entry point is reasonably named `client.js`. All it does is load the routes, initiate `react-router`, rehydrate the redux state from the `window.__data` passed in from the server, and render the page over top of the server-rendered DOM. This makes React enable all its event listeners without having to re-render the DOM.

#### Redux Middleware

The middleware, [`clientMiddleware.js`](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/redux/middleware/clientMiddleware.js), serves two functions:

1. To allow the action creators access to the client API facade. Remember this is the same on both the client and the server, and cannot simply be `import`ed because it holds the cookie needed to maintain session on server-to-server requests.
2. To allow some actions to pass a "promise generator", a function that takes the API client and returns a promise. Such actions require three action types, the `REQUEST` action that initiates the data loading, and a `SUCCESS` and `FAILURE` action that will be fired depending on the result of the promise. There are other ways to accomplish this, some discussed [here](https://github.com/rackt/redux/issues/99), which you may prefer, but to the author of this example, the middleware way feels cleanest.

#### Redux Modules... *What the Duck*?

The `src/redux/modules` folder contains "modules" to help
isolate concerns within a Redux application (aka [Ducks](https://github.com/erikras/ducks-modular-redux), a Redux Style Proposal that I came up with). I encourage you to read the
[Ducks Docs](https://github.com/erikras/ducks-modular-redux) and provide feedback.

#### API Server

This is where the meat of your server-side application goes. It doesn't have to be implemented in Node or Express at all. This is where you connect to your database and provide authentication and session management. In this example, it's just spitting out some json with the current time stamp.

#### Getting data and actions into components

To understand how the data and action bindings get into the components – there's only one, `InfoBar`, in this example – I'm going to refer to you to the [Redux](https://github.com/gaearon/redux) library. The only innovation I've made is to package the component and its wrapper in the same js file. This is to encapsulate the fact that the component is bound to the `redux` actions and state. The component using `InfoBar` needn't know or care if `InfoBar` uses the `redux` data or not.

#### Images

Now it's possible to render the image both on client and server. Please refer to issue [#39](https://github.com/erikras/react-redux-universal-hot-example/issues/39) for more detail discussion, the usage would be like below (super easy):

```javascript
let logoImage = require('./logo.png');
```

#### Styles

This project uses [local styles](https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284) using [css-loader](https://github.com/webpack/css-loader). The way it works is that you import your stylesheet at the top of the `render()` function in your React Component, and then you use the classnames returned from that import. Like so:

```javascript
render() {
const styles = require('./App.scss');
...
```

Then you set the `className` of your element to match one of the CSS classes in your SCSS file, and you're good to go!

```jsx
<div className={styles.mySection}> ... </div>
```

#### Alternative to Local Styles

If you'd like to use plain inline styles this is possible with a few modifications to your webpack configuration.

**1. Configure Isomorphic Tools to Accept CSS**

In `webpack-isomorphic-tools.js` add **css** to the list of style module extensions

```javascript
    style_modules: {
      extensions: ['less','scss','css'],
```

**2. Add a CSS loader to webpack dev config**

In `dev.config.js` modify **module loaders** to include a test and loader for css

```javascript
  module: {
    loaders: [
      { test: /\.css$/, loader: 'style-loader!css-loader'},
```

**3. Add a CSS loader to the webpack prod config**

You must use the **ExtractTextPlugin** in this loader. In `prod.config.js` modify **module loaders** to include a test and loader for css

```javascript
  module: {
    loaders: [
      { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader')},
```

**Now you may simply omit assigning the `required` stylesheet to a variable and keep it at the top of your `render()` function.**

```javascript
render() {
require('./App.css');
require('aModule/dist/style.css');
...
```

**NOTE** In order to use this method with **scss or less** files one more modification must be made. In both `dev.config.js` and `prod.config.js` in the loaders for less and scss files remove 

1. `modules`
2. `localIdentName...`

Before:
```javascript
{ test: /\.less$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap' },
```
After:
```javascript
{ test: /\.less$/, loader: 'style!css?importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap' },
```

After this modification to both loaders you will be able to use scss and less files in the same way as css files.

#### Unit Tests

The project uses [Mocha](https://mochajs.org/) to run your unit tests, it uses [Karma](http://karma-runner.github.io/0.13/index.html) as the test runner, it enables the feature that you are able to render your tests to the browser (e.g: Firefox, Chrome etc.), which means you are able to use the [Test Utilities](http://facebook.github.io/react/docs/test-utils.html) from Facebook api like `renderIntoDocument()`.

To run the tests in the project, just simply run `npm test` if you have `Chrome` installed, it will be automatically launched as a test service for you.

To keep watching your test suites that you are working on, just set `singleRun: false` in the `karma.conf.js` file. Please be sure set it to `true` if you are running `npm test` on a continuous integration server (travis-ci, etc).

## Deployment on Heroku

To get this project to work on Heroku, you need to:

1. Remove the `"PORT": 8080` line from the `betterScripts` / `start-prod` section of `package.json`.
2. `heroku config:set NODE_ENV=production`
3. `heroku config:set NODE_PATH=./src`
4. `heroku config:set NPM_CONFIG_PRODUCTION=false`
  * This is to enable webpack to run the build on deploy.

The first deploy might take a while, but after that your `node_modules` dir should be cached.

## FAQ

This project moves fast and has an active community, so if you have a question that is not answered below please visit our [Discord channel](https://discord.gg/0ZcbPKXt5bZZb1Ko) or file an issue.


## Roadmap 

Although this isn't a library, we recently started versioning to make it easier to track breaking changes and emerging best practices. 

* [Inline Styles](docs/InlineStyles.md) - CSS is dead

## Contributing

I am more than happy to accept external contributions to the project in the form of feedback, bug reports and even better - pull requests :) 

If you would like to submit a pull request, please make an effort to follow the guide in [CONTRIBUTING.md](CONTRIBUTING.md). 
 
---
Thanks for checking this out.

– Erik Rasmussen, [@erikras](https://twitter.com/erikras)


================================================
FILE: api/__tests__/api-test.js
================================================
import {expect} from 'chai';
import {mapUrl} from '../utils/url';

describe('mapUrl', () => {
  it('extracts nothing if both params are undefined', () => {
    expect(mapUrl(undefined, undefined)).to.deep.equal({
      action: null,
      params: []
    });
  });

  it('extracts nothing if the url is empty', () => {
    const url = '';
    const splittedUrlPath = url.split('?')[0].split('/').slice(1);
    const availableActions = {a: 1, widget: {c: 1, load: () => 'baz'}};

    expect(mapUrl(availableActions, splittedUrlPath)).to.deep.equal({
      action: null,
      params: []
    });
  });

  it('extracts nothing if nothing was found', () => {
    const url = '/widget/load/?foo=bar';
    const splittedUrlPath = url.split('?')[0].split('/').slice(1);
    const availableActions = {a: 1, info: {c: 1, load: () => 'baz'}};

    expect(mapUrl(availableActions, splittedUrlPath)).to.deep.equal({
      action: null,
      params: []
    });
  });

  it('extracts the available actions and the params from an relative url string with GET params', () => {
    const url = '/widget/load/param1/xzy?foo=bar';
    const splittedUrlPath = url.split('?')[0].split('/').slice(1);
    const availableActions = {a: 1, widget: {c: 1, load: () => 'baz'}};

    expect(mapUrl(availableActions, splittedUrlPath)).to.deep.equal({
      action: availableActions.widget.load,
      params: ['param1', 'xzy']
    });
  });

  it('extracts the available actions from an url string without GET params', () => {
    const url = '/widget/load/?foo=bar';
    const splittedUrlPath = url.split('?')[0].split('/').slice(1);
    const availableActions = {a: 1, widget: {c: 1, load: () => 'baz'}};

    expect(mapUrl(availableActions, splittedUrlPath)).to.deep.equal({
      action: availableActions.widget.load,
      params: ['']
    });
  });

  it('does not find the available action if deeper nesting is required', () => {
    const url = '/widget';
    const splittedUrlPath = url.split('?')[0].split('/').slice(1);
    const availableActions = {a: 1, widget: {c: 1, load: () => 'baz'}};

    expect(mapUrl(availableActions, splittedUrlPath)).to.deep.equal({
      action: null,
      params: []
    });
  });
});


================================================
FILE: api/actions/__tests__/loadInfo-test.js
================================================
import {expect} from 'chai';
import loadInfo from '../loadInfo';
import timekeeper from 'timekeeper';

describe('loadInfo', () => {
  it('loads the current date', () => {
    const now = Date.now();
    timekeeper.freeze(now);

    return loadInfo().then(data => {
      expect(data).to.deep.equal({time: now, message: 'This came from the api server'});
    });
  });
});


================================================
FILE: api/actions/__tests__/widget-load-test.js
================================================
import {expect} from 'chai';
import load from '../widget/load';
import sinon from 'sinon';

describe('widget load', () => {
  afterEach(()=> {
    if ('restore' in Math.random) {
      Math.random.restore(); // reset the Math.random fixture
    }
  });

  describe('successful', () => {
    beforeEach(()=> {
      sinon.stub(Math, 'random').returns(0.4);
    });

    it('uses the widgets from the session', () => {
      return load({session: {widgets: ['a', 'b', 'c']}}).then(widgets => {
        expect(widgets.length).to.equal(3);
      });
    });

    it('initializes the widgets ', () => {
      return load({session: {}}).then(widgets => {
        expect(widgets.length).to.equal(4);
        expect(widgets[0].color).to.equal('Red');
      });
    });
  });

  describe('unsuccessful', () => {
    beforeEach(()=> {
      sinon.stub(Math, 'random').returns(0.2);
    });

    it('rejects the call', () => {
      return load({session: {}}).
      then(
        ()=> {
        },
        (err)=> {
          expect(err).to.equal('Widget load fails 33% of the time. You were unlucky.');
        });
    });
  });
});


================================================
FILE: api/actions/__tests__/widget-update-test.js
================================================
import {expect} from 'chai';
import update from '../widget/update';
import * as load from '../widget/load';
import sinon from 'sinon';

describe('widget update', () => {
  afterEach(()=> {
    if ('restore' in Math.random) {
      Math.random.restore(); // reset the Math.random fixture
    }
  });

  describe('randomly successful', () => {
    const widgets = [{}, {id: 2, color: 'Red'}];

    beforeEach(()=> {
      sinon.stub(Math, 'random').returns(0.3);
    });

    afterEach(()=> {
      if ('restore' in load.default) {
        load.default.restore();
      }
    });

    it('does not accept green widgets', () => {
      sinon.stub(load, 'default').returns(new Promise((resolve) => {
        resolve(widgets);
      }));
      return update({session: {}, body: {color: 'Green'}}).
      then(
        ()=> {
        },
        (err)=> {
          expect(err.color).to.equal('We do not accept green widgets');
        });
    });

    it('fails to load widgets', () => {
      sinon.stub(load, 'default').returns(new Promise((resolve, reject) => {
        reject('Widget fail to load.');
      }));
      return update({session: {}, body: {color: 'Blue'}}).
      then(
        ()=> {
        },
        (err)=> {
          expect(err).to.equal('Widget fail to load.');
        });
    });

    it('updates a widget', () => {
      sinon.stub(load, 'default').returns(new Promise((resolve) => {
        resolve(widgets);
      }));
      const widget = {id: 2, color: 'Blue'};
      return update({session: {}, body: widget}).
      then(
        (res)=> {
          expect(res).to.deep.equal(widget);
          expect(widgets[1]).to.deep.equal(widget);
        });
    });
  });

  describe('randomly unsuccessful', () => {
    beforeEach(()=> {
      sinon.stub(Math, 'random').returns(0.1);
    });

    it('rejects the call in 20% of the time', () => {
      return update().
      then(
        ()=> {
        },
        (err)=> {
          expect(err).to.equal('Oh no! Widget save fails 20% of the time. Try again.');
        });
    });
  });
});


================================================
FILE: api/actions/index.js
================================================
export loadInfo from './loadInfo';
export loadAuth from './loadAuth';
export login from './login';
export logout from './logout';
export * as widget from './widget/index';
export * as survey from './survey/index';


================================================
FILE: api/actions/loadAuth.js
================================================
export default function loadAuth(req) {
  return Promise.resolve(req.session.user || null);
}


================================================
FILE: api/actions/loadInfo.js
================================================
export default function loadInfo() {
  return new Promise((resolve) => {
    resolve({
      message: 'This came from the api server',
      time: Date.now()
    });
  });
}


================================================
FILE: api/actions/login.js
================================================
export default function login(req) {
  const user = {
    name: req.body.name
  };
  req.session.user = user;
  return Promise.resolve(user);
}


================================================
FILE: api/actions/logout.js
================================================
export default function logout(req) {
  return new Promise((resolve) => {
    req.session.destroy(() => {
      req.session = null;
      return resolve(null);
    });
  });
}


================================================
FILE: api/actions/survey/index.js
================================================
export isValid from './isValid';


================================================
FILE: api/actions/survey/isValid.js
================================================
export default function survey(req) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const errors = {};
      let valid = true;
      if (~['bobby@gmail.com', 'timmy@microsoft.com'].indexOf(req.body.email)) {
        errors.email = 'Email address already used';
        valid = false;
      }
      if (valid) {
        resolve();
      } else {
        reject(errors);
      }
    }, 1000);
  });
}


================================================
FILE: api/actions/widget/index.js
================================================
export update from './update';
export load from './load';


================================================
FILE: api/actions/widget/load.js
================================================
const initialWidgets = [
  {id: 1, color: 'Red', sprocketCount: 7, owner: 'John'},
  {id: 2, color: 'Taupe', sprocketCount: 1, owner: 'George'},
  {id: 3, color: 'Green', sprocketCount: 8, owner: 'Ringo'},
  {id: 4, color: 'Blue', sprocketCount: 2, owner: 'Paul'}
];

export function getWidgets(req) {
  let widgets = req.session.widgets;
  if (!widgets) {
    widgets = initialWidgets;
    req.session.widgets = widgets;
  }
  return widgets;
}

export default function load(req) {
  return new Promise((resolve, reject) => {
    // make async call to database
    setTimeout(() => {
      if (Math.random() < 0.33) {
        reject('Widget load fails 33% of the time. You were unlucky.');
      } else {
        resolve(getWidgets(req));
      }
    }, 1000); // simulate async load
  });
}


================================================
FILE: api/actions/widget/update.js
================================================
import load from './load';

export default function update(req) {
  return new Promise((resolve, reject) => {
    // write to database
    setTimeout(() => {
      if (Math.random() < 0.2) {
        reject('Oh no! Widget save fails 20% of the time. Try again.');
      } else {
        load(req).then(data => {
          const widgets = data;
          const widget = req.body;
          if (widget.color === 'Green') {
            reject({
              color: 'We do not accept green widgets' // example server-side validation error
            });
          }
          if (widget.id) {
            widgets[widget.id - 1] = widget;  // id is 1-based. please don't code like this in production! :-)
            req.session.widgets = widgets;
          }
          resolve(widget);
        }, err => {
          reject(err);
        });
      }
    }, 1500); // simulate async db write
  });
}


================================================
FILE: api/api.js
================================================
import express from 'express';
import session from 'express-session';
import bodyParser from 'body-parser';
import config from '../src/config';
import * as actions from './actions/index';
import {mapUrl} from 'utils/url.js';
import PrettyError from 'pretty-error';
import http from 'http';
import SocketIo from 'socket.io';

const pretty = new PrettyError();
const app = express();

const server = new http.Server(app);

const io = new SocketIo(server);
io.path('/ws');

app.use(session({
  secret: 'react and redux rule!!!!',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 60000 }
}));
app.use(bodyParser.json());


app.use((req, res) => {
  const splittedUrlPath = req.url.split('?')[0].split('/').slice(1);

  const {action, params} = mapUrl(actions, splittedUrlPath);

  if (action) {
    action(req, params)
      .then((result) => {
        if (result instanceof Function) {
          result(res);
        } else {
          res.json(result);
        }
      }, (reason) => {
        if (reason && reason.redirect) {
          res.redirect(reason.redirect);
        } else {
          console.error('API ERROR:', pretty.render(reason));
          res.status(reason.status || 500).json(reason);
        }
      });
  } else {
    res.status(404).end('NOT FOUND');
  }
});


const bufferSize = 100;
const messageBuffer = new Array(bufferSize);
let messageIndex = 0;

if (config.apiPort) {
  const runnable = app.listen(config.apiPort, (err) => {
    if (err) {
      console.error(err);
    }
    console.info('----\n==> 🌎  API is running on port %s', config.apiPort);
    console.info('==> 💻  Send requests to http://%s:%s', config.apiHost, config.apiPort);
  });

  io.on('connection', (socket) => {
    socket.emit('news', {msg: `'Hello World!' from server`});

    socket.on('history', () => {
      for (let index = 0; index < bufferSize; index++) {
        const msgNo = (messageIndex + index) % bufferSize;
        const msg = messageBuffer[msgNo];
        if (msg) {
          socket.emit('msg', msg);
        }
      }
    });

    socket.on('msg', (data) => {
      data.id = messageIndex;
      messageBuffer[messageIndex % bufferSize] = data;
      messageIndex++;
      io.emit('msg', data);
    });
  });
  io.listen(runnable);
} else {
  console.error('==>     ERROR: No PORT environment variable has been specified');
}


================================================
FILE: api/utils/url.js
================================================
export function mapUrl(availableActions = {}, url = []) {
  const notFound = {action: null, params: []};

  // test for empty input
  if (url.length === 0 || Object.keys(availableActions).length === 0) {
    return notFound;
  }
  /*eslint-disable */
  const reducer = (prev, current) => {
    if (prev.action && prev.action[current]) {
      return {action: prev.action[current], params: []}; // go deeper
    } else {
      if (typeof prev.action === 'function') {
        return {action: prev.action, params: prev.params.concat(current)}; // params are found
      } else {
        return notFound;
      }
    }
  };
  /*eslint-enable */

  const actionAndParams = url.reduce(reducer, {action: availableActions, params: []});

  return (typeof actionAndParams.action === 'function') ? actionAndParams : notFound;
}


================================================
FILE: app.json
================================================
{
  "name": "react-redux-universal-hot-example",
  "description": "Example of an isomorphic (universal) webapp using react redux and hot reloading",
  "repository": "https://github.com/erikras/react-redux-universal-hot-example",
  "logo": "http://node-js-sample.herokuapp.com/node.svg",
  "keywords": [
    "react",
    "isomorphic",
    "universal",
    "webpack",
    "express",
    "hot reloading",
    "react-hot-reloader",
    "redux",
    "starter",
    "boilerplate",
    "babel"
  ]
}


================================================
FILE: bin/api.js
================================================
#!/usr/bin/env node
if (process.env.NODE_ENV !== 'production') {
  if (!require('piping')({
    hook: true,
    ignore: /(\/\.|~$|\.json$)/i
  })) {
    return;
  }
}
require('../server.babel'); // babel registration (runtime transpilation for node)
require('../api/api');


================================================
FILE: bin/server.js
================================================
#!/usr/bin/env node
require('../server.babel'); // babel registration (runtime transpilation for node)
var path = require('path');
var rootDir = path.resolve(__dirname, '..');
/**
 * Define isomorphic constants.
 */
global.__CLIENT__ = false;
global.__SERVER__ = true;
global.__DISABLE_SSR__ = false;  // <----- DISABLES SERVER SIDE RENDERING FOR ERROR DEBUGGING
global.__DEVELOPMENT__ = process.env.NODE_ENV !== 'production';

if (__DEVELOPMENT__) {
  if (!require('piping')({
      hook: true,
      ignore: /(\/\.|~$|\.json|\.scss$)/i
    })) {
    return;
  }
}

// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicTools = require('webpack-isomorphic-tools');
global.webpackIsomorphicTools = new WebpackIsomorphicTools(require('../webpack/webpack-isomorphic-tools'))
  .development(__DEVELOPMENT__)
  .server(rootDir, function() {
    require('../src/server');
  });


================================================
FILE: circle.yml
================================================
machine:
  node:
    version: 4.0
  environment:
    CONTINUOUS_INTEGRATION: true

dependencies:
  cache_directories:
    - node_modules
  override:
    - npm prune && npm install

test:
  override:
    - npm run lint
    - npm test
    - npm run test-node


================================================
FILE: docs/AddingAPage/AddingAPage.md
================================================
# Adding A Hello Page

This guide adds a `/hello` page to the sample application by 
following the existing outline.

## Using Ack on About

Searching strings is one way to [grok](https://en.wikipedia.org/wiki/Grok) the structure
of the kit and sample application.   You can use *grep* or [ack](http://beyondgrep.com) (`brew install ack`).
I use *ack* with this alias:

![ick Alias](ick_alias.png)

Looking with `ick about` and ignoring documentation, the word *about* appears in these files:

![ick for About](ick_about.png)

## Add the Hello page container

A new page requires new page renderer.  Copy the About page to a 
new directory and trim out almost all of it:

*  `cd ./src/containers && mkdir ./Hello` because each container goes in its own 
    directory by convention.
*  `cp About/About.js Hello/Hello.js`

Edit `Hello/Hello.js` into this file:

![New Hello.js](new_hello.png)



## Edit three files to add Hello

#### Add to `./src/containers/index.js` to include and export the React component:

![Edit index.js](edit_index.png)

#### Add to `./routes.js` to connect the `/hello` url path to the component:

![Edit routes.js 1](edit_route1.png)
![Edit routes.js 2](edit_route2.png)

#### Add to `./src/containers/App/App.js` to add "Hello" to the NavBar

![Edit App.js](edit_app.png)

And voila, the new 'Hello' page:

![Show Hello](show_hello.png)

# Take-away:  Notice the trade-offs

The task of adding a new page exemplifies two trade-offs in the kit:
**code versus convention** and the **cut and paste** style.

Convention is a set of constraining rules that automatically trigger
routine configuration tasks.  For example, WebPack automatically picked up the 
new directory `./src/containers/Hello` without adding to any configuration files.

On the other hand, routine code was added to `./src/containers/index.js` and 
`./src/routes.js` to handle the new page.  A convention could automatically
accomplish the same tasks at either compile or run time.  The cost is new 
constraints, such as requiring `Hello/Hello.js` to be renamed
`HelloPage/HelloPage.js`.

Following a style in the code that has no automatic effects is just organic
growth, not convention.  For example, developers reading `./src/containers/index.js`
must stop and figure out why all subdirectories except `DevTools` are exported.
(`DevTools`)[`./src/containers/DevTools/DevTools.js`](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/DevTools/DevTools.js)
contains a single function which should be
[randomly](https://github.com/erikras/react-redux-universal-hot-example/issues/808)
moved to `./src/utils` or `./src/helpers`.  Using a convention rule that all 
containers must contain an exported React component would raise an error.
Organic growth leads to disorder in a project.

Similarly, the **cut and paste** style of coding also degrades the project.
For example, In `App.js`, the new *NavItem* tag included a new value for the
*eventkey* property.  The *eventkey*  property is
[poorly](https://github.com/react-bootstrap/react-bootstrap/issues/320)
[understood](https://github.com/react-bootstrap/react-bootstrap/issues/432).
All *eventkey* fields in `App.js` are unused and can be removed.  The 
**cut and paste** style just compounds an
[old error](https://github.com/erikras/react-redux-universal-hot-example/commit/d67a79c1e7da5367dc8922019ca726e69d56bf0e)
and reinforces confusion.

![Edit App revisted](edit_app2.png)

The use of the **cut and paste** style raises well known issues in
maintenance, documentation, and code quality.  It is not for use in
production code.  

Some choices about trade-offs are easier than others.


================================================
FILE: docs/AddingToHomePage/AddingToHomePage.md
================================================
# Adding Hello, World as static text

Printing *Hello, World!* is a traditional task.  This guides you through adding the text "Hello, World!" to the
home page of the sample application.

## Find the home page

First, find the correct file to change by walking through the kit's directory tree:

![Finding The Home Page 1](find_home1.png)


![Finding The Home Page 2](find_home2.png)

![Finding The Home Page 3](find_home3.png)

![Finding The Home Page 4](find_home4.png)

So, the likely file is `src/containers/Home/Home.js`.

## Start the server and open the browser

Execute `npm run dev` and open http://localhost:3000:

*  `./package.json`, using [concurrently](https://www.npmjs.com/package/concurrently)
and [better-npm-run](https://www.npmjs.com/package/better-npm-run), runs 
 `./webpack/webpack-dev-server.js` on port 3001; runs `./bin/server.js` for HTTP on port 3000; 
 and runs `./bin/api.js` for the REST API on port 3030.
  
* `./bin/server.js` calls `./src/server.js` and uses the [HMR plugin](http://andrewhfarmer.com/webpack-hmr-tutorial/)
for hot reloading, meaning the browser refreshes automatically when any file in `./src` is changed.

* `./webpack/webpack-dev-server` does teh actual compilation with the 
[webpack dev middleware package](https://github.com/webpack/webpack-dev-middleware) to provide a key feature found
in Glup:  compilation without writing intermediate files to disk.  Configuring webpack
[can be confusing](https://medium.com/@dtothefp/why-can-t-anyone-write-a-simple-webpack-tutorial-d0b075db35ed#.cle1vv5ql).

* `./bin/api.js` calls `./api/api.js`.  It receives incoming REST requests as JSON objects and responds with
other JSON objects.   

## Change the text

Add the static text to (`src/containers/Home/Home.js`):

![Add Hello Header to Home](add_home.png)


When you save the file to disk, the change to the `./src` directory is picked up by the 
[piping](https://www.npmjs.com/package/piping) module, triggering the webpack-dev-server to rebuild 
`./static/dist/[checksum].js`, and triggering a stub injected into the HTML file served to the browser to 
reload.   The rebuilding processes through webpack middleware and plugins that compile `*.sccs` files, 
transpile JAX and ES6 (or ES7), and bundles together all the resources into one package in about 6 seconds.
That is, the browser will show "Hello, World!" on your web page in about 6 seconds:

![Hello World rendered on home page](hello_rendered.png)

## Conclusion

You added **Hello, World!**.   The process is [as clear as is the summer's sun](https://www.youtube.com/watch?v=EhGiSfv5FJk&t=3m23s).



================================================
FILE: docs/ApiConfig.md
================================================
# Switching to a real API

Chances are, once you get comfortable with this setup, you'll want to hook into some existing API rather than use the simple one that comes with this project. Here's how:

## Update `package.json`

First things first, you need to add `APIHOST` settings in `package.json`. If you look in `src/config.js`, you'll see that it's already configured to read this `APIHOST` setting if it's present.

If the port you use differs between your dev & prod API hosts, you may want to get rid of the `APIPORT` setting, including it right in `APIHOST`. Same with the protocol – if you use HTTP in dev but HTTPS in prod, you may want to include the protocol right in `APIHOST`, and then get rid of the explicit `"http://"` found in the next section.

## Update `ApiClient`

Open up `src/helpers/ApiClient.js`. You'll see this line:

``` javascript
   if (__SERVER__) {
     // Prepend host and port of the API server to the path.
    return 'http://' + config.apiHost + adjustedPath;
   }
```

If you added `http://` or `https://` to your APIHOST setting, then you need to remove it here.

In this file, you'll also see that there's a `/api` that gets prepended to the URL when on the client side. That gets routed through a proxy that's configured in server.js, which we'll get to next.

Why do you need a proxy? So that the `APIHOST` can be set as part of the Node environment, and your client side code can still work. A user's browser doesn't have access to your server's Node environment, so instead the client-side code makes all API calls to this `/api` proxy, which the server configures to hit your real API. That way you can control everything sanely, through environment variables, and set different API endpoints for your different environments.

## Update `server.js`

To update the proxy, find this chunk in `src/server.js`:

``` javascript
  const proxy = httpProxy.createProxyServer({
    target: 'http://' + config.apiHost,
    ws: true
  });
```

You'll want to change this in a few ways:

### 1. Update `target` protocol

If you changed APIHOST to include the `http://` or `https://` protocol, then get rid of the `'http://' +` in the `target` setting. Note that you'll need to restart your server after making these changes or things will break.

### 2. Decide if you need WebSockets

The `ws: true` setting is there to support WebSockets connections, which this demo app supports using [socket.io](http://socket.io/). If your API doesn't use WebSockets, then you can remove this line.

### 3. Add a `changeOrigin` setting

This might be the most important part! It's possible that your API lives at a totally different URL than your front-end app. If that's the case, then you need to add a `changeOrigin` setting. 

Here's an example of what the final chunk of code might look like:

``` javascript
  const proxy = httpProxy.createProxyServer({
    target: config.apiHost,
    changeOrigin: true
  });
```

Finally, after doing all of that, you can get rid of the demo API.

## Get rid of stuff

You can remove the whole `api` folder, as well as `bin/api.js`.

Once you do that, you'll also need to remove the lines in `package.json` that called those things. Remove all this:

* ` \"npm run start-prod-api\"` from the `start` script command
* ` \"npm run start-dev-api\"` from the `dev` script command
* the `start-prod-api` and `start-dev-api` scripts altogether
* the ` api` argument from the `lint` script
* the `test-node` and `test-node-watch` scripts, which were there to test the demo API
* the `start-prod-api` and `start-dev-api` settings in the `betterScripts` section

If you want, you can also remove all references to `socket`, if you're not using it.


================================================
FILE: docs/Ducks.md
================================================
This document has found [another, hopefully permanent, home](https://github.com/erikras/ducks-modular-redux).

Quack.


================================================
FILE: docs/ExploringTheDemoApp/ExploringTheDemoApp.md
================================================
# Guide - Exploring the Demo App

This guide covers your first look and can be used even before installing software.  

## Overview

This project is a kit for developing interactive applications in JavaScript centered
around React and Redux.  Like all JavaScript kits, it includes a large number of configured
modules and a sample, or Demo App, from which to start your application.  This guide walks 
through that Demo App to show some features and code.

### Open the demo in your browser

The project hosts a running demo on Heroku, a hosting company.  Open 
[https://react-redux.herokuapp.com/](https://react-redux.herokuapp.com/) in your browser to see this page:

![Screenshot](frontpage.png)

Much of the text is cut-and-paste from the project's 
[README.md](https://github.com/erikras/react-redux-universal-hot-example/blob/master/README.md) file into 
the code for this page [./src/containers/Home/Home.js](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/Home/Home.js).  

The text provides a one line overview of about twenty of the main 
modules of hundreds shown during installation.   The selection 
and configuration of all these modules is the value of using a kit.   When you run across
a module you have never heard of and want to get a quick overview, the fastest way is
to google for 'slideshare theModuleName' and skim someone's presentation.

The page is rendered from HTML including React components coded as custom HTML tags.
The components use properties to alter appearance and sets of data.  Notice some of the components on the page:

![Screenshot with Annotations](frontpage_markup.png)

### Explore the Widgets Page

Click on *Widgets* link on the top of the screen.   You come to a page with some arbitrary widgets and more logic
in the form.  Notice how much state affects the display and formatting of buttons:

![Screenshot with Annotations](widgets_markup.png)

### Explore the Survey Page

Click on the *Survey* link.  Following the programming style of this kit, the code for this page is 
spread over a [Survey container][scont], a [SurveyForm component][scomp], mentioned in the 
[container][conlist] and [component][complist] lists, 
mentioned in the [routes][routes] function and the navigation of the main [App][app].  The code also uses
various libraries for React, Redux, validation, memoize and other functions.   Learn to use [ack](http://beyondgrep.com) 
or the project wide search built into your editor.

Try clicking on the 'Initialize Form' button and then hitting Submit.  You will see just an error under
'Little Bobby Tables'.  Now click in the email field then the name field.   You now see errors in both
the name and the email.  Even with a good kit, forms can be difficult to code.

![Screenshot with Annotations](survey_markup.png)
[scont]: https://github.com/erikras/react-redux-universal-hot-example/tree/master/src/containers/Survey
[scomp]: https://github.com/erikras/react-redux-universal-hot-example/tree/master/src/components/SurveyForm
[conlist]: https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/index.js
[complist]: https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/components/index.js
[routes]: https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/routes.js
[app]: https://github.com/erikras/react-redux-universal-hot-example/tree/master/src/containers/App/App.js

### Explore the About Page

Click on the *About* link.   The source for this page 
[./src/containers/About/About.js](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/About/About.js)
uses a casual mix of HTML, ECMA7 JavaScript, and React components.   This translates into 
simple JavaScript code for the browser.   Notice how the local state `showKitten` being false causes no
`div` or `img` tag in the output.

![Screenshot with Annotations](about_markup.png)

### Explore the Login Page

Finally, click on the *Login* page and explore.   Looking at the styling for this page 
[./src/containers/Login/Login.scss]](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/Login/Login.scss)
will show an example of how the using styling files litters the code base with extra files.   
Consider the alternative of using 
[Inline Styles](https://github.com/erikras/react-redux-universal-hot-example/blob/master/docs/InlineStyles.md).

# The Take Away

Looking through each page of the DemoApp will lead you to more questions which will keep you 
searching and learning and you will slowly master this technology.

Here are some additional quests you could undertake:

* How do the About page MiniBar and the status bar share data about the time last loaded?
* How would you add a fourth counter that incremented by two?   How many files would you need
  to touch?
* What order are calls made when you click "Reload Widgets" on the widgets page?
* Why does surveyValidation use memoize?

Install, hack, explore!

*All guides are works in progress, and pull requests are always welcome.  If you make an
accepted pull request and live in Silicon Valley, I'll treat you to coffee.  -- Charles*




================================================
FILE: docs/InlineStyles.md
================================================
# Inline Styles

In the long term, CSS, LESS and SASS are dead. To keep this project on the bleeding edge, we should drop SASS support in favor of inline styles.

## Why?

I think the case is made pretty strongly in these three presentations.

Christopher Chedeau | Michael Chan | Colin Megill
--- | --- | ---
[![CSS In Your JS by Christopher Chedeau](https://i.vimeocdn.com/video/502495328_295x166.jpg)](http://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html) | [![Michael Chan - Inline Styles: themes, media queries, contexts, & when it's best to use CSS](https://i.ytimg.com/vi/ERB1TJBn32c/mqdefault.jpg)](https://www.youtube.com/watch?v=ERB1TJBn32c) | [![Colin Megill - Inline Styles are About to Kill CSS](https://i.ytimg.com/vi/NoaxsCi13yQ/mqdefault.jpg)](https://www.youtube.com/watch?v=NoaxsCi13yQ)

Clearly this is the direction in which web development is moving.

## Why not?

At the moment, all the inline CSS libraries suffer from some or all of these problems:

* Client side only
* No vendor auto prefixing (requires `User-Agent` checking on server side)
* No server side media queries, resulting in a flicker on load to adjust to client device width

Ideally, a library would allow for all the benefits of inline calculable styles, but, in production, would allow some generation of a CSS block, with media queries to handle device width conditionals, to be inserted into the page with a `<style>` tag, and then each element that was using a style that was dependent on a media query would have a class name generated.

## Contenders

This is a list of possible style libraries that we could implement into this project.

* [Radium](https://github.com/FormidableLabs/radium)
* [React-JSS](https://github.com/jsstyles/react-jss)
* [jsxstyle](https://github.com/petehunt/jsxstyle)
* [css-modules](https://github.com/css-modules/css-modules)
* [babel-plugin-react-autoprefix](https://github.com/UXtemple/babel-plugin-react-autoprefix)
* [babel-plugin-css-in-js](https://github.com/martinandert/babel-plugin-css-in-js)
* _Add more if you know of others._

Users and contributors to this project should periodically go through this list and see if any of them have developed features that make them really worthy of server side rendering in a production environment.

## Additional Reading

* [How do we make “styles in components” play nicely with server-side rendering?](https://medium.com/@jedwatson/how-do-we-make-styles-in-components-play-nicely-with-server-side-rendering-25de9ecb1b49)


================================================
FILE: docs/InstallingTheKit/InstallingTheKit.md
================================================
# Installing the Kit

Are you ready to install the kit and start playing with it?  No?  Good.  Let's begin.

There is no failure except when nothing is learned.

##  Clone from github

First you should clone the project from GitHub.  The easiest way is to use something like this:

![Cloning from GitHub](git_clone.png)

This puts a copy of the whole project into your directory `mycopy`.   All the files you will change are
in this directory.   You can `rm -rf mycopy`, clone it again, and continue on.   

That's the minimum view of git, equivalent to the [xkcd view](https://xkcd.com/1597/).   Git and 
[github](https://github.com) make every manipulation and automated workflow possible but none easy.
Nothing more complex than creating new copy is necessary until contributing code to a project.


## Run npm install

Only part of the project is stored in github.  All the JavaScript libraries upon which the project depends 
are imported using npm, the node package manager.  It might be a good time to check that your version of `npm` is
up to date; OS/X users type `brew upgrade && brew update`.  

Npm installs libraries according to the [semvar](https://docs.npmjs.com/getting-started/semantic-versioning) 
minimum versions in the `dependencies` section of `package.json` file:

![The Dependencies](dependencies.png)

We also install the packages listed in the `devDependencies` section,  
[because we are installing from source](http://stackoverflow.com/questions/18875674/whats-the-difference-between-dependencies-devdependencies-and-peerdependencies).
Every package also has its own `package.json` file containing more dependencies.  Multiple versions 
of the same packages may be listed as dependencies causing copies to be installed in package subdirectories
`./node_modules/*/node_modules`, `./node_modules/*/node_modules/*/node_modules`, etc.  This accumulates 
to over 1,000 packages.

### Let's install a thousand packages:

![Start of install](start_npm.png)

And then you should see pages and pages of output.  Some packages suggest installing "globally" or "-g"; don't. 
Global packages install into  `/usr/local/lib/node_modules` and mix links to binaries into `/usr/local/bin`.
Local packages install into `./node_modules` with the links to binaries separate in `./node_modules/.bin`.
This provides you the to option of doing a `rm -rf node_modules && npm install` to get back to a known state.

This installation step fails some days; the kit juggles many moving parts and these packages are independently 
developed.  Some packages have [peer dependencies](https://nodejs.org/en/blog/npm/peer-dependencies/)
which end up conflicting with one another.   You can check the 
[open install issues](https://github.com/erikras/react-redux-universal-hot-example/issues?utf8=✓&q=is%3Aissue+is%3Aopen+install),
run `npm install` again, or try `npm outdated && npm update`.   Errors in installation are often not caught by 
running `npm run test`.  The only way to know it works is to run it.

## Run the Development Server

First, find the command. 

![List of scripts](npm_run.png)

Now, run it!

![Run dev, part 1](run_dev1.png)

We run:

*  A *watch client* to trigger webpack to rebuild if we change code.
*  A *restful api server* listening on port 3030 to handle requests in JSON format.
*  A *webpack dev server* which serves your application on port 3000.  It also grabs port 3001
   for status and internal information, such as [polling middleware](http://localhost:3001/__webpack_hmr).
   You could try installing [BrowserSync](https://www.browsersync.io) to see something more interesting.
   
The second part shows these running:

![Run dev, part 2](run_dev2.png)

You can see WebPack rebuilding static assets into `./webpack-assets.json`.  If you check the id, you
can also view it on port 3001, http://localhost:3001/dist/main-b6c55eaa1c8d8efc7190.js in this example.

Now, open up your browser to [port 3000](http://localhost:3000/):

![Port 3000](port3000.png)

The Redux developer bar takes up much of the screen.  Hide it with 'control-H'.

You are now running the kit on your local machine.


# The Take Away

You cloned the repository, installed all the packages, and ran the development server locally.  
You are now ready to hack on the application.


================================================
FILE: karma.conf.js
================================================
var webpack = require('webpack');

module.exports = function (config) {
  config.set({

    browsers: ['PhantomJS'],

    singleRun: !!process.env.CI,

    frameworks: [ 'mocha' ],

    files: [
      './node_modules/phantomjs-polyfill/bind-polyfill.js',
      'tests.webpack.js'
    ],

    preprocessors: {
      'tests.webpack.js': [ 'webpack', 'sourcemap' ]
    },

    reporters: [ 'mocha' ],

    plugins: [
      require("karma-webpack"),
      require("karma-mocha"),
      require("karma-mocha-reporter"),
      require("karma-phantomjs-launcher"),
      require("karma-sourcemap-loader")
    ],

    webpack: {
      devtool: 'inline-source-map',
      module: {
        loaders: [
          { test: /\.(jpe?g|png|gif|svg)$/, loader: 'url', query: {limit: 10240} },
          { test: /\.js$/, exclude: /node_modules/, loaders: ['babel']},
          { test: /\.json$/, loader: 'json-loader' },
          { test: /\.less$/, loader: 'style!css!less' },
          { test: /\.scss$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap' }
        ]
      },
      resolve: {
        modulesDirectories: [
          'src',
          'node_modules'
        ],
        extensions: ['', '.json', '.js']
      },
      plugins: [
        new webpack.IgnorePlugin(/\.json$/),
        new webpack.NoErrorsPlugin(),
        new webpack.DefinePlugin({
          __CLIENT__: true,
          __SERVER__: false,
          __DEVELOPMENT__: true,
          __DEVTOOLS__: false  // <-------- DISABLE redux-devtools HERE
        })
      ]
    },

    webpackServer: {
      noInfo: true
    }

  });
};


================================================
FILE: package.json
================================================
{
  "name": "react-redux-universal-hot-example",
  "description": "Example of an isomorphic (universal) webapp using react redux and hot reloading",
  "author": "Erik Rasmussen <rasmussenerik@gmail.com> (http://github.com/erikras)",
  "license": "MIT",
  "version": "0.9.0",
  "repository": {
    "type": "git",
    "url": "https://github.com/erikras/react-redux-universal-hot-example"
  },
  "homepage": "https://github.com/erikras/react-redux-universal-hot-example",
  "keywords": [
    "react",
    "isomorphic",
    "universal",
    "webpack",
    "express",
    "hot reloading",
    "react-hot-reloader",
    "redux",
    "starter",
    "boilerplate",
    "babel"
  ],
  "main": "bin/server.js",
  "scripts": {
    "start": "concurrent --kill-others \"npm run start-prod\" \"npm run start-prod-api\"",
    "start-prod": "better-npm-run start-prod",
    "start-prod-api": "better-npm-run start-prod-api",
    "build": "better-npm-run build",
    "postinstall": "npm run build",
    "lint": "eslint -c .eslintrc src api",
    "start-dev": "better-npm-run start-dev",
    "start-dev-api": "better-npm-run start-dev-api",
    "watch-client": "better-npm-run watch-client",
    "dev": "concurrent --kill-others \"npm run watch-client\" \"npm run start-dev\" \"npm run start-dev-api\"",
    "test": "karma start",
    "test-node": "./node_modules/mocha/bin/mocha $(find api -name '*-test.js') --compilers js:babel-core/register",
    "test-node-watch": "./node_modules/mocha/bin/mocha $(find api -name '*-test.js') --compilers js:babel-core/register --watch"
  },
  "betterScripts": {
    "start-prod": {
      "command": "node ./bin/server.js",
      "env": {
        "NODE_PATH": "./src",
        "NODE_ENV": "production",
        "PORT": 8080,
        "APIPORT": 3030
      }
    },
    "start-prod-api": {
      "command": "node ./bin/api.js",
      "env": {
        "NODE_PATH": "./api",
        "NODE_ENV": "production",
        "APIPORT": 3030
      }
    },
    "start-dev": {
      "command": "node ./bin/server.js",
      "env": {
        "NODE_PATH": "./src",
        "NODE_ENV": "development",
        "PORT": 3000,
        "APIPORT": 3030
      }
    },
    "start-dev-api": {
      "command": "node ./bin/api.js",
      "env": {
        "NODE_PATH": "./api",
        "NODE_ENV": "development",
        "APIPORT": 3030
      }
    },
    "watch-client": {
      "command": "node webpack/webpack-dev-server.js",
      "env": {
        "UV_THREADPOOL_SIZE": 100,
        "NODE_PATH": "./src",
        "PORT": 3000,
        "APIPORT": 3030
      }
    },
    "build": {
      "command": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js",
      "env": {
        "NODE_ENV": "production"
      }
    }
  },
  "dependencies": {
    "babel-core": "^6.5.2",
    "babel-loader": "^6.2.1",
    "babel-plugin-add-module-exports": "^0.1.2",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-plugin-transform-react-display-name": "^6.3.13",
    "babel-plugin-transform-runtime": "^6.3.13",
    "babel-polyfill": "^6.3.14",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "babel-preset-stage-0": "^6.3.13",
    "babel-register": "^6.3.13",
    "babel-runtime": "^6.3.19",
    "body-parser": "^1.14.1",
    "compression": "^1.6.0",
    "express": "^4.13.3",
    "express-session": "^1.12.1",
    "file-loader": "^0.8.5",
    "hoist-non-react-statics": "^1.0.3",
    "http-proxy": "^1.12.0",
    "immutable": "^3.8.1",
    "invariant": "^2.2.0",
    "less": "^2.5.3",
    "less-loader": "^2.2.1",
    "lru-memoize": "^1.0.0",
    "map-props": "^1.0.0",
    "multireducer": "^2.0.0",
    "piping": "^0.3.0",
    "pretty-error": "^1.2.0",
    "react": "0.14.8",
    "react-bootstrap": "^0.28.1",
    "react-dom": "0.14.8",
    "react-helmet": "^2.2.0",
    "react-inline-css": "^2.0.0",
    "react-redux": "^4.0.0",
    "react-router": "2.0.0",
    "react-router-bootstrap": "^0.20.1",
    "react-router-redux": "^4.0.0",
    "redux": "^3.0.4",
    "redux-async-connect": "^1.0.0-rc2",
    "redux-form": "^3.0.12",
    "redux-thunk": "^2.1.0",
    "scroll-behavior": "^0.3.2",
    "serialize-javascript": "^1.1.2",
    "serve-favicon": "^2.3.0",
    "socket.io": "^1.3.7",
    "socket.io-client": "^1.3.7",
    "superagent": "^1.4.0",
    "url-loader": "^0.5.7",
    "violet-paginator": "^1.8.1",
    "warning": "^2.1.0",
    "webpack-isomorphic-tools": "^2.2.18"
  },
  "devDependencies": {
    "autoprefixer-loader": "^3.1.0",
    "babel-eslint": "^5.0.0-beta6",
    "babel-plugin-react-transform": "^2.0.0",
    "babel-plugin-typecheck": "^3.6.0",
    "better-npm-run": "0.0.8",
    "bootstrap-sass": "^3.3.5",
    "bootstrap-sass-loader": "^1.0.9",
    "chai": "^3.3.0",
    "clean-webpack-plugin": "^0.1.6",
    "concurrently": "^0.1.1",
    "css-loader": "^0.23.1",
    "eslint": "1.10.3",
    "eslint-config-airbnb": "0.1.0",
    "eslint-loader": "^1.0.0",
    "eslint-plugin-import": "^0.8.0",
    "eslint-plugin-react": "^3.5.0",
    "extract-text-webpack-plugin": "^0.9.1",
    "font-awesome": "^4.4.0",
    "font-awesome-webpack": "0.0.4",
    "json-loader": "^0.5.4",
    "karma": "^0.13.10",
    "karma-cli": "^0.1.1",
    "karma-mocha": "^0.2.0",
    "karma-mocha-reporter": "^1.1.1",
    "karma-phantomjs-launcher": "^0.2.1",
    "karma-sourcemap-loader": "^0.3.5",
    "karma-webpack": "^1.7.0",
    "mocha": "^2.3.3",
    "node-sass": "^3.4.2",
    "phantomjs": "^1.9.18",
    "phantomjs-polyfill": "0.0.1",
    "react-a11y": "^0.2.6",
    "react-addons-test-utils": "0.14.8",
    "react-transform-catch-errors": "^1.0.0",
    "react-transform-hmr": "^1.0.1",
    "redbox-react": "^1.1.1",
    "redux-devtools": "^3.0.0-beta-3",
    "redux-devtools-dock-monitor": "^1.0.0-beta-3",
    "redux-devtools-log-monitor": "^1.0.0-beta-3",
    "sass-loader": "^3.1.2",
    "sinon": "^1.17.2",
    "strip-loader": "^0.1.0",
    "style-loader": "^0.13.0",
    "timekeeper": "0.0.5",
    "webpack": "^1.12.9",
    "webpack-dev-middleware": "^1.4.0",
    "webpack-hot-middleware": "^2.5.0"
  },
  "engines": {
    "node": "5.6.0"
  }
}


================================================
FILE: server.babel.js
================================================
//  enable runtime transpilation to use ES6/7 in node

var fs = require('fs');

var babelrc = fs.readFileSync('./.babelrc');
var config;

try {
  config = JSON.parse(babelrc);
} catch (err) {
  console.error('==>     ERROR: Error parsing your .babelrc.');
  console.error(err);
}

require('babel-register')(config);


================================================
FILE: src/client.js
================================================
/**
 * THIS IS THE ENTRY POINT FOR THE CLIENT, JUST LIKE server.js IS THE ENTRY POINT FOR THE SERVER.
 */
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import createStore from './redux/create';
import ApiClient from './helpers/ApiClient';
import io from 'socket.io-client';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { ReduxAsyncConnect } from 'redux-async-connect';
import useScroll from 'scroll-behavior/lib/useStandardScroll';

import getRoutes from './routes';

const client = new ApiClient();
const _browserHistory = useScroll(() => browserHistory)();
const dest = document.getElementById('content');
const store = createStore(_browserHistory, client, window.__data);
const history = syncHistoryWithStore(_browserHistory, store);

function initSocket() {
  const socket = io('', {path: '/ws'});
  socket.on('news', (data) => {
    console.log(data);
    socket.emit('my other event', { my: 'data from client' });
  });
  socket.on('msg', (data) => {
    console.log(data);
  });

  return socket;
}

global.socket = initSocket();

const component = (
  <Router render={(props) =>
        <ReduxAsyncConnect {...props} helpers={{client}} filter={item => !item.deferred} />
      } history={history}>
    {getRoutes(store)}
  </Router>
);

ReactDOM.render(
  <Provider store={store} key="provider">
    {component}
  </Provider>,
  dest
);

if (process.env.NODE_ENV !== 'production') {
  window.React = React; // enable debugger

  if (!dest || !dest.firstChild || !dest.firstChild.attributes || !dest.firstChild.attributes['data-react-checksum']) {
    console.error('Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.');
  }
}

if (__DEVTOOLS__ && !window.devToolsExtension) {
  const DevTools = require('./containers/DevTools/DevTools');
  ReactDOM.render(
    <Provider store={store} key="provider">
      <div>
        {component}
        <DevTools />
      </div>
    </Provider>,
    dest
  );
}


================================================
FILE: src/components/CounterButton/CounterButton.js
================================================
import React, {Component, PropTypes} from 'react';
import {connectMultireducer} from 'multireducer';
import {increment} from 'redux/modules/counter';

@connectMultireducer(
  (key, state) => ({count: state.multireducer[key].count}),
  {increment}
)
export default class CounterButton extends Component {
  static propTypes = {
    count: PropTypes.number,
    increment: PropTypes.func.isRequired,
    className: PropTypes.string
  }

  props = {
    className: ''
  }

  render() {
    const {count, increment} = this.props; // eslint-disable-line no-shadow
    let {className} = this.props;
    className += ' btn btn-default';
    return (
      <button className={className} onClick={increment}>
        You have clicked me {count} time{count === 1 ? '' : 's'}.
      </button>
    );
  }
}



================================================
FILE: src/components/GithubButton/GithubButton.js
================================================
import React from 'react';

const GithubButton = (props) => {
  const {user, repo, type, width, height, count, large} = props;
  let src = `https://ghbtns.com/github-btn.html?user=${user}&repo=${repo}&type=${type}`;
  if (count) src += '&count=true';
  if (large) src += '&size=large';

  return (
    <iframe
      src={src}
      frameBorder="0"
      allowTransparency="true"
      scrolling="0"
      width={width}
      height={height}
      style={{border: 'none', width: width, height: height}}></iframe>
  );
};

GithubButton.propTypes = {
  user: React.PropTypes.string.isRequired,
  repo: React.PropTypes.string.isRequired,
  type: React.PropTypes.oneOf(['star', 'watch', 'fork', 'follow']).isRequired,
  width: React.PropTypes.number.isRequired,
  height: React.PropTypes.number.isRequired,
  count: React.PropTypes.bool,
  large: React.PropTypes.bool
};

export default GithubButton;


================================================
FILE: src/components/InfoBar/InfoBar.js
================================================
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {load} from 'redux/modules/info';

@connect(
    state => ({info: state.info.data}),
    dispatch => bindActionCreators({load}, dispatch))
export default class InfoBar extends Component {
  static propTypes = {
    info: PropTypes.object,
    load: PropTypes.func.isRequired
  }

  render() {
    const {info, load} = this.props; // eslint-disable-line no-shadow
    const styles = require('./InfoBar.scss');
    return (
      <div className={styles.infoBar + ' well'}>
        <div className="container">
          This is an info bar
          {' '}
          <strong>{info ? info.message : 'no info!'}</strong>
          <span className={styles.time}>{info && new Date(info.time).toString()}</span>
          <button className="btn btn-primary" onClick={load}>Reload from server</button>
        </div>
      </div>
    );
  }
}


================================================
FILE: src/components/InfoBar/InfoBar.scss
================================================
.infoBar {
  font-variant: italics;
}

.time {
  margin: 0 30px;
}


================================================
FILE: src/components/MiniInfoBar/MiniInfoBar.js
================================================
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';

@connect(state => ({ time: state.info.data.time }))
export default class MiniInfoBar extends Component {
  static propTypes = {
    time: PropTypes.number
  }

  render() {
    const {time} = this.props;
    return (
      <div className="mini-info-bar">
        The info bar was last loaded at
        {' '}
        <span>{time && new Date(time).toString()}</span>
      </div>
    );
  }
}


================================================
FILE: src/components/SurveyForm/SurveyForm.js
================================================
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import surveyValidation from './surveyValidation';
import * as surveyActions from 'redux/modules/survey';

function asyncValidate(data, dispatch, {isValidEmail}) {
  if (!data.email) {
    return Promise.resolve({});
  }
  return isValidEmail(data);
}
@connect(() => ({}),
  dispatch => bindActionCreators(surveyActions, dispatch)
)
@reduxForm({
  form: 'survey',
  fields: ['name', 'email', 'occupation', 'currentlyEmployed', 'sex'],
  validate: surveyValidation,
  asyncValidate,
  asyncBlurFields: ['email']
})
export default
class SurveyForm extends Component {
  static propTypes = {
    active: PropTypes.string,
    asyncValidating: PropTypes.bool.isRequired,
    fields: PropTypes.object.isRequired,
    dirty: PropTypes.bool.isRequired,
    handleSubmit: PropTypes.func.isRequired,
    resetForm: PropTypes.func.isRequired,
    invalid: PropTypes.bool.isRequired,
    pristine: PropTypes.bool.isRequired,
    valid: PropTypes.bool.isRequired
  }

  render() {
    const {
      asyncValidating,
      dirty,
      fields: {name, email, occupation, currentlyEmployed, sex},
      active,
      handleSubmit,
      invalid,
      resetForm,
      pristine,
      valid
      } = this.props;
    const styles = require('./SurveyForm.scss');
    const renderInput = (field, label, showAsyncValidating) =>
      <div className={'form-group' + (field.error && field.touched ? ' has-error' : '')}>
        <label htmlFor={field.name} className="col-sm-2">{label}</label>
        <div className={'col-sm-8 ' + styles.inputGroup}>
          {showAsyncValidating && asyncValidating && <i className={'fa fa-cog fa-spin ' + styles.cog}/>}
          <input type="text" className="form-control" id={field.name} {...field}/>
          {field.error && field.touched && <div className="text-danger">{field.error}</div>}
          <div className={styles.flags}>
            {field.dirty && <span className={styles.dirty} title="Dirty">D</span>}
            {field.active && <span className={styles.active} title="Active">A</span>}
            {field.visited && <span className={styles.visited} title="Visited">V</span>}
            {field.touched && <span className={styles.touched} title="Touched">T</span>}
          </div>
        </div>
      </div>;

    return (
      <div>
        <form className="form-horizontal" onSubmit={handleSubmit}>
          {renderInput(name, 'Full Name')}
          {renderInput(email, 'Email', true)}
          {renderInput(occupation, 'Occupation')}
          <div className="form-group">
            <label htmlFor="currentlyEmployed" className="col-sm-2">Currently Employed?</label>
            <div className="col-sm-8">
              <input type="checkbox" id="currentlyEmployed" {...currentlyEmployed}/>
            </div>
          </div>
          <div className="form-group">
            <label className="col-sm-2">Sex</label>
            <div className="col-sm-8">
              <input type="radio" id="sex-male" {...sex} value="male" checked={sex.value === 'male'}/>
              <label htmlFor="sex-male" className={styles.radioLabel}>Male</label>
              <input type="radio" id="sex-female" {...sex} value="female" checked={sex.value === 'female'}/>
              <label htmlFor="sex-female" className={styles.radioLabel}>Female</label>
            </div>
          </div>
          <div className="form-group">
            <div className="col-sm-offset-2 col-sm-10">
              <button className="btn btn-success" onClick={handleSubmit}>
                <i className="fa fa-paper-plane"/> Submit
              </button>
              <button className="btn btn-warning" onClick={resetForm} style={{marginLeft: 15}}>
                <i className="fa fa-undo"/> Reset
              </button>
            </div>
          </div>
        </form>

        <h4>Props from redux-form</h4>

        <table className="table table-striped">
          <tbody>
          <tr>
            <th>Active Field</th>
            <td>{active}</td>
          </tr>
          <tr>
            <th>Dirty</th>
            <td className={dirty ? 'success' : 'danger'}>{dirty ? 'true' : 'false'}</td>
          </tr>
          <tr>
            <th>Pristine</th>
            <td className={pristine ? 'success' : 'danger'}>{pristine ? 'true' : 'false'}</td>
          </tr>
          <tr>
            <th>Valid</th>
            <td className={valid ? 'success' : 'danger'}>{valid ? 'true' : 'false'}</td>
          </tr>
          <tr>
            <th>Invalid</th>
            <td className={invalid ? 'success' : 'danger'}>{invalid ? 'true' : 'false'}</td>
          </tr>
          </tbody>
        </table>
      </div>
    );
  }
}


================================================
FILE: src/components/SurveyForm/SurveyForm.scss
================================================
.inputGroup {
  position: relative;
}

.flags {
  position: absolute;
  right: 20px;
  top: 7px;
  & > * {
    margin: 0 2px;
    width: 20px;
    height: 20px;
    border-radius: 20px;
    box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.4);
    color: white;
    float: right;
    text-align: center;
  }
  .active {
    background: linear-gradient(#cc0, #aa0);
    color: black;
  }
  .dirty {
    background: linear-gradient(#090, #060);
  }
  .visited {
    background: linear-gradient(#009, #006);
  }
  .touched {
    background: linear-gradient(#099, #066);
  }
}

.radioLabel {
  margin: 0 25px 0 5px;
}
.cog {
  position: absolute;
  left: 0;
  top: 10px;
}


================================================
FILE: src/components/SurveyForm/surveyValidation.js
================================================
import memoize from 'lru-memoize';
import {createValidator, required, maxLength, email} from 'utils/validation';

const surveyValidation = createValidator({
  name: [required, maxLength(10)],
  email: [required, email],
  occupation: maxLength(20) // single rules don't have to be in an array
});
export default memoize(10)(surveyValidation);


================================================
FILE: src/components/WidgetForm/WidgetForm.js
================================================
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {reduxForm} from 'redux-form';
import widgetValidation, {colors} from './widgetValidation';
import * as widgetActions from 'redux/modules/widgets';

@connect(
  state => ({
    saveError: state.widgets.saveError
  }),
  dispatch => bindActionCreators(widgetActions, dispatch)
)
@reduxForm({
  form: 'widget',
  fields: ['id', 'color', 'sprocketCount', 'owner'],
  validate: widgetValidation
})
export default class WidgetForm extends Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    editStop: PropTypes.func.isRequired,
    handleSubmit: PropTypes.func.isRequired,
    invalid: PropTypes.bool.isRequired,
    pristine: PropTypes.bool.isRequired,
    save: PropTypes.func.isRequired,
    submitting: PropTypes.bool.isRequired,
    saveError: PropTypes.object,
    formKey: PropTypes.string.isRequired,
    values: PropTypes.object.isRequired
  };

  render() {
    const { editStop, fields: {id, color, sprocketCount, owner}, formKey, handleSubmit, invalid,
      pristine, save, submitting, saveError: { [formKey]: saveError }, values } = this.props;
    const styles = require('containers/Widgets/Widgets.scss');
    return (
      <tr className={submitting ? styles.saving : ''}>
        <td className={styles.idCol}>{id.value}</td>
        <td className={styles.colorCol}>
          <select name="color" className="form-control" {...color}>
            {colors.map(valueColor => <option value={valueColor} key={valueColor}>{valueColor}</option>)}
          </select>
          {color.error && color.touched && <div className="text-danger">{color.error}</div>}
        </td>
        <td className={styles.sprocketsCol}>
          <input type="text" className="form-control" {...sprocketCount}/>
          {sprocketCount.error && sprocketCount.touched && <div className="text-danger">{sprocketCount.error}</div>}
        </td>
        <td className={styles.ownerCol}>
          <input type="text" className="form-control" {...owner}/>
          {owner.error && owner.touched && <div className="text-danger">{owner.error}</div>}
        </td>
        <td className={styles.buttonCol}>
          <button className="btn btn-default"
                  onClick={() => editStop(formKey)}
                  disabled={submitting}>
            <i className="fa fa-ban"/> Cancel
          </button>
          <button className="btn btn-success"
                  onClick={handleSubmit(() => save(values)
                    .then(result => {
                      if (result && typeof result.error === 'object') {
                        return Promise.reject(result.error);
                      }
                    })
                  )}
                  disabled={pristine || invalid || submitting}>
            <i className={'fa ' + (submitting ? 'fa-cog fa-spin' : 'fa-cloud')}/> Save
          </button>
          {saveError && <div className="text-danger">{saveError}</div>}
        </td>
      </tr>
    );
  }
}


================================================
FILE: src/components/WidgetForm/widgetValidation.js
================================================
import {createValidator, required, maxLength, integer, oneOf} from 'utils/validation';

export const colors = ['Blue', 'Fuchsia', 'Green', 'Orange', 'Red', 'Taupe'];

const widgetValidation = createValidator({
  color: [required, oneOf(colors)],
  sprocketCount: [required, integer],
  owner: [required, maxLength(30)]
});
export default widgetValidation;


================================================
FILE: src/components/__tests__/InfoBar-test.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import {renderIntoDocument} from 'react-addons-test-utils';
import { expect} from 'chai';
import { InfoBar } from 'components';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import createStore from 'redux/create';
import ApiClient from 'helpers/ApiClient';
const client = new ApiClient();

describe('InfoBar', () => {
  const mockStore = {
    info: {
      load: () => {},
      loaded: true,
      loading: false,
      data: {
        message: 'This came from the api server',
        time: Date.now()
      }
    }
  };
  const store = createStore(browserHistory, client, mockStore);
  const renderer = renderIntoDocument(
    <Provider store={store} key="provider">
      <InfoBar/>
    </Provider>
  );
  const dom = ReactDOM.findDOMNode(renderer);

  it('should render correctly', () => {
    return expect(renderer).to.be.ok;
  });

  it('should render with correct value', () => {
    const text = dom.getElementsByTagName('strong')[0].textContent;
    expect(text).to.equal(mockStore.info.data.message);
  });

  it('should render with a reload button', () => {
    const text = dom.getElementsByTagName('button')[0].textContent;
    expect(text).to.be.a('string');
  });

  it('should render the correct className', () => {
    const styles = require('components/InfoBar/InfoBar.scss');
    expect(styles.infoBar).to.be.a('string');
    expect(dom.className).to.include(styles.infoBar);
  });
});


================================================
FILE: src/components/index.js
================================================
/**
 *  Point of contact for component modules
 *
 *  ie: import { CounterButton, InfoBar } from 'components';
 *
 */

export CounterButton from './CounterButton/CounterButton';
export GithubButton from './GithubButton/GithubButton';
export InfoBar from './InfoBar/InfoBar';
export MiniInfoBar from './MiniInfoBar/MiniInfoBar';
export SurveyForm from './SurveyForm/SurveyForm';
export WidgetForm from './WidgetForm/WidgetForm';


================================================
FILE: src/config.js
================================================
require('babel-polyfill');

const environment = {
  development: {
    isProduction: false
  },
  production: {
    isProduction: true
  }
}[process.env.NODE_ENV || 'development'];

module.exports = Object.assign({
  host: process.env.HOST || 'localhost',
  port: process.env.PORT,
  apiHost: process.env.APIHOST || 'localhost',
  apiPort: process.env.APIPORT,
  app: {
    title: 'React Redux Example',
    description: 'All the modern best practices in one example.',
    head: {
      titleTemplate: 'React Redux Example: %s',
      meta: [
        {name: 'description', content: 'All the modern best practices in one example.'},
        {charset: 'utf-8'},
        {property: 'og:site_name', content: 'React Redux Example'},
        {property: 'og:image', content: 'https://react-redux.herokuapp.com/logo.jpg'},
        {property: 'og:locale', content: 'en_US'},
        {property: 'og:title', content: 'React Redux Example'},
        {property: 'og:description', content: 'All the modern best practices in one example.'},
        {property: 'og:card', content: 'summary'},
        {property: 'og:site', content: '@erikras'},
        {property: 'og:creator', content: '@erikras'},
        {property: 'og:image:width', content: '200'},
        {property: 'og:image:height', content: '200'}
      ]
    }
  },

}, environment);


================================================
FILE: src/containers/About/About.js
================================================
import React, {Component} from 'react';
import Helmet from 'react-helmet';
import { MiniInfoBar } from 'components';

export default class About extends Component {

  state = {
    showKitten: false
  }

  handleToggleKitten = () => this.setState({showKitten: !this.state.showKitten});

  render() {
    const {showKitten} = this.state;
    const kitten = require('./kitten.jpg');
    return (
      <div className="container">
        <h1>About Us</h1>
        <Helmet title="About Us"/>

        <p>This project was originally created by Erik Rasmussen
          (<a href="https://twitter.com/erikras" target="_blank">@erikras</a>), but has since seen many contributions
          from the open source community. Thank you to <a
            href="https://github.com/erikras/react-redux-universal-hot-example/graphs/contributors"
            target="_blank">all the contributors</a>.
        </p>

        <h3>Mini Bar <span style={{color: '#aaa'}}>(not that kind)</span></h3>

        <p>Hey! You found the mini info bar! The following component is display-only. Note that it shows the same
          time as the info bar.</p>

        <MiniInfoBar/>

        <h3>Images</h3>

        <p>
          Psst! Would you like to see a kitten?

          <button className={'btn btn-' + (showKitten ? 'danger' : 'success')}
                  style={{marginLeft: 50}}
                  onClick={this.handleToggleKitten}>
            {showKitten ? 'No! Take it away!' : 'Yes! Please!'}</button>
        </p>

        {showKitten && <div><img src={kitten}/></div>}
      </div>
    );
  }
}


================================================
FILE: src/containers/App/App.js
================================================
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { IndexLink } from 'react-router';
import { LinkContainer } from 'react-router-bootstrap';
import Navbar from 'react-bootstrap/lib/Navbar';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import Helmet from 'react-helmet';
import { isLoaded as isInfoLoaded, load as loadInfo } from 'redux/modules/info';
import { isLoaded as isAuthLoaded, load as loadAuth, logout } from 'redux/modules/auth';
import { InfoBar } from 'components';
import { push } from 'react-router-redux';
import config from '../../config';
import { asyncConnect } from 'redux-async-connect';

@asyncConnect([{
  promise: ({store: {dispatch, getState}}) => {
    const promises = [];

    if (!isInfoLoaded(getState())) {
      promises.push(dispatch(loadInfo()));
    }
    if (!isAuthLoaded(getState())) {
      promises.push(dispatch(loadAuth()));
    }

    return Promise.all(promises);
  }
}])
@connect(
  state => ({user: state.auth.user}),
  {logout, pushState: push})
export default class App extends Component {
  static propTypes = {
    children: PropTypes.object.isRequired,
    user: PropTypes.object,
    logout: PropTypes.func.isRequired,
    pushState: PropTypes.func.isRequired
  };

  static contextTypes = {
    store: PropTypes.object.isRequired
  };

  componentWillReceiveProps(nextProps) {
    if (!this.props.user && nextProps.user) {
      // login
      this.props.pushState('/loginSuccess');
    } else if (this.props.user && !nextProps.user) {
      // logout
      this.props.pushState('/');
    }
  }

  handleLogout = (event) => {
    event.preventDefault();
    this.props.logout();
  };

  render() {
    const {user} = this.props;
    const styles = require('./App.scss');

    return (
      <div className={styles.app}>
        <Helmet {...config.app.head}/>
        <Navbar fixedTop>
          <Navbar.Header>
            <Navbar.Brand>
              <IndexLink to="/" activeStyle={{color: '#33e0ff'}}>
                <div className={styles.brand}/>
                <span>{config.app.title}</span>
              </IndexLink>
            </Navbar.Brand>
            <Navbar.Toggle/>
          </Navbar.Header>

          <Navbar.Collapse eventKey={0}>
            <Nav navbar>
              {user && <LinkContainer to="/chat">
                <NavItem eventKey={1}>Chat</NavItem>
              </LinkContainer>}

              <LinkContainer to="/widgets">
                <NavItem eventKey={2}>Widgets</NavItem>
              </LinkContainer>
              <LinkContainer to="/survey">
                <NavItem eventKey={3}>Survey</NavItem>
              </LinkContainer>
              <LinkContainer to="/pagination">
                <NavItem eventKey={4}>Pagination</NavItem>
              </LinkContainer>
              <LinkContainer to="/about">
                <NavItem eventKey={5}>About Us</NavItem>
              </LinkContainer>

              {!user &&
              <LinkContainer to="/login">
                <NavItem eventKey={6}>Login</NavItem>
              </LinkContainer>}
              {user &&
              <LinkContainer to="/logout">
                <NavItem eventKey={7} className="logout-link" onClick={this.handleLogout}>
                  Logout
                </NavItem>
              </LinkContainer>}
            </Nav>
            {user &&
            <p className={styles.loggedInMessage + ' navbar-text'}>Logged in as <strong>{user.name}</strong>.</p>}
            <Nav navbar pullRight>
              <NavItem eventKey={1} target="_blank" title="View on Github" href="https://github.com/erikras/react-redux-universal-hot-example">
                <i className="fa fa-github"/>
              </NavItem>
            </Nav>
          </Navbar.Collapse>
        </Navbar>

        <div className={styles.appContent}>
          {this.props.children}
        </div>
        <InfoBar/>

        <div className="well text-center">
          Have questions? Ask for help <a
          href="https://github.com/erikras/react-redux-universal-hot-example/issues"
          target="_blank">on Github</a> or in the <a
          href="https://discord.gg/0ZcbPKXt5bZZb1Ko" target="_blank">#react-redux-universal</a> Discord channel.
        </div>
      </div>
    );
  }
}


================================================
FILE: src/containers/App/App.scss
================================================
.app {
  .brand {
    position: absolute;
    $size: 40px;
    top: 5px;
    left: 5px;
    display: inline-block;
    background: #2d2d2d url('../Home/logo.png') no-repeat center center;
    width: $size;
    height: $size;
    background-size: 80%;
    margin: 0 10px 0 0;
    border-radius: $size / 2;
  }
  nav :global(.fa) {
    font-size: 2em;
    line-height: 20px;
  }
}
.appContent {
  margin: 50px 0; // for fixed navbar
}


================================================
FILE: src/containers/Chat/Chat.js
================================================
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';

@connect(
  state => ({user: state.auth.user})
)
export default class Chat extends Component {

  static propTypes = {
    user: PropTypes.object
  };

  state = {
    message: '',
    messages: []
  };

  componentDidMount() {
    if (socket) {
      socket.on('msg', this.onMessageReceived);
      setTimeout(() => {
        socket.emit('history', {offset: 0, length: 100});
      }, 100);
    }
  }

  componentWillUnmount() {
    if (socket) {
      socket.removeListener('msg', this.onMessageReceived);
    }
  }

  onMessageReceived = (data) => {
    const messages = this.state.messages;
    messages.push(data);
    this.setState({messages});
  }

  handleSubmit = (event) => {
    event.preventDefault();

    const msg = this.state.message;

    this.setState({message: ''});

    socket.emit('msg', {
      from: this.props.user.name,
      text: msg
    });
  }

  render() {
    const style = require('./Chat.scss');
    const {user} = this.props;

    return (
      <div className={style.chat + ' container'}>
        <h1 className={style}>Chat</h1>

        {user &&
        <div>
          <ul>
          {this.state.messages.map((msg) => {
            return <li key={`chat.msg.${msg.id}`}>{msg.from}: {msg.text}</li>;
          })}
          </ul>
          <form className="login-form" onSubmit={this.handleSubmit}>
            <input type="text" ref="message" placeholder="Enter your message"
             value={this.state.message}
             onChange={(event) => {
               this.setState({message: event.target.value});
             }
            }/>
            <button className="btn" onClick={this.handleSubmit}>Send</button>
          </form>
        </div>
        }
      </div>
    );
  }
}


================================================
FILE: src/containers/Chat/Chat.scss
================================================
.chat {
  input {
    padding: 5px 10px;
    border-radius: 5px;
    border: 1px solid #ccc;
  }
  form {
    margin: 30px 0;
    :global(.btn) {
      margin-left: 10px;
    }
  }
}

================================================
FILE: src/containers/DevTools/DevTools.js
================================================
import React from 'react';
import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';

export default createDevTools(
  <DockMonitor toggleVisibilityKey="ctrl-H"
               changePositionKey="ctrl-Q">
    <LogMonitor />
  </DockMonitor>
);


================================================
FILE: src/containers/Home/Home.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import { CounterButton, GithubButton } from 'components';
import config from '../../config';
import Helmet from 'react-helmet';

export default class Home extends Component {
  render() {
    const styles = require('./Home.scss');
    // require the logo image both from client and server
    const logoImage = require('./logo.png');
    return (
      <div className={styles.home}>
        <Helmet title="Home"/>
        <div className={styles.masthead}>
          <div className="container">
            <div className={styles.logo}>
              <p>
                <img src={logoImage}/>
              </p>
            </div>
            <h1>{config.app.title}</h1>

            <h2>{config.app.description}</h2>

            <p>
              <a className={styles.github} href="https://github.com/erikras/react-redux-universal-hot-example"
                 target="_blank">
                <i className="fa fa-github"/> View on Github
              </a>
            </p>
            <GithubButton user="erikras"
                          repo="react-redux-universal-hot-example"
                          type="star"
                          width={160}
                          height={30}
                          count large/>
            <GithubButton user="erikras"
                          repo="react-redux-universal-hot-example"
                          type="fork"
                          width={160}
                          height={30}
                          count large/>

            <p className={styles.humility}>
              Created and maintained by <a href="https://twitter.com/erikras" target="_blank">@erikras</a>.
            </p>
          </div>
        </div>

        <div className="container">
          <div className={styles.counterContainer}>
            <CounterButton multireducerKey="counter1"/>
            <CounterButton multireducerKey="counter2"/>
            <CounterButton multireducerKey="counter3"/>
          </div>

          <p>This starter boilerplate app uses the following technologies:</p>

          <ul>
            <li>
              <del>Isomorphic</del>
              {' '}
              <a href="https://medium.com/@mjackson/universal-javascript-4761051b7ae9">Universal</a> rendering
            </li>
            <li>Both client and server make calls to load data from separate API server</li>
            <li><a href="https://github.com/facebook/react" target="_blank">React</a></li>
            <li><a href="https://github.com/rackt/react-router" target="_blank">React Router</a></li>
            <li><a href="http://expressjs.com" target="_blank">Express</a></li>
            <li><a href="http://babeljs.io" target="_blank">Babel</a> for ES6 and ES7 magic</li>
            <li><a href="http://webpack.github.io" target="_blank">Webpack</a> for bundling</li>
            <li><a href="http://webpack.github.io/docs/webpack-dev-middleware.html" target="_blank">Webpack Dev Middleware</a>
            </li>
            <li><a href="https://github.com/glenjamin/webpack-hot-middleware" target="_blank">Webpack Hot Middleware</a></li>
            <li><a href="https://github.com/rackt/redux" target="_blank">Redux</a>'s futuristic <a
              href="https://facebook.github.io/react/blog/2014/05/06/flux.html" target="_blank">Flux</a> implementation
            </li>
            <li><a href="https://github.com/gaearon/redux-devtools" target="_blank">Redux Dev Tools</a> for next
              generation DX (developer experience).
              Watch <a href="https://www.youtube.com/watch?v=xsSnOQynTHs" target="_blank">Dan Abramov's talk</a>.
            </li>
            <li><a href="https://github.com/rackt/redux-router" target="_blank">Redux Router</a> Keep
              your router state in your Redux store
            </li>
            <li><a href="http://eslint.org" target="_blank">ESLint</a> to maintain a consistent code style</li>
            <li><a href="https://github.com/erikras/redux-form" target="_blank">redux-form</a> to manage form state
              in Redux
            </li>
            <li><a href="https://github.com/sslotsky/violet-paginator" target="_blank">violet-paginator</a> to manage list state
              in Redux, including pagination, sorting, filtering, updating, and more.
            </li>
            <li><a href="https://github.com/erikras/multireducer" target="_blank">multireducer</a> combine several
              identical reducer states into one key-based reducer</li>
            <li><a href="https://github.com/webpack/style-loader" target="_blank">style-loader</a> and <a
              href="https://github.com/jtangelder/sass-loader" target="_blank">sass-loader</a> to allow import of
              stylesheets
            </li>
            <li><a href="https://github.com/shakacode/bootstrap-sass-loader" target="_blank">bootstrap-sass-loader</a> and <a
              href="https://github.com/gowravshekar/font-awesome-webpack" target="_blank">font-awesome-webpack</a> to customize Bootstrap and FontAwesome
            </li>
            <li><a href="http://socket.io/">socket.io</a> for real-time communication</li>
          </ul>

          <h3>Features demonstrated in this project</h3>

          <dl>
            <dt>Multiple components subscribing to same redux store slice</dt>
            <dd>
              The <code>App.js</code> that wraps all the pages contains an <code>InfoBar</code> component
              that fetches data from the server initially, but allows for the user to refresh the data from
              the client. <code>About.js</code> contains a <code>MiniInfoBar</code> that displays the same
              data.
            </dd>
            <dt>Server-side data loading</dt>
            <dd>
              The <Link to="/widgets">Widgets page</Link> demonstrates how to fetch data asynchronously from
              some source that is needed to complete the server-side rendering. <code>Widgets.js</code>'s
              <code>asyncConnect()</code> function is called before the widgets page is loaded, on either the server
              or the client, allowing all the widget data to be loaded and ready for the page to render.
            </dd>
            <dt>Data loading errors</dt>
            <dd>
              The <Link to="/widgets">Widgets page</Link> also demonstrates how to deal with data loading
              errors in Redux. The API endpoint that delivers the widget data intentionally fails 33% of
              the time to highlight this. The <code>clientMiddleware</code> sends an error action which
              the <code>widgets</code> reducer picks up and saves to the Redux state for presenting to the user.
            </dd>
            <dt>Session based login</dt>
            <dd>
              On the <Link to="/login">Login page</Link> you can submit a username which will be sent to the server
              and stored in the session. Subsequent refreshes will show that you are still logged in.
            </dd>
            <dt>Redirect after state change</dt>
            <dd>
              After you log in, you will be redirected to a Login Success page. This <strike>magic</strike> logic
              is performed in <code>componentWillReceiveProps()</code> in <code>App.js</code>, but it could
              be done in any component that listens to the appropriate store slice, via Redux's <code>@connect</code>,
              and pulls the router from the context.
            </dd>
            <dt>Auth-required views</dt>
            <dd>
              The aforementioned Login Success page is only visible to you if you are logged in. If you try
              to <Link to="/loginSuccess">go there</Link> when you are not logged in, you will be forwarded back
              to this home page. This <strike>magic</strike> logic is performed by the
              <code>onEnter</code> hook within <code>routes.js</code>.
            </dd>
            <dt>Forms</dt>
            <dd>
              The <Link to="/survey">Survey page</Link> uses the
              still-experimental <a href="https://github.com/erikras/redux-form" target="_blank">redux-form</a> to
              manage form state inside the Redux store. This includes immediate client-side validation.
            </dd>
            <dt>Pagination</dt>
            <dd>
              The <Link to="/pagination">Pagination page</Link> uses
              <a href="https://www.npmjs.com/package/violet-paginator" target="_blank">violet-paginator</a> to
              paginate and sort records in a data table.
            </dd>
            <dt>WebSockets / socket.io</dt>
            <dd>
              The <Link to="/chat">Chat</Link> uses the socket.io technology for real-time
              communication between clients. You need to <Link to="/login">login</Link> first.
            </dd>
          </dl>

          <h3>From the author</h3>

          <p>
            I cobbled this together from a wide variety of similar "starter" repositories. As I post this in June 2015,
            all of these libraries are right at the bleeding edge of web development. They may fall out of fashion as
            quickly as they have come into it, but I personally believe that this stack is the future of web development
            and will survive for several years. I'm building my new projects like this, and I recommend that you do,
            too.
          </p>

          <p>Thanks for taking the time to check this out.</p>

          <p>– Erik Rasmussen</p>
        </div>
      </div>
    );
  }
}


================================================
FILE: src/containers/Home/Home.scss
================================================
@import "../../theme/variables.scss";

.home {
  dd {
    margin-bottom: 15px;
  }
}
.masthead {
  background: #2d2d2d;
  padding: 40px 20px;
  color: white;
  text-align: center;
  .logo {
    $size: 200px;
    margin: auto;
    height: $size;
    width: $size;
    border-radius: $size / 2;
    border: 1px solid $cyan;
    box-shadow: inset 0 0 10px $cyan;
    vertical-align: middle;
    p {
      line-height: $size;
      margin: 0px;
    }
    img {
      width: 75%;
      margin: auto;
    }
  }
  h1 {
    color: $cyan;
    font-size: 4em;
  }
  h2 {
    color: #ddd;
    font-size: 2em;
    margin: 20px;
  }
  a {
    color: #ddd;
  }
  p {
    margin: 10px;
  }
  .humility {
    color: $humility;
    a {
      color: $humility;
    }
  }
  .github {
    font-size: 1.5em;
  }
}

.counterContainer {
  text-align: center;
  margin: 20px;
}


================================================
FILE: src/containers/Login/Login.js
================================================
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import Helmet from 'react-helmet';
import * as authActions from 'redux/modules/auth';

@connect(
  state => ({user: state.auth.user}),
  authActions)
export default class Login extends Component {
  static propTypes = {
    user: PropTypes.object,
    login: PropTypes.func,
    logout: PropTypes.func
  }

  handleSubmit = (event) => {
    event.preventDefault();
    const input = this.refs.username;
    this.props.login(input.value);
    input.value = '';
  }

  render() {
    const {user, logout} = this.props;
    const styles = require('./Login.scss');
    return (
      <div className={styles.loginPage + ' container'}>
        <Helmet title="Login"/>
        <h1>Login</h1>
        {!user &&
        <div>
          <form className="login-form form-inline" onSubmit={this.handleSubmit}>
            <div className="form-group">
              <input type="text" ref="username" placeholder="Enter a username" className="form-control"/>
            </div>
            <button className="btn btn-success" onClick={this.handleSubmit}><i className="fa fa-sign-in"/>{' '}Log In
            </button>
          </form>
          <p>This will "log you in" as this user, storing the username in the session of the API server.</p>
        </div>
        }
        {user &&
        <div>
          <p>You are currently logged in as {user.name}.</p>

          <div>
            <button className="btn btn-danger" onClick={logout}><i className="fa fa-sign-out"/>{' '}Log Out</button>
          </div>
        </div>
        }
      </div>
    );
  }
}


================================================
FILE: src/containers/Login/Login.scss
================================================
.loginPage {
  input {
    padding: 5px 10px;
    border-radius: 5px;
    border: 1px solid #ccc;
  }
  form {
    margin: 30px 0;
    :global(.btn) {
      margin-left: 10px;
    }
  }
}


================================================
FILE: src/containers/LoginSuccess/LoginSuccess.js
================================================
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import * as authActions from 'redux/modules/auth';

@connect(
    state => ({user: state.auth.user}),
    authActions)
export default
class LoginSuccess extends Component {
  static propTypes = {
    user: PropTypes.object,
    logout: PropTypes.func
  }

  render() {
    const {user, logout} = this.props;
    return (user &&
      <div className="container">
        <h1>Login Success</h1>

        <div>
          <p>Hi, {user.name}. You have just successfully logged in, and were forwarded here
            by <code>componentWillReceiveProps()</code> in <code>App.js</code>, which is listening to
            the auth reducer via redux <code>@connect</code>. How exciting!
          </p>

          <p>
            The same function will forward you to <code>/</code> should you chose to log out. The choice is yours...
          </p>

          <div>
            <button className="btn btn-danger" onClick={logout}><i className="fa fa-sign-out"/>{' '}Log Out</button>
          </div>
        </div>
      </div>
    );
  }
}


================================================
FILE: src/containers/NotFound/NotFound.js
================================================
import React from 'react';

export default function NotFound() {
  return (
    <div className="container">
      <h1>Doh! 404!</h1>
      <p>These are <em>not</em> the droids you are looking for!</p>
    </div>
  );
}


================================================
FILE: src/containers/Pagination/Pagination.jsx
================================================
import React from 'react';
import Helmet from 'react-helmet';
import { List } from 'immutable';
import { connect } from 'react-redux';
import { VioletDataTable, VioletPaginator } from 'violet-paginator';

import './violet.min.scss';
import './Pagination.scss';

function paginate(list, page, pageSize) {
  return list.skip((page - 1) * pageSize).take(pageSize);
}

function order(list, sort, sortOrder) {
  if (sort) {
    const sorted = list.sortBy(item => item[sort]);
    if (sortOrder === 'desc') {
      return sorted.reverse();
    }

    return sorted;
  }

  return list;
}

function mockFetch({ query: { pageSize, page, sort, sortOrder } }) {
  const records = List([{
    name: 'Ewe and IPA',
    rank: 75
  }, {
    name: 'Pouty Stout',
    rank: 86
  }, {
    name: 'WPA Evil Angel',
    rank: 63
  }, {
    name: 'Maltster',
    rank: 68
  }, {
    name: 'Beer Mosaic Pale',
    rank: 92
  }, {
    name: 'Honey Porter IDK',
    rank: 93
  }, {
    name: 'Puntification BeerSocialist Brown',
    rank: 88
  }, {
    name: 'HefeLite Dubble All-Grain',
    rank: 55
  }]);

  const filtered = paginate(
    order(records, sort, sortOrder),
    page,
    pageSize
  );

  return () => Promise.resolve({
    data: {
      results: filtered.toJS(),
      total_count: records.count()
    }
  });
}

export function Pagination({ fetch }) {
  const headers = [{
    field: 'name',
    text: 'Name'
  }, {
    field: 'rank',
    text: 'Rank'
  }];

  const config = {
    fetch,
    listId: 'recipes',
    pageSize: 3
  };

  return (
    <section style={{ width: '50%' }}>
      <h1>Pagination</h1>
      <Helmet title="Pagination" />

      <p>
        This is an example of a datatable in redux with sorting and pagination capability
        provided by <a href="https://www.npmjs.com/package/violet-paginator" target="_blank">violet-paginator</a>.
      </p>
      <VioletPaginator {...config} />
      <VioletDataTable
        {...config}
        headers={headers}
      />
    </section>
  );
}

export default connect(
  undefined,
  { fetch: mockFetch }
)(Pagination);


================================================
FILE: src/containers/Pagination/Pagination.scss
================================================
a {
  cursor: pointer;
}


================================================
FILE: src/containers/Pagination/violet.min.scss
================================================
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}
abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible;text-transform:none}
select{text-transform:none}button,html input[type="button"]{-webkit-appearance:button;cursor:pointer}input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input::-moz-focus-inner{border:0;padding:0}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}*{box-sizing:border-box}html{font-family:"Work Sans","Helvetica Neue",Arial,sans-serif;font-size:14px;line-height:19px}
@media(min-width:500px){html{font-size:16px;line-height:22px}}@media(min-width:900px){html{font-size:17px;line-height:24px}}img{margin:1rem 0}h1,h2,h3,h4,h5,h6{font-family:"Work Sans","Helvetica Neue",Arial,sans-serif;font-weight:normal}
h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{border:0}h1{line-height:1.1}p{margin:0}p+p{margin-top:1rem}.align-center{text-align:center}a{color:#333;text-decoration:none;border-bottom:1px solid #555}footer{text-align:center}
footer a{border:0}pre{padding:1rem;font-family:"Source Code Pro","Menlo",monospace;border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #eee;border-right:1px solid #eee;background:#fafafa}
@media(min-width:500px){pre{padding:1.7rem}}@media(min-width:900px){pre{padding:2rem}}.highlight{background-color:#ffa;padding:.1rem}.form-group{padding-bottom:1rem}@media(min-width:767px){.form-group{padding-bottom:1.7rem}
}@media(min-width:900px){.form-group{padding-bottom:2rem}}.form-group.submit{padding-top:.5rem;padding-bottom:0!important}@media(min-width:767px){.form-group.submit{padding-top:.85rem}}@media(min-width:900px){.form-group.submit{padding-top:1rem}
}.form-group.checkbox label{padding-bottom:0!important;line-height:2rem}.form-group.checkbox input[type=checkbox],.form-group.checkbox input[type=radio]{height:2rem;margin-right:.5rem;float:left}label{display:block;padding-bottom:.25rem;font-family:"Work Sans","Helvetica Neue",Arial,sans-serif;color:rgba(0,0,0,0.54);line-height:2rem}
@media(min-width:767px){label{padding-bottom:.425rem}}@media(min-width:900px){label{padding-bottom:.5rem}}input[type=text],input[type=submit],textarea,select{-webkit-appearance:none}input[type=text],input[type=date],input[type=password],input[type=email],input[type=datetime-local],textarea,select{font-family:"Work Sans","Helvetica Neue",Arial,sans-serif;border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #eee;border-right:1px solid #eee;width:100%;padding:8px;font-size:16px;background:#fff;border-radius:0}
input[type=text][disabled],input[type=date][disabled],input[type=password][disabled],input[type=email][disabled],input[type=datetime-local][disabled],textarea[disabled],select[disabled]{background:#fafafa}
@media(min-width:500px){textarea{width:100%;height:10em}}.btn,input[type=submit],button{font-family:"Work Sans","Helvetica Neue",Arial,sans-serif;-webkit-appearance:none;border:0;display:inline-block;padding:.5rem 1rem;text-decoration:none;color:#FFF;text-shadow:-1px -1px 0 rgba(0,0,0,0.2);background:#666;line-height:1.8rem;padding:.5rem 1rem}
.btn i,input[type=submit] i,button i{margin-right:.5rem}@media(min-width:767px){.row{display:flex}}.col-1{flex:1}.col-2{flex:2}.col-3{flex:3}.col-4{flex:4}.col-5{flex:5}.col-6{flex:6}.col-7{flex:7}.col-8{flex:8}
.col-9{flex:9}.col-10{flex:10}body{background:#fdfdfd;padding:1rem}@media(min-width:500px){body{padding:1.7rem}}@media(min-width:900px){body{padding:2rem}}@media(min-width:900px){body{padding:2rem}}@media(min-width:900px) and (min-width:500px){body{padding:3.4rem}
}@media(min-width:900px) and (min-width:900px){body{padding:4rem}}.soft{padding:2rem}@media(min-width:500px){.soft{padding:3.4rem}}@media(min-width:900px){.soft{padding:4rem}}.soft-half{padding:1rem}@media(min-width:500px){.soft-half{padding:1.7rem}
}@media(min-width:900px){.soft-half{padding:2rem}}.soft-quarter{padding:.5rem}@media(min-width:500px){.soft-quarter{padding:.85rem}}@media(min-width:900px){.soft-quarter{padding:1rem}}.soft-sides{padding-left:2rem;padding-right:2rem}
@media(min-width:767px){.soft-sides{padding-left:3.4rem;padding-right:3.4rem}}@media(min-width:900px){.soft-sides{padding-left:4rem;padding-right:4rem}}.soft-half-sides{padding-left:1rem;padding-right:1rem}
@media(min-width:767px){.soft-half-sides{padding-left:1.7rem;padding-right:1.7rem}}@media(min-width:900px){.soft-half-sides{padding-left:2rem;padding-right:2rem}}.soft-quarter-sides{padding-left:.5rem;padding-right:.5rem}
@media(min-width:767px){.soft-quarter-sides{padding-left:.85rem;padding-right:.85rem}}@media(min-width:900px){.soft-quarter-sides{padding-left:1rem;padding-right:1rem}}.soft-ends{padding-top:2rem;padding-bottom:2rem}
@media(min-width:767px){.soft-ends{padding-top:3.4rem;padding-bottom:3.4rem}}@media(min-width:900px){.soft-ends{padding-top:4rem;padding-bottom:4rem}}.soft-half-ends{padding-top:1rem;padding-bottom:1rem}
@media(min-width:767px){.soft-half-ends{padding-top:1.7rem;padding-bottom:1.7rem}}@media(min-width:900px){.soft-half-ends{padding-top:2rem;padding-bottom:2rem}}.soft-quarter-ends{padding-top:.5rem;padding-bottom:.5rem}
@media(min-width:767px){.soft-quarter-ends{padding-top:.85rem;padding-bottom:.85rem}}@media(min-width:900px){.soft-quarter-ends{padding-top:1rem;padding-bottom:1rem}}.soft-top{padding-top:2rem}@media(min-width:767px){.soft-top{padding-top:3.4rem}
}@media(min-width:900px){.soft-top{padding-top:4rem}}.soft-half-top{padding-top:1rem}@media(min-width:767px){.soft-half-top{padding-top:1.7rem}}@media(min-width:900px){.soft-half-top{padding-top:2rem}
}.soft-quarter-top{padding-top:.5rem}@media(min-width:767px){.soft-quarter-top{padding-top:.85rem}}@media(min-width:900px){.soft-quarter-top{padding-top:1rem}}.soft-bottom{padding-bottom:2rem}@media(min-width:767px){.soft-bottom{padding-bottom:3.4rem}
}@media(min-width:900px){.soft-bottom{padding-bottom:4rem}}.soft-half-bottom{padding-bottom:1rem}@media(min-width:767px){.soft-half-bottom{padding-bottom:1.7rem}}@media(min-width:900px){.soft-half-bottom{padding-bottom:2rem}
}.soft-quarter-bottom{padding-bottom:.5rem}@media(min-width:767px){.soft-quarter-bottom{padding-bottom:.85rem}}@media(min-width:900px){.soft-quarter-bottom{padding-bottom:1rem}}.soft-right{padding-right:2rem}
@media(min-width:767px){.soft-right{padding-right:3.4rem}}@media(min-width:900px){.soft-right{padding-right:4rem}}.soft-half-right{padding-right:1rem}@media(min-width:767px){.soft-half-right{padding-right:1.7rem}
}@media(min-width:900px){.soft-half-right{padding-right:2rem}}.soft-quarter-right{padding-right:.5rem}@media(min-width:767px){.soft-quarter-right{padding-right:.85rem}}@media(min-width:900px){.soft-quarter-right{padding-right:1rem}
}.soft-left{padding-left:2rem}@media(min-width:767px){.soft-left{padding-left:3.4rem}}@media(min-width:900px){.soft-left{padding-left:4rem}}.soft-half-left{padding-left:1rem}@media(min-width:767px){.soft-half-left{padding-left:1.7rem}
}@media(min-width:900px){.soft-half-left{padding-left:2rem}}.soft-quarter-left{padding-left:.5rem}@media(min-width:767px){.soft-quarter-left{padding-left:.85rem}}@media(min-width:900px){.soft-quarter-left{padding-left:1rem}
}.hard{padding:0!important}.hard-ends{padding-top:0!important;padding-bottom:0!important}.hard-sides{padding-left:0!important;padding-right:0!important}.hard-top{padding-top:0!important}.hard-bottom{padding-bottom:0!important}
.hard-left{padding-left:0!important}.hard-right{padding-right:0!important}.flush{margin:0!important}.flush-ends{margin-top:0!important;margin-bottom:0!important}.flush-sides{margin-left:0!important;margin-right:0!important}
.flush-top{margin-top:0!important}.flush-bottom{margin-bottom:0!important}.flush-left{margin-left:0!important}.flush-right{margin-right:0!important}@media(max-width:500px){.hide-on-mobile{display:none}
}@media(min-width:500px) and (max-width:900px){.hide-on-tablet{display:none}}@media(min-width:500px){.hide-above-mobile{display:none}}.sep{height:2px;background:#fff;border-top:1px solid #ccc}section{padding-top:.5rem;padding-bottom:.5rem}
@media(min-width:767px){section{padding-top:.85rem;padding-bottom:.85rem}}@media(min-width:900px){section{padding-top:1rem;padding-bottom:1rem}}.inset{border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #eee;border-right:1px solid #eee}
.outset{border-top:1px solid #eee;border-left:1px solid #eee;border-bottom:1px solid #ddd;border-right:1px solid #ddd}table{width:100%;margin-bottom:1.25rem}table th{text-align:left}table th,table td{padding-top:.25rem;padding-bottom:.25rem}
@media(min-width:767px){table th,table td{padding-top:.425rem;padding-bottom:.425rem}}@media(min-width:900px){table th,table td{padding-top:.5rem;padding-bottom:.5rem}}table.border th,table.border td{padding:.33333rem;border:1px solid #dfdfdf}
@media(min-width:500px){table.border th,table.border td{padding:.56667rem}}@media(min-width:900px){table.border th,table.border td{padding:.66667rem}}.alert{position:relative;background-color:#7e69c6;color:#fff;padding:4rem}
.alert a.close{position:absolute;top:50%;right:4rem;border:0;color:#fff;width:2rem;text-align:center;line-height:2rem;margin-top:-1rem}.pagination{padding:0!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%;cursor:default;list-style:none;text-align:center}
.pagination li{border-top:1px solid #eee;border-left:1px solid #eee;border-bottom:1px solid #ddd;border-right:1px solid #ddd;display:inline-block;text-align:center;margin:.25rem}.pagination li:active{border-top:1px solid #ddd;border-left:1px solid #ddd;border-bottom:1px solid #eee;border-right:1px solid #eee}
.pagination li.disabled{padding:.5rem;color:rgba(0,0,0,0.54)}@media(min-width:500px){.pagination li.disabled{padding:.85rem}}@media(min-width:900px){.pagination li.disabled{padding:1rem}}.pagination li.disabled:active{border-top:1px solid #eee;border-left:1px solid #eee;border-bottom:1px solid #ddd;border-right:1px solid #ddd}
.pagination li.skip,.pagination li.current{padding:.5rem;border:0;margin:0}@media(min-width:500px){.pagination li.skip,.pagination li.current{padding:.85rem}}@media(min-width:900px){.pagination li.skip,.pagination li.current{padding:1rem}
}.pagination li a{display:block;padding:.5rem;cursor:pointer;border:0}@media(min-width:500px){.pagination li a{padding:.85rem}}@media(min-width:900px){.pagination li a{padding:1rem}}

================================================
FILE: src/containers/Survey/Survey.js
================================================
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import Helmet from 'react-helmet';
import {initialize} from 'redux-form';
import {SurveyForm} from 'components';

@connect(
  () => ({}),
  {initialize})
export default class Survey extends Component {
  static propTypes = {
    initialize: PropTypes.func.isRequired
  }

  handleSubmit = (data) => {
    window.alert('Data submitted! ' + JSON.stringify(data));
    this.props.initialize('survey', {});
  }

  handleInitialize = () => {
    this.props.initialize('survey', {
      name: 'Little Bobby Tables',
      email: 'bobby@gmail.com',
      occupation: 'Redux Wizard',
      currentlyEmployed: true,
      sex: 'male'
    });
  }

  render() {
    return (
      <div className="container">
        <h1>Survey</h1>
        <Helmet title="Survey"/>

        <p>
          This is an example of a form in redux in which all the state is kept within the redux store.
          All the components are pure "dumb" components.
        </p>

        <p>
          Things to notice:
        </p>

        <ul>
          <li>No validation errors are shown initially.</li>
          <li>Validation errors are only shown onBlur</li>
          <li>Validation errors are hidden onChange when the error is rectified</li>
          <li><code>valid</code>, <code>invalid</code>, <code>pristine</code> and <code>dirty</code> flags
            are passed with each change
          </li>
          <li><em>Except</em> when you submit the form, in which case they are shown for all invalid fields.</li>
          <li>If you click the Initialize Form button, the form will be prepopupated with some values and
            the <code>pristine</code> and <code>dirty</code> flags will be based on those values.
          </li>
        </ul>

        <p>
          Pardon the use of <code>window.alert()</code>, but I wanted to keep this component stateless.
        </p>

        <div style={{textAlign: 'center', margin: 15}}>
          <button className="btn btn-primary" onClick={this.handleInitialize}>
            <i className="fa fa-pencil"/> Initialize Form
          </button>
        </div>

        <p>The circles to the left of the inputs correspond to flags provided by <code>redux-form</code>:
          Touched, Visited, Active, and Dirty.</p>

        <SurveyForm onSubmit={this.handleSubmit}/>
      </div>
    );
  }
}


================================================
FILE: src/containers/Widgets/Widgets.js
================================================
import React, {Component, PropTypes} from 'react';
import Helmet from 'react-helmet';
import {connect} from 'react-redux';
import * as widgetActions from 'redux/modules/widgets';
import {isLoaded, load as loadWidgets} from 'redux/modules/widgets';
import {initializeWithKey} from 'redux-form';
import { WidgetForm } from 'components';
import { asyncConnect } from 'redux-async-connect';

@asyncConnect([{
  deferred: true,
  promise: ({store: {dispatch, getState}}) => {
    if (!isLoaded(getState())) {
      return dispatch(loadWidgets());
    }
  }
}])
@connect(
  state => ({
    widgets: state.widgets.data,
    editing: state.widgets.editing,
    error: state.widgets.error,
    loading: state.widgets.loading
  }),
  {...widgetActions, initializeWithKey })
export default class Widgets extends Component {
  static propTypes = {
    widgets: PropTypes.array,
    error: PropTypes.string,
    loading: PropTypes.bool,
    initializeWithKey: PropTypes.func.isRequired,
    editing: PropTypes.object.isRequired,
    load: PropTypes.func.isRequired,
    editStart: PropTypes.func.isRequired
  };

  render() {
    const handleEdit = (widget) => {
      const {editStart} = this.props; // eslint-disable-line no-shadow
      return () => editStart(String(widget.id));
    };
    const {widgets, error, editing, loading, load} = this.props;
    let refreshClassName = 'fa fa-refresh';
    if (loading) {
      refreshClassName += ' fa-spin';
    }
    const styles = require('./Widgets.scss');
    return (
      <div className={styles.widgets + ' container'}>
        <h1>
          Widgets
          <button className={styles.refreshBtn + ' btn btn-success'} onClick={load}>
            <i className={refreshClassName}/> {' '} Reload Widgets
          </button>
        </h1>
        <Helmet title="Widgets"/>
        <p>
          If you hit refresh on your browser, the data loading will take place on the server before the page is returned.
          If you navigated here from another page, the data was fetched from the client after the route transition.
          This uses the decorator method <code>@asyncConnect</code> with the <code>deferred: true</code> flag. To block
          a route transition until some data is loaded, remove the <code>deffered: true</code> flag.
          To always render before loading data, even on the server, use <code>componentDidMount</code>.
        </p>
        <p>
          This widgets are stored in your session, so feel free to edit it and refresh.
        </p>
        {error &&
        <div className="alert alert-danger" role="alert">
          <span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
          {' '}
          {error}
        </div>}
        {widgets && widgets.length &&
        <table className="table table-striped">
          <thead>
          <tr>
            <th className={styles.idCol}>ID</th>
            <th className={styles.colorCol}>Color</th>
            <th className={styles.sprocketsCol}>Sprockets</th>
            <th className={styles.ownerCol}>Owner</th>
            <th className={styles.buttonCol}></th>
          </tr>
          </thead>
          <tbody>
          {
            widgets.map((widget) => editing[widget.id] ?
              <WidgetForm formKey={String(widget.id)} key={String(widget.id)} initialValues={widget}/> :
              <tr key={widget.id}>
                <td className={styles.idCol}>{widget.id}</td>
                <td className={styles.colorCol}>{widget.color}</td>
                <td className={styles.sprocketsCol}>{widget.sprocketCount}</td>
                <td className={styles.ownerCol}>{widget.owner}</td>
                <td className={styles.buttonCol}>
                  <button className="btn btn-primary" onClick={handleEdit(widget)}>
                    <i className="fa fa-pencil"/> Edit
                  </button>
                </td>
              </tr>)
          }
          </tbody>
        </table>}
      </div>
    );
  }
}



================================================
FILE: src/containers/Widgets/Widgets.scss
================================================
.widgets {
  .refreshBtn {
    margin-left: 20px;
  }
  .idCol {
    width: 5%;
  }
  .colorCol {
    width: 20%;
  }
  .sprocketsCol {
    width: 20%;
    text-align: right;
    input {
      text-align: right;
    }
  }
  .ownerCol {
    width: 30%;
  }
  .buttonCol {
    width: 25%;
    :global(.btn) {
      margin: 0 5px;
    }
  }
  tr.saving {
    opacity: 0.8;
    :global(.btn) {
      &[disabled] {
        opacity: 1;
      }
    }
  }
}


================================================
FILE: src/containers/index.js
================================================
export App from './App/App';
export Chat from './Chat/Chat';
export Home from './Home/Home';
export Widgets from './Widgets/Widgets';
export About from './About/About';
export Login from './Login/Login';
export LoginSuccess from './LoginSuccess/LoginSuccess';
export Survey from './Survey/Survey';
export NotFound from './NotFound/NotFound';
export Pagination from './Pagination/Pagination';


================================================
FILE: src/helpers/ApiClient.js
================================================
import superagent from 'superagent';
import config from '../config';

const methods = ['get', 'post', 'put', 'patch', 'del'];

function formatUrl(path) {
  const adjustedPath = path[0] !== '/' ? '/' + path : path;
  if (__SERVER__) {
    // Prepend host and port of the API server to the path.
    return 'http://' + config.apiHost + ':' + config.apiPort + adjustedPath;
  }
  // Prepend `/api` to relative URL, to proxy to API server.
  return '/api' + adjustedPath;
}

export default class ApiClient {
  constructor(req) {
    methods.forEach((method) =>
      this[method] = (path, { params, data } = {}) => new Promise((resolve, reject) => {
        const request = superagent[method](formatUrl(path));

        if (params) {
          request.query(params);
        }

        if (__SERVER__ && req.get('cookie')) {
          request.set('cookie', req.get('cookie'));
        }

        if (data) {
          request.send(data);
        }

        request.end((err, { body } = {}) => err ? reject(body || err) : resolve(body));
      }));
  }
  /*
   * There's a V8 bug where, when using Babel, exporting classes with only
   * constructors sometimes fails. Until it's patched, this is a solution to
   * "ApiClient is not defined" from issue #14.
   * https://github.com/erikras/react-redux-universal-hot-example/issues/14
   *
   * Relevant Babel bug (but they claim it's V8): https://phabricator.babeljs.io/T2455
   *
   * Remove it at your own risk.
   */
  empty() {}
}


================================================
FILE: src/helpers/Html.js
================================================
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom/server';
import serialize from 'serialize-javascript';
import Helmet from 'react-helmet';

/**
 * Wrapper component containing HTML metadata and boilerplate tags.
 * Used in server-side code only to wrap the string output of the
 * rendered route component.
 *
 * The only thing this component doesn't (and can't) include is the
 * HTML doctype declaration, which is added to the rendered output
 * by the server.js file.
 */
export default class Html extends Component {
  static propTypes = {
    assets: PropTypes.object,
    component: PropTypes.node,
    store: PropTypes.object
  };

  render() {
    const {assets, component, store} = this.props;
    const content = component ? ReactDOM.renderToString(component) : '';
    const head = Helmet.rewind();

    return (
      <html lang="en-us">
        <head>
          {head.base.toComponent()}
          {head.title.toComponent()}
          {head.meta.toComponent()}
          {head.link.toComponent()}
          {head.script.toComponent()}

          <link rel="shortcut icon" href="/favicon.ico" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {/* styles (will be present only in production with webpack extract text plugin) */}
          {Object.keys(assets.styles).map((style, key) =>
            <link href={assets.styles[style]} key={key} media="screen, projection"
                  rel="stylesheet" type="text/css" charSet="UTF-8"/>
          )}

          {/* (will be present only in development mode) */}
          {/* outputs a <style/> tag with all bootstrap styles + App.scss + it could be CurrentPage.scss. */}
          {/* can smoothen the initial style flash (flicker) on page load in development mode. */}
          {/* ideally one could also include here the style for the current page (Home.scss, About.scss, etc) */}
          { Object.keys(assets.styles).length === 0 ? <style dangerouslySetInnerHTML={{__html: require('../theme/bootstrap.config.js') + require('../containers/App/App.scss')._style}}/> : null }
        </head>
        <body>
          <div id="content" dangerouslySetInnerHTML={{__html: content}}/>
          <script dangerouslySetInnerHTML={{__html: `window.__data=${serialize(store.getState())};`}} charSet="UTF-8"/>
          <script src={assets.javascript.main} charSet="UTF-8"/>
        </body>
      </html>
    );
  }
}


================================================
FILE: src/redux/create.js
================================================
import { createStore as _createStore, applyMiddleware, compose } from 'redux';
import createMiddleware from './middleware/clientMiddleware';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import Immutable from 'immutable';

export default function createStore(history, client, data) {
  // Sync dispatched route actions to the history
  const reduxRouterMiddleware = routerMiddleware(history);

  const middleware = [createMiddleware(client), reduxRouterMiddleware, thunk];

  let finalCreateStore;
  if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
    const { persistState } = require('redux-devtools');
    const DevTools = require('../containers/DevTools/DevTools');
    finalCreateStore = compose(
      applyMiddleware(...middleware),
      window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument(),
      persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
    )(_createStore);
  } else {
    finalCreateStore = applyMiddleware(...middleware)(_createStore);
  }

  const reducer = require('./modules/reducer');
  if (data) {
    data.pagination = Immutable.fromJS(data.pagination);
  }
  const store = finalCreateStore(reducer, data);


  if (__DEVELOPMENT__ && module.hot) {
    module.hot.accept('./modules/reducer', () => {
      store.replaceReducer(require('./modules/reducer'));
    });
  }

  return store;
}


================================================
FILE: src/redux/middleware/clientMiddleware.js
================================================
export default function clientMiddleware(client) {
  return ({dispatch, getState}) => {
    return next => action => {
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }

      const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
      if (!promise) {
        return next(action);
      }

      const [REQUEST, SUCCESS, FAILURE] = types;
      next({...rest, type: REQUEST});

      const actionPromise = promise(client);
      actionPromise.then(
        (result) => next({...rest, result, type: SUCCESS}),
        (error) => next({...rest, error, type: FAILURE})
      ).catch((error)=> {
        console.error('MIDDLEWARE ERROR:', error);
        next({...rest, error, type: FAILURE});
      });

      return actionPromise;
    };
  };
}


================================================
FILE: src/redux/modules/auth.js
================================================
const LOAD = 'redux-example/auth/LOAD';
const LOAD_SUCCESS = 'redux-example/auth/LOAD_SUCCESS';
const LOAD_FAIL = 'redux-example/auth/LOAD_FAIL';
const LOGIN = 'redux-example/auth/LOGIN';
const LOGIN_SUCCESS = 'redux-example/auth/LOGIN_SUCCESS';
const LOGIN_FAIL = 'redux-example/auth/LOGIN_FAIL';
const LOGOUT = 'redux-example/auth/LOGOUT';
const LOGOUT_SUCCESS = 'redux-example/auth/LOGOUT_SUCCESS';
const LOGOUT_FAIL = 'redux-example/auth/LOGOUT_FAIL';

const initialState = {
  loaded: false
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case LOAD:
      return {
        ...state,
        loading: true
      };
    case LOAD_SUCCESS:
      return {
        ...state,
        loading: false,
        loaded: true,
        user: action.result
      };
    case LOAD_FAIL:
      return {
        ...state,
        loading: false,
        loaded: false,
        error: action.error
      };
    case LOGIN:
      return {
        ...state,
        loggingIn: true
      };
    case LOGIN_SUCCESS:
      return {
        ...state,
        loggingIn: false,
        user: action.result
      };
    case LOGIN_FAIL:
      return {
        ...state,
        loggingIn: false,
        user: null,
        loginError: action.error
      };
    case LOGOUT:
      return {
        ...state,
        loggingOut: true
      };
    case LOGOUT_SUCCESS:
      return {
        ...state,
        loggingOut: false,
        user: null
      };
    case LOGOUT_FAIL:
      return {
        ...state,
        loggingOut: false,
        logoutError: action.error
      };
    default:
      return state;
  }
}

export function isLoaded(globalState) {
  return globalState.auth && globalState.auth.loaded;
}

export function load() {
  return {
    types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
    promise: (client) => client.get('/loadAuth')
  };
}

export function login(name) {
  return {
    types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL],
    promise: (client) => client.post('/login', {
      data: {
        name: name
      }
    })
  };
}

export function logout() {
  return {
    types: [LOGOUT, LOGOUT_SUCCESS, LOGOUT_FAIL],
    promise: (client) => client.get('/logout')
  };
}


================================================
FILE: src/redux/modules/counter.js
================================================
const INCREMENT = 'redux-example/counter/INCREMENT';

const initialState = {
  count: 0
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case INCREMENT:
      const {count} = state;
      return {
        count: count + 1
      };
    default:
      return state;
  }
}

export function increment() {
  return {
    type: INCREMENT
  };
}


================================================
FILE: src/redux/modules/info.js
================================================
const LOAD = 'redux-example/LOAD';
const LOAD_SUCCESS = 'redux-example/LOAD_SUCCESS';
const LOAD_FAIL = 'redux-example/LOAD_FAIL';

const initialState = {
  loaded: false
};

export default function info(state = initialState, action = {}) {
  switch (action.type) {
    case LOAD:
      return {
        ...state,
        loading: true
      };
    case LOAD_SUCCESS:
      return {
        ...state,
        loading: false,
        loaded: true,
        data: action.result
      };
    case LOAD_FAIL:
      return {
        ...state,
        loading: false,
        loaded: false,
        error: action.error
      };
    default:
      return state;
  }
}

export function isLoaded(globalState) {
  return globalState.info && globalState.info.loaded;
}

export function load() {
  return {
    types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
    promise: (client) => client.get('/loadInfo')
  };
}


================================================
FILE: src/redux/modules/reducer.js
================================================
import { combineReducers } from 'redux';
import multireducer from 'multireducer';
import { routerReducer } from 'react-router-redux';
import {reducer as reduxAsyncConnect} from 'redux-async-connect';
import { pagination } from 'violet-paginator';

import auth from './auth';
import counter from './counter';
import {reducer as form} from 'redux-form';
import info from './info';
import widgets from './widgets';

export default combineReducers({
  routing: routerReducer,
  reduxAsyncConnect,
  auth,
  form,
  multireducer: multireducer({
    counter1: counter,
    counter2: counter,
    counter3: counter
  }),
  info,
  pagination,
  widgets
});


================================================
FILE: src/redux/modules/survey.js
================================================
const IS_VALID = 'redux-example/survey/IS_VALID';
const IS_VALID_SUCCESS = 'redux-example/survey/IS_VALID_SUCCESS';
const IS_VALID_FAIL = 'redux-example/survey/IS_VALID_FAIL';

const initialState = {
  saveError: null,
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case IS_VALID:
      return state; // 'saving' flag handled by redux-form
    case IS_VALID_SUCCESS:
      const data = [...state.data];
      data[action.result.id - 1] = action.result;
      return {
        ...state,
        data: data,
        saveError: null,
      };
    case IS_VALID_FAIL:
      return typeof action.error === 'string' ? {
        ...state,
        saveError: action.error
      } : state;
    default:
      return state;
  }
}

export function isValidEmail(data) {
  return {
    types: [IS_VALID, IS_VALID_SUCCESS, IS_VALID_FAIL],
    promise: (client) => client.post('/survey/isValid', {
      data
    })
  };
}


================================================
FILE: src/redux/modules/widgets.js
================================================
const LOAD = 'redux-example/widgets/LOAD';
const LOAD_SUCCESS = 'redux-example/widgets/LOAD_SUCCESS';
const LOAD_FAIL = 'redux-example/widgets/LOAD_FAIL';
const EDIT_START = 'redux-example/widgets/EDIT_START';
const EDIT_STOP = 'redux-example/widgets/EDIT_STOP';
const SAVE = 'redux-example/widgets/SAVE';
const SAVE_SUCCESS = 'redux-example/widgets/SAVE_SUCCESS';
const SAVE_FAIL = 'redux-example/widgets/SAVE_FAIL';

const initialState = {
  loaded: false,
  editing: {},
  saveError: {}
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case LOAD:
      return {
        ...state,
        loading: true
      };
    case LOAD_SUCCESS:
      return {
        ...state,
        loading: false,
        loaded: true,
        data: action.result,
        error: null
      };
    case LOAD_FAIL:
      return {
        ...state,
        loading: false,
        loaded: false,
        data: null,
        error: action.error
      };
    case EDIT_START:
      return {
        ...state,
        editing: {
          ...state.editing,
          [action.id]: true
        }
      };
    case EDIT_STOP:
      return {
        ...state,
        editing: {
          ...state.editing,
          [action.id]: false
        }
      };
    case SAVE:
      return state; // 'saving' flag handled by redux-form
    case SAVE_SUCCESS:
      const data = [...state.data];
      data[action.result.id - 1] = action.result;
      return {
        ...state,
        data: data,
        editing: {
          ...state.editing,
          [action.id]: false
        },
        saveError: {
          ...state.saveError,
          [action.id]: null
        }
      };
    case SAVE_FAIL:
      return typeof action.error === 'string' ? {
        ...state,
        saveError: {
          ...state.saveError,
          [action.id]: action.error
        }
      } : state;
    default:
      return state;
  }
}

export function isLoaded(globalState) {
  return globalState.widgets && globalState.widgets.loaded;
}

export function load() {
  return {
    types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
    promise: (client) => client.get('/widget/load/param1/param2') // params not used, just shown as demonstration
  };
}

export function save(widget) {
  return {
    types: [SAVE, SAVE_SUCCESS, SAVE_FAIL],
    id: widget.id,
    promise: (client) => client.post('/widget/update', {
      data: widget
    })
  };
}

export function editStart(id) {
  return { type: EDIT_START, id };
}

export function editStop(id) {
  return { type: EDIT_STOP, id };
}


================================================
FILE: src/routes.js
================================================
import React from 'react';
import {IndexRoute, Route} from 'react-router';
import { isLoaded as isAuthLoaded, load as loadAuth } from 'redux/modules/auth';
import {
    App,
    Chat,
    Home,
    Widgets,
    About,
    Login,
    LoginSuccess,
    Survey,
    NotFound,
    Pagination,
  } from 'containers';

export default (store) => {
  const requireLogin = (nextState, replace, cb) => {
    function checkAuth() {
      const { auth: { user }} = store.getState();
      if (!user) {
        // oops, not logged in, so can't be here!
        replace('/');
      }
      cb();
    }

    if (!isAuthLoaded(store.getState())) {
      store.dispatch(loadAuth()).then(checkAuth);
    } else {
      checkAuth();
    }
  };

  /**
   * Please keep routes in alphabetical order
   */
  return (
    <Route path="/" component={App}>
      { /* Home (main) route */ }
      <IndexRoute component={Home}/>

      { /* Routes requiring login */ }
      <Route onEnter={requireLogin}>
        <Route path="chat" component={Chat}/>
        <Route path="loginSuccess" component={LoginSuccess}/>
      </Route>

      { /* Routes */ }
      <Route path="about" component={About}/>
      <Route path="login" component={Login}/>
      <Route path="pagination" component={Pagination}/>
      <Route path="survey" component={Survey}/>
      <Route path="widgets" component={Widgets}/>

      { /* Catch all route */ }
      <Route path="*" component={NotFound} status={404} />
    </Route>
  );
};


================================================
FILE: src/server.js
================================================
import Express from 'express';
import React from 'react';
import ReactDOM from 'react-dom/server';
import config from './config';
import favicon from 'serve-favicon';
import compression from 'compression';
import httpProxy from 'http-proxy';
import path from 'path';
import createStore from './redux/create';
import ApiClient from './helpers/ApiClient';
import Html from './helpers/Html';
import PrettyError from 'pretty-error';
import http from 'http';

import { match } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { ReduxAsyncConnect, loadOnServer } from 'redux-async-connect';
import createHistory from 'react-router/lib/createMemoryHistory';
import {Provider} from 'react-redux';
import getRoutes from './routes';

const targetUrl = 'http://' + config.apiHost + ':' + config.apiPort;
const pretty = new PrettyError();
const app = new Express();
const server = new http.Server(app);
const proxy = httpProxy.createProxyServer({
  target: targetUrl,
  ws: true
});

app.use(compression());
app.use(favicon(path.join(__dirname, '..', 'static', 'favicon.ico')));

app.use(Express.static(path.join(__dirname, '..', 'static')));

// Proxy to API server
app.use('/api', (req, res) => {
  proxy.web(req, res, {target: targetUrl});
});

app.use('/ws', (req, res) => {
  proxy.web(req, res, {target: targetUrl + '/ws'});
});

server.on('upgrade', (req, socket, head) => {
  proxy.ws(req, socket, head);
});

// added the error handling to avoid https://github.com/nodejitsu/node-http-proxy/issues/527
proxy.on('error', (error, req, res) => {
  let json;
  if (error.code !== 'ECONNRESET') {
    console.error('proxy error', error);
  }
  if (!res.headersSent) {
    res.writeHead(500, {'content-type': 'application/json'});
  }

  json = {error: 'proxy_error', reason: error.message};
  res.end(JSON.stringify(json));
});

app.use((req, res) => {
  if (__DEVELOPMENT__) {
    // Do not cache webpack stats: the script file would change since
    // hot module replacement is enabled in the development env
    webpackIsomorphicTools.refresh();
  }
  const client = new ApiClient(req);
  const memoryHistory = createHistory(req.originalUrl);
  const store = createStore(memoryHistory, client);
  const history = syncHistoryWithStore(memoryHistory, store);

  function hydrateOnClient() {
    res.send('<!doctype html>\n' +
      ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} store={store}/>));
  }

  if (__DISABLE_SSR__) {
    hydrateOnClient();
    return;
  }

  match({ history, routes: getRoutes(store), location: req.originalUrl }, (error, redirectLocation, renderProps) => {
    if (redirectLocation) {
      res.redirect(redirectLocation.pathname + redirectLocation.search);
    } else if (error) {
      console.error('ROUTER ERROR:', pretty.render(error));
      res.status(500);
      hydrateOnClient();
    } else if (renderProps) {
      loadOnServer({...renderProps, store, helpers: {client}}).then(() => {
        const component = (
          <Provider store={store} key="provider">
            <ReduxAsyncConnect {...renderProps} />
          </Provider>
        );

        res.status(200);

        global.navigator = {userAgent: req.headers['user-agent']};

        res.send('<!doctype html>\n' +
          ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>));
      });
    } else {
      res.status(404).send('Not found');
    }
  });
});

if (config.port) {
  server.listen(config.port, (err) => {
    if (err) {
      console.error(err);
    }
    console.info('----\n==> ✅  %s is running, talking to API server on %s.', config.app.title, config.apiPort);
    console.info('==> 💻  Open http://%s:%s in a browser to view the app.', config.host, config.port);
  });
} else {
  console.error('==>     ERROR: No PORT environment variable has been specified');
}


================================================
FILE: src/theme/bootstrap.config.js
================================================
/**
 * Bootstrap configuration for bootstrap-sass-loader
 *
 * Scripts are disabled to not load jQuery.
 * If you depend on Bootstrap scripts consider react-bootstrap instead.
 * https://github.com/react-bootstrap/react-bootstrap
 *
 * In order to keep the bundle size low in production
 * disable components you don't use.
 *
 */

module.exports = {
  preBootstrapCustomizations: './src/theme/variables.scss',
  mainSass: './src/theme/bootstrap.overrides.scss',
  verbose: false,
  debug: false,
  scripts: {
    transition: false,
    alert: false,
    button: false,
    carousel: false,
    collapse: false,
    dropdown: false,
    modal: false,
    tooltip: false,
    popover: false,
    scrollspy: false,
    tab: false,
    affix: false
  },
  styles: {
    mixins: true,
    normalize: true,
    print: true,
    glyphicons: true,
    scaffolding: true,
    type: true,
    code: true,
    grid: true,
    tables: true,
    forms: true,
    buttons: true,
    'component-animations': true,
    dropdowns: true,
    'button-groups': true,
    'input-groups': true,
    navs: true,
    navbar: true,
    breadcrumbs: true,
    pagination: true,
    pager: true,
    labels: true,
    badges: true,
    jumbotron: true,
    thumbnails: true,
    alerts: true,
    'progress-bars': true,
    media: true,
    'list-group': true,
    panels: true,
    wells: true,
    'responsive-embed': true,
    close: true,
    modals: true,
    tooltip: true,
    popovers: true,
    carousel: true,
    utilities: true,
    'responsive-utilities': true
  }
};


================================================
FILE: src/theme/bootstrap.config.prod.js
================================================
const bootstrapConfig = require('./bootstrap.config.js');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
bootstrapConfig.styleLoader = ExtractTextPlugin.extract('style-loader', 'css-loader!sass-loader');
module.exports = bootstrapConfig;



================================================
FILE: src/theme/bootstrap.overrides.scss
================================================
/**
 * Override Bootstrap styles that you can't modify via variables here.
 *
 */

.navbar-brand {
  position: relative;
  padding-left: 50px;
}

.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
  color: #33e0ff;
  background-color: transparent;
}


================================================
FILE: src/theme/font-awesome.config.js
================================================
/**
 * Configuration file for font-awesome-webpack
 *
 * In order to keep the bundle size low in production,
 * disable components you don't use.
 *
 */

module.exports = {
  styles: {
    mixins: true,
    core: true,
    icons: true,
    larger: true,
    path: true,
    animated: true,
  }
};


================================================
FILE: src/theme/font-awesome.config.less
================================================
/**
 * Configuration file for font-awesome-webpack
 *
 */

// Example:
// @fa-border-color: #ddd;


================================================
FILE: src/theme/font-awesome.config.prod.js
================================================
const fontAwesomeConfig = require('./font-awesome.config.js');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
fontAwesomeConfig.styleLoader = ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader');
module.exports = fontAwesomeConfig;



================================================
FILE: src/theme/variables.scss
================================================
/**
 *  Define scss variables here.
 *
 *  Available options for Bootstrap:
 *  http://getbootstrap.com/customize/
 *
 */

// Custom Colors
$cyan: #33e0ff;
$humility: #777;

// Bootstrap Variables
$brand-primary: darken(#428bca, 6.5%);
$brand-secondary: #e25139;
$brand-success: #5cb85c;
$brand-warning: #f0ad4e;
$brand-danger: #d9534f;
$brand-info: #5bc0de;

$text-color: #333;

$font-size-base: 14px;
$font-family-sans-serif: "Helvetica Neue", Helvetica, sans-serif;


================================================
FILE: src/utils/validation.js
================================================
const isEmpty = value => value === undefined || value === null || value === '';
const join = (rules) => (value, data) => rules.map(rule => rule(value, data)).filter(error => !!error)[0 /* first error */ ];

export function email(value) {
  // Let's not start a debate on email regex. This is just for an example app!
  if (!isEmpty(value) && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
    return 'Invalid email address';
  }
}

export function required(value) {
  if (isEmpty(value)) {
    return 'Required';
  }
}

export function minLength(min) {
  return value => {
    if (!isEmpty(value) && value.length < min) {
      return `Must be at least ${min} characters`;
    }
  };
}

export function maxLength(max) {
  return value => {
    if (!isEmpty(value) && value.length > max) {
      return `Must be no more than ${max} characters`;
    }
  };
}

export function integer(value) {
  if (!Number.isInteger(Number(value))) {
    return 'Must be an integer';
  }
}

export function oneOf(enumeration) {
  return value => {
    if (!~enumeration.indexOf(value)) {
      return `Must be one of: ${enumeration.join(', ')}`;
    }
  };
}

export function match(field) {
  return (value, data) => {
    if (data) {
      if (value !== data[field]) {
        return 'Do not match';
      }
    }
  };
}

export function createValidator(rules) {
  return (data = {}) => {
    const errors = {};
    Object.keys(rules).forEach((key) => {
      const rule = join([].concat(rules[key])); // concat enables both functions and arrays of functions
      const error = rule(data[key], data);
      if (error) {
        errors[key] = error;
      }
    });
    return errors;
  };
}


================================================
FILE: tests.webpack.js
================================================
var context = require.context('./src', true, /-test\.js$/);
context.keys().forEach(context);


================================================
FILE: webpack/dev.config.js
================================================
require('babel-polyfill');

// Webpack config for development
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
var assetsPath = path.resolve(__dirname, '../static/dist');
var host = (process.env.HOST || 'localhost');
var port = (+process.env.PORT + 1) || 3001;

// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));

var babelrc = fs.readFileSync('./.babelrc');
var babelrcObject = {};

try {
  babelrcObject = JSON.parse(babelrc);
} catch (err) {
  console.error('==>     ERROR: Error parsing your .babelrc.');
  console.error(err);
}


var babelrcObjectDevelopment = babelrcObject.env && babelrcObject.env.development || {};

// merge global and dev-only plugins
var combinedPlugins = babelrcObject.plugins || [];
combinedPlugins = combinedPlugins.concat(babelrcObjectDevelopment.plugins);

var babelLoaderQuery = Object.assign({}, babelrcObjectDevelopment, babelrcObject, {plugins: combinedPlugins});
delete babelLoaderQuery.env;

// Since we use .babelrc for client and server, and we don't want HMR enabled on the server, we have to add
// the babel plugin react-transform-hmr manually here.

// make sure react-transform is enabled
babelLoaderQuery.plugins = babelLoaderQuery.plugins || [];
var reactTransform = null;
for (var i = 0; i < babelLoaderQuery.plugins.length; ++i) {
  var plugin = babelLoaderQuery.plugins[i];
  if (Array.isArray(plugin) && plugin[0] === 'react-transform') {
    reactTransform = plugin;
  }
}

if (!reactTransform) {
  reactTransform = ['react-transform', {transforms: []}];
  babelLoaderQuery.plugins.push(reactTransform);
}

if (!reactTransform[1] || !reactTransform[1].transforms) {
  reactTransform[1] = Object.assign({}, reactTransform[1], {transforms: []});
}

// make sure react-transform-hmr is enabled
reactTransform[1].transforms.push({
  transform: 'react-transform-hmr',
  imports: ['react'],
  locals: ['module']
});

module.exports = {
  devtool: 'inline-source-map',
  context: path.resolve(__dirname, '..'),
  entry: {
    'main': [
      'webpack-hot-middleware/client?path=http://' + host + ':' + port + '/__webpack_hmr',
      'bootstrap-sass!./src/theme/bootstrap.config.js',
      'font-awesome-webpack!./src/theme/font-awesome.config.js',
      './src/client.js'
    ]
  },
  output: {
    path: assetsPath,
    filename: '[name]-[hash].js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: 'http://' + host + ':' + port + '/dist/'
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel?' + JSON.stringify(babelLoaderQuery), 'eslint-loader']},
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.less$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap' },
      { test: /\.scss$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap' },
      { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
      { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
    ]
  },
  progress: true,
  resolve: {
    modulesDirectories: [
      'src',
      'node_modules'
    ],
    extensions: ['', '.json', '.js', '.jsx']
  },
  plugins: [
    // hot reload
    new webpack.HotModuleReplacementPlugin(),
    new webpack.IgnorePlugin(/webpack-stats\.json$/),
    new webpack.DefinePlugin({
      __CLIENT__: true,
      __SERVER__: false,
      __DEVELOPMENT__: true,
      __DEVTOOLS__: true  // <-------- DISABLE redux-devtools HERE
    }),
    webpackIsomorphicToolsPlugin.development()
  ]
};


================================================
FILE: webpack/prod.config.js
================================================
require('babel-polyfill');

// Webpack config for creating the production bundle.
var path = require('path');
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var strip = require('strip-loader');

var projectRootPath = path.resolve(__dirname, '../');
var assetsPath = path.resolve(projectRootPath, './static/dist');

// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));

module.exports = {
  devtool: 'source-map',
  context: path.resolve(__dirname, '..'),
  entry: {
    'main': [
      'bootstrap-sass!./src/theme/bootstrap.config.prod.js',
      'font-awesome-webpack!./src/theme/font-awesome.config.prod.js',
      './src/client.js'
    ]
  },
  output: {
    path: assetsPath,
    filename: '[name]-[chunkhash].js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel']},
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap=true&sourceMapContents=true') },
      { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true') },
      { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
      { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
    ]
  },
  progress: true,
  resolve: {
    modulesDirectories: [
      'src',
      'node_modules'
    ],
    extensions: ['', '.json', '.js', '.jsx']
  },
  plugins: [
    new CleanPlugin([assetsPath], { root: projectRootPath }),

    // css files from the extract-text-plugin loader
    new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      },

      __CLIENT__: true,
      __SERVER__: false,
      __DEVELOPMENT__: false,
      __DEVTOOLS__: false
    }),

    // ignore dev config
    new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),

    // optimizations
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),

    webpackIsomorphicToolsPlugin
  ]
};


================================================
FILE: webpack/webpack-dev-server.js
================================================
var Express = require('express');
var webpack = require('webpack');

var config = require('../src/config');
var webpackConfig = require('./dev.config');
var compiler = webpack(webpackConfig);

var host = config.host || 'localhost';
var port = (Number(config.port) + 1) || 3001;
var serverOptions = {
  contentBase: 'http://' + host + ':' + port,
  quiet: true,
  noInfo: true,
  hot: true,
  inline: true,
  lazy: false,
  publicPath: webpackConfig.output.publicPath,
  headers: {'Access-Control-Allow-Origin': '*'},
  stats: {colors: true}
};

var app = new Express();

app.use(require('webpack-dev-middleware')(compiler, serverOptions));
app.use(require('webpack-hot-middleware')(compiler));

app.listen(port, function onAppListening(err) {
  if (err) {
    console.error(err);
  } else {
    console.info('==> 🚧  Webpack development server listening on port %s', port);
  }
});


================================================
FILE: webpack/webpack-isomorphic-tools.js
================================================
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');

// see this link for more info on what all of this means
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
module.exports = {

  // when adding "js" extension to asset types 
  // and then enabling debug mode, it may cause a weird error:
  //
  // [0] npm run start-prod exited with code 1
  // Sending SIGTERM to other processes..
  //
  // debug: true, 

  assets: {
    images: {
      extensions: [
        'jpeg',
        'jpg',
        'png',
        'gif'
      ],
      parser: WebpackIsomorphicToolsPlugin.url_loader_parser
    },
    fonts: {
      extensions: [
        'woff',
        'woff2',
        'ttf',
        'eot'
      ],
      parser: WebpackIsomorphicToolsPlugin.url_loader_parser
    },
    svg: {
      extension: 'svg',
      parser: WebpackIsomorphicToolsPlugin.url_loader_parser
    },
    // this whole "bootstrap" asset type is only used once in development mode.
    // the only place it's used is the Html.js file
    // where a <style/> tag is created with the contents of the
    // './src/theme/bootstrap.config.js' file.
    // (the aforementioned <style/> tag can reduce the white flash 
    //  when refreshing page in development mode)
    //
    // hooking into 'js' extension require()s isn't the best solution
    // and I'm leaving this comment here in case anyone finds a better idea.
    bootstrap: {
      extension: 'js',
      include: ['./src/theme/bootstrap.config.js'],
      filter: function(module, regex, options, log) {
        function is_bootstrap_style(name) {
          return name.indexOf('./src/theme/bootstrap.config.js') >= 0;
        }
        if (options.development) {
          return is_bootstrap_style(module.name) && WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
        }
        // no need for it in production mode
      },
      // in development mode there's webpack "style-loader",
      // so the module.name is not equal to module.name
      path: WebpackIsomorphicToolsPlugin.style_loader_path_extractor,
      parser: WebpackIsomorphicToolsPlugin.css_loader_parser
    },
    style_modules: {
      extensions: ['less','scss'],
      filter: function(module, regex, options, log) {
        if (options.development) {
          // in development mode there's webpack "style-loader",
          // so the module.name is not equal to module.name
          return WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
        } else {
          // in production mode there's no webpack "style-loader",
          // so the module.name will be equal to the asset path
          return regex.test(module.name);
        }
      },
      path: function(module, options, log) {
        if (options.development) {
          // in development mode there's webpack "style-loader",
          // so the module.name is not equal to module.name
          return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
        } else {
          // in production mode there's no webpack "style-loader",
          // so the module.name will be equal to the asset path
          return module.name;
        }
      },
      parser: function(module, options, log) {
        if (options.development) {
          return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
        } else {
          // in production mode there's Extract Text Loader which extracts CSS text away
          return module.source;
        }
      }
    }
  }
}
Download .txt
gitextract_bh9t0pcb/

├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── api/
│   ├── __tests__/
│   │   └── api-test.js
│   ├── actions/
│   │   ├── __tests__/
│   │   │   ├── loadInfo-test.js
│   │   │   ├── widget-load-test.js
│   │   │   └── widget-update-test.js
│   │   ├── index.js
│   │   ├── loadAuth.js
│   │   ├── loadInfo.js
│   │   ├── login.js
│   │   ├── logout.js
│   │   ├── survey/
│   │   │   ├── index.js
│   │   │   └── isValid.js
│   │   └── widget/
│   │       ├── index.js
│   │       ├── load.js
│   │       └── update.js
│   ├── api.js
│   └── utils/
│       └── url.js
├── app.json
├── bin/
│   ├── api.js
│   └── server.js
├── circle.yml
├── docs/
│   ├── AddingAPage/
│   │   └── AddingAPage.md
│   ├── AddingToHomePage/
│   │   └── AddingToHomePage.md
│   ├── ApiConfig.md
│   ├── Ducks.md
│   ├── ExploringTheDemoApp/
│   │   └── ExploringTheDemoApp.md
│   ├── InlineStyles.md
│   └── InstallingTheKit/
│       └── InstallingTheKit.md
├── karma.conf.js
├── package.json
├── server.babel.js
├── src/
│   ├── client.js
│   ├── components/
│   │   ├── CounterButton/
│   │   │   └── CounterButton.js
│   │   ├── GithubButton/
│   │   │   └── GithubButton.js
│   │   ├── InfoBar/
│   │   │   ├── InfoBar.js
│   │   │   └── InfoBar.scss
│   │   ├── MiniInfoBar/
│   │   │   └── MiniInfoBar.js
│   │   ├── SurveyForm/
│   │   │   ├── SurveyForm.js
│   │   │   ├── SurveyForm.scss
│   │   │   └── surveyValidation.js
│   │   ├── WidgetForm/
│   │   │   ├── WidgetForm.js
│   │   │   └── widgetValidation.js
│   │   ├── __tests__/
│   │   │   └── InfoBar-test.js
│   │   └── index.js
│   ├── config.js
│   ├── containers/
│   │   ├── About/
│   │   │   └── About.js
│   │   ├── App/
│   │   │   ├── App.js
│   │   │   └── App.scss
│   │   ├── Chat/
│   │   │   ├── Chat.js
│   │   │   └── Chat.scss
│   │   ├── DevTools/
│   │   │   └── DevTools.js
│   │   ├── Home/
│   │   │   ├── Home.js
│   │   │   └── Home.scss
│   │   ├── Login/
│   │   │   ├── Login.js
│   │   │   └── Login.scss
│   │   ├── LoginSuccess/
│   │   │   └── LoginSuccess.js
│   │   ├── NotFound/
│   │   │   └── NotFound.js
│   │   ├── Pagination/
│   │   │   ├── Pagination.jsx
│   │   │   ├── Pagination.scss
│   │   │   └── violet.min.scss
│   │   ├── Survey/
│   │   │   └── Survey.js
│   │   ├── Widgets/
│   │   │   ├── Widgets.js
│   │   │   └── Widgets.scss
│   │   └── index.js
│   ├── helpers/
│   │   ├── ApiClient.js
│   │   └── Html.js
│   ├── redux/
│   │   ├── create.js
│   │   ├── middleware/
│   │   │   └── clientMiddleware.js
│   │   └── modules/
│   │       ├── auth.js
│   │       ├── counter.js
│   │       ├── info.js
│   │       ├── reducer.js
│   │       ├── survey.js
│   │       └── widgets.js
│   ├── routes.js
│   ├── server.js
│   ├── theme/
│   │   ├── bootstrap.config.js
│   │   ├── bootstrap.config.prod.js
│   │   ├── bootstrap.overrides.scss
│   │   ├── font-awesome.config.js
│   │   ├── font-awesome.config.less
│   │   ├── font-awesome.config.prod.js
│   │   └── variables.scss
│   └── utils/
│       └── validation.js
├── tests.webpack.js
└── webpack/
    ├── dev.config.js
    ├── prod.config.js
    ├── webpack-dev-server.js
    └── webpack-isomorphic-tools.js
Download .txt
SYMBOL INDEX (106 symbols across 37 files)

FILE: api/actions/loadAuth.js
  function loadAuth (line 1) | function loadAuth(req) {

FILE: api/actions/loadInfo.js
  function loadInfo (line 1) | function loadInfo() {

FILE: api/actions/login.js
  function login (line 1) | function login(req) {

FILE: api/actions/logout.js
  function logout (line 1) | function logout(req) {

FILE: api/actions/survey/isValid.js
  function survey (line 1) | function survey(req) {

FILE: api/actions/widget/load.js
  function getWidgets (line 8) | function getWidgets(req) {
  function load (line 17) | function load(req) {

FILE: api/actions/widget/update.js
  function update (line 3) | function update(req) {

FILE: api/utils/url.js
  function mapUrl (line 1) | function mapUrl(availableActions = {}, url = []) {

FILE: src/client.js
  function initSocket (line 24) | function initSocket() {

FILE: src/components/CounterButton/CounterButton.js
  class CounterButton (line 9) | class CounterButton extends Component {
    method render (line 20) | render() {

FILE: src/components/InfoBar/InfoBar.js
  class InfoBar (line 9) | class InfoBar extends Component {
    method render (line 15) | render() {

FILE: src/components/MiniInfoBar/MiniInfoBar.js
  class MiniInfoBar (line 5) | class MiniInfoBar extends Component {
    method render (line 10) | render() {

FILE: src/components/SurveyForm/SurveyForm.js
  function asyncValidate (line 8) | function asyncValidate(data, dispatch, {isValidEmail}) {
  class SurveyForm (line 25) | class SurveyForm extends Component {
    method render (line 38) | render() {

FILE: src/components/WidgetForm/WidgetForm.js
  class WidgetForm (line 19) | class WidgetForm extends Component {
    method render (line 33) | render() {

FILE: src/containers/About/About.js
  class About (line 5) | class About extends Component {
    method render (line 13) | render() {

FILE: src/containers/App/App.js
  class App (line 33) | class App extends Component {
    method componentWillReceiveProps (line 45) | componentWillReceiveProps(nextProps) {
    method render (line 60) | render() {

FILE: src/containers/Chat/Chat.js
  class Chat (line 7) | class Chat extends Component {
    method componentDidMount (line 18) | componentDidMount() {
    method componentWillUnmount (line 27) | componentWillUnmount() {
    method render (line 52) | render() {

FILE: src/containers/Home/Home.js
  class Home (line 7) | class Home extends Component {
    method render (line 8) | render() {

FILE: src/containers/Login/Login.js
  class Login (line 9) | class Login extends Component {
    method render (line 23) | render() {

FILE: src/containers/LoginSuccess/LoginSuccess.js
  class LoginSuccess (line 9) | class LoginSuccess extends Component {
    method render (line 15) | render() {

FILE: src/containers/NotFound/NotFound.js
  function NotFound (line 3) | function NotFound() {

FILE: src/containers/Pagination/Pagination.jsx
  function paginate (line 10) | function paginate(list, page, pageSize) {
  function order (line 14) | function order(list, sort, sortOrder) {
  function mockFetch (line 27) | function mockFetch({ query: { pageSize, page, sort, sortOrder } }) {
  function Pagination (line 68) | function Pagination({ fetch }) {

FILE: src/containers/Survey/Survey.js
  class Survey (line 10) | class Survey extends Component {
    method render (line 30) | render() {

FILE: src/containers/Widgets/Widgets.js
  class Widgets (line 26) | class Widgets extends Component {
    method render (line 37) | render() {

FILE: src/helpers/ApiClient.js
  function formatUrl (line 6) | function formatUrl(path) {
  class ApiClient (line 16) | class ApiClient {
    method constructor (line 17) | constructor(req) {
    method empty (line 47) | empty() {}

FILE: src/helpers/Html.js
  class Html (line 15) | class Html extends Component {
    method render (line 22) | render() {

FILE: src/redux/create.js
  function createStore (line 7) | function createStore(history, client, data) {

FILE: src/redux/middleware/clientMiddleware.js
  function clientMiddleware (line 1) | function clientMiddleware(client) {

FILE: src/redux/modules/auth.js
  constant LOAD (line 1) | const LOAD = 'redux-example/auth/LOAD';
  constant LOAD_SUCCESS (line 2) | const LOAD_SUCCESS = 'redux-example/auth/LOAD_SUCCESS';
  constant LOAD_FAIL (line 3) | const LOAD_FAIL = 'redux-example/auth/LOAD_FAIL';
  constant LOGIN (line 4) | const LOGIN = 'redux-example/auth/LOGIN';
  constant LOGIN_SUCCESS (line 5) | const LOGIN_SUCCESS = 'redux-example/auth/LOGIN_SUCCESS';
  constant LOGIN_FAIL (line 6) | const LOGIN_FAIL = 'redux-example/auth/LOGIN_FAIL';
  constant LOGOUT (line 7) | const LOGOUT = 'redux-example/auth/LOGOUT';
  constant LOGOUT_SUCCESS (line 8) | const LOGOUT_SUCCESS = 'redux-example/auth/LOGOUT_SUCCESS';
  constant LOGOUT_FAIL (line 9) | const LOGOUT_FAIL = 'redux-example/auth/LOGOUT_FAIL';
  function reducer (line 15) | function reducer(state = initialState, action = {}) {
  function isLoaded (line 76) | function isLoaded(globalState) {
  function load (line 80) | function load() {
  function login (line 87) | function login(name) {
  function logout (line 98) | function logout() {

FILE: src/redux/modules/counter.js
  constant INCREMENT (line 1) | const INCREMENT = 'redux-example/counter/INCREMENT';
  function reducer (line 7) | function reducer(state = initialState, action = {}) {
  function increment (line 19) | function increment() {

FILE: src/redux/modules/info.js
  constant LOAD (line 1) | const LOAD = 'redux-example/LOAD';
  constant LOAD_SUCCESS (line 2) | const LOAD_SUCCESS = 'redux-example/LOAD_SUCCESS';
  constant LOAD_FAIL (line 3) | const LOAD_FAIL = 'redux-example/LOAD_FAIL';
  function info (line 9) | function info(state = initialState, action = {}) {
  function isLoaded (line 35) | function isLoaded(globalState) {
  function load (line 39) | function load() {

FILE: src/redux/modules/survey.js
  constant IS_VALID (line 1) | const IS_VALID = 'redux-example/survey/IS_VALID';
  constant IS_VALID_SUCCESS (line 2) | const IS_VALID_SUCCESS = 'redux-example/survey/IS_VALID_SUCCESS';
  constant IS_VALID_FAIL (line 3) | const IS_VALID_FAIL = 'redux-example/survey/IS_VALID_FAIL';
  function reducer (line 9) | function reducer(state = initialState, action = {}) {
  function isValidEmail (line 31) | function isValidEmail(data) {

FILE: src/redux/modules/widgets.js
  constant LOAD (line 1) | const LOAD = 'redux-example/widgets/LOAD';
  constant LOAD_SUCCESS (line 2) | const LOAD_SUCCESS = 'redux-example/widgets/LOAD_SUCCESS';
  constant LOAD_FAIL (line 3) | const LOAD_FAIL = 'redux-example/widgets/LOAD_FAIL';
  constant EDIT_START (line 4) | const EDIT_START = 'redux-example/widgets/EDIT_START';
  constant EDIT_STOP (line 5) | const EDIT_STOP = 'redux-example/widgets/EDIT_STOP';
  constant SAVE (line 6) | const SAVE = 'redux-example/widgets/SAVE';
  constant SAVE_SUCCESS (line 7) | const SAVE_SUCCESS = 'redux-example/widgets/SAVE_SUCCESS';
  constant SAVE_FAIL (line 8) | const SAVE_FAIL = 'redux-example/widgets/SAVE_FAIL';
  function reducer (line 16) | function reducer(state = initialState, action = {}) {
  function isLoaded (line 85) | function isLoaded(globalState) {
  function load (line 89) | function load() {
  function save (line 96) | function save(widget) {
  function editStart (line 106) | function editStart(id) {
  function editStop (line 110) | function editStop(id) {

FILE: src/routes.js
  function checkAuth (line 19) | function checkAuth() {

FILE: src/server.js
  function hydrateOnClient (line 74) | function hydrateOnClient() {

FILE: src/utils/validation.js
  function email (line 4) | function email(value) {
  function required (line 11) | function required(value) {
  function minLength (line 17) | function minLength(min) {
  function maxLength (line 25) | function maxLength(max) {
  function integer (line 33) | function integer(value) {
  function oneOf (line 39) | function oneOf(enumeration) {
  function match (line 47) | function match(field) {
  function createValidator (line 57) | function createValidator(rules) {

FILE: webpack/webpack-isomorphic-tools.js
  function is_bootstrap_style (line 51) | function is_bootstrap_style(name) {
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (175K chars).
[
  {
    "path": ".babelrc",
    "chars": 496,
    "preview": "{\n  \"presets\": [\"react\", \"es2015\", \"stage-0\"],\n\n  \"plugins\": [\n    \"transform-runtime\",\n    \"add-module-exports\",\n    \"t"
  },
  {
    "path": ".editorconfig",
    "chars": 167,
    "preview": "[*]\nindent_style = space\nend_of_line = lf\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_li"
  },
  {
    "path": ".eslintignore",
    "chars": 41,
    "preview": "webpack/*\nkarma.conf.js\ntests.webpack.js\n"
  },
  {
    "path": ".eslintrc",
    "chars": 939,
    "preview": "{ \"extends\": \"eslint-config-airbnb\",\n  \"env\": {\n    \"browser\": true,\n    \"node\": true,\n    \"mocha\": true\n  },\n  \"rules\":"
  },
  {
    "path": ".gitignore",
    "chars": 92,
    "preview": ".idea/\nnode_modules/\ndist/\n*.iml\nwebpack-assets.json\nwebpack-stats.json\nnpm-debug.log\n*.swp\n"
  },
  {
    "path": ".travis.yml",
    "chars": 226,
    "preview": "language: node_js\n\nnode_js:\n  - \"0.12\"\n  - \"4.0\"\n  - \"4\"\n  - \"5\"\n  - \"stable\"\n\nsudo: false\n\nbefore_script:\n  - export DI"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1918,
    "preview": "# Contributing Guidelines\n\nSome basic conventions for contributing to this project.\n\n### General\n\nPlease make sure that "
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Erik Rasmussen\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "README.md",
    "chars": 15950,
    "preview": "# ⚠️ DO NOT USE THIS!! ⚠️\n\nOnce upon a time, this repo helped a lot of people, but it's _waaaay_ out of date. It's now m"
  },
  {
    "path": "api/__tests__/api-test.js",
    "chars": 2200,
    "preview": "import {expect} from 'chai';\nimport {mapUrl} from '../utils/url';\n\ndescribe('mapUrl', () => {\n  it('extracts nothing if "
  },
  {
    "path": "api/actions/__tests__/loadInfo-test.js",
    "chars": 372,
    "preview": "import {expect} from 'chai';\nimport loadInfo from '../loadInfo';\nimport timekeeper from 'timekeeper';\n\ndescribe('loadInf"
  },
  {
    "path": "api/actions/__tests__/widget-load-test.js",
    "chars": 1124,
    "preview": "import {expect} from 'chai';\nimport load from '../widget/load';\nimport sinon from 'sinon';\n\ndescribe('widget load', () ="
  },
  {
    "path": "api/actions/__tests__/widget-update-test.js",
    "chars": 2065,
    "preview": "import {expect} from 'chai';\nimport update from '../widget/update';\nimport * as load from '../widget/load';\nimport sinon"
  },
  {
    "path": "api/actions/index.js",
    "chars": 214,
    "preview": "export loadInfo from './loadInfo';\nexport loadAuth from './loadAuth';\nexport login from './login';\nexport logout from '."
  },
  {
    "path": "api/actions/loadAuth.js",
    "chars": 94,
    "preview": "export default function loadAuth(req) {\n  return Promise.resolve(req.session.user || null);\n}\n"
  },
  {
    "path": "api/actions/loadInfo.js",
    "chars": 174,
    "preview": "export default function loadInfo() {\n  return new Promise((resolve) => {\n    resolve({\n      message: 'This came from th"
  },
  {
    "path": "api/actions/login.js",
    "chars": 144,
    "preview": "export default function login(req) {\n  const user = {\n    name: req.body.name\n  };\n  req.session.user = user;\n  return P"
  },
  {
    "path": "api/actions/logout.js",
    "chars": 176,
    "preview": "export default function logout(req) {\n  return new Promise((resolve) => {\n    req.session.destroy(() => {\n      req.sess"
  },
  {
    "path": "api/actions/survey/index.js",
    "chars": 33,
    "preview": "export isValid from './isValid';\n"
  },
  {
    "path": "api/actions/survey/isValid.js",
    "chars": 426,
    "preview": "export default function survey(req) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      const err"
  },
  {
    "path": "api/actions/widget/index.js",
    "chars": 58,
    "preview": "export update from './update';\nexport load from './load';\n"
  },
  {
    "path": "api/actions/widget/load.js",
    "chars": 793,
    "preview": "const initialWidgets = [\n  {id: 1, color: 'Red', sprocketCount: 7, owner: 'John'},\n  {id: 2, color: 'Taupe', sprocketCou"
  },
  {
    "path": "api/actions/widget/update.js",
    "chars": 895,
    "preview": "import load from './load';\n\nexport default function update(req) {\n  return new Promise((resolve, reject) => {\n    // wri"
  },
  {
    "path": "api/api.js",
    "chars": 2358,
    "preview": "import express from 'express';\nimport session from 'express-session';\nimport bodyParser from 'body-parser';\nimport confi"
  },
  {
    "path": "api/utils/url.js",
    "chars": 819,
    "preview": "export function mapUrl(availableActions = {}, url = []) {\n  const notFound = {action: null, params: []};\n\n  // test for "
  },
  {
    "path": "app.json",
    "chars": 493,
    "preview": "{\n  \"name\": \"react-redux-universal-hot-example\",\n  \"description\": \"Example of an isomorphic (universal) webapp using rea"
  },
  {
    "path": "bin/api.js",
    "chars": 273,
    "preview": "#!/usr/bin/env node\nif (process.env.NODE_ENV !== 'production') {\n  if (!require('piping')({\n    hook: true,\n    ignore: "
  },
  {
    "path": "bin/server.js",
    "chars": 903,
    "preview": "#!/usr/bin/env node\nrequire('../server.babel'); // babel registration (runtime transpilation for node)\nvar path = requir"
  },
  {
    "path": "circle.yml",
    "chars": 257,
    "preview": "machine:\n  node:\n    version: 4.0\n  environment:\n    CONTINUOUS_INTEGRATION: true\n\ndependencies:\n  cache_directories:\n  "
  },
  {
    "path": "docs/AddingAPage/AddingAPage.md",
    "chars": 3676,
    "preview": "# Adding A Hello Page\n\nThis guide adds a `/hello` page to the sample application by \nfollowing the existing outline.\n\n##"
  },
  {
    "path": "docs/AddingToHomePage/AddingToHomePage.md",
    "chars": 2612,
    "preview": "# Adding Hello, World as static text\n\nPrinting *Hello, World!* is a traditional task.  This guides you through adding th"
  },
  {
    "path": "docs/ApiConfig.md",
    "chars": 3700,
    "preview": "# Switching to a real API\n\nChances are, once you get comfortable with this setup, you'll want to hook into some existing"
  },
  {
    "path": "docs/Ducks.md",
    "chars": 118,
    "preview": "This document has found [another, hopefully permanent, home](https://github.com/erikras/ducks-modular-redux).\n\nQuack.\n"
  },
  {
    "path": "docs/ExploringTheDemoApp/ExploringTheDemoApp.md",
    "chars": 5223,
    "preview": "# Guide - Exploring the Demo App\n\nThis guide covers your first look and can be used even before installing software.  \n\n"
  },
  {
    "path": "docs/InlineStyles.md",
    "chars": 2516,
    "preview": "# Inline Styles\n\nIn the long term, CSS, LESS and SASS are dead. To keep this project on the bleeding edge, we should dro"
  },
  {
    "path": "docs/InstallingTheKit/InstallingTheKit.md",
    "chars": 4289,
    "preview": "# Installing the Kit\n\nAre you ready to install the kit and start playing with it?  No?  Good.  Let's begin.\n\nThere is no"
  },
  {
    "path": "karma.conf.js",
    "chars": 1708,
    "preview": "var webpack = require('webpack');\n\nmodule.exports = function (config) {\n  config.set({\n\n    browsers: ['PhantomJS'],\n\n  "
  },
  {
    "path": "package.json",
    "chars": 6120,
    "preview": "{\n  \"name\": \"react-redux-universal-hot-example\",\n  \"description\": \"Example of an isomorphic (universal) webapp using rea"
  },
  {
    "path": "server.babel.js",
    "chars": 316,
    "preview": "//  enable runtime transpilation to use ES6/7 in node\n\nvar fs = require('fs');\n\nvar babelrc = fs.readFileSync('./.babelr"
  },
  {
    "path": "src/client.js",
    "chars": 2125,
    "preview": "/**\n * THIS IS THE ENTRY POINT FOR THE CLIENT, JUST LIKE server.js IS THE ENTRY POINT FOR THE SERVER.\n */\nimport 'babel-"
  },
  {
    "path": "src/components/CounterButton/CounterButton.js",
    "chars": 796,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connectMultireducer} from 'multireducer';\nimport {increment} "
  },
  {
    "path": "src/components/GithubButton/GithubButton.js",
    "chars": 896,
    "preview": "import React from 'react';\n\nconst GithubButton = (props) => {\n  const {user, repo, type, width, height, count, large} = "
  },
  {
    "path": "src/components/InfoBar/InfoBar.js",
    "chars": 970,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {bindActionCreators} from 'redux';\nimport {connect} from 'reac"
  },
  {
    "path": "src/components/InfoBar/InfoBar.scss",
    "chars": 67,
    "preview": ".infoBar {\n  font-variant: italics;\n}\n\n.time {\n  margin: 0 30px;\n}\n"
  },
  {
    "path": "src/components/MiniInfoBar/MiniInfoBar.js",
    "chars": 481,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connect} from 'react-redux';\n\n@connect(state => ({ time: stat"
  },
  {
    "path": "src/components/SurveyForm/SurveyForm.js",
    "chars": 4829,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {reduxForm} from 'redux-form';\nimport {connect} from 'react-re"
  },
  {
    "path": "src/components/SurveyForm/SurveyForm.scss",
    "chars": 659,
    "preview": ".inputGroup {\n  position: relative;\n}\n\n.flags {\n  position: absolute;\n  right: 20px;\n  top: 7px;\n  & > * {\n    margin: 0"
  },
  {
    "path": "src/components/SurveyForm/surveyValidation.js",
    "chars": 343,
    "preview": "import memoize from 'lru-memoize';\nimport {createValidator, required, maxLength, email} from 'utils/validation';\n\nconst "
  },
  {
    "path": "src/components/WidgetForm/WidgetForm.js",
    "chars": 3084,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from"
  },
  {
    "path": "src/components/WidgetForm/widgetValidation.js",
    "chars": 356,
    "preview": "import {createValidator, required, maxLength, integer, oneOf} from 'utils/validation';\n\nexport const colors = ['Blue', '"
  },
  {
    "path": "src/components/__tests__/InfoBar-test.js",
    "chars": 1510,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport {renderIntoDocument} from 'react-addons-test-utils';"
  },
  {
    "path": "src/components/index.js",
    "chars": 428,
    "preview": "/**\n *  Point of contact for component modules\n *\n *  ie: import { CounterButton, InfoBar } from 'components';\n *\n */\n\ne"
  },
  {
    "path": "src/config.js",
    "chars": 1330,
    "preview": "require('babel-polyfill');\n\nconst environment = {\n  development: {\n    isProduction: false\n  },\n  production: {\n    isPr"
  },
  {
    "path": "src/containers/About/About.js",
    "chars": 1584,
    "preview": "import React, {Component} from 'react';\nimport Helmet from 'react-helmet';\nimport { MiniInfoBar } from 'components';\n\nex"
  },
  {
    "path": "src/containers/App/App.js",
    "chars": 4345,
    "preview": "import React, { Component, PropTypes } from 'react';\nimport { connect } from 'react-redux';\nimport { IndexLink } from 'r"
  },
  {
    "path": "src/containers/App/App.scss",
    "chars": 433,
    "preview": ".app {\n  .brand {\n    position: absolute;\n    $size: 40px;\n    top: 5px;\n    left: 5px;\n    display: inline-block;\n    b"
  },
  {
    "path": "src/containers/Chat/Chat.js",
    "chars": 1818,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connect} from 'react-redux';\n\n@connect(\n  state => ({user: st"
  },
  {
    "path": "src/containers/Chat/Chat.scss",
    "chars": 182,
    "preview": ".chat {\n  input {\n    padding: 5px 10px;\n    border-radius: 5px;\n    border: 1px solid #ccc;\n  }\n  form {\n    margin: 30"
  },
  {
    "path": "src/containers/DevTools/DevTools.js",
    "chars": 342,
    "preview": "import React from 'react';\nimport { createDevTools } from 'redux-devtools';\nimport LogMonitor from 'redux-devtools-log-m"
  },
  {
    "path": "src/containers/Home/Home.js",
    "chars": 9656,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport { CounterButton, GithubButton } fr"
  },
  {
    "path": "src/containers/Home/Home.scss",
    "chars": 854,
    "preview": "@import \"../../theme/variables.scss\";\n\n.home {\n  dd {\n    margin-bottom: 15px;\n  }\n}\n.masthead {\n  background: #2d2d2d;\n"
  },
  {
    "path": "src/containers/Login/Login.js",
    "chars": 1638,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connect} from 'react-redux';\nimport Helmet from 'react-helmet"
  },
  {
    "path": "src/containers/Login/Login.scss",
    "chars": 188,
    "preview": ".loginPage {\n  input {\n    padding: 5px 10px;\n    border-radius: 5px;\n    border: 1px solid #ccc;\n  }\n  form {\n    margi"
  },
  {
    "path": "src/containers/LoginSuccess/LoginSuccess.js",
    "chars": 1120,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connect} from 'react-redux';\nimport * as authActions from 're"
  },
  {
    "path": "src/containers/NotFound/NotFound.js",
    "chars": 219,
    "preview": "import React from 'react';\n\nexport default function NotFound() {\n  return (\n    <div className=\"container\">\n      <h1>Do"
  },
  {
    "path": "src/containers/Pagination/Pagination.jsx",
    "chars": 2082,
    "preview": "import React from 'react';\nimport Helmet from 'react-helmet';\nimport { List } from 'immutable';\nimport { connect } from "
  },
  {
    "path": "src/containers/Pagination/Pagination.scss",
    "chars": 25,
    "preview": "a {\n  cursor: pointer;\n}\n"
  },
  {
    "path": "src/containers/Pagination/violet.min.scss",
    "chars": 12127,
    "preview": "/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webki"
  },
  {
    "path": "src/containers/Survey/Survey.js",
    "chars": 2408,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport {connect} from 'react-redux';\nimport Helmet from 'react-helmet"
  },
  {
    "path": "src/containers/Widgets/Widgets.js",
    "chars": 4000,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport Helmet from 'react-helmet';\nimport {connect} from 'react-redux"
  },
  {
    "path": "src/containers/Widgets/Widgets.scss",
    "chars": 450,
    "preview": ".widgets {\n  .refreshBtn {\n    margin-left: 20px;\n  }\n  .idCol {\n    width: 5%;\n  }\n  .colorCol {\n    width: 20%;\n  }\n  "
  },
  {
    "path": "src/containers/index.js",
    "chars": 392,
    "preview": "export App from './App/App';\nexport Chat from './Chat/Chat';\nexport Home from './Home/Home';\nexport Widgets from './Widg"
  },
  {
    "path": "src/helpers/ApiClient.js",
    "chars": 1480,
    "preview": "import superagent from 'superagent';\nimport config from '../config';\n\nconst methods = ['get', 'post', 'put', 'patch', 'd"
  },
  {
    "path": "src/helpers/Html.js",
    "chars": 2454,
    "preview": "import React, {Component, PropTypes} from 'react';\nimport ReactDOM from 'react-dom/server';\nimport serialize from 'seria"
  },
  {
    "path": "src/redux/create.js",
    "chars": 1408,
    "preview": "import { createStore as _createStore, applyMiddleware, compose } from 'redux';\nimport createMiddleware from './middlewar"
  },
  {
    "path": "src/redux/middleware/clientMiddleware.js",
    "chars": 812,
    "preview": "export default function clientMiddleware(client) {\n  return ({dispatch, getState}) => {\n    return next => action => {\n "
  },
  {
    "path": "src/redux/modules/auth.js",
    "chars": 2224,
    "preview": "const LOAD = 'redux-example/auth/LOAD';\nconst LOAD_SUCCESS = 'redux-example/auth/LOAD_SUCCESS';\nconst LOAD_FAIL = 'redux"
  },
  {
    "path": "src/redux/modules/counter.js",
    "chars": 392,
    "preview": "const INCREMENT = 'redux-example/counter/INCREMENT';\n\nconst initialState = {\n  count: 0\n};\n\nexport default function redu"
  },
  {
    "path": "src/redux/modules/info.js",
    "chars": 894,
    "preview": "const LOAD = 'redux-example/LOAD';\nconst LOAD_SUCCESS = 'redux-example/LOAD_SUCCESS';\nconst LOAD_FAIL = 'redux-example/L"
  },
  {
    "path": "src/redux/modules/reducer.js",
    "chars": 650,
    "preview": "import { combineReducers } from 'redux';\nimport multireducer from 'multireducer';\nimport { routerReducer } from 'react-r"
  },
  {
    "path": "src/redux/modules/survey.js",
    "chars": 963,
    "preview": "const IS_VALID = 'redux-example/survey/IS_VALID';\nconst IS_VALID_SUCCESS = 'redux-example/survey/IS_VALID_SUCCESS';\ncons"
  },
  {
    "path": "src/redux/modules/widgets.js",
    "chars": 2581,
    "preview": "const LOAD = 'redux-example/widgets/LOAD';\nconst LOAD_SUCCESS = 'redux-example/widgets/LOAD_SUCCESS';\nconst LOAD_FAIL = "
  },
  {
    "path": "src/routes.js",
    "chars": 1486,
    "preview": "import React from 'react';\nimport {IndexRoute, Route} from 'react-router';\nimport { isLoaded as isAuthLoaded, load as lo"
  },
  {
    "path": "src/server.js",
    "chars": 3900,
    "preview": "import Express from 'express';\nimport React from 'react';\nimport ReactDOM from 'react-dom/server';\nimport config from '."
  },
  {
    "path": "src/theme/bootstrap.config.js",
    "chars": 1555,
    "preview": "/**\n * Bootstrap configuration for bootstrap-sass-loader\n *\n * Scripts are disabled to not load jQuery.\n * If you depend"
  },
  {
    "path": "src/theme/bootstrap.config.prod.js",
    "chars": 258,
    "preview": "const bootstrapConfig = require('./bootstrap.config.js');\nconst ExtractTextPlugin = require('extract-text-webpack-plugin"
  },
  {
    "path": "src/theme/bootstrap.overrides.scss",
    "chars": 341,
    "preview": "/**\n * Override Bootstrap styles that you can't modify via variables here.\n *\n */\n\n.navbar-brand {\n  position: relative;"
  },
  {
    "path": "src/theme/font-awesome.config.js",
    "chars": 297,
    "preview": "/**\n * Configuration file for font-awesome-webpack\n *\n * In order to keep the bundle size low in production,\n * disable "
  },
  {
    "path": "src/theme/font-awesome.config.less",
    "chars": 98,
    "preview": "/**\n * Configuration file for font-awesome-webpack\n *\n */\n\n// Example:\n// @fa-border-color: #ddd;\n"
  },
  {
    "path": "src/theme/font-awesome.config.prod.js",
    "chars": 267,
    "preview": "const fontAwesomeConfig = require('./font-awesome.config.js');\nconst ExtractTextPlugin = require('extract-text-webpack-p"
  },
  {
    "path": "src/theme/variables.scss",
    "chars": 469,
    "preview": "/**\n *  Define scss variables here.\n *\n *  Available options for Bootstrap:\n *  http://getbootstrap.com/customize/\n *\n *"
  },
  {
    "path": "src/utils/validation.js",
    "chars": 1688,
    "preview": "const isEmpty = value => value === undefined || value === null || value === '';\nconst join = (rules) => (value, data) =>"
  },
  {
    "path": "tests.webpack.js",
    "chars": 93,
    "preview": "var context = require.context('./src', true, /-test\\.js$/);\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "webpack/dev.config.js",
    "chars": 4344,
    "preview": "require('babel-polyfill');\n\n// Webpack config for development\nvar fs = require('fs');\nvar path = require('path');\nvar we"
  },
  {
    "path": "webpack/prod.config.js",
    "chars": 3176,
    "preview": "require('babel-polyfill');\n\n// Webpack config for creating the production bundle.\nvar path = require('path');\nvar webpac"
  },
  {
    "path": "webpack/webpack-dev-server.js",
    "chars": 881,
    "preview": "var Express = require('express');\nvar webpack = require('webpack');\n\nvar config = require('../src/config');\nvar webpackC"
  },
  {
    "path": "webpack/webpack-isomorphic-tools.js",
    "chars": 3598,
    "preview": "var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');\n\n// see this link for more info on what a"
  }
]

About this extraction

This page contains the full source code of the erikras/react-redux-universal-hot-example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (159.3 KB), approximately 44.1k tokens, and a symbol index with 106 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!