49 | );
50 |
51 | // Render the content into a list item
52 | return (
53 |
54 | );
55 | }
56 | }
57 |
58 | RepoListItem.propTypes = {
59 | item: React.PropTypes.object,
60 | currentUser: React.PropTypes.string,
61 | };
62 |
63 | export default connect(createSelector(
64 | selectCurrentUser(),
65 | (currentUser) => ({ currentUser })
66 | ))(RepoListItem);
67 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/styles.css:
--------------------------------------------------------------------------------
1 | .linkWrapper {
2 | width: 100%;
3 | height: 100%;
4 | display: flex;
5 | align-items: space-between;
6 | }
7 |
8 | .linkRepo {
9 | height: 100%;
10 | color: black;
11 | display: flex;
12 | align-items: center;
13 | width: 100%;
14 | }
15 |
16 | .linkIssues {
17 | color: black;
18 | height: 100%;
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 | }
23 |
24 | .issueIcon {
25 | fill: #CCC;
26 | margin-right: 0.25em;
27 | }
28 |
--------------------------------------------------------------------------------
/app/containers/RepoListItem/tests/index.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test the repo list item
3 | */
4 |
5 | import expect from 'expect';
6 | import { shallow, mount } from 'enzyme';
7 | import React from 'react';
8 |
9 | import { IntlProvider } from 'react-intl';
10 | import { RepoListItem } from '../index';
11 | import ListItem from 'components/ListItem';
12 |
13 | describe('', () => {
14 | let item;
15 |
16 | // Before each test reset the item data for safety
17 | beforeEach(() => {
18 | item = {
19 | owner: {
20 | login: 'mxstbr',
21 | },
22 | html_url: 'https://github.com/mxstbr/react-boilerplate',
23 | name: 'react-boilerplate',
24 | open_issues_count: 20,
25 | full_name: 'mxstbr/react-boilerplate',
26 | };
27 | });
28 |
29 | it('should render a ListItem', () => {
30 | const renderedComponent = shallow(
31 |
32 | );
33 | expect(renderedComponent.find(ListItem).length).toEqual(1);
34 | });
35 |
36 | it('should not render the current username', () => {
37 | const renderedComponent = mount(
38 |
39 |
40 |
41 | );
42 | expect(renderedComponent.text().indexOf(item.owner.login)).toBeLessThan(0);
43 | });
44 |
45 | it('should render usernames that are not the current one', () => {
46 | const renderedComponent = mount(
47 |
48 |
49 |
50 | );
51 | expect(renderedComponent.text().indexOf(item.owner.login)).toBeGreaterThan(-1);
52 | });
53 |
54 | it('should render the repo name', () => {
55 | const renderedComponent = mount(
56 |
57 |
58 |
59 | );
60 | expect(renderedComponent.text().indexOf(item.name)).toBeGreaterThan(-1);
61 | });
62 |
63 | it('should render the issue count', () => {
64 | const renderedComponent = mount(
65 |
66 |
67 |
68 | );
69 | expect(renderedComponent.text().indexOf(item.open_issues_count)).toBeGreaterThan(1);
70 | });
71 |
72 | it('should render the IssueIcon', () => {
73 | const renderedComponent = mount(
74 |
75 |
76 |
77 | );
78 | expect(renderedComponent.find('svg').length).toEqual(1);
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/app/contracts/SimpleStore.sol:
--------------------------------------------------------------------------------
1 |
2 |
3 | contract SimpleStore {
4 | uint public store;
5 |
6 | function set(uint _store) {
7 | store = _store;
8 | }
9 |
10 | function get() constant public returns (uint storeValue) {
11 | return store;
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/contracts/SimpleStoreFactory.sol:
--------------------------------------------------------------------------------
1 | import "SimpleStore.sol";
2 | import "SimpleStoreRegistry.sol";
3 |
4 |
5 | contract SimpleStoreFactory {
6 | function SimpleStoreFactory(address _registry){
7 | registry = SimpleStoreRegistry(_registry);
8 | }
9 |
10 | function createSimpleStore() returns (address simpleStore) {
11 | simpleStore = address(new SimpleStore());
12 | registry.register(simpleStore);
13 | return simpleStore;
14 | }
15 |
16 | SimpleStoreRegistry registry;
17 | }
18 |
--------------------------------------------------------------------------------
/app/contracts/SimpleStoreRegistry.sol:
--------------------------------------------------------------------------------
1 |
2 |
3 | contract SimpleStoreRegistry {
4 |
5 | function register(address simpleStore) public returns (uint serviceId) {
6 | serviceId = services.length++;
7 | ids[simpleStore] = serviceId;
8 | services[serviceId] = simpleStore;
9 | }
10 |
11 | function getService(uint serviceId) returns (address serviceAddres) {
12 | return services[serviceId];
13 | }
14 |
15 | address[] public services;
16 | mapping(address => uint) public ids;
17 | }
18 |
--------------------------------------------------------------------------------
/app/contracts/tests/test.SimpleStore.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* globals describe, it, before */
3 |
4 | const contracts = require('../build/classes.json');
5 | const chaithereum = require('chaithereum');
6 |
7 | before(() => chaithereum.promise);
8 |
9 | describe('SimpleStore', () => {
10 | let simpleStore;
11 |
12 | it('successfully instantiates with blank params', () => {
13 | return chaithereum.web3.eth.contract(JSON.parse(contracts.SimpleStore.interface)).new.q({ data: contracts.SimpleStore.bytecode }).should.eventually.be.contract.then((_simpleStore) => {
14 | simpleStore = _simpleStore;
15 | }).should.eventually.be.fulfilled;
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/app/contracts/tests/test.SimpleStore.sol:
--------------------------------------------------------------------------------
1 | import "dapple/test.sol";
2 | import "SimpleStore.sol";
3 |
4 |
5 | contract SimpleStoreTest is Test {
6 | SimpleStore target;
7 |
8 | function refreshTargetInstance() {
9 | target = new SimpleStore();
10 | }
11 |
12 | function setUp() {
13 | refreshTargetInstance();
14 | }
15 |
16 | function testSetMethod() {
17 | uint256 testValue = 45;
18 | target.set(testValue);
19 | assertEq(target.get(), testValue);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/contracts/tests/test.SimpleStoreFactory.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* globals describe, it, before */
3 |
4 | const contracts = require('../build/classes.json');
5 | const chaithereum = require('chaithereum');
6 |
7 | before(() => chaithereum.promise);
8 |
9 | describe('SimpleStoreFactory', () => {
10 | let simpleStoreFactory;
11 |
12 | it('successfully instantiates with blank params', () => {
13 | return chaithereum.web3.eth.contract(JSON.parse(contracts.SimpleStoreFactory.interface)).new.q({ data: contracts.SimpleStoreFactory.bytecode }).should.eventually.be.contract.then((_simpleStoreFactory) => {
14 | simpleStoreFactory = _simpleStoreFactory;
15 | }).should.eventually.be.fulfilled;
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/app/contracts/tests/test.SimpleStoreFactory.sol:
--------------------------------------------------------------------------------
1 | import "dapple/test.sol";
2 | import "SimpleStoreFactory.sol";
3 |
4 |
5 | contract SimpleStoreFactoryTest is Test {
6 | SimpleStoreFactory target;
7 | SimpleStoreRegistry registry;
8 |
9 | function refreshTarget() {
10 | registry = new SimpleStoreRegistry();
11 | target = new SimpleStoreFactory(address(registry));
12 | }
13 |
14 | function testFactory() {
15 | address simpleStore = target.createSimpleStore();
16 | assertEq(registry.getService(0), simpleStore);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/contracts/tests/test.SimpleStoreRegistry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* globals describe, it, before */
3 |
4 | const contracts = require('../build/classes.json');
5 | const chaithereum = require('chaithereum');
6 |
7 | before(() => chaithereum.promise);
8 |
9 | describe('SimpleStoreRegistry', () => {
10 | let simpleStoreRegistry;
11 |
12 | it('successfully instantiates with blank params', () => {
13 | return chaithereum.web3.eth.contract(JSON.parse(contracts.SimpleStoreRegistry.interface)).new.q({ data: contracts.SimpleStoreRegistry.bytecode }).should.eventually.be.contract.then((_simpleStoreRegistry) => {
14 | simpleStoreRegistry = _simpleStoreRegistry;
15 | }).should.eventually.be.fulfilled;
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/app/contracts/tests/test.SimpleStoreRegistry.sol:
--------------------------------------------------------------------------------
1 | import "dapple/test.sol";
2 | import "SimpleStoreRegistry.sol";
3 |
4 |
5 | contract SimpleStoreRegistryTest is Test {
6 | SimpleStoreRegistry target;
7 |
8 | function refreshTarget() {
9 | target = new SimpleStoreRegistry();
10 | }
11 |
12 | function testRegistry() {
13 | address someAddr = address(target);
14 | target.register(someAddr);
15 | assertEq(target.getService(0), someAddr);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/dappfile:
--------------------------------------------------------------------------------
1 | version: 1.0.0
2 | tags: []
3 | layout:
4 | sol_sources: ./contracts/
5 | build_dir: ./contracts/build/
6 | dependencies: {}
7 | ignore: []
8 | name: react-dapp-boilerplate
9 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SilentCicero/react-dapp-boilerplate/23404782642847d99b751240f62fde31f10e218b/app/favicon.ico
--------------------------------------------------------------------------------
/app/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * i18n.js
3 | *
4 | * This will setup the i18n language files and locale data for your app.
5 | *
6 | */
7 | import { addLocaleData } from 'react-intl';
8 |
9 | import enLocaleData from 'react-intl/locale-data/en';
10 | import deLocaleData from 'react-intl/locale-data/de';
11 |
12 | addLocaleData(enLocaleData);
13 | addLocaleData(deLocaleData);
14 |
15 | export const appLocales = [
16 | 'en',
17 | 'de',
18 | ];
19 |
20 | import enTranslationMessages from './translations/en.json';
21 | import deTranslationMessages from './translations/de.json';
22 |
23 | export const formatTranslationMessages = (messages) => {
24 | const formattedMessages = {};
25 | for (const message of messages) {
26 | formattedMessages[message.id] = message.message || message.defaultMessage;
27 | }
28 |
29 | return formattedMessages;
30 | };
31 |
32 | export const translationMessages = {
33 | en: formatTranslationMessages(enTranslationMessages),
34 | de: formatTranslationMessages(deTranslationMessages),
35 | };
36 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | React.js Boilerplate
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "React Boilerplate",
3 | "icons": [
4 | {
5 | "src": "favicon.png",
6 | "sizes": "48x48",
7 | "type": "image/png",
8 | "density": 1.0
9 | },
10 | {
11 | "src": "favicon.png",
12 | "sizes": "96x96",
13 | "type": "image/png",
14 | "density": 2.0
15 | },
16 | {
17 | "src": "favicon.png",
18 | "sizes": "144x144",
19 | "type": "image/png",
20 | "density": 3.0
21 | },
22 | {
23 | "src": "favicon.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "density": 4.0
27 | }
28 | ],
29 | "start_url": "index.html",
30 | "display": "standalone",
31 | "orientation": "portrait",
32 | "background_color": "#FFFFFF"
33 | }
--------------------------------------------------------------------------------
/app/reducers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | * If we were to do this in store.js, reducers wouldn't be hot reloadable.
4 | */
5 |
6 | import { fromJS } from 'immutable';
7 | import { combineReducers } from 'redux-immutable';
8 | import { LOCATION_CHANGE } from 'react-router-redux';
9 |
10 | import globalReducer from 'containers/App/reducer';
11 | import languageProviderReducer from 'containers/LanguageProvider/reducer';
12 |
13 | /*
14 | * routeReducer
15 | *
16 | * The reducer merges route location changes into our immutable state.
17 | * The change is necessitated by moving to react-router-redux@4
18 | *
19 | */
20 |
21 | // Initial routing state
22 | const routeInitialState = fromJS({
23 | locationBeforeTransitions: null,
24 | });
25 |
26 | /**
27 | * Merge route into the global application state
28 | */
29 | function routeReducer(state = routeInitialState, action) {
30 | switch (action.type) {
31 | /* istanbul ignore next */
32 | case LOCATION_CHANGE:
33 | return state.merge({
34 | locationBeforeTransitions: action.payload,
35 | });
36 | default:
37 | return state;
38 | }
39 | }
40 |
41 | /**
42 | * Creates the main reducer with the asynchronously loaded ones
43 | */
44 | export default function createReducer(asyncReducers) {
45 | return combineReducers({
46 | route: routeReducer,
47 | global: globalReducer,
48 | language: languageProviderReducer,
49 | ...asyncReducers,
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/app/routes.js:
--------------------------------------------------------------------------------
1 | // These are the pages you can go to.
2 | // They are all wrapped in the App component, which should contain the navbar etc
3 | // See http://blog.mxstbr.com/2016/01/react-apps-with-pages for more information
4 | // about the code splitting business
5 | import { getAsyncInjectors } from './utils/asyncInjectors';
6 |
7 | const errorLoading = (err) => {
8 | console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
9 | };
10 |
11 | const loadModule = (cb) => (componentModule) => {
12 | cb(null, componentModule.default);
13 | };
14 |
15 | export default function createRoutes(store) {
16 | // create reusable async injectors using getAsyncInjectors factory
17 | const { injectReducer, injectSagas } = getAsyncInjectors(store);
18 |
19 | return [
20 | {
21 | path: '/',
22 | name: 'home',
23 | getComponent(nextState, cb) {
24 | const importModules = Promise.all([
25 | System.import('containers/HomePage/reducer'),
26 | System.import('containers/HomePage/sagas'),
27 | System.import('containers/HomePage'),
28 | ]);
29 |
30 | const renderRoute = loadModule(cb);
31 |
32 | importModules.then(([reducer, sagas, component]) => {
33 | injectReducer('home', reducer.default);
34 | injectSagas(sagas.default);
35 |
36 | renderRoute(component);
37 | });
38 |
39 | importModules.catch(errorLoading);
40 | },
41 | }, {
42 | path: '/features',
43 | name: 'features',
44 | getComponent(nextState, cb) {
45 | System.import('containers/FeaturePage')
46 | .then(loadModule(cb))
47 | .catch(errorLoading);
48 | },
49 | }, {
50 | path: '*',
51 | name: 'notfound',
52 | getComponent(nextState, cb) {
53 | System.import('containers/NotFoundPage')
54 | .then(loadModule(cb))
55 | .catch(errorLoading);
56 | },
57 | },
58 | ];
59 | }
60 |
--------------------------------------------------------------------------------
/app/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create the store with asynchronously loaded reducers
3 | */
4 |
5 | import { createStore, applyMiddleware, compose } from 'redux';
6 | import { fromJS } from 'immutable';
7 | import { routerMiddleware } from 'react-router-redux';
8 | import createSagaMiddleware from 'redux-saga';
9 | import createReducer from './reducers';
10 |
11 | const sagaMiddleware = createSagaMiddleware();
12 | const devtools = window.devToolsExtension || (() => noop => noop);
13 |
14 | export default function configureStore(initialState = {}, history) {
15 | // Create the store with two middlewares
16 | // 1. sagaMiddleware: Makes redux-sagas work
17 | // 2. routerMiddleware: Syncs the location/URL path to the state
18 | const middlewares = [
19 | sagaMiddleware,
20 | routerMiddleware(history),
21 | ];
22 |
23 | const enhancers = [
24 | applyMiddleware(...middlewares),
25 | devtools(),
26 | ];
27 |
28 | const store = createStore(
29 | createReducer(),
30 | fromJS(initialState),
31 | compose(...enhancers)
32 | );
33 |
34 | // Extensions
35 | store.runSaga = sagaMiddleware.run;
36 | store.asyncReducers = {}; // Async reducer registry
37 |
38 | // Make reducers hot reloadable, see http://mxs.is/googmo
39 | /* istanbul ignore next */
40 | if (module.hot) {
41 | module.hot.accept('./reducers', () => {
42 | System.import('./reducers').then((reducerModule) => {
43 | const createReducers = reducerModule.default;
44 | const nextReducers = createReducers(store.asyncReducers);
45 |
46 | store.replaceReducer(nextReducers);
47 | });
48 | });
49 | }
50 |
51 | return store;
52 | }
53 |
--------------------------------------------------------------------------------
/app/tests/store.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test store addons
3 | */
4 |
5 | import expect from 'expect';
6 | import configureStore from '../store';
7 | import { browserHistory } from 'react-router';
8 |
9 | describe('configureStore', () => {
10 | let store;
11 |
12 | before(() => {
13 | store = configureStore({}, browserHistory);
14 | });
15 |
16 | describe('asyncReducers', () => {
17 | it('should contain an object for async reducers', () => {
18 | expect(typeof store.asyncReducers).toEqual('object');
19 | });
20 | });
21 |
22 | describe('runSaga', () => {
23 | it('should contain a hook for `sagaMiddleware.run`', () => {
24 | expect(typeof store.runSaga).toEqual('function');
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/utils/asyncInjectors.js:
--------------------------------------------------------------------------------
1 | import { conformsTo, isEmpty, isFunction, isObject, isString } from 'lodash';
2 | import invariant from 'invariant';
3 | import warning from 'warning';
4 | import createReducer from '../reducers';
5 |
6 | /**
7 | * Validate the shape of redux store
8 | */
9 | export function checkStore(store) {
10 | const shape = {
11 | dispatch: isFunction,
12 | subscribe: isFunction,
13 | getState: isFunction,
14 | replaceReducer: isFunction,
15 | runSaga: isFunction,
16 | asyncReducers: isObject,
17 | };
18 | invariant(
19 | conformsTo(store, shape),
20 | '(app/utils...) asyncInjectors: Expected a valid redux store'
21 | );
22 | }
23 |
24 | /**
25 | * Inject an asynchronously loaded reducer
26 | */
27 | export function injectAsyncReducer(store, isValid) {
28 | return function injectReducer(name, asyncReducer) {
29 | if (!isValid) checkStore(store);
30 |
31 | invariant(
32 | isString(name) && !isEmpty(name) && isFunction(asyncReducer),
33 | '(app/utils...) injectAsyncReducer: Expected `asyncReducer` to be a reducer function'
34 | );
35 |
36 | store.asyncReducers[name] = asyncReducer; // eslint-disable-line no-param-reassign
37 | store.replaceReducer(createReducer(store.asyncReducers));
38 | };
39 | }
40 |
41 | /**
42 | * Inject an asynchronously loaded saga
43 | */
44 | export function injectAsyncSagas(store, isValid) {
45 | return function injectSagas(sagas) {
46 | if (!isValid) checkStore(store);
47 |
48 | invariant(
49 | Array.isArray(sagas),
50 | '(app/utils...) injectAsyncSagas: Expected `sagas` to be an array of generator functions'
51 | );
52 |
53 | warning(
54 | !isEmpty(sagas),
55 | '(app/utils...) injectAsyncSagas: Received an empty `sagas` array'
56 | );
57 |
58 | sagas.map(store.runSaga);
59 | };
60 | }
61 |
62 | /**
63 | * Helper for creating injectors
64 | */
65 | export function getAsyncInjectors(store) {
66 | checkStore(store);
67 |
68 | return {
69 | injectReducer: injectAsyncReducer(store, true),
70 | injectSagas: injectAsyncSagas(store, true),
71 | };
72 | }
73 |
--------------------------------------------------------------------------------
/app/utils/request.js:
--------------------------------------------------------------------------------
1 | import 'whatwg-fetch';
2 |
3 | /**
4 | * Parses the JSON returned by a network request
5 | *
6 | * @param {object} response A response from a network request
7 | *
8 | * @return {object} The parsed JSON from the request
9 | */
10 | function parseJSON(response) {
11 | return response.json();
12 | }
13 |
14 | /**
15 | * Checks if a network request came back fine, and throws an error if not
16 | *
17 | * @param {object} response A response from a network request
18 | *
19 | * @return {object|undefined} Returns either the response, or throws an error
20 | */
21 | function checkStatus(response) {
22 | if (response.status >= 200 && response.status < 300) {
23 | return response;
24 | }
25 |
26 | const error = new Error(response.statusText);
27 | error.response = response;
28 | throw error;
29 | }
30 |
31 | /**
32 | * Requests a URL, returning a promise
33 | *
34 | * @param {string} url The URL we want to request
35 | * @param {object} [options] The options we want to pass to "fetch"
36 | *
37 | * @return {object} An object containing either "data" or "err"
38 | */
39 | export default function request(url, options) {
40 | return fetch(url, options)
41 | .then(checkStatus)
42 | .then(parseJSON)
43 | .then((data) => ({ data }))
44 | .catch((err) => ({ err }));
45 | }
46 |
--------------------------------------------------------------------------------
/app/utils/tests/request.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test the request function
3 | */
4 |
5 | import request from '../request';
6 | import sinon from 'sinon';
7 | import expect from 'expect';
8 |
9 | describe('request', () => {
10 | // Before each test, stub the fetch function
11 | beforeEach(() => {
12 | sinon.stub(window, 'fetch');
13 | });
14 |
15 | // After each test, restore the fetch function
16 | afterEach(() => {
17 | window.fetch.restore();
18 | });
19 |
20 | describe('stubbing successful response', () => {
21 | // Before each test, pretend we got a successful response
22 | beforeEach(() => {
23 | const res = new Response('{"hello":"world"}', {
24 | status: 200,
25 | headers: {
26 | 'Content-type': 'application/json',
27 | },
28 | });
29 |
30 | window.fetch.returns(Promise.resolve(res));
31 | });
32 |
33 | it('should format the response correctly', (done) => {
34 | request('/thisurliscorrect')
35 | .catch(done)
36 | .then((json) => {
37 | expect(json.data.hello).toEqual('world');
38 | done();
39 | });
40 | });
41 | });
42 |
43 | describe('stubbing error response', () => {
44 | // Before each test, pretend we got an unsuccessful response
45 | beforeEach(() => {
46 | const res = new Response('', {
47 | status: 404,
48 | statusText: 'Not Found',
49 | headers: {
50 | 'Content-type': 'application/json',
51 | },
52 | });
53 |
54 | window.fetch.returns(Promise.resolve(res));
55 | });
56 |
57 | it('should catch errors', (done) => {
58 | request('/thisdoesntexist')
59 | .then((json) => {
60 | expect(json.err.response.status).toEqual(404);
61 | expect(json.err.response.statusText).toEqual('Not Found');
62 | done();
63 | });
64 | });
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/app/web3.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3';
2 |
3 | // instantiate new web3 instance
4 | const web3 = new Web3();
5 |
6 | // providers
7 | export const providers = {
8 | livenet: web3.setProvider(new web3.providers.HttpProvider('https://livenet.infura.io/')),
9 | testnet: web3.setProvider(new web3.providers.HttpProvider('https://morden.infura.io/')),
10 | testrpc: web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')),
11 | };
12 |
13 | // if window provider exists
14 | if (typeof window.web3 !== 'undefined' && typeof window.web3.currentProvider !== 'undefined') {
15 | providers.window = web3.setProvider(window.web3.currentProvider);
16 | }
17 |
18 | // get current provider
19 | export function getCurrentProvider() {
20 | return web3.currentProvider;
21 | }
22 |
23 | // set provider abstraction
24 | export function setProvider(provider) {
25 | if (typeof provider === 'string') {
26 | web3.setProvider(providers[provider]);
27 | } else {
28 | web3.setProvider(provider);
29 | }
30 | }
31 |
32 | // Abstraction:
33 | // The web3 object may change in the future
34 | // it is best to abstract the critical methods
35 | // so we dont get hung up on object design that may change in the future
36 |
37 | // abstract the getBalance object
38 | export function getBalance() {
39 | return web3.eth.getBalance.apply(web3.eth, arguments); // eslint-disable-line
40 | }
41 |
42 | // abstract the contract object
43 | export function contract() {
44 | return web3.eth.contract.apply(web3.eth, arguments); // eslint-disable-line
45 | }
46 |
47 | // export web3 object instance
48 | export default web3;
49 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # http://www.appveyor.com/docs/appveyor-yml
2 |
3 | # Set build version format here instead of in the admin panel
4 | version: "{build}"
5 |
6 | # Do not build on gh tags
7 | skip_tags: true
8 |
9 | # Test against these versions of Node.js
10 | environment:
11 |
12 | matrix:
13 | # Node versions to run
14 | - nodejs_version: "5.0"
15 |
16 | # Fix line endings in Windows. (runs before repo cloning)
17 | init:
18 | - git config --global core.autocrlf input
19 |
20 | # Install scripts--runs after repo cloning
21 | install:
22 | # Install chrome
23 | - choco install -y googlechrome
24 | # Install the latest stable version of Node
25 | - ps: Install-Product node $env:nodejs_version
26 | - npm -g install npm
27 | - set PATH=%APPDATA%\npm;%PATH%
28 | - npm install
29 |
30 | # Disable automatic builds
31 | build: off
32 |
33 | # Post-install test scripts
34 | test_script:
35 | # Output debugging info
36 | - node --version
37 | - npm --version
38 | # run build and run tests
39 | - npm run build
40 |
41 | # remove, as appveyor doesn't support secure variables on pr builds
42 | # so `COVERALLS_REPO_TOKEN` cannot be set, without hard-coding in this file
43 | #on_success:
44 | #- npm run coveralls
45 |
--------------------------------------------------------------------------------
/docs/css/README.md:
--------------------------------------------------------------------------------
1 | # CSS
2 |
3 | This boilerplate uses PostCSS as a CSS preprocessor with a few utility plugins
4 | to make it "batteries included".
5 |
6 | CSS Modules lets us embrace component encapsulation while sanitize.css gives us
7 | data-driven cross-browser normalisation.
8 |
9 | Learn more:
10 |
11 | - [PostCSS](postcss.md)
12 | - [CSS Modules](css-modules.md)
13 | - [sanitize.css](sanitize.md)
14 | - [stylelint.css](stylelint.md)
15 | - [Using Sass](sass.md)
16 |
--------------------------------------------------------------------------------
/docs/css/postcss.md:
--------------------------------------------------------------------------------
1 | # PostCSS
2 |
3 | PostCSS is a modular CSS preprocessor based on JavaScript. It comes pre-
4 | configured with the plugins listed below.
5 |
6 | See the [official documentation](https://github.com/postcss/postcss) for more
7 | information!
8 |
9 | ## Plugins
10 |
11 | This boilerplate bundles a few of the most useful PostCSS plugins by default:
12 |
13 | - [`postcss-focus`](https://github.com/postcss/postcss-focus): Adds a `:focus`
14 | selector to every `:hover` selector for keyboard accessibility.
15 | - [`autoprefixer`](https://github.com/postcss/autoprefixer): Prefixes your CSS
16 | automatically for the last two versions of all major browsers and IE10+.
17 | - [`cssnext`](https://github.com/moox/postcss-cssnext): Use tomorrow's CSS
18 | features today. Transpiles CSS4 features down to CSS3.
19 | - [`cssnano`](https://github.com/ben-eb/cssnano): Optimizes your CSS file. For a
20 | full list of optimizations check [the offical website](http://cssnano.co/optimisations/).
21 |
22 | For more awesome features that the PostCSS ecosystem offers, check out the
23 | comprehensive, fully-searchable catalog of available plugins at [postcss.parts](http://postcss.parts).
24 |
25 | ## Adding a new PostCSS plugin
26 |
27 | 1. Add the plugin to your project (e.g. `npm install --save-dev postcss-super-plugin`).
28 | 2. Modify `internals/webpack/webpack.dev.babel.js`:
29 | - Add `const postcssSuperPlugin = require('postcss-super-plugin');`
30 | to `// PostCSS plugins` section.
31 | - Find `postcss: () => [/* ... current set of plugins ... */]` and add
32 | the new plugin to the list: `postcssPlugins: [/* ... */, postcssSuperPlugin()]`.
33 | 3. Restart the server (`CTRL+C`, `npm start`) for the new plugin to become available
34 | (webpack does not pick config changes while running).
35 |
36 | Before installing a new plugin, make sure that you are not trying to add a feature
37 | that is already available. It is likely that what you are looking for
38 | [is supported by `cssnext`](http://cssnext.io/features/), which is a part of the boilerplate.
39 |
40 | ---
41 |
42 | _Don't like this feature? [Click here](remove.md)_
43 |
--------------------------------------------------------------------------------
/docs/css/remove.md:
--------------------------------------------------------------------------------
1 | ## Removing CSS modules
2 |
3 | To remove this feature from your setup, stop importing `.css` files in your
4 | components and delete the `modules` option from the `css-loader` declaration in
5 | [`webpack.prod.babel.js`](/internals/webpack/webpack.prod.babel.js) and
6 | [`webpack.base.babel.js`](/internals/webpack/webpack.base.babel.js)!
7 |
8 | ## Removing PostCSS
9 |
10 | To remove PostCSS, delete the `postcssPlugins` option and remove all occurences
11 | of the `postcss-loader` from
12 |
13 | - [`webpack.dev.babel.js`](/internals/webpack/webpack.dev.babel.js)
14 | - [`webpack.prod.babel.js`](/internals/webpack/webpack.prod.babel.js)
15 | - [`webpack.base.babel.js`](/internals/webpack/webpack.base.babel.js)
16 |
17 | When that is done - and you've verified that everything is still working - remove
18 | all related dependencies from [`package.json`](/package.json)!
19 |
20 | ## Removing `sanitize.css`
21 |
22 | Delete [lines 44 and 45 in `app.js`](../../app/app.js#L44-L45) and remove it
23 | from the `dependencies` in [`package.json`](../../package.json)!
24 |
--------------------------------------------------------------------------------
/docs/css/sanitize.md:
--------------------------------------------------------------------------------
1 | # `sanitize.css`
2 |
3 | Sanitize.css makes browsers render elements more in
4 | line with developer expectations (e.g. having the box model set to a cascading
5 | `box-sizing: border-box`) and preferences (its defaults can be individually
6 | overridden).
7 |
8 | It was selected over older projects like `normalize.css` and `reset.css` due
9 | to its greater flexibility and better alignment with CSSNext features like CSS
10 | variables.
11 |
12 | See the [official documentation](https://github.com/10up/sanitize.css) for more
13 | information.
14 |
15 | ---
16 |
17 | _Don't like this feature? [Click here](remove.md)_
18 |
--------------------------------------------------------------------------------
/docs/css/sass.md:
--------------------------------------------------------------------------------
1 | # Can I use Sass with this boilerplate?
2 |
3 | Yes, although we advise against it and **do not support this**. We selected
4 | PostCSS over Sass because its approach is more powerful: instead of trying to
5 | give a styling language programmatic abilities, it pulls logic and configuration
6 | out into JS where we believe those features belong.
7 |
8 | As an alternative, consider installing a PostCSS plugin called [`PreCSS`](https://github.com/jonathantneal/precss):
9 | it lets you use familiar syntax - $variables, nesting, mixins, etc. - but retain
10 | the advantages (speed, memory efficiency, extensibility, etc) of PostCSS.
11 |
12 | If you _really_ still want (or need) to use Sass then...
13 |
14 | 1. Change `internals/webpack/webpack.base.babel.js` so that line 22 reads
15 | ```JavaScript
16 | test: /\.s?css$/,
17 | ```
18 |
19 | This means that both `.scss` and `.css` will be picked up by the compiler
20 |
21 | 1. Update each of
22 |
23 | - `internals/webpack/webpack.dev.babel.js`
24 | - `internals/webpack/webpack.prod.babel.js`
25 |
26 | changing the config option for `cssLoaders` to
27 |
28 | ```JavaScript
29 | cssLoaders: 'style-loader!css-loader?modules&importLoaders=1&sourceMap!postcss-loader!sass-loader',
30 | ```
31 |
32 | Then run `npm i -D sass-loader node-sass`
33 |
34 | ...and you should be good to go!
35 |
--------------------------------------------------------------------------------
/docs/css/stylelint.md:
--------------------------------------------------------------------------------
1 | # stylelint
2 |
3 | stylelint catches bugs and helps keep you and your team on consistent with the
4 | standards and conventions you define.
5 |
6 | We've pre-configured it to extend [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard)
7 | but you can (and should!) adapt it to your house style.
8 |
9 | See the [official documentation](http://stylelint.io/) for more information!
10 |
--------------------------------------------------------------------------------
/docs/general/deployment.md:
--------------------------------------------------------------------------------
1 | # Deployment
2 |
3 | ## Heroku
4 |
5 | ### Easy 5-Step Deployment Process
6 |
7 | *Step 1:* Create a Procfile with the following line: `web: npm run start:prod`. We are doing this because heroku runs `npm run start` by default, so we need this setting to override the default run command.
8 |
9 | *Step 2:* Install heroku's buildpack on your heroku app by running the following command: `heroku buildpacks:set https://github.com/heroku/heroku-buildpack-nodejs#v90 -a [your app name]`. Make sure to replace `#v90` with whatever the latest buildpack is which you can [find here](https://github.com/heroku/heroku-buildpack-nodejs/releases).
10 |
11 | *Step 3:* Add this line to your Package.json file in the scripts area: `"postinstall": "npm run build:clean",`. This is because Heroku runs this as part of their build process (more of which you can [read about here](https://devcenter.heroku.com/articles/nodejs-support#build-behavior)).
12 |
13 | *Step 4:* Run `heroku config:set NPM_CONFIG_PRODUCTION=false` so that Heroku can compile the NPM Modules included in your devDependencies (since many of these packages are required for the build process).
14 |
15 | *Step 5:* Follow the standard Heroku deploy process at this point:
16 |
17 | 1. `git add .`
18 | 2. `git commit -m 'Made some epic changes as per usual'`
19 | 3. `git push heroku master`
20 |
--------------------------------------------------------------------------------
/docs/general/files.md:
--------------------------------------------------------------------------------
1 | # Configuration: A Glossary
2 |
3 | A guide to the configuration files for this project: where they live and what
4 | they do.
5 |
6 | ## The root folder
7 |
8 | * `.editorconfig`: Sets the default configuration for certain files across editors. (e.g. indentation)
9 |
10 | * `.gitattributes`: Normalizes how `git`, the version control system this boilerplate uses, handles certain files.
11 |
12 | * `.gitignore`: Tells `git` to ignore certain files and folders which don't need to be version controlled, like the build folder.
13 |
14 | * `.travis.yml` and `appveyor.yml`: Continuous Integration configuration
15 | This boilerplate uses [Travis CI](https://travis-ci.com) for Linux environments
16 | and [AppVeyor](https://www.appveyor.com/) for Windows platforms, but feel free
17 | to swap either out for your own choice of CI.
18 |
19 | * `package.json`: Our `npm` configuration file has three functions:
20 |
21 | 1. It's where Babel, ESLint and stylelint are configured
22 | 1. It's the API for the project: a consistent interface for all its controls
23 | 1. It lists the project's package dependencies
24 |
25 | Baking the config in is a slightly unusual set-up, but it allows us to keep
26 | the project root as uncluttered and grokkable-at-a-glance as possible.
27 |
28 | ## The `./internals` folder
29 |
30 | This is where the bulk of the tooling configuration lives, broken out into
31 | recognisable units of work.
32 |
33 | Feel free to change anything you like but don't be afraid to [ask upfront](https://gitter.im/mxstbr/react-boilerplate)
34 | whether you should: build systems are easy to break!
35 |
--------------------------------------------------------------------------------
/docs/general/gotchas.md:
--------------------------------------------------------------------------------
1 | # Gotchas
2 |
3 | These are some things to be aware of when using this boilerplate.
4 |
5 | ## Special images in HTML files
6 |
7 | If you specify your images in the `.html` files using the `` tag, everything
8 | will work fine. The problem comes up if you try to include images using anything
9 | except that tag, like meta tags:
10 |
11 | ```HTML
12 |
13 | ```
14 |
15 | The webpack `html-loader` does not recognise this as an image file and will not
16 | transfer the image to the build folder. To get webpack to transfer them, you
17 | have to import them with the file loader in your JavaScript somewhere, e.g.:
18 |
19 | ```JavaScript
20 | import 'file?name=[name].[ext]!../img/yourimg.png';
21 | ```
22 |
23 | Then webpack will correctly transfer the image to the build folder.
24 |
--------------------------------------------------------------------------------
/docs/general/remove.md:
--------------------------------------------------------------------------------
1 | ### Removing offline access
2 |
3 | **Careful** about removing this, as there is no real downside to having your
4 | application available when the users network connection isn't perfect.
5 |
6 | To remove offline capability, delete the `offline-plugin` from the
7 | [`package.json`](../../package.json), remove the import of the plugin in
8 | [`app.js`](../../app/app.js) and remove the plugin from the
9 | [`webpack.prod.babel.js`](../../internals/webpack/webpack.prod.babel.js).
10 |
11 | ### Removing add to homescreen functionality
12 |
13 | Delete [`manifest.json`](../../app/manifest.json) and remove the
14 | `` tag from the
15 | [`index.html`](../../app/index.html).
16 |
17 | ### Removing performant web font loading
18 |
19 | **Careful** about removing this, as perceived performance might be highly impacted.
20 |
21 | To remove `FontFaceObserver`, don't import it in [`app.js`](../../app/app.js) and
22 | remove it from the [`package.json`](../../package.json).
23 |
24 | ### Removing image optimization
25 |
26 | To remove image optimization, delete the `image-webpack-loader` from the
27 | [`package.json`](../../package.json), and remove the `image-loader` from [`webpack.base.babel.js`](../../internals/webpack/webpack.base.babel.js):
28 | ```
29 | …
30 | {
31 | test: /\.(jpg|png|gif)$/,
32 | loaders: [
33 | 'file-loader',
34 | 'image-webpack?{progressive:true, optimizationLevel: 7, interlaced: false, pngquant:{quality: "65-90", speed: 4}}',
35 | ],
36 | }
37 | …
38 | ```
39 |
40 | Then replace it with classic `file-loader`:
41 |
42 | ```
43 | …
44 | {
45 | test: /\.(jpg|png|gif)$/,
46 | loader: 'file-loader',
47 | }
48 | …
49 | ```
50 |
--------------------------------------------------------------------------------
/docs/general/server-configs.md:
--------------------------------------------------------------------------------
1 | # Server Configurations
2 |
3 | ## Apache
4 |
5 | This boilerplate includes a `.htaccess` file that does two things:
6 |
7 | 1. Redirect all traffic to HTTPS because ServiceWorker only works for encrypted
8 | traffic.
9 | 1. Rewrite all pages (e.g. `yourdomain.com/subpage`) to `yourdomain.com/index.html`
10 | to let `react-router` take care of presenting the correct page.
11 |
12 | > Note: For performance reasons you should probably adapt it to run as a static
13 | `.conf` file (typically under `/etc/apache2/sites-enabled` or similar) so that
14 | your server doesn't have to apply its rules dynamically per request)
15 |
16 | ## Nginx
17 |
18 | Also it includes a `.nginx.conf` file that does the same on Nginx server.
19 |
--------------------------------------------------------------------------------
/docs/general/webstorm-debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SilentCicero/react-dapp-boilerplate/23404782642847d99b751240f62fde31f10e218b/docs/general/webstorm-debug.png
--------------------------------------------------------------------------------
/docs/general/webstorm-eslint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SilentCicero/react-dapp-boilerplate/23404782642847d99b751240f62fde31f10e218b/docs/general/webstorm-eslint.png
--------------------------------------------------------------------------------
/docs/js/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript
2 |
3 | ## State management
4 |
5 | This boilerplate manages application state using [Redux](redux.md), makes it
6 | immutable with [`ImmutableJS`](immutablejs.md) and keeps access performant
7 | via [`reselect`](reselect.md).
8 |
9 | For managing asynchronous flows (e.g. logging in) we use [`redux-saga`](redux-saga.md).
10 |
11 | For routing, we use [`react-router` in combination with `react-router-redux`](routing.md).
12 |
13 | We include a generator for components, containers, sagas, routes and selectors.
14 | Run `npm run generate` to choose from the available generators, and automatically
15 | add new parts of your application!
16 |
17 | > Note: If you want to skip the generator selection process,
18 | `npm run generate ` also works. (e.g. `npm run generate route`)
19 |
20 | ### Learn more
21 |
22 | - [Redux](redux.md)
23 | - [ImmutableJS](immutablejs.md)
24 | - [reselect](reselect.md)
25 | - [redux-saga](redux-saga.md)
26 | - [react-intl](i18n.md)
27 | - [routing](routing.md)
28 |
29 | ## Architecture: `components` and `containers`
30 |
31 | We adopted a split between stateless, reusable components called (wait for it...)
32 | `components` and stateful parent components called `containers`.
33 |
34 | ### Learn more
35 |
36 | See [this article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
37 | by Dan Abramov for a great introduction to this approach.
38 |
--------------------------------------------------------------------------------
/docs/js/immutablejs.md:
--------------------------------------------------------------------------------
1 | # ImmutableJS
2 |
3 | Immutable data structures can be deeply compared in no time. This allows us to
4 | efficiently determine if our components need to rerender since we know if the
5 | `props` changed or not!
6 |
7 | Check out the [official documentation](https://facebook.github.io/immutable-js/)
8 | for a good explanation of the more intricate benefits it has.
9 |
10 | ## Usage
11 |
12 | In our reducers, we make the initial state an immutable data structure with the
13 | `fromJS` function. We pass it an object or an array, and it takes care of
14 | converting it to a compatible one. (Note: the conversion is performed deeply so
15 | that even arbitrarily nested arrays/objects are immutable stuctures too!)
16 |
17 | ```JS
18 | import { fromJS } from 'immutable';
19 |
20 | const initialState = fromJS({
21 | myData: 'Hello World!',
22 | });
23 | ```
24 |
25 | To react to an incoming actions our reducers can use the `.set` and the `.setIn`
26 | functions.
27 |
28 | ```JS
29 | import { SOME_ACTION } from './actions';
30 |
31 | // […]
32 |
33 | function myReducer(state = initialState, action) {
34 | switch (action.type) {
35 | case SOME_ACTION:
36 | return state.set('myData', action.payload);
37 | default:
38 | return state;
39 | }
40 | }
41 | ```
42 |
43 | We use [`reselect`](./reselect.md) to efficiently cache our computed application
44 | state. Since that state is now immutable, we need to use the `.get` and `.getIn`
45 | functions to select the part we want.
46 |
47 | ```JS
48 | const myDataSelector = (state) => state.get('myData');
49 |
50 | export default myDataSelector;
51 | ```
52 |
53 | To learn more, check out [`reselect.md`](reselect.md)!
54 |
55 | ## Advanced Usage
56 |
57 | ImmutableJS provide many immutable structures like `Map`, `Set` and `List`. But the downside to using ImmutableJS data structures is that they are not normal JavaScript data structures.
58 |
59 | That means you must use getters to access properties : for instance you'll do `map.get("property")` instead of `map.property`, and `array.get(0)` instead of `array[0]`. It's not natural and your code becoming bigger, you finish by not knowing anymore if you are working with a JavaScript object or an Immutable one. While it's possible to be clear where you are using immutable objects, you still pass them through the system into places where it's not clear. This makes reasoning about functions harder.
60 |
61 | The `Record` structure tries to get rid of this drawback. `Record` is like a `Map` whose shape is fixed : you can't later add a new property after the record is created. The benefit of `Record` is that you can now, along with others .get, .set and .merge methods, use the dot notation to access properties, which is a good point to write simpler code.
62 |
63 | The creation of a record is less simple. You got to first create the `Record` shape. With the example above, to create your initial state, you'll write :
64 |
65 | ```JS
66 | //the shape
67 | const StateRecord = Record({
68 | myData: 'Hello World!',
69 | });
70 |
71 | const initialState = new StateRecord({}); // initialState is now a new StateRecord instance
72 | // initialized with myData set by default as 'Hello World!'
73 | ```
74 |
75 | Now, if you want to access `myData`, you can just write `state.myData` in your reducer code.
76 |
77 |
--------------------------------------------------------------------------------
/docs/js/redux-saga.md:
--------------------------------------------------------------------------------
1 | # `redux-saga`
2 |
3 | `redux-saga` is a library to manage side effects in your application. It works
4 | beautifully for data fetching, concurrent computations and a lot more.
5 | [Sebastien Lorber](https://twitter.com/sebastienlorber) put it best:
6 |
7 | > Imagine there is widget1 and widget2. When some button on widget1 is clicked,
8 | then it should have an effect on widget2. Instead of coupling the 2 widgets
9 | together (ie widget1 dispatch an action that targets widget2), widget1 only
10 | dispatch that its button was clicked. Then the saga listen for this button
11 | click and then update widget2 by dispaching a new event that widget2 is aware of.
12 | >
13 | > This adds a level of indirection that is unnecessary for simple apps, but make
14 | it more easy to scale complex applications. You can now publish widget1 and
15 | widget2 to different npm repositories so that they never have to know about
16 | each others, without having them to share a global registry of actions. The 2
17 | widgets are now bounded contexts that can live separately. They do not need
18 | each others to be consistent and can be reused in other apps as well. **The saga
19 | is the coupling point between the two widgets that coordinate them in a
20 | meaningful way for your business.**
21 |
22 | _Note: It is well worth reading the [source](https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34623840#34623840)
23 | of this quote in its entirety!_
24 |
25 | To learn more about this amazing way to handle concurrent flows, start with the
26 | [official documentation](https://github.com/yelouafi/redux-saga) and explore
27 | some examples! (read [this comparison](https://stackoverflow.com/questions/34930735/pros-cons-of-using-redux-saga-with-es6-generators-vs-redux-thunk-with-es7-async/34933395) if you're used to `redux-thunk`)
28 |
29 | ## Usage
30 |
31 | Sagas are associated with a container, just like actions, constants, selectors
32 | and reducers. If your container already has a `sagas.js` file, simply add your
33 | saga to that. If your container does not yet have a `sagas.js` file, add one with
34 | this boilerplate structure:
35 |
36 | ```JS
37 | import { take, call, put, select } from 'redux-saga/effects';
38 |
39 | // Your sagas for this container
40 | export default [
41 | sagaName,
42 | ];
43 |
44 | // Individual exports for testing
45 | export function* sagaName() {
46 |
47 | }
48 | ```
49 |
50 | Then, in your `routes.js`, add injection for the newly added saga:
51 |
52 | ```JS
53 | getComponent(nextState, cb) {
54 | const importModules = Promise.all([
55 | System.import('containers/YourComponent/reducer'),
56 | System.import('containers/YourComponent/sagas'),
57 | System.import('containers/YourComponent'),
58 | ]);
59 |
60 | const renderRoute = loadModule(cb);
61 |
62 | importModules.then(([reducer, sagas, component]) => {
63 | injectReducer('home', reducer.default);
64 | injectSagas(sagas.default); // Inject the saga
65 |
66 | renderRoute(component);
67 | });
68 |
69 | importModules.catch(errorLoading);
70 | },
71 | ```
72 |
73 | Now add as many sagas to your `sagas.js` file as you want!
74 |
75 | ---
76 |
77 | _Don't like this feature? [Click here](remove.md)_
78 |
--------------------------------------------------------------------------------
/docs/js/redux.md:
--------------------------------------------------------------------------------
1 | # Redux
2 |
3 | If you haven't worked with Redux, it's highly recommended (possibly indispensable!)
4 | to read through the (amazing) [official documentation](http://redux.js.org)
5 | and/or watch this [free video tutorial series](https://egghead.io/series/getting-started-with-redux).
6 |
7 | ## Usage
8 |
9 | See above! As minimal as Redux is, the challenge it addresses - app state
10 | management - is a complex topic that is too involved to properly discuss here.
11 |
12 | ## Removing redux
13 |
14 | There are a few reasons why we chose to bundle redux with React Boilerplate, the
15 | biggest being that it is widely regarded as the current best Flux implementation
16 | in terms of architecture, support and documentation.
17 |
18 | You may feel differently! This is completely OK :)
19 |
20 | Below are a few reasons you might want to remove it:
21 |
22 | ### I'm just getting started and Flux is hard
23 |
24 | You're under no obligation to use Redux or any other Flux library! The complexity
25 | of your application will determine the point at which you need to introduce it.
26 |
27 | Here are a couple of great resources for taking a minimal approach:
28 |
29 | - [Misconceptions of Tooling in JavaScript](http://javascriptplayground.com/blog/2016/02/the-react-webpack-tooling-problem)
30 | - [Learn Raw React — no JSX, no Flux, no ES6, no Webpack…](http://jamesknelson.com/learn-raw-react-no-jsx-flux-es6-webpack/)
31 |
32 | ### It's overkill for my project!
33 |
34 | See above.
35 |
36 | ### I prefer `(Alt|MobX|SomethingElse)`!
37 |
38 | React Boilerplate is a baseline for _your_ app: go for it!
39 |
40 | If you feel that we should take a closer look at supporting your preference
41 | out of the box, please let us know.
42 |
--------------------------------------------------------------------------------
/docs/js/remove.md:
--------------------------------------------------------------------------------
1 | ## Removing `redux-saga`
2 |
3 | **We don't recommend removing `redux-saga`**, as we strongly feel that it's the
4 | way to go for most redux based applications.
5 |
6 | If you really want to get rid of it, delete the `sagas/` folder, remove the
7 | `import` and the `sagaMiddleware` from the `store.js` and finally remove it from
8 | the `package.json`. Then you should be good to go with whatever side-effect
9 | management library you want to use!
10 |
11 | ## Removing `reselect`
12 |
13 | To remove `reselect`, delete the `app/selectors` folder, remove it from your
14 | dependencies in `package.json` and then write your `mapStateToProps` functions
15 | like you normally would!
16 |
17 | You'll also need to hook up the history directly to the store. Change the const
18 | `history` in `app/app.js` to the following:
19 |
20 | ```js
21 | const history = syncHistoryWithStore(browserHistory, store, {
22 | selectLocationState: (state) => state.get('route').toJS(),
23 | });
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/js/reselect.md:
--------------------------------------------------------------------------------
1 | # `reselect`
2 |
3 | reselect memoizes ("caches") previous state trees and calculations based on said
4 | tree. This means repeated changes and calculations are fast and efficient,
5 | providing us with a performance boost over standard `mapStateToProps`
6 | implementations.
7 |
8 | The [official documentation](https://github.com/reactjs/reselect)
9 | offers a good starting point!
10 |
11 | ## Usage
12 |
13 | There are two different kinds of selectors, simple and complex ones.
14 |
15 | ### Simple selectors
16 |
17 | Simple selectors are just that: they take the application state and select a
18 | part of it.
19 |
20 | ```javascript
21 | const mySelector = (state) => state.get('someState');
22 |
23 | export {
24 | mySelector,
25 | };
26 | ```
27 |
28 | ### Complex selectors
29 |
30 | If we need to, we can combine simple selectors to build more complex ones which
31 | get nested state parts with reselects `createSelector` function. We import other
32 | selectors and pass them to the `createSelector` call:
33 |
34 | ```javascript
35 | import { createSelector } from 'reselect';
36 | import mySelector from 'mySelector';
37 |
38 | const myComplexSelector = createSelector(
39 | mySelector,
40 | (myState) => myState.get('someNestedState')
41 | );
42 |
43 | export {
44 | myComplexSelector,
45 | };
46 | ```
47 |
48 | These selectors can then either be used directly in our containers as
49 | `mapStateToProps` functions or be nested with `createSelector` once again:
50 |
51 | ```javascript
52 | export default connect(createSelector(
53 | myComplexSelector,
54 | (myNestedState) => ({ data: myNestedState })
55 | ))(SomeComponent);
56 | ```
57 |
58 | ### Adding a new selector
59 |
60 | If you have a `selectors.js` file next to the reducer which's part of the state
61 | you want to select, add your selector to said file. If you don't have one yet,
62 | add a new one into your container folder and fill it with this boilerplate code:
63 |
64 | ```JS
65 | import { createSelector } from 'reselect';
66 |
67 | const selectMyState = () => createSelector(
68 |
69 | );
70 |
71 | export {
72 | selectMyState,
73 | };
74 | ```
75 |
76 | ---
77 |
78 | _Don't like this feature? [Click here](remove.md)_
79 |
--------------------------------------------------------------------------------
/docs/testing/README.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | - [Unit Testing](unit-testing.md)
4 | - [Component Testing](component-testing.md)
5 | - [Remote Testing](remote-testing.md)
6 |
7 | Testing your application is a vital part of serious development. There are a few
8 | things you should test. If you've never done this before start with [unit testing](unit-testing.md).
9 | Move on to [component testing](component-testing.md) when you feel like you
10 | understand that!
11 |
12 | We also support [remote testing](remote-testing.md) your local application,
13 | which is quite awesome, so definitely check that out!
14 |
15 | ## Usage with this boilerplate
16 |
17 | To test your application started with this boilerplate do the following:
18 |
19 | 1. Sprinkle `.test.js` files directly next to the parts of your application you
20 | want to test. (Or in `test/` subdirectories, it doesn't really matter as long
21 | as they are directly next to those parts and end in `.test.js`)
22 |
23 | 1. Write your unit and component tests in those files.
24 |
25 | 1. Run `npm run test` in your terminal and see all the tests pass! (hopefully)
26 |
27 | There are a few more commands related to testing, checkout the [commands documentation](../general/commands.md#testing)
28 | for the full list!
29 |
--------------------------------------------------------------------------------
/docs/testing/remote-testing.md:
--------------------------------------------------------------------------------
1 | # Remote testing
2 |
3 | ```Shell
4 | npm run start:tunnel
5 | ```
6 |
7 | This command will start a server and tunnel it with `ngrok`. You'll get a URL
8 | that looks a bit like this: `http://abcdef.ngrok.com`
9 |
10 | This URL will show the version of your application that's in the `build` folder,
11 | and it's accessible from the entire world! This is great for testing on different
12 | devices and from different locations!
13 |
--------------------------------------------------------------------------------
/internals/config.js:
--------------------------------------------------------------------------------
1 | const resolve = require('path').resolve;
2 | const pullAll = require('lodash/pullAll');
3 | const uniq = require('lodash/uniq');
4 |
5 | const ReactBoilerplate = {
6 | // This refers to the react-boilerplate version this project is based on.
7 | version: '3.1.0',
8 |
9 | /**
10 | * The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading
11 | * by caching the module metadata for all of our npm dependencies. We enable it by default
12 | * in development.
13 | *
14 | *
15 | * To disable the DLL Plugin, set this value to false.
16 | */
17 | dllPlugin: {
18 | defaults: {
19 | /**
20 | * we need to exclude dependencies which are not intended for the browser
21 | * by listing them here.
22 | */
23 | exclude: [
24 | 'chalk',
25 | 'compression',
26 | 'cross-env',
27 | 'express',
28 | 'ip',
29 | 'minimist',
30 | 'sanitize.css',
31 | ],
32 |
33 | /**
34 | * Specify any additional dependencies here. We include core-js and lodash
35 | * since a lot of our dependencies depend on them and they get picked up by webpack.
36 | */
37 | include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'],
38 |
39 | // The path where the DLL manifest and bundle will get built
40 | path: resolve('../node_modules/react-boilerplate-dlls'),
41 | },
42 |
43 | entry(pkg) {
44 | const dependencyNames = Object.keys(pkg.dependencies);
45 | const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude;
46 | const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include;
47 | const includeDependencies = uniq(dependencyNames.concat(include));
48 |
49 | return {
50 | reactBoilerplateDeps: pullAll(includeDependencies, exclude),
51 | };
52 | },
53 | },
54 | };
55 |
56 | module.exports = ReactBoilerplate;
57 |
--------------------------------------------------------------------------------
/internals/ethdeploy/ethdeploy.base.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const contracts = require('../../app/contracts/build/classes.json');
3 | const environments = require('../../app/contracts/build/environments.json');
4 |
5 | // new entry point
6 | var entry = {
7 | testrpc: contracts,
8 | testnet: contracts,
9 | livenet: contracts,
10 | };
11 |
12 | // use environments
13 | if (typeof environments !== 'object') {
14 | if (Object.keys(environments).length !== 0) {
15 | entry = environments;
16 | }
17 | }
18 |
19 | // main module export
20 | module.exports = {
21 | entry: entry,
22 | config: {
23 | defaultAccount: 0,
24 | defaultGas: 3000000,
25 | environments: {
26 | testrpc: {
27 | provider: {
28 | type: 'http',
29 | host: 'http://localhost',
30 | port: 8545,
31 | },
32 | },
33 | testnet: {
34 | provider: {
35 | type: 'zero-client',
36 | getAccounts: function(cb) {
37 | cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']);
38 | },
39 | signTransaction: function(rawTx, cb) {
40 | const privateKey = new Buffer('', 'hex');
41 |
42 | const tx = new Tx(rawTx);
43 | tx.sign(privateKey);
44 |
45 | cb(null, ethUtil.bufferToHex(tx.serialize()));
46 | },
47 | host: 'https://morden.infura.io',
48 | port: 8545,
49 | }
50 | },
51 | livenet: {
52 | provider: {
53 | type: 'zero-client',
54 | getAccounts: function(cb) {
55 | cb(null, ['0x2233eD250Ea774146B0fBbC1da0Ffa6a81514cCC']);
56 | },
57 | signTransaction: function(rawTx, cb) {
58 | const privateKey = new Buffer('', 'hex');
59 |
60 | const tx = new Tx(rawTx);
61 | tx.sign(privateKey);
62 |
63 | cb(null, ethUtil.bufferToHex(tx.serialize()));
64 | },
65 | host: 'https://livenet.infura.io',
66 | port: 8545,
67 | }
68 | },
69 | },
70 | },
71 | };
72 |
--------------------------------------------------------------------------------
/internals/ethdeploy/ethdeploy.livenet.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const base = require('./ethdeploy.base.js');
3 |
4 | // main module export
5 | module.exports = Object.assign(base, {
6 | output: {
7 | environment: 'livenet',
8 | },
9 | module: function(deploy, contracts){
10 | deploy(contracts.SimpleStoreRegistry).then(function(simpleStoreRegistry){
11 | deploy(contracts.SimpleStoreFactory, simpleStoreRegistry.address);
12 | });
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/internals/ethdeploy/ethdeploy.testnet.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const base = require('./ethdeploy.base.js');
3 |
4 | // main module export
5 | module.exports = Object.assign(base, {
6 | output: {
7 | environment: 'testnet',
8 | },
9 | module: function(deploy, contracts){
10 | deploy(contracts.SimpleStoreRegistry).then(function(simpleStoreRegistry){
11 | deploy(contracts.SimpleStoreFactory, simpleStoreRegistry.address);
12 | });
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/internals/ethdeploy/ethdeploy.testrpc.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const base = require('./ethdeploy.base.js');
3 |
4 | // main module export
5 | module.exports = Object.assign(base, {
6 | output: {
7 | environment: 'testrpc',
8 | },
9 | module: function(deploy, contracts){
10 | deploy(contracts.SimpleStoreRegistry).then(function(simpleStoreRegistry){
11 | deploy(contracts.SimpleStoreFactory, simpleStoreRegistry.address);
12 | });
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 |
9 | {{#if wantMessages}}
10 | import { FormattedMessage } from 'react-intl';
11 | import messages from './messages';
12 | {{/if}}
13 | {{#if wantCSS}}
14 | import styles from './styles.css';
15 | {{/if}}
16 |
17 | class {{ properCase name }} extends React.Component { // eslint-disable-line react/prefer-stateless-function
18 | render() {
19 | return (
20 | {{#if wantCSS}}
21 |