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

    todos

    29 | 30 |
    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 | ![react-horizon Preview](static/images/preview.png) 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 | --------------------------------------------------------------------------------