├── CHANGELOG.md
├── source
├── server
│ ├── entry.server.js
│ ├── auto
│ │ ├── index.js
│ │ ├── deleteTodos.js
│ │ └── createTodos.js
│ ├── db.js
│ └── app.js
├── client
│ ├── constants
│ │ └── config.js
│ ├── middleware
│ │ ├── index.js
│ │ └── actionLogger.js
│ ├── entry.js
│ ├── epics
│ │ ├── index.js
│ │ └── todos.js
│ ├── actions
│ │ ├── app.js
│ │ └── todos.js
│ ├── db.js
│ ├── containers
│ │ ├── TodoList
│ │ │ ├── style.css
│ │ │ └── index.js
│ │ └── App
│ │ │ └── index.js
│ ├── reducers
│ │ ├── index.js
│ │ ├── todos.js
│ │ └── app.js
│ ├── store
│ │ ├── index.js
│ │ ├── configureStore.prod.js
│ │ └── configureStore.dev.js
│ ├── components
│ │ ├── Footer
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── TodoItem
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ ├── TodoAdd
│ │ │ ├── index.js
│ │ │ └── style.css
│ │ └── HtmlHead
│ │ │ └── index.js
│ ├── index.prod.js
│ ├── index.dev.js
│ └── styles
│ │ └── app.css
└── utils
│ └── createConstants.js
├── .eslintignore
├── static
└── images
│ └── preview.png
├── .gitignore
├── config
├── page.js
├── db.js
├── webpack
│ ├── devProps.js
│ ├── webpack.config.client.prod.js
│ ├── webpack.config.client.dev.js
│ ├── webpack.config.server.js
│ └── webpack.config.client.js
└── dirs.js
├── .babelrc
├── bin
├── prod.js
└── dev.js
├── LICENSE
├── .eslintrc
├── README.md
└── package.json
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.4.0
2 | - Updated to Horizon 2
3 |
--------------------------------------------------------------------------------
/source/server/entry.server.js:
--------------------------------------------------------------------------------
1 | import app from './app'
2 |
3 | app.run()
4 |
--------------------------------------------------------------------------------
/source/client/constants/config.js:
--------------------------------------------------------------------------------
1 | export const url = 'https://localhost:3000/'
2 |
--------------------------------------------------------------------------------
/source/server/auto/index.js:
--------------------------------------------------------------------------------
1 | import './deleteTodos'
2 | import './createTodos'
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | [
2 | "*.js",
3 | "meteor_core/**/*.js",
4 | "config/**/*.js"
5 | ]
6 |
--------------------------------------------------------------------------------
/static/images/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casoetan/react-horizon/HEAD/static/images/preview.png
--------------------------------------------------------------------------------
/source/client/middleware/index.js:
--------------------------------------------------------------------------------
1 | import actionLogger from './actionLogger'
2 |
3 | export {
4 | actionLogger
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules*
2 | npm-debug.log
3 | stats.json
4 | .idea
5 | .DS_STORE*
6 | .build
7 | .vscode
8 | horizon-cert.pem
9 | horizon-key.pem
--------------------------------------------------------------------------------
/source/server/db.js:
--------------------------------------------------------------------------------
1 | import rethinkdbdash from 'rethinkdbdash'
2 | import config from '../../config/db'
3 |
4 | export const r = rethinkdbdash(config)
5 |
6 |
--------------------------------------------------------------------------------
/config/page.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'React Horizon',
3 | port: 3000,
4 | token_secret: 'react-horizon-redux-app-key' // change to top secret
5 | };
6 |
--------------------------------------------------------------------------------
/source/utils/createConstants.js:
--------------------------------------------------------------------------------
1 | export default (...args) => args.reduce((acc, name) => {
2 | acc[name] = name; // eslint-disable-line
3 | return acc;
4 | }, []);
5 |
--------------------------------------------------------------------------------
/config/db.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | db: 'react_horizon',
3 | servers: [
4 | {
5 | host: '127.0.0.1',
6 | port: 28015
7 | }
8 | ]
9 | };
10 |
--------------------------------------------------------------------------------
/source/client/entry.js:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV === 'production') {
2 | module.exports = require('./index.prod');
3 | } else {
4 | module.exports = require('./index.dev');
5 | }
6 |
--------------------------------------------------------------------------------
/config/webpack/devProps.js:
--------------------------------------------------------------------------------
1 | const host = '127.0.0.1';
2 | const webpackPort = 9095;
3 |
4 | module.exports = {
5 | host,
6 | webpackPort,
7 | baseUrl: `https://${host}:${webpackPort}`
8 | };
9 |
--------------------------------------------------------------------------------
/source/client/epics/index.js:
--------------------------------------------------------------------------------
1 | import { combineEpics } from 'redux-observable'
2 |
3 | import todosEpic from './todos'
4 |
5 | const epics = [...todosEpic]
6 |
7 | export default combineEpics(...epics)
--------------------------------------------------------------------------------
/source/client/actions/app.js:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux-actions'
2 |
3 | export const requestPostTodo = createAction('show notification')
4 | export const requestDeleteTodo = createAction('clear notification')
5 |
--------------------------------------------------------------------------------
/source/client/db.js:
--------------------------------------------------------------------------------
1 | import Horizon from '@horizon/client'
2 |
3 | const hz = Horizon({
4 | secure: true
5 | })
6 |
7 | hz.connect()
8 |
9 | export default hz
10 |
11 | export const todoHz = hz('todos')
12 |
--------------------------------------------------------------------------------
/source/client/containers/TodoList/style.css:
--------------------------------------------------------------------------------
1 | .main {
2 | position: relative;
3 | z-index: 2;
4 | border-top: 1px solid #e6e6e6;
5 | }
6 |
7 | .todoList {
8 | margin: 0;
9 | padding: 0;
10 | list-style: none;
11 | }
12 |
--------------------------------------------------------------------------------
/source/client/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import app from './app'
4 | import todos from './todos'
5 |
6 | const reducers = {
7 | app,
8 | todos
9 | }
10 |
11 | export default combineReducers(reducers)
--------------------------------------------------------------------------------
/source/client/store/index.js:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV === 'production') {
2 | module.exports = require('./configureStore.prod') // eslint-disable-line
3 | } else {
4 | module.exports = require('./configureStore.dev') // eslint-disable-line
5 | }
6 |
--------------------------------------------------------------------------------
/config/dirs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 |
4 | module.exports = {
5 | webpack: path.join(__dirname, 'webpack'),
6 | source: path.join(__dirname, '../source')
7 | };
8 |
9 | module.exports.assets = path.join(module.exports.webpack, 'assets');
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0",
5 | "react"
6 | ],
7 | "plugins": [
8 | "react-hot-loader/babel",
9 | "transform-runtime",
10 | "system-import-transformer"
11 | ],
12 | "only": [
13 | "source/**/*.js"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/source/client/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import style from './style'
4 |
5 | export default () => (
6 |
10 | )
11 |
--------------------------------------------------------------------------------
/source/client/actions/todos.js:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux-actions'
2 |
3 | export const requestTodos = createAction('request todos')
4 | export const requestPostTodo = createAction('request post todo')
5 | export const requestDeleteTodo = createAction('request delete todo')
6 |
7 | export const getTodosSuccessful = createAction('watch todos successful')
8 |
--------------------------------------------------------------------------------
/source/client/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions'
2 |
3 | const initialState = []
4 |
5 | export default handleActions({
6 | 'watch todos successful'(state, action) {
7 | if (Array.isArray(action.payload)) {
8 | return action.payload
9 | } else {
10 | return state
11 | }
12 | }
13 | }, initialState)
14 |
--------------------------------------------------------------------------------
/source/client/components/Footer/style.css:
--------------------------------------------------------------------------------
1 | .info {
2 | margin: 65px auto 0;
3 | color: #bfbfbf;
4 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
5 | text-align: center;
6 | }
7 |
8 | .info p {
9 | line-height: 1;
10 | }
11 |
12 | .info a {
13 | color: inherit;
14 | text-decoration: none;
15 | font-weight: 400;
16 | }
17 |
18 | .info a:hover {
19 | text-decoration: underline;
20 | }
--------------------------------------------------------------------------------
/source/server/auto/deleteTodos.js:
--------------------------------------------------------------------------------
1 | import later from 'later'
2 | import { r } from '../db'
3 |
4 | /**
5 | * Delete all todos every 10 minutes.
6 | */
7 |
8 | const every5minutes = later.parse.text('every 5 minutes')
9 |
10 | const deleteTodos = () => {
11 | r.table('hz_collections').get('todos').run()
12 | .then(function(result) {
13 | r.table('todos').delete().run()
14 | })
15 | }
16 |
17 | later.setInterval(deleteTodos, every5minutes)
18 |
--------------------------------------------------------------------------------
/source/client/index.prod.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import { Provider } from 'react-redux'
5 |
6 | import App from './containers/App'
7 |
8 | import configure from './store'
9 | const store = configure()
10 |
11 | // Define the target container for our application
12 | const rootElement = document.getElementById('root')
13 |
14 | // Render application to target container
15 | ReactDOM.render(
16 |
17 |
18 | ,
19 | rootElement
20 | )
21 |
--------------------------------------------------------------------------------
/source/client/components/TodoItem/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import style from './style'
4 |
5 | class TodoItem extends Component {
6 | render() {
7 | const { todo, deleteTodoAction } = this.props
8 | return (
9 |
10 |
13 |
15 | )
16 | }
17 | }
18 |
19 | export default TodoItem
20 |
--------------------------------------------------------------------------------
/source/client/middleware/actionLogger.js:
--------------------------------------------------------------------------------
1 | export default () => next => action => {
2 | if (typeof(action.type) !== 'undefined') {
3 | if (action.type === 'BATCHING_REDUCER.BATCH') {
4 | action.payload.forEach(
5 | a => {
6 | console.log(`%c Action Type: ${a.type} `, 'background: #c6285c; color: #fff', a); // eslint-disable-line
7 | }
8 | );
9 | } else {
10 | console.log(`%c Action Type: ${action.type}, `, 'background: #c6285c; color: #fff', action) // eslint-disable-line
11 | }
12 | }
13 |
14 | return next(action);
15 | }
16 |
--------------------------------------------------------------------------------
/source/client/store/configureStore.prod.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux'
2 |
3 | import { createEpicMiddleware } from 'redux-observable'
4 |
5 | import rootReducer from '../reducers'
6 | import rootEpic from '../epics'
7 |
8 | export default function configure(initialState) {
9 | const middleware = [
10 | createEpicMiddleware(rootEpic),
11 | ]
12 |
13 | const enhancer = compose(
14 | applyMiddleware(...middleware)
15 | )
16 |
17 | const store = createStore(rootReducer, initialState, enhancer)
18 |
19 | return store
20 | }
21 |
--------------------------------------------------------------------------------
/config/webpack/webpack.config.client.prod.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const config = require('./webpack.config.client');
3 | const _ = require('lodash');
4 |
5 | module.exports = _.assign(_.clone(config), {
6 | devtool: 'eval',
7 | plugins: (config.plugins || []).concat([
8 | new webpack.DefinePlugin({
9 | 'process.env.NODE_ENV': JSON.stringify('production')
10 | }),
11 | new webpack.NoErrorsPlugin(),
12 | new webpack.optimize.DedupePlugin(),
13 | new webpack.optimize.UglifyJsPlugin({
14 | compress: { warnings: false }
15 | }),
16 | new webpack.optimize.AggressiveMergingPlugin(),
17 | new webpack.optimize.OccurenceOrderPlugin(true)
18 | ])
19 | });
20 |
--------------------------------------------------------------------------------
/source/client/reducers/app.js:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions'
2 |
3 | const initialState = {
4 | alert: {
5 | show: false,
6 | message: ''
7 | },
8 | error: {
9 | state: false,
10 | stack: null
11 | }
12 | }
13 |
14 | export default handleActions({
15 |
16 | 'show notification'(state, action) {
17 | return Object.assign({}, state, {
18 | error: {
19 | state: action.error,
20 | stack: action.payload.stack
21 | },
22 | alert: {
23 | show: true,
24 | message: action.payload.message
25 | }
26 | })
27 | },
28 |
29 | 'clear notification'(state) {
30 | return Object.assign({}, state, {
31 | alert: {
32 | show: false,
33 | message: ''
34 | }
35 | })
36 | }
37 |
38 | }, initialState)
39 |
--------------------------------------------------------------------------------
/source/client/containers/App/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import { connect } from 'react-redux'
4 |
5 | import TodoList from '../TodoList'
6 |
7 | import Footer from '../../components/Footer'
8 | import HtmlHead from '../../components/HtmlHead'
9 |
10 | import styles from 'styles/app'
11 |
12 | class App extends Component {
13 | render() {
14 | const { app } = this.props
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
27 | const mapStateToProps = (state) => {
28 | return {
29 | app: state.app
30 | }
31 | }
32 |
33 | export default connect(
34 | mapStateToProps
35 | )(App)
36 |
--------------------------------------------------------------------------------
/source/client/components/TodoItem/style.css:
--------------------------------------------------------------------------------
1 | .list {
2 | position: relative;
3 | font-size: 24px;
4 | border-bottom: 1px solid #ededed;
5 | }
6 |
7 | .list:last-child {
8 | border-bottom: none;
9 | }
10 |
11 | .listLabel {
12 | white-space: pre-line;
13 | word-break: break-all;
14 | padding: 15px 60px 15px 15px;
15 | margin-left: 45px;
16 | display: block;
17 | line-height: 1.2;
18 | transition: color 0.4s;
19 | }
20 |
21 | .destroyButton {
22 | /*display: none;*/
23 | position: absolute;
24 | top: 0;
25 | right: 10px;
26 | bottom: 0;
27 | width: 40px;
28 | height: 40px;
29 | margin: auto 0;
30 | font-size: 30px;
31 | color: #cc9a9a;
32 | margin-bottom: 11px;
33 | transition: color 0.2s ease-out;
34 | }
35 |
36 | .destroyButton:hover {
37 | display: inline;
38 | color: #af5b5e;
39 | }
40 |
41 | .destroyButton:after {
42 | content: '×';
43 | }
44 |
--------------------------------------------------------------------------------
/bin/prod.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | 'use strict';
3 | require('shelljs/global');
4 |
5 | const path = require('path');
6 | const dirs = require('../config/dirs');
7 | const webpack = require('webpack');
8 |
9 | const clientConfig = require(path.join(dirs.webpack, 'webpack.config.client.prod.js'));
10 | const serverConfig = require(path.join(dirs.webpack, 'webpack.config.server.js'));
11 |
12 | function compileClient() {
13 | const clientCompiler = webpack(clientConfig);
14 | const serverCompiler = webpack(serverConfig);
15 |
16 | // generate bundle
17 | clientCompiler.run((err) => {
18 | if (err) {
19 | console.log(err); // eslint-disable-line
20 | } else {
21 | serverCompiler.run((errServer) => {
22 | if (errServer) {
23 | console.log(errServer); // eslint-disable-line
24 | }
25 | });
26 | }
27 | });
28 | }
29 |
30 | compileClient();
31 |
--------------------------------------------------------------------------------
/source/client/components/TodoAdd/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import style from './style'
4 |
5 | class TodoAdd extends Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | text: ''
10 | }
11 | }
12 |
13 | render() {
14 | const { addTodoAction } = this.props
15 | return (
16 | {
25 | if (e.key === 'Enter') {
26 | addTodoAction(this.state.text)
27 | this.setState({text: ''})
28 | }
29 | } }
30 | />
31 | )
32 | }
33 |
34 | handleTextChange(event) {
35 | this.setState({ text: event.target.value })
36 | }
37 | }
38 |
39 | export default TodoAdd
40 |
--------------------------------------------------------------------------------
/bin/dev.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | 'use strict';
3 | require('shelljs/global');
4 |
5 | const path = require('path');
6 | const dirs = require('../config/dirs');
7 | const webpack = require('webpack');
8 | const WebpackDevServer = require('webpack-dev-server');
9 |
10 | const clientConfig = require(path.join(dirs.webpack, 'webpack.config.client.dev.js'));
11 |
12 | function webpackServerReady() {
13 | console.log(`\nWebpack Dev Server listening on port ${clientConfig.devServer.port}`); // eslint-disable-line
14 |
15 | const app = require('../source/server/app.js').default;
16 |
17 | app.run();
18 | }
19 |
20 | function compileClient() {
21 | const clientCompiler = webpack(clientConfig);
22 |
23 | const clientDevServer = new WebpackDevServer(
24 | clientCompiler,
25 | clientConfig.devServer
26 | );
27 |
28 | clientDevServer.listen(
29 | clientConfig.devServer.port,
30 | clientConfig.devServer.host,
31 | webpackServerReady
32 | );
33 | }
34 |
35 | compileClient();
36 |
--------------------------------------------------------------------------------
/source/server/auto/createTodos.js:
--------------------------------------------------------------------------------
1 | import later from 'later'
2 | import { r } from '../db'
3 |
4 | const todos = [
5 | 'Getting started with React.',
6 | 'Getting to know Redux.',
7 | 'Redux Observable brings it all together.',
8 | 'Got to clean up this',
9 | 'Add authentication.',
10 | 'Create a short tutorial.',
11 | 'Upgrade to horizon 2 at first light.',
12 | 'Creat a React Native Boilerplate as well.',
13 | 'Thank Lovli.js.',
14 | 'Thank Redux Observables particularly JayPhelps.',
15 | 'Done with GoT Season 6.',
16 | 'Now to House of Cards.',
17 | 'Spend some time with the Wife.',
18 | 'Help with the young boy.',
19 | 'Play more. Work can wait.',
20 | 'Go to church!'
21 | ]
22 |
23 | const createRandomTodo = () => {
24 | const rand = Math.round(Math.random() * todos.length - 1, 0)
25 | r.table('hz_collections').get('todos').run()
26 | .then(function(result) {
27 | r.table('todos').insert({ text: todos[rand], $hz_v$: 1 }).run()
28 | })
29 | }
30 |
31 | const every1minutes = later.parse.text('every 1 minute')
32 |
33 | later.setInterval(createRandomTodo, every1minutes)
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Charles Soetan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/source/client/store/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux'
2 |
3 | import invariant from 'redux-immutable-state-invariant'
4 | import devTools from 'remote-redux-devtools'
5 |
6 | import { createEpicMiddleware } from 'redux-observable'
7 |
8 | import { actionLogger } from '../middleware'
9 |
10 | import rootReducer from '../reducers'
11 | import rootEpic from '../epics'
12 |
13 | export default function configure(initialState) {
14 | const middleware = [
15 | invariant(),
16 | createEpicMiddleware(rootEpic),
17 | actionLogger
18 | ]
19 |
20 | const enhancer = compose(
21 | applyMiddleware(...middleware),
22 | devTools({
23 | name: 'react_horizon',
24 | realtime: true,
25 | hostname: 'localhost',
26 | port: 8000,
27 | maxAge: 30,
28 | filters: { blacklist: ['EFFECT_RESOLVED'] }
29 | })
30 | )
31 |
32 | const store = createStore(rootReducer, initialState, enhancer)
33 |
34 | if (module.hot) {
35 | module.hot.accept('../reducers', () => {
36 | const nextRootReducer = require('../reducers/index')
37 | store.replaceReducer(nextRootReducer)
38 | })
39 | }
40 |
41 | return store
42 | }
43 |
--------------------------------------------------------------------------------
/source/client/components/TodoAdd/style.css:
--------------------------------------------------------------------------------
1 | .button {
2 | display: block;
3 | box-sizing: border-box;
4 |
5 | width: 100%;
6 | padding: 12px;
7 |
8 | color: #fff;
9 | background-color: #961842;
10 |
11 | transition: all 250ms ease;
12 |
13 | &:hover {
14 | cursor: pointer;
15 |
16 | background-color: #d03117;
17 | }
18 | }
19 |
20 | .input {
21 | width: 100%;
22 | padding: 12px;
23 | box-sizing: border-box;
24 |
25 | outline: none;
26 |
27 | border: 1px solid #f2f2f2;
28 | transition: all 250ms ease;
29 |
30 | &:focus, &:active {
31 | border-color: #961842;
32 | }
33 | }
34 |
35 |
36 | .addTodo,
37 | .edit {
38 | position: relative;
39 | margin: 0;
40 | width: 100%;
41 | font-size: 24px;
42 | font-family: inherit;
43 | font-weight: inherit;
44 | line-height: 1.4em;
45 | border: 0;
46 | outline: none;
47 | color: inherit;
48 | padding: 6px;
49 | border: 1px solid #999;
50 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
51 | box-sizing: border-box;
52 | -webkit-font-smoothing: antialiased;
53 | -moz-font-smoothing: antialiased;
54 | font-smoothing: antialiased;
55 | }
56 |
57 | .addTodo {
58 | padding: 16px 16px 16px 60px;
59 | border: none;
60 | background: rgba(0, 0, 0, 0.003);
61 | box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
62 | }
--------------------------------------------------------------------------------
/source/client/index.dev.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import { Provider } from 'react-redux'
5 |
6 | import { AppContainer } from 'react-hot-loader'
7 | import Redbox from "redbox-react";
8 |
9 | import App from './containers/App'
10 |
11 | import configure from './store'
12 | const store = configure()
13 |
14 | // Define the target container for our application
15 | const rootElement = document.getElementById('root')
16 |
17 | // Render application to target container
18 | ReactDOM.render(
19 |
20 |
21 |
22 |
23 | ,
24 | rootElement
25 | )
26 |
27 | // // react-hot-loader 3 specific - rerender AppContainer
28 | // // in case of problems with react-router, check this issue:
29 | // // https://github.com/gaearon/react-hot-loader/issues/249
30 | if (module.hot) {
31 | module.hot.accept('./containers/App', () => {
32 | // If you use Webpack 2 in ES modules mode, you can
33 | // use here rather than require() a .
34 | const NextApp = require('./containers/App/index').default;
35 | ReactDOM.render(
36 |
37 |
38 |
39 |
40 | ,
41 | rootElement
42 | )
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/source/client/epics/todos.js:
--------------------------------------------------------------------------------
1 | import Rx, { Observable } from 'rxjs'
2 | import { todoHz } from '../db'
3 |
4 | import {
5 | getTodosSuccessful
6 | } from '../actions/todos'
7 |
8 | import {
9 | showNotification
10 | } from '../actions/app'
11 |
12 | const watchTodosEpic = action$ =>
13 | action$
14 | .ofType('request todos')
15 | .merge(
16 | todoHz
17 | .watch()
18 | .catch(err => Observable.of(showNotification(err))) // ideally catch any errors and dispatch a notification
19 | )
20 | .do(todos => delete todos.type)
21 | .map(todos => getTodosSuccessful(todos))
22 |
23 | const addTodoEpic = action$ =>
24 | action$
25 | .ofType('request post todo')
26 | .mergeMap(action =>
27 | todoHz
28 | .store(action.payload)
29 | .ignoreElements()
30 | .takeUntil(action$.ofType('cancel requests'))
31 | .catch(err => Observable.of(showNotification(err))) // ideally catch any errors and dispatch a notification
32 | )
33 |
34 | const deleteTodoEpic = action$ =>
35 | action$
36 | .ofType('request delete todo')
37 | .mergeMap(action =>
38 | todoHz
39 | .remove(action.payload)
40 | .ignoreElements()
41 | .takeUntil(action$.ofType('cancel requests'))
42 | .catch(err => Observable.of(showNotification(err))) // ideally catch any errors and dispatch a notification
43 | )
44 |
45 | export default [watchTodosEpic, addTodoEpic, deleteTodoEpic]
46 |
--------------------------------------------------------------------------------
/source/client/styles/app.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | button {
8 | margin: 0;
9 | padding: 0;
10 | border: 0;
11 | background: none;
12 | font-size: 100%;
13 | vertical-align: baseline;
14 | font-family: inherit;
15 | font-weight: inherit;
16 | color: inherit;
17 | -webkit-appearance: none;
18 | appearance: none;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-font-smoothing: antialiased;
21 | font-smoothing: antialiased;
22 | }
23 |
24 | body {
25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
26 | line-height: 1.4em;
27 | background: #f5f5f5;
28 | color: #4d4d4d;
29 | min-width: 230px;
30 | max-width: 550px;
31 | margin: 0 auto;
32 | -webkit-font-smoothing: antialiased;
33 | -moz-font-smoothing: antialiased;
34 | font-smoothing: antialiased;
35 | font-weight: 300;
36 | }
37 |
38 | button,
39 | input[type="checkbox"] {
40 | outline: none;
41 | }
42 |
43 | .hidden {
44 | display: none;
45 | }
46 |
47 | .main {
48 | background: #fff;
49 | margin: 130px 0 40px 0;
50 | position: relative;
51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
52 | }
53 |
54 | .main input::-webkit-input-placeholder {
55 | font-style: italic;
56 | font-weight: 300;
57 | color: #e6e6e6;
58 | }
59 |
60 | .main input::-moz-placeholder {
61 | font-style: italic;
62 | font-weight: 300;
63 | color: #e6e6e6;
64 | }
65 |
66 | .main input::input-placeholder {
67 | font-style: italic;
68 | font-weight: 300;
69 | color: #e6e6e6;
70 | }
71 |
72 | .main h1 {
73 | position: absolute;
74 | top: -155px;
75 | width: 100%;
76 | font-size: 100px;
77 | font-weight: 100;
78 | text-align: center;
79 | color: rgba(175, 47, 47, 0.15);
80 | -webkit-text-rendering: optimizeLegibility;
81 | -moz-text-rendering: optimizeLegibility;
82 | text-rendering: optimizeLegibility;
83 | }
--------------------------------------------------------------------------------
/config/webpack/webpack.config.client.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const defaultConfig = require('./webpack.config.client');
3 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | const _ = require('lodash');
5 | const devProps = require('./devProps');
6 |
7 | const devConfig = _.assign(_.clone(defaultConfig), {
8 | devtool: 'source-map',
9 | entry: _.assign(_.clone(defaultConfig.entry), {
10 | app: _.union(
11 | [
12 | 'react-hot-loader/patch',
13 | `webpack-dev-server/client?${devProps.baseUrl}`,
14 | 'webpack/hot/only-dev-server'
15 | ],
16 | defaultConfig.entry.app
17 | )
18 | }),
19 | output: _.assign(_.cloneDeep(defaultConfig.output), {
20 | publicPath: `https://127.0.0.1:${devProps.webpackPort}/static/`,
21 | pathinfo: true,
22 | crossOriginLoading: 'anonymous'
23 | }),
24 | plugins: (defaultConfig.plugins || []).concat([
25 | new ExtractTextPlugin('styles.css'),
26 | new webpack.HotModuleReplacementPlugin(),
27 | new webpack.NoErrorsPlugin(),
28 | new webpack.DefinePlugin({
29 | 'process.env.NODE_ENV': JSON.stringify('development')
30 | })
31 | ]),
32 | devServer: {
33 | publicPath: `${devProps.baseUrl}/static`,
34 | host: devProps.host,
35 | hot: true,
36 | https: true,
37 | historyApiFallback: true,
38 | contentBase: devProps.contentBase,
39 | port: devProps.webpackPort,
40 | stats: {
41 | colors: true,
42 | chunkModules: false,
43 | modules: false
44 | }
45 | }
46 | });
47 |
48 | const localCssConfig = devConfig.module.loaders.find(
49 | l => l.name && l.name === 'local-css-config'
50 | );
51 |
52 | delete localCssConfig.name;
53 | localCssConfig.loader = ExtractTextPlugin.extract(
54 | 'style',
55 | 'css?sourceMap&modules&importLoaders=1&localIdentName=react_horizon_[local]_[hash:base64:5]!postcss'
56 | );
57 |
58 | module.exports = devConfig;
59 |
--------------------------------------------------------------------------------
/config/webpack/webpack.config.server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const chalk = require('chalk');
4 | const ProgressBarPlugin = require('progress-bar-webpack-plugin');
5 | const WebpackAnybarPlugin = require('webpack-anybar-plugin').default;
6 |
7 | const basePath = path.join(__dirname, '../../source');
8 | const buildPath = path.join(__dirname, '../../.build');
9 |
10 | module.exports = {
11 | target: 'node',
12 | context: __dirname,
13 | cache: true,
14 | entry: path.join(basePath, '/server/entry.server'),
15 | output: {
16 | path: buildPath,
17 | filename: 'server.bundle.js',
18 | publicPath: '/assets/'
19 | },
20 | resolve: {
21 | extensions: ['', '.js', '.jsx'],
22 | root: basePath,
23 | alias: {
24 | utils: path.join(basePath, '/utils')
25 | }
26 | },
27 | module: {
28 | loaders: [
29 | {
30 | test: /\.jsx?$/,
31 | loader: 'babel',
32 | include: basePath
33 | },
34 | {
35 | test : /\.json$/,
36 | loader : 'json'
37 | }
38 | ]
39 | },
40 | plugins: [
41 | new ProgressBarPlugin({
42 | format: `${chalk.blue.bold('Building server bundle')} [:bar] ${chalk.green.bold(':percent')} (:elapsed seconds)`,
43 | renderThrottle: 100,
44 | summary: false,
45 | customSummary: (t) => {
46 | return console.log(chalk.blue.bold(`Built server in ${t}.`));
47 | }
48 | }),
49 | new webpack.DefinePlugin({
50 | 'process.env.NODE_ENV': JSON.stringify('production'),
51 | BUILD_TIME: JSON.stringify((new Date()).getTime())
52 | }),
53 | new webpack.optimize.OccurenceOrderPlugin(),
54 | new WebpackAnybarPlugin({
55 | port: 1738
56 | })
57 | ],
58 | externals: [
59 | {
60 | winston: 'commonjs winston',
61 | express: 'commonjs express',
62 | later: 'commonjs later',
63 | '@horizon/server': 'commonjs @horizon/server'
64 | }
65 | ]
66 | };
67 |
--------------------------------------------------------------------------------
/source/client/containers/TodoList/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import { bindActionCreators } from 'redux'
4 | import { connect } from 'react-redux'
5 |
6 | import TodoItem from '../../components/TodoItem'
7 | import TodoAdd from '../../components/TodoAdd'
8 |
9 | import style from './style'
10 |
11 | import {
12 | requestTodos,
13 | requestPostTodo,
14 | requestDeleteTodo
15 | } from '../../actions/todos'
16 |
17 | class TodoList extends Component {
18 | componentWillMount() {
19 | const { actions } = this.props
20 | actions.requestTodos()
21 | }
22 |
23 | render() {
24 | const { todos } = this.props
25 | return (
26 |
27 |
31 |
32 |
33 | {todos.map(
34 | todo => (
35 |
39 | )
40 | ) }
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | addNewTodo(todo) {
48 | const { actions } = this.props
49 | actions.requestPostTodo({ text: todo })
50 | }
51 |
52 | deleteTodo(todoId) {
53 | const { actions } = this.props
54 | actions.requestDeleteTodo(todoId)
55 | }
56 | }
57 |
58 | // TodoList.propTypes = { todos: React.PropTypes.array };
59 | // TodoList.defaultProps = { todos: [] };
60 |
61 | const mapStateToProps = (state) => ({ todos: state.todos })
62 |
63 | const mapDispatchToProps = (dispatch) => {
64 | return {
65 | actions: bindActionCreators({
66 | requestTodos,
67 | requestPostTodo,
68 | requestDeleteTodo
69 | }, dispatch)
70 | }
71 | }
72 |
73 | export default connect(
74 | mapStateToProps,
75 | mapDispatchToProps
76 | )(TodoList)
77 |
--------------------------------------------------------------------------------
/source/client/components/HtmlHead/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Helmet from "react-helmet";
3 |
4 | import { url } from '../../constants/config';
5 |
6 | class HtmlHead extends Component {
7 | render() {
8 | const { name, title } = this.props;
9 | return (
10 | newState}
43 | />
44 | );
45 | }
46 | }
47 |
48 | export default HtmlHead
49 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 6,
4 | "sourceType": "module",
5 | "ecmaFeatures": {
6 | "jsx": true
7 | }
8 | },
9 | "plugins": [
10 | "react"
11 | ],
12 | "extends": ["eslint:recommended", "plugin:react/recommended"],
13 | "globals": {
14 | "__DEV__" : false,
15 | "__PROD__" : false,
16 | "__DEBUG__" : false,
17 | "__DEBUG_NW__" : false
18 | },
19 | "rules": {
20 | "strict": 0,
21 | "no-irregular-whitespace": 0,
22 | "new-cap": [2, {"newIsCap": false, "capIsNew": false}],
23 | "comma-dangle": [1, "never"],
24 | "key-spacing": [0, {
25 | "beforeColon": true,
26 | "afterColon": true
27 | }],
28 | "indent": [2, 2, {"SwitchCase": 1}],
29 | "no-else-return": 0,
30 | "import/no-unresolved": 0,
31 | "space-before-function-paren": [0, "always"],
32 | "react/jsx-quotes": [0, "single"],
33 | "react/prop-types": 0,
34 | "react/no-multi-comp": [1, { "ignoreStateless": true }],
35 | "react/sort-comp": [1, {
36 | "order": [
37 | "/^propTypes$/",
38 | "/^defaultProps$/",
39 | "/^getMeteorData$/",
40 | "lifecycle",
41 | "/^on.+$/",
42 | "render",
43 | "/^render.+$/",
44 | "everything-else"
45 | ]
46 | }],
47 | "no-undef": 1,
48 | "no-unused-vars": 1,
49 | "react/display-name": 1,
50 | "react/jsx-no-duplicate-props": 1,
51 | "react/jsx-no-undef": 1,
52 | "react/jsx-uses-react": 1,
53 | "react/jsx-uses-vars": 1,
54 | "react/no-danger": 1,
55 | "react/no-deprecated": [1, {"react": "15.1.0"}],
56 | "react/no-did-mount-set-state": 1,
57 | "react/no-did-update-set-state": 1,
58 | "react/no-direct-mutation-state": 1,
59 | "react/no-is-mounted": 1,
60 | "react/no-unknown-property": 1,
61 | "react/react-in-jsx-scope": 1
62 | },
63 | "settings": {
64 | "react": {
65 | "pragma": "React",
66 | "version": "15.1.0"
67 | }
68 | },
69 | "env": {
70 | "browser": true,
71 | "node": true
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/source/server/app.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs'
3 | import https from 'https'
4 | import express from 'express'
5 | import horizon from '@horizon/server'
6 | import devProps from '../../config/webpack/devProps'
7 | import config from '../../config/page'
8 |
9 | import './auto'
10 |
11 | const app = express()
12 |
13 | app.use('/static', express.static(path.join(process.cwd(), '.build')))
14 |
15 | /**
16 | * @TODO move the html out of the server dir
17 | */
18 | const host = process.env.NODE_ENV === 'production' ? '' : `https://127.0.0.1:${devProps.webpackPort}`
19 | const bundle = `${host}/static/client.bundle.js`
20 | const styles = `${host}/static/styles.css`
21 |
22 | app.use('/', (req, res) => {
23 | res.status(200).send(`
24 |
25 |
26 | ${config.title}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | `)
35 | })
36 |
37 | const run = () => {
38 | const port = process.env.PORT || config.port
39 |
40 | const options = {
41 | key: fs.readFileSync(__dirname + '/horizon-key.pem'),
42 | cert: fs.readFileSync(__dirname + '/horizon-cert.pem')
43 | };
44 |
45 | const secureServer = https
46 | .createServer(options, app)
47 | .listen(port, err => {
48 | err ? console.log(err) : null // eslint-disable-line
49 | console.log(`Express listening at https://localhost:${port}`) // eslint-disable-line
50 | })
51 |
52 | const horizonServer = horizon(secureServer, {
53 | auto_create_collection: true,
54 | auto_create_index: true,
55 | project_name: 'react_horizon',
56 | permissions: false, // waiting for additions to permission system atm
57 | auth: {
58 | // success_redirect: '/user/account',
59 | allow_anonymous: false,
60 | allow_unauthenticated: true,
61 | token_secret: config.token_secret
62 | }
63 | })
64 |
65 | // TODO: Add authentication
66 | // horizonServer.add_auth_provider(
67 | // horizon.auth.google, require('../../config/oauth').google
68 | // )
69 | // horizonServer.add_auth_provider(
70 | // horizon.auth.facebook, require('../../config/oauth').facebook
71 | // )
72 | }
73 |
74 | export default {
75 | run
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-horizon
2 |
3 | Start a realtime (live and production ready) web app in seconds.
4 |
5 | Updated for horizon 2.0.
6 |
7 | PS. More updates coming soon
8 |
9 | 
10 |
11 | ### Stack
12 |
13 | - [facebook/react](https://github.com/facebook/react) - View
14 | - [reactjs/redux](https://github.com/reactjs/redux) - State
15 | - [rethinkdb/horizon](https://github.com/rethinkdb/horizon) - Realtime Database Sync
16 | - [redux-observable/redux-observable](https://github.com/redux-observable/redux-observable) - Async calls with rxjs
17 | - [gaearon/react-hot-loader](https://github.com/gaearon/react-hot-loader) - Hot Reloading
18 | - [webpack/webpack](https://github.com/webpack/webpack) - Builds & Dev-Server
19 |
20 | ### Installation
21 | ``` bash
22 | $ npm install -g horizon
23 | $ git clone --depth=1 https://github.com/casoetan/react-horizon
24 | $ cd react-horizon
25 | $ npm i
26 | $ hz create-cert
27 | $ mv horizon-cert.pem ./source/server
28 | $ mv horizon-key.pem ./source/server
29 | ```
30 |
31 | ### Realtime DB
32 | Using brew?
33 | ``` bash
34 | $ brew update
35 | $ brew install rethinkdb
36 | $ brew tap homebrew/services
37 | $ brew services start rethinkdb
38 | ```
39 |
40 |
41 | ### Run
42 | ``` bash
43 | $ npm start # starts app in dev mode
44 | $ npm run prod # starts server in production mode
45 | $ npm run build # builds source files in .build/
46 | $ node .build/server.bundle.js # starts server (after you built with npm run build)
47 | ```
48 |
49 | This will start a server listening on ```https://localhost:3000``` for dev (Allow https for localhost).
50 | *You can change the port in* ```config/page.js``` *or by setting the ```PORT``` environment variable*.
51 |
52 | ### RemoteDev
53 | To monitor redux actions in remote dev run
54 | ```bash
55 | $ npm run remotedev # to start remote dev and monitor redux actions
56 | ```
57 | More info on RemoteDev at [zalmoxisus/remotedev-app](https://github.com/zalmoxisus/remotedev-app)
58 | You should also download the RemoteDev app to monitor your actions from the [Chrome Store](https://chrome.google.com/webstore/detail/remotedev/faicmgpfiaijcedapokpbdejaodbelph)
59 |
60 | ### Contributing
61 | Pull Requests are very welcome!
62 |
63 | ### Thanks
64 | - A big thank you to [flipace/lovli.js](http://github.com/flipace/lovli.js) - Lovli.js for his great boilerplate which guided this development
65 | - Another big thank you to [jayphelps](http://github.com/jayphelps) of [redux-observable/redux-observable](http://github.com/redux-observable/redux-observable) for helping smooth all the rough edges
66 |
67 | ### License
68 | (MIT)
69 |
--------------------------------------------------------------------------------
/config/webpack/webpack.config.client.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const chalk = require('chalk');
4 | const ProgressBarPlugin = require('progress-bar-webpack-plugin');
5 | const WebpackAnybarPlugin = require('webpack-anybar-plugin').default;
6 |
7 | const basePath = path.join(__dirname, '../../source');
8 | const buildPath = path.join(__dirname, '../../.build');
9 | const staticPath = path.join(__dirname, '../../static');
10 |
11 | module.exports = {
12 | target: 'web',
13 | context: __dirname,
14 | cache: true,
15 | entry: {
16 | app: [
17 | path.join(basePath, '/client/entry')
18 | ]
19 | },
20 | output: {
21 | path: buildPath,
22 | filename: 'client.bundle.js',
23 | publicPath: '/static/'
24 | },
25 | resolve: {
26 | extensions: ['', '.js', '.jsx', '.css'],
27 | root: basePath,
28 | alias: {
29 | utils: path.join(basePath, '/utils'),
30 | styles: path.join(basePath, '/client/styles'),
31 | images: path.join(staticPath, 'images'),
32 | static: path.join(staticPath)
33 | }
34 | },
35 | module: {
36 | loaders: [
37 | {
38 | test: /\.(jpe?g|png|gif|mp3|ogg|wav|ogv|mov|mp4|svg|ttf|eot|woff)/,
39 | loader: 'file?limit=2000',
40 | include: staticPath
41 | },
42 | {
43 | test: /\.jsx?$/,
44 | loader: 'babel',
45 | include: basePath
46 | },
47 | {
48 | test: /\.jsx?$/,
49 | loader: 'script',
50 | include: path.join(staticPath, 'vendor')
51 | },
52 | {
53 | test : /\.json$/,
54 | loader : 'json'
55 | },
56 | {
57 | // vendor css can be put into the "static/vendor" folder, it won't be localized then
58 | test: /\.(css)$/,
59 | loader: 'style!css',
60 | include: path.join(staticPath, 'vendor')
61 | },
62 | {
63 | name: 'local-css-config',
64 | // css inside the source folder will be localized by default. see https://github.com/css-modules/css-modules
65 | test: /\.(css)$/,
66 | loader: 'style!css?modules&importLoaders=1&localIdentName=[local]_[hash:base64:5]!postcss',
67 | include: basePath
68 | }
69 | ]
70 | },
71 | postcss: function postcssPlugins() {
72 | return [
73 | require('autoprefixer'),
74 | require('css-mqpacker'),
75 | require('postcss-nested'),
76 | require('postcss-discard-comments')({
77 | removeAll: true
78 | })
79 | ];
80 | },
81 | browser: {
82 | child_process: 'empty',
83 | net: 'empty',
84 | tls: 'empty',
85 | fs: 'empty'
86 | },
87 | plugins: [
88 | new ProgressBarPlugin({
89 | format: `${chalk.blue.bold('Building client bundle')} [:bar] ${chalk.green.bold(':percent')} (:elapsed seconds)`,
90 | renderThrottle: 100,
91 | summary: false,
92 | customSummary: (t) => {
93 | return console.log(chalk.blue.bold(`Built client in ${t}.`));
94 | }
95 | }),
96 | new webpack.DefinePlugin({
97 | BUILD_TIME: JSON.stringify((new Date()).getTime())
98 | }),
99 | new webpack.optimize.OccurenceOrderPlugin(),
100 | new WebpackAnybarPlugin({
101 | port: 1738
102 | })
103 | ]
104 | };
105 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-horizon",
3 | "version": "0.4.0",
4 | "license": "MIT",
5 | "description": "A boilerplate for developing react + redux applications using redux observables with rethinkdb + horizon as realtime database and express for the server.",
6 | "repository": "https://github.com/casoetan/react-horizon",
7 | "contributors": [
8 | {
9 | "name": "Charles Soetan",
10 | "email": "casoetan@gmail.com"
11 | }
12 | ],
13 | "scripts": {
14 | "start": "babel-node bin/dev.js",
15 | "prod": "npm run build && node ./.build/server.bundle.js",
16 | "build": "webpack --config=config/webpack/webpack.config.client.prod.js && webpack --config=config/webpack/webpack.config.server.js",
17 | "remotedev": "remotedev --hostname=localhost --port=8000",
18 | "lint": "eslint"
19 | },
20 | "dependencies": {
21 | "@horizon/client": "^2.0.0",
22 | "@horizon/server": "^2.0.0",
23 | "es6-promise": "^3.2.1",
24 | "express": "^4.13.4",
25 | "history": "^3.0.0",
26 | "https": "^1.0.0",
27 | "later": "^1.2.0",
28 | "localforage": "^1.4.2",
29 | "lodash": "^4.13.1",
30 | "node-sass": "^3.7.0",
31 | "postcss-nested": "^1.0.0",
32 | "react": "^15.2.0",
33 | "react-addons-css-transition-group": "^15.2.0",
34 | "react-dom": "^15.2.0",
35 | "react-helmet": "^3.1.0",
36 | "react-redux": "^4.4.5",
37 | "redux": "^3.5.2",
38 | "redux-actions": "^0.10.1",
39 | "redux-connect": "^2.4.0",
40 | "redux-immutable-state-invariant": "^1.2.3",
41 | "redux-observable": "^0.7.0",
42 | "redux-persist": "^3.2.2",
43 | "redux-persist-transform-encrypt": "^0.1.0",
44 | "rethinkdbdash": "^2.3.23",
45 | "winston": "^2.2.0"
46 | },
47 | "devDependencies": {
48 | "async": "^2.0.0-rc.6",
49 | "autoprefixer": "^6.3.7",
50 | "babel-cli": "6.7.7",
51 | "babel-core": "6.7.7",
52 | "babel-eslint": "6.0.4",
53 | "babel-loader": "6.2.4",
54 | "babel-plugin-react-transform": "2.0.2",
55 | "babel-plugin-syntax-jsx": "6.5.0",
56 | "babel-plugin-system-import-transformer": "^2.2.0",
57 | "babel-plugin-transform-runtime": "^6.7.5",
58 | "babel-preset-es2015": "6.6.0",
59 | "babel-preset-react": "6.5.0",
60 | "babel-preset-stage-0": "6.5.0",
61 | "babel-runtime": "^6.9.2",
62 | "chalk": "^1.1.3",
63 | "chunk-manifest-webpack-plugin": "^0.1.0",
64 | "css-loader": "^0.23.1",
65 | "css-mqpacker": "^4.0.1",
66 | "eslint": "^3.7.1",
67 | "eslint-config-airbnb": "8.0.0",
68 | "eslint-loader": "^1.3.0",
69 | "eslint-plugin-import": "^1.6.1",
70 | "eslint-plugin-jsx-a11y": "^1.0.4",
71 | "eslint-plugin-react": "^5.0.1",
72 | "exports-loader": "^0.6.3",
73 | "extract-text-webpack-plugin": "^1.0.1",
74 | "file-loader": "^0.8.5",
75 | "html-loader": "^0.4.3",
76 | "html-webpack-plugin": "^2.21.0",
77 | "imports-loader": "^0.6.5",
78 | "json-loader": "^0.5.4",
79 | "postcss-assets": "^4.1.0",
80 | "postcss-cssnext": "^2.6.0",
81 | "postcss-discard-comments": "^2.0.4",
82 | "postcss-loader": "^0.8.2",
83 | "precss": "^1.4.0",
84 | "progress-bar-webpack-plugin": "^1.6.0",
85 | "raw-loader": "^0.5.1",
86 | "react-addons-perf": "^15.2.0",
87 | "react-hot-loader": "^3.0.0-beta.2",
88 | "react-loader": "^2.4.0",
89 | "react-transform-catch-errors": "^1.0.2",
90 | "redbox-react": "^1.2.10",
91 | "redux-devtools": "^3.3.1",
92 | "redux-devtools-diff-monitor": "^5.0.5",
93 | "redux-devtools-dock-monitor": "^1.1.1",
94 | "redux-devtools-inspector": "^0.6.1",
95 | "redux-devtools-log-monitor": "^1.0.11",
96 | "redux-slider-monitor": "^1.0.6",
97 | "remote-redux-devtools": "^0.3.3",
98 | "remotedev-server": "^0.1.2",
99 | "script-loader": "^0.7.0",
100 | "shelljs": "^0.7.0",
101 | "style-loader": "^0.13.1",
102 | "url-loader": "^0.5.7",
103 | "webpack": "^1.13.1",
104 | "webpack-anybar-plugin": "^2.1.0",
105 | "webpack-dev-server": "^1.14.1",
106 | "webpack-hot-middleware": "^2.12.1",
107 | "webpack-manifest-plugin": "^1.0.1",
108 | "webpack-validator": "^2.2.2"
109 | }
110 | }
111 |
--------------------------------------------------------------------------------