├── postcss.config.js ├── public ├── robots.txt ├── favicon.ico └── humans.txt ├── .eslintignore ├── src ├── images │ ├── jsa-128.jpg │ └── nature-600-337.jpg ├── components │ ├── Drawer │ │ ├── Drawer.scss │ │ ├── index.js │ │ ├── MyDrawerContainer.js │ │ ├── MyDrawerModule.js │ │ └── MyDrawerComponent.js │ └── topBar │ │ ├── index.js │ │ ├── topBarContainer.js │ │ ├── topBarModule.js │ │ └── topBarComponent.js ├── routes │ ├── Home │ │ ├── components │ │ │ ├── HomeView.scss │ │ │ └── HomeView.js │ │ ├── assets │ │ │ └── Duck.jpg │ │ └── index.js │ ├── MetaCoin │ │ ├── components │ │ │ ├── HeaderComponent.js │ │ │ ├── CoinCount.js │ │ │ ├── Layout.js │ │ │ └── SendCoin.js │ │ ├── containers │ │ │ ├── HeaderContainer.js │ │ │ ├── CoinCountContainer.js │ │ │ ├── LayoutContainer.js │ │ │ └── SendCoinContainer.js │ │ ├── index.js │ │ └── modules │ │ │ └── metaCoinModule.js │ ├── index.js │ └── Counter │ │ ├── components │ │ └── Counter.js │ │ ├── index.js │ │ ├── containers │ │ └── CounterContainer.js │ │ └── modules │ │ └── counter.js ├── layouts │ └── CoreLayout │ │ ├── CoreLayout.scss │ │ ├── index.js │ │ └── CoreLayout.js ├── styles │ ├── _base.scss │ └── core.scss ├── index.html ├── containers │ └── AppContainer.js ├── store │ ├── location.js │ ├── reducers.js │ ├── createStore.js │ └── web3Reducer.js └── main.js ├── .gitignore ├── walkthrough ├── Metamask2.jpg ├── truffle_2_to_3.jpg ├── UpgradeToTruffle3.md └── WalkThrough.md ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── contracts ├── ConvertLib.sol ├── Migrations.sol └── MetaCoin.sol ├── tests ├── .eslintrc ├── routes │ ├── Home │ │ ├── index.spec.js │ │ └── components │ │ │ └── HomeView.spec.js │ └── Counter │ │ ├── index.spec.js │ │ ├── components │ │ └── Counter.spec.js │ │ └── modules │ │ └── counter.spec.js ├── store │ ├── createStore.spec.js │ └── location.spec.js ├── layouts │ └── CoreLayout.spec.js ├── components │ └── Header │ │ └── Header.spec.js └── test-bundler.js ├── bin ├── dev-server.js └── compile.js ├── .travis.yml ├── truffle.js ├── .eslintrc ├── test ├── TestMetacoin.sol └── metacoin.js ├── .editorconfig ├── LICENSE ├── config ├── environments.config.js ├── karma.config.js ├── project.config.js └── webpack.config.js ├── CONTRIBUTING.md ├── server └── main.js ├── package.json ├── README.md └── CHANGELOG.md /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | blueprints/**/files/** 2 | coverage/** 3 | node_modules/** 4 | dist/** 5 | src/index.html 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogoiii/web3-react-redux-starter-kit/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/images/jsa-128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogoiii/web3-react-redux-starter-kit/HEAD/src/images/jsa-128.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *.log 3 | 4 | node_modules 5 | 6 | dist 7 | coverage 8 | 9 | .idea/ 10 | build/ 11 | -------------------------------------------------------------------------------- /src/components/Drawer/Drawer.scss: -------------------------------------------------------------------------------- 1 | .route--active { 2 | font-weight: bold; 3 | text-decoration: underline; 4 | } 5 | -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.scss: -------------------------------------------------------------------------------- 1 | .duck { 2 | display: block; 3 | width: 100px; 4 | margin: 0 auto; 5 | } 6 | -------------------------------------------------------------------------------- /walkthrough/Metamask2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogoiii/web3-react-redux-starter-kit/HEAD/walkthrough/Metamask2.jpg -------------------------------------------------------------------------------- /src/components/topBar/index.js: -------------------------------------------------------------------------------- 1 | import TopBar from './topBarContainer' 2 | 3 | // Sync route definition 4 | export default TopBar 5 | -------------------------------------------------------------------------------- /src/images/nature-600-337.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogoiii/web3-react-redux-starter-kit/HEAD/src/images/nature-600-337.jpg -------------------------------------------------------------------------------- /walkthrough/truffle_2_to_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogoiii/web3-react-redux-starter-kit/HEAD/walkthrough/truffle_2_to_3.jpg -------------------------------------------------------------------------------- /src/routes/Home/assets/Duck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogoiii/web3-react-redux-starter-kit/HEAD/src/routes/Home/assets/Duck.jpg -------------------------------------------------------------------------------- /src/components/Drawer/index.js: -------------------------------------------------------------------------------- 1 | import MyDrawer from './MyDrawerContainer' 2 | 3 | // Sync route definition 4 | export default MyDrawer 5 | -------------------------------------------------------------------------------- /src/routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomeView from './components/HomeView' 2 | 3 | // Sync route definition 4 | export default { 5 | component : HomeView 6 | } 7 | -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # Check it out: http://humanstxt.org/ 2 | 3 | # TEAM 4 | 5 | -- -- 6 | 7 | # THANKS 8 | 9 | 10 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/ConvertLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | library ConvertLib{ 4 | function convert(uint amount,uint conversionRate) returns (uint convertedAmount) 5 | { 6 | return amount * conversionRate; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : "../.eslintrc", 3 | "env" : { 4 | "mocha" : true 5 | }, 6 | "globals" : { 7 | "expect" : false, 8 | "should" : false, 9 | "sinon" : false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/CoreLayout.scss: -------------------------------------------------------------------------------- 1 | .core-layout__viewport { 2 | padding-top: 4rem; 3 | display: flex; 4 | } 5 | // 6 | // .container { 7 | // display: flex; 8 | // flex-direction: 'column' 9 | // } 10 | 11 | .testwidth { 12 | width: 256px; 13 | } 14 | -------------------------------------------------------------------------------- /bin/dev-server.js: -------------------------------------------------------------------------------- 1 | const project = require('../config/project.config') 2 | const server = require('../server/main') 3 | const debug = require('debug')('app:bin:dev-server') 4 | 5 | server.listen(project.server_port) 6 | debug(`Server is now running at http://localhost:${project.server_port}.`) 7 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ConvertLib = artifacts.require("./ConvertLib.sol"); 2 | var MetaCoin = artifacts.require("./MetaCoin.sol"); 3 | 4 | module.exports = function(deployer) { 5 | deployer.deploy(ConvertLib); 6 | deployer.link(ConvertLib, MetaCoin); 7 | deployer.deploy(MetaCoin); 8 | }; 9 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/components/HeaderComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Header = () => { 4 | return ( 5 |
6 |

MetaCoin Example Webpack react-redux Example!

7 |
8 | ) 9 | } 10 | 11 | Header.propTypes = { 12 | } 13 | 14 | export default Header 15 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/containers/HeaderContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import Header from '../components/HeaderComponent' 4 | 5 | const mapDispatchToProps = { 6 | } 7 | 8 | const mapStateToProps = (state) => ({ 9 | }) 10 | 11 | export default connect(mapStateToProps, mapDispatchToProps)(Header) 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | 10 | install: 11 | - npm install -g yarn 12 | - yarn install 13 | 14 | script: 15 | - npm run deploy:dev 16 | - npm run deploy:prod 17 | 18 | after_success: 19 | - npm run codecov 20 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/index.js: -------------------------------------------------------------------------------- 1 | import CoreLayout from './CoreLayout' 2 | import { connect } from 'react-redux' 3 | import { web3Connect } from '../../store/web3Reducer' 4 | 5 | const mapDispatchToProps = { 6 | web3Connect 7 | } 8 | 9 | const mapStateToProps = (state) => ({ 10 | }) 11 | 12 | export default connect(mapStateToProps, mapDispatchToProps)(CoreLayout) 13 | -------------------------------------------------------------------------------- /src/styles/_base.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Application Settings Go Here 3 | ------------------------------------ 4 | This file acts as a bundler for all variables/mixins/themes, so they 5 | can easily be swapped out without `core.scss` ever having to know. 6 | 7 | For example: 8 | 9 | @import './variables/colors'; 10 | @import './variables/components'; 11 | @import './themes/default'; 12 | */ 13 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/containers/CoinCountContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import CoinCount from '../components/CoinCount' 4 | 5 | const mapDispatchToProps = { 6 | } 7 | 8 | const mapStateToProps = (state) => ({ 9 | isConnected: state.web3Wrap.isConnected, 10 | balance: state.metaCoin.value 11 | }) 12 | 13 | export default connect(mapStateToProps, mapDispatchToProps)(CoinCount) 14 | -------------------------------------------------------------------------------- /src/components/Drawer/MyDrawerContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { toggleDrawer } from './MyDrawerModule' 3 | 4 | import MyDrawer from './MyDrawerComponent' 5 | 6 | const mapDispatchToProps = { 7 | toggleDrawer 8 | } 9 | 10 | const mapStateToProps = (state) => ({ 11 | open: state.drawerState 12 | }) 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(MyDrawer) 15 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/components/CoinCount.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | 3 | export const CoinCount = ({ balance, isConnected }) => { 4 | balance = (isConnected) ? balance : '¯\\_(ツ)_/¯' 5 | return ( 6 |
7 | Your Balance: {balance} 8 |
9 | ) 10 | } 11 | 12 | CoinCount.propTypes = { 13 | balance: PropTypes.string, 14 | isConnected: PropTypes.bool 15 | } 16 | 17 | export default CoinCount 18 | -------------------------------------------------------------------------------- /src/styles/core.scss: -------------------------------------------------------------------------------- 1 | // @import 'base'; 2 | // @import '~normalize.css/normalize'; 3 | // 4 | // // Some best-practice CSS that's useful for most apps 5 | // // Just remove them if they're not what you want 6 | // html { 7 | // box-sizing: border-box; 8 | // } 9 | // 10 | // html, 11 | // body { 12 | // margin: 0; 13 | // padding: 0; 14 | // height: 100%; 15 | // } 16 | // 17 | // *, 18 | // *:before, 19 | // *:after { 20 | // box-sizing: inherit; 21 | // } 22 | -------------------------------------------------------------------------------- /tests/routes/Home/index.spec.js: -------------------------------------------------------------------------------- 1 | import HomeRoute from 'routes/Home' 2 | 3 | describe('(Route) Home', () => { 4 | let _component 5 | 6 | beforeEach(() => { 7 | _component = HomeRoute.component() 8 | }) 9 | 10 | it('Should return a route configuration object', () => { 11 | expect(typeof HomeRoute).to.equal('object') 12 | }) 13 | 14 | it('Should define a route component', () => { 15 | expect(_component.type).to.equal('div') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/components/topBar/topBarContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { openDrawer } from '../Drawer/MyDrawerModule' 3 | import TopBar from './topBarComponent' 4 | 5 | const mapDispatchToProps = { 6 | openDrawer 7 | } 8 | 9 | const mapStateToProps = (state) => ({ 10 | title: state.topBarState.title, 11 | web3Wrap: state.web3Wrap, 12 | balance: state.metaCoin 13 | }) 14 | 15 | export default connect(mapStateToProps, mapDispatchToProps)(TopBar) 16 | -------------------------------------------------------------------------------- /tests/routes/Counter/index.spec.js: -------------------------------------------------------------------------------- 1 | import CounterRoute from 'routes/Counter' 2 | 3 | describe('(Route) Counter', () => { 4 | let _route 5 | 6 | beforeEach(() => { 7 | _route = CounterRoute({}) 8 | }) 9 | 10 | it('Should return a route configuration object', () => { 11 | expect(typeof _route).to.equal('object') 12 | }) 13 | 14 | it('Configuration should contain path `counter`', () => { 15 | expect(_route.path).to.equal('counter') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DuckImage from '../assets/Duck.jpg' 3 | import './HomeView.scss' 4 | import { Row } from 'react-flexbox-grid/lib/index' 5 | 6 | export const HomeView = () => ( 7 |
8 | 9 |

Welcome!

10 |
11 | This is a duck, because Redux! 15 |
16 | ) 17 | 18 | export default HomeView 19 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | // Allows us to use ES6 in our migrations and tests. 2 | require('babel-register') 3 | 4 | module.exports = { 5 | build: 'webpack', 6 | networks: { 7 | development: { 8 | host: 'localhost', 9 | port: 8545, 10 | network_id: '*' // Match any network id 11 | }, 12 | production: { 13 | host: 'localhost', 14 | port: 8888, 15 | network_id: '*' // Match any network id 16 | } 17 | }, 18 | migrations_directory: './migrations' 19 | } 20 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | // We only need to import the modules necessary for initial render 2 | import CoreLayout from '../layouts/CoreLayout' 3 | import Home from './Home' 4 | import CounterRoute from './Counter' 5 | import MetaCoinRoute from './MetaCoin' 6 | 7 | export const createRoutes = (store) => ({ 8 | path : '/', 9 | component : CoreLayout, 10 | indexRoute : Home, 11 | childRoutes : [ 12 | CounterRoute(store), 13 | MetaCoinRoute(store) 14 | ] 15 | }) 16 | 17 | export default createRoutes 18 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/containers/LayoutContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import { getBalance } from '../modules/metaCoinModule' 4 | 5 | import Layout from '../components/Layout' 6 | 7 | const mapDispatchToProps = { 8 | getBalance 9 | } 10 | 11 | const mapStateToProps = (state) => ({ 12 | isConnected: state.web3Wrap.isConnected, 13 | accounts: (state.web3Wrap.web3 && state.web3Wrap.web3.eth) ? state.web3Wrap.web3.eth.accounts : [] 14 | }) 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(Layout) 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Redux Starter Kit 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react" 6 | ], 7 | "plugins": [ 8 | "babel", 9 | "react", 10 | "promise" 11 | ], 12 | "env": { 13 | "browser" : true 14 | }, 15 | "globals": { 16 | "__DEV__" : false, 17 | "__TEST__" : false, 18 | "__PROD__" : false, 19 | "__COVERAGE__" : false 20 | }, 21 | "rules": { 22 | "key-spacing" : 0, 23 | "jsx-quotes" : [2, "prefer-single"], 24 | "max-len" : [2, 120, 2], 25 | "object-curly-spacing" : [2, "always"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { browserHistory, Router } from 'react-router' 3 | import { Provider } from 'react-redux' 4 | 5 | class AppContainer extends Component { 6 | static propTypes = { 7 | routes : PropTypes.object.isRequired, 8 | store : PropTypes.object.isRequired 9 | } 10 | 11 | render () { 12 | const { routes, store } = this.props 13 | return ( 14 | 15 |
16 | 17 |
18 |
19 | ) 20 | } 21 | } 22 | 23 | export default AppContainer 24 | -------------------------------------------------------------------------------- /tests/routes/Home/components/HomeView.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { HomeView } from 'routes/Home/components/HomeView' 3 | import { render } from 'enzyme' 4 | 5 | describe.skip('(View) Home', () => { 6 | let _component 7 | 8 | beforeEach(() => { 9 | _component = render() 10 | }) 11 | 12 | it('Renders a welcome message', () => { 13 | const welcome = _component.find('h4') 14 | expect(welcome).to.exist 15 | expect(welcome.text()).to.match(/Welcome!/) 16 | }) 17 | 18 | it('Renders an awesome duck image', () => { 19 | const duck = _component.find('img') 20 | expect(duck).to.exist 21 | expect(duck.attr('alt')).to.match(/This is a duck, because Redux!/) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/routes/Counter/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import RaisedButton from 'material-ui/RaisedButton' 4 | import { Row } from 'react-flexbox-grid/lib/index' 5 | 6 | export const Counter = (props) => ( 7 | 8 |
9 |

Counter: {props.counter}

10 | 11 | {' '} 12 | 13 |
14 |
15 | ) 16 | 17 | Counter.propTypes = { 18 | counter : React.PropTypes.number.isRequired, 19 | doubleAsync : React.PropTypes.func.isRequired, 20 | increment : React.PropTypes.func.isRequired 21 | } 22 | 23 | export default Counter 24 | -------------------------------------------------------------------------------- /test/TestMetacoin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | import "truffle/Assert.sol"; 4 | import "truffle/DeployedAddresses.sol"; 5 | import "../contracts/MetaCoin.sol"; 6 | 7 | contract TestMetacoin { 8 | 9 | function testInitialBalanceUsingDeployedContract() { 10 | MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin()); 11 | 12 | uint expected = 10000; 13 | 14 | Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially"); 15 | } 16 | 17 | function testInitialBalanceWithNewMetaCoin() { 18 | MetaCoin meta = new MetaCoin(); 19 | 20 | uint expected = 10000; 21 | 22 | Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/store/createStore.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | default as createStore 3 | } from 'store/createStore' 4 | 5 | describe('(Store) createStore', () => { 6 | let store 7 | 8 | before(() => { 9 | store = createStore() 10 | }) 11 | 12 | it('should have an empty asyncReducers object', () => { 13 | expect(store.asyncReducers).to.be.an('object') 14 | expect(store.asyncReducers).to.be.empty 15 | }) 16 | 17 | describe('(Location)', () => { 18 | it('store should be initialized with Location state', () => { 19 | const location = { 20 | pathname : '/echo' 21 | } 22 | store.dispatch({ 23 | type : 'LOCATION_CHANGE', 24 | payload : location 25 | }) 26 | expect(store.getState().location).to.deep.equal(location) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/layouts/CoreLayout.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TestUtils from 'react-addons-test-utils' 3 | import CoreLayout from 'layouts/CoreLayout/CoreLayout' 4 | 5 | function shallowRender (component) { 6 | const renderer = TestUtils.createRenderer() 7 | 8 | renderer.render(component) 9 | return renderer.getRenderOutput() 10 | } 11 | 12 | function shallowRenderWithProps (props = {}) { 13 | return shallowRender() 14 | } 15 | 16 | describe.skip('(Layout) Core', function () { 17 | let _component 18 | let _props 19 | let _child 20 | 21 | beforeEach(function () { 22 | _child =

Child

23 | _props = { 24 | children : _child 25 | } 26 | 27 | _component = shallowRenderWithProps(_props) 28 | }) 29 | 30 | it('Should render as a
.', function () { 31 | expect(_component.type).to.equal('div') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # A special property that should be specified at the top of the file outside of 4 | # any sections. Set to true to stop .editor config file search on current file 5 | root = true 6 | 7 | [*] 8 | # Indentation style 9 | # Possible values - tab, space 10 | indent_style = space 11 | 12 | # Indentation size in single-spaced characters 13 | # Possible values - an integer, tab 14 | indent_size = 2 15 | 16 | # Line ending file format 17 | # Possible values - lf, crlf, cr 18 | end_of_line = lf 19 | 20 | # File character encoding 21 | # Possible values - latin1, utf-8, utf-16be, utf-16le 22 | charset = utf-8 23 | 24 | # Denotes whether to trim whitespace at the end of lines 25 | # Possible values - true, false 26 | trim_trailing_whitespace = true 27 | 28 | # Denotes whether file should end with a newline 29 | # Possible values - true, false 30 | insert_final_newline = true 31 | -------------------------------------------------------------------------------- /src/routes/Counter/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | path : 'counter', 5 | /* Async getComponent is only invoked when route matches */ 6 | getComponent (nextState, cb) { 7 | /* Webpack - use 'require.ensure' to create a split point 8 | and embed an async module loader (jsonp) when bundling */ 9 | require.ensure([], (require) => { 10 | /* Webpack - use require callback to define 11 | dependencies for bundling */ 12 | const Counter = require('./containers/CounterContainer').default 13 | const reducer = require('./modules/counter').default 14 | 15 | /* Add the reducer to the store on key 'counter' */ 16 | injectReducer(store, { key: 'counter', reducer }) 17 | 18 | /* Return getComponent */ 19 | cb(null, Counter) 20 | 21 | /* Webpack named bundle */ 22 | }, 'counter') 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | path : 'metaCoin', 5 | /* Async getComponent is only invoked when route matches */ 6 | getComponent (nextState, cb) { 7 | /* Webpack - use 'require.ensure' to create a split point 8 | and embed an async module loader (jsonp) when bundling */ 9 | require.ensure([], (require) => { 10 | /* Webpack - use require callback to define 11 | dependencies for bundling */ 12 | 13 | const container = require('./containers/LayoutContainer').default 14 | const reducer = require('./modules/metaCoinModule').default 15 | 16 | /* Add the reducer to the store on key 'metaCoin' */ 17 | injectReducer(store, { key: 'metaCoin', reducer }) 18 | 19 | /* Return getComponent */ 20 | cb(null, container) 21 | 22 | /* Webpack named bundle */ 23 | }, 'metaCoin') 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { Row, Col } from 'react-flexbox-grid/lib/index' 3 | 4 | import Header from '../containers/HeaderContainer' 5 | import CoinCount from '../containers/CoinCountContainer' 6 | import SendCoin from '../containers/SendCoinContainer' 7 | 8 | export class Layout extends Component { 9 | 10 | componentWillMount () { 11 | if (this.props.isConnected) { 12 | this.props.getBalance({ account: this.props.accounts[0] }) 13 | } 14 | } 15 | 16 | render () { 17 | return ( 18 | 19 |
20 | 21 | 22 | 23 | ) 24 | } 25 | } 26 | 27 | Layout.propTypes = { 28 | isConnected: PropTypes.bool.isRequired, 29 | getBalance: PropTypes.func.isRequired, 30 | accounts: PropTypes.array.isRequired 31 | } 32 | 33 | export default Layout 34 | -------------------------------------------------------------------------------- /src/store/location.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const LOCATION_CHANGE = 'LOCATION_CHANGE' 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | export function locationChange (location = '/') { 10 | return { 11 | type: LOCATION_CHANGE, 12 | payload: location 13 | } 14 | } 15 | 16 | // ------------------------------------ 17 | // Specialized Action Creator 18 | // ------------------------------------ 19 | export const updateLocation = ({ dispatch }) => { 20 | return (nextLocation) => dispatch(locationChange(nextLocation)) 21 | } 22 | 23 | // ------------------------------------ 24 | // Reducer 25 | // ------------------------------------ 26 | const initialState = null 27 | export default function locationReducer (state = initialState, action) { 28 | return action.type === LOCATION_CHANGE 29 | ? action.payload 30 | : state 31 | } 32 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { reducer as formReducer } from 'redux-form' 3 | import locationReducer from './location' 4 | import web3Reducer from './web3Reducer' 5 | import MyDrawerReducer from '../components/Drawer/MyDrawerModule' 6 | import MyTopBarReducer from '../components/topBar/topBarModule' 7 | import MetaCoinReducer from '../routes/MetaCoin/modules/metaCoinModule' 8 | 9 | export const makeRootReducer = (asyncReducers) => { 10 | return combineReducers({ 11 | drawerState: MyDrawerReducer, 12 | topBarState: MyTopBarReducer, 13 | web3Wrap: web3Reducer, 14 | metaCoin: MetaCoinReducer, 15 | location: locationReducer, 16 | form: formReducer, 17 | ...asyncReducers 18 | }) 19 | } 20 | 21 | export const injectReducer = (store, { key, reducer }) => { 22 | if (Object.hasOwnProperty.call(store.asyncReducers, key)) return 23 | 24 | store.asyncReducers[key] = reducer 25 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 26 | } 27 | 28 | export default makeRootReducer 29 | -------------------------------------------------------------------------------- /contracts/MetaCoin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | import "./ConvertLib.sol"; 4 | 5 | // This is just a simple example of a coin-like contract. 6 | // It is not standards compatible and cannot be expected to talk to other 7 | // coin/token contracts. If you want to create a standards-compliant 8 | // token, see: https://github.com/ConsenSys/Tokens. Cheers! 9 | 10 | contract MetaCoin { 11 | mapping (address => uint) balances; 12 | 13 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 14 | 15 | function MetaCoin() { 16 | balances[tx.origin] = 10000; 17 | } 18 | 19 | function sendCoin(address receiver, uint amount) returns(bool sufficient) { 20 | if (balances[msg.sender] < amount) return false; 21 | balances[msg.sender] -= amount; 22 | balances[receiver] += amount; 23 | Transfer(msg.sender, receiver, amount); 24 | return true; 25 | } 26 | 27 | function getBalanceInEth(address addr) returns(uint){ 28 | return ConvertLib.convert(getBalance(addr),2); 29 | } 30 | 31 | function getBalance(address addr) returns(uint) { 32 | return balances[addr]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/topBar/topBarModule.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const CHANGE_TB_TITLE = 'CHANGE_TB_TITLE' 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | export function changeTBTitle (title = '') { 10 | return { 11 | type: CHANGE_TB_TITLE, 12 | payload: { 13 | title: title 14 | } 15 | } 16 | } 17 | 18 | export const actions = { 19 | changeTBTitle 20 | } 21 | 22 | // ------------------------------------ 23 | // Action Handlers 24 | // ------------------------------------ 25 | const ACTION_HANDLERS = { 26 | [CHANGE_TB_TITLE]: (state, action) => { 27 | return Object.assign({}, state.topBarState, action.payload) 28 | } 29 | } 30 | 31 | // ------------------------------------ 32 | // Reducer 33 | // ------------------------------------ 34 | const initialState = { title: null } 35 | export default function MyTopBarReducer (state = initialState, action) { 36 | const handler = ACTION_HANDLERS[action.type] 37 | return handler ? handler(state, action) : state 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Zukowski 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 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/containers/SendCoinContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { reduxForm } from 'redux-form' 3 | import SendCoin from '../components/SendCoin' 4 | import { sendCoin as sendCoinFn } from '../modules/metaCoinModule' 5 | 6 | const validate = values => { 7 | const errors = {} 8 | if (!values.address) { 9 | errors.address = 'Required' 10 | } else if (values.address.length < 40) { 11 | errors.address = 'Must be 32 characters or less' 12 | } 13 | 14 | if (!values.amount) { 15 | errors.amount = 'Required' 16 | } else if (isNaN(Number(values.amount))) { 17 | errors.amount = 'Must be a number' 18 | } 19 | return errors 20 | } 21 | 22 | const warn = values => { // todo add warnings if desired 23 | const warnings = {} 24 | return warnings 25 | } 26 | 27 | const mapDispatchToProps = { 28 | submitTransaction: sendCoinFn 29 | } 30 | 31 | const mapStateToProps = (state) => ({ 32 | accounts: (state.web3Wrap.web3 && state.web3Wrap.web3.eth) ? state.web3Wrap.web3.eth.accounts : [] 33 | }) 34 | 35 | export default reduxForm({ 36 | form: 'sendCoins', 37 | validate, 38 | warn 39 | })(connect(mapStateToProps, mapDispatchToProps)(SendCoin)) 40 | -------------------------------------------------------------------------------- /tests/components/Header/Header.spec.js: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | // import { Header } from 'components/Header/Header' 3 | // import { IndexLink, Link } from 'react-router' 4 | // import { shallow } from 'enzyme' 5 | // 6 | // describe.skip('(Component) Header', () => { 7 | // let _wrapper 8 | // 9 | // beforeEach(() => { 10 | // _wrapper = shallow(
) 11 | // }) 12 | // 13 | // it('Renders a welcome message', () => { 14 | // const welcome = _wrapper.find('h1') 15 | // expect(welcome).to.exist 16 | // expect(welcome.text()).to.match(/React Redux Starter Kit/) 17 | // }) 18 | // 19 | // describe('Navigation links...', () => { 20 | // it('Should render a Link to Home route', () => { 21 | // expect(_wrapper.contains( 22 | // 23 | // Home 24 | // 25 | // )).to.be.true 26 | // }) 27 | // 28 | // it('Should render a Link to Counter route', () => { 29 | // expect(_wrapper.contains( 30 | // 31 | // Counter 32 | // 33 | // )).to.be.true 34 | // }) 35 | // }) 36 | // }) 37 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './CoreLayout.scss' 3 | import '../../styles/core.scss' 4 | 5 | import TopBar from '../../components/topBar' 6 | import MyDrawer from '../../components/Drawer' 7 | import { Grid, Row, Col } from 'react-flexbox-grid/lib/index' 8 | 9 | export class CoreLayout extends Component { 10 | 11 | componentWillMount () { 12 | this.props.web3Connect() // check if web3 exists. metamask compatibility 13 | } 14 | 15 | render () { 16 | const { children } = this.props 17 | return ( 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | {children} 30 | 31 |
32 |
33 | ) 34 | } 35 | } 36 | 37 | CoreLayout.propTypes = { 38 | children: React.PropTypes.element.isRequired, 39 | web3Connect: React.PropTypes.func.isRequired 40 | } 41 | 42 | export default CoreLayout 43 | -------------------------------------------------------------------------------- /src/components/Drawer/MyDrawerModule.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const OPEN_DRAWER = 'OPEN_DRAWER' 5 | export const TOGGLE_DRAWER = 'TOGGLE_DRAWER' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export function openDrawer (open = true) { 11 | return { 12 | type: OPEN_DRAWER, 13 | payload: open 14 | } 15 | } 16 | 17 | export function toggleDrawer () { 18 | return { 19 | type: TOGGLE_DRAWER, 20 | payload: {} 21 | } 22 | } 23 | 24 | export const actions = { 25 | openDrawer, 26 | toggleDrawer 27 | } 28 | 29 | // ------------------------------------ 30 | // Action Handlers 31 | // ------------------------------------ 32 | const ACTION_HANDLERS = { 33 | [OPEN_DRAWER]: (state, action) => action.payload, 34 | [TOGGLE_DRAWER]: (state, action) => !state 35 | } 36 | 37 | // ------------------------------------ 38 | // Reducer 39 | // ------------------------------------ 40 | const initialState = false 41 | export default function MyDrawerReducer (state = initialState, action) { 42 | const handler = ACTION_HANDLERS[action.type] 43 | 44 | return handler ? handler(state, action) : state 45 | } 46 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/components/SendCoin.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { Field } from 'redux-form' 3 | 4 | import { AutoComplete, TextField } from 'redux-form-material-ui' 5 | import { Col } from 'react-flexbox-grid/lib/index' 6 | import RaisedButton from 'material-ui/RaisedButton' 7 | 8 | export class SendCoin extends Component { 9 | 10 | render () { 11 | const { accounts, handleSubmit, submitTransaction } = this.props 12 | 13 | return ( 14 | 15 |

Send Coin

16 |
17 | 22 | 27 |
28 | 32 | 33 | 34 | ) 35 | } 36 | } 37 | 38 | SendCoin.propTypes = { 39 | accounts: PropTypes.array, 40 | handleSubmit: PropTypes.func, 41 | submitTransaction: PropTypes.func 42 | } 43 | 44 | export default SendCoin 45 | -------------------------------------------------------------------------------- /config/environments.config.js: -------------------------------------------------------------------------------- 1 | // Here is where you can define configuration overrides based on the execution environment. 2 | // Supply a key to the default export matching the NODE_ENV that you wish to target, and 3 | // the base configuration will apply your overrides before exporting itself. 4 | module.exports = { 5 | // ====================================================== 6 | // Overrides when NODE_ENV === 'development' 7 | // ====================================================== 8 | // NOTE: In development, we use an explicit public path when the assets 9 | // are served webpack by to fix this issue: 10 | // http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809 11 | development : (config) => ({ 12 | compiler_public_path : `http://${config.server_host}:${config.server_port}/` 13 | }), 14 | 15 | // ====================================================== 16 | // Overrides when NODE_ENV === 'production' 17 | // ====================================================== 18 | production : (config) => ({ 19 | compiler_public_path : '/', 20 | compiler_fail_on_warning : false, 21 | compiler_hash_type : 'chunkhash', 22 | compiler_devtool : null, 23 | compiler_stats : { 24 | chunks : true, 25 | chunkModules : true, 26 | colors : true 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /tests/test-bundler.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------- 2 | // Test Environment Setup 3 | // --------------------------------------- 4 | import sinon from 'sinon' 5 | import chai from 'chai' 6 | import sinonChai from 'sinon-chai' 7 | import chaiAsPromised from 'chai-as-promised' 8 | import chaiEnzyme from 'chai-enzyme' 9 | 10 | chai.use(sinonChai) 11 | chai.use(chaiAsPromised) 12 | chai.use(chaiEnzyme()) 13 | 14 | global.chai = chai 15 | global.sinon = sinon 16 | global.expect = chai.expect 17 | global.should = chai.should() 18 | 19 | // --------------------------------------- 20 | // Require Tests 21 | // --------------------------------------- 22 | // for use with karma-webpack-with-fast-source-maps 23 | const __karmaWebpackManifest__ = []; // eslint-disable-line 24 | const inManifest = (path) => ~__karmaWebpackManifest__.indexOf(path) 25 | 26 | // require all `tests/**/*.spec.js` 27 | const testsContext = require.context('./', true, /\.spec\.js$/) 28 | 29 | // only run tests that have changed after the first pass. 30 | const testsToRun = testsContext.keys().filter(inManifest) 31 | ;(testsToRun.length ? testsToRun : testsContext.keys()).forEach(testsContext) 32 | 33 | // require all `src/**/*.js` except for `main.js` (for isparta coverage reporting) 34 | if (__COVERAGE__) { 35 | const componentsContext = require.context('../src/', true, /^((?!main|reducers).)*\.js$/) 36 | componentsContext.keys().forEach(componentsContext) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Drawer/MyDrawerComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './Drawer.scss' 3 | import { IndexLink, Link } from 'react-router' 4 | 5 | import Drawer from 'material-ui/Drawer' 6 | import MenuItem from 'material-ui/MenuItem' 7 | import Subheader from 'material-ui/Subheader' 8 | 9 | let leftNavTop = { 10 | fontSize: '24px', 11 | color: 'rgb(255, 255, 255)', 12 | lineHeight: '64px', 13 | fontWeight: 300, 14 | backgroundColor: 'rgb(0, 188, 212)', 15 | paddingLeft: '24px', 16 | marginBottom: '8px' 17 | } 18 | 19 | export const MyDrawer = (props) => ( 20 | props.toggleDrawer()}> 21 | Navigation 22 | 23 | { props.toggleDrawer() }} /> 26 | 27 | 28 | { props.toggleDrawer() }} /> 31 | 32 | 33 | { props.toggleDrawer() }} /> 36 | 37 | 38 | ) 39 | 40 | MyDrawer.propTypes = { 41 | open: React.PropTypes.bool.isRequired, 42 | toggleDrawer: React.PropTypes.func.isRequired 43 | } 44 | 45 | export default MyDrawer 46 | -------------------------------------------------------------------------------- /src/routes/Counter/containers/CounterContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { increment, doubleAsync } from '../modules/counter' 3 | 4 | /* This is a container component. Notice it does not contain any JSX, 5 | nor does it import React. This component is **only** responsible for 6 | wiring in the actions and state necessary to render a presentational 7 | component - in this case, the counter: */ 8 | 9 | import Counter from '../components/Counter' 10 | 11 | /* Object of action creators (can also be function that returns object). 12 | Keys will be passed as props to presentational components. Here we are 13 | implementing our wrapper around increment; the component doesn't care */ 14 | 15 | const mapDispatchToProps = { 16 | increment : () => increment(1), 17 | doubleAsync 18 | } 19 | 20 | const mapStateToProps = (state) => ({ 21 | counter : state.counter 22 | }) 23 | 24 | /* Note: mapStateToProps is where you should use `reselect` to create selectors, ie: 25 | 26 | import { createSelector } from 'reselect' 27 | const counter = (state) => state.counter 28 | const tripleCount = createSelector(counter, (count) => count * 3) 29 | const mapStateToProps = (state) => ({ 30 | counter: tripleCount(state) 31 | }) 32 | 33 | Selectors can compute derived data, allowing Redux to store the minimal possible state. 34 | Selectors are efficient. A selector is not recomputed unless one of its arguments change. 35 | Selectors are composable. They can be used as input to other selectors. 36 | https://github.com/reactjs/reselect */ 37 | 38 | export default connect(mapStateToProps, mapDispatchToProps)(Counter) 39 | -------------------------------------------------------------------------------- /src/routes/Counter/modules/counter.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const COUNTER_INCREMENT = 'COUNTER_INCREMENT' 5 | export const COUNTER_DOUBLE_ASYNC = 'COUNTER_DOUBLE_ASYNC' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export function increment (value = 1) { 11 | return { 12 | type: COUNTER_INCREMENT, 13 | payload: value 14 | } 15 | } 16 | 17 | /* This is a thunk, meaning it is a function that immediately 18 | returns a function for lazy evaluation. It is incredibly useful for 19 | creating async actions, especially when combined with redux-thunk! */ 20 | 21 | export const doubleAsync = () => { 22 | return (dispatch, getState) => { 23 | return new Promise((resolve) => { 24 | setTimeout(() => { 25 | dispatch({ 26 | type: COUNTER_DOUBLE_ASYNC, 27 | payload: getState().counter 28 | }) 29 | resolve() 30 | }, 200) 31 | }) 32 | } 33 | } 34 | 35 | export const actions = { 36 | increment, 37 | doubleAsync 38 | } 39 | 40 | // ------------------------------------ 41 | // Action Handlers 42 | // ------------------------------------ 43 | const ACTION_HANDLERS = { 44 | [COUNTER_INCREMENT]: (state, action) => state + action.payload, 45 | [COUNTER_DOUBLE_ASYNC]: (state, action) => state * 2 46 | } 47 | 48 | // ------------------------------------ 49 | // Reducer 50 | // ------------------------------------ 51 | const initialState = 0 52 | export default function counterReducer (state = initialState, action) { 53 | const handler = ACTION_HANDLERS[action.type] 54 | 55 | return handler ? handler(state, action) : state 56 | } 57 | -------------------------------------------------------------------------------- /src/store/createStore.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import { browserHistory } from 'react-router' 4 | import makeRootReducer from './reducers' 5 | import { updateLocation } from './location' 6 | 7 | export default (initialState = {}) => { 8 | // ====================================================== 9 | // Middleware Configuration 10 | // ====================================================== 11 | const middleware = [thunk] 12 | 13 | // ====================================================== 14 | // Store Enhancers 15 | // ====================================================== 16 | const enhancers = [] 17 | 18 | let composeEnhancers = compose 19 | 20 | if (__DEV__) { 21 | const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 22 | if (typeof composeWithDevToolsExtension === 'function') { 23 | composeEnhancers = composeWithDevToolsExtension 24 | } 25 | } 26 | 27 | // ====================================================== 28 | // Store Instantiation and HMR Setup 29 | // ====================================================== 30 | const store = createStore( 31 | makeRootReducer(), 32 | initialState, 33 | composeEnhancers( 34 | applyMiddleware(...middleware), 35 | ...enhancers 36 | ) 37 | ) 38 | store.asyncReducers = {} 39 | 40 | // To unsubscribe, invoke `store.unsubscribeHistory()` anytime 41 | store.unsubscribeHistory = browserHistory.listen(updateLocation(store)) 42 | 43 | if (module.hot) { 44 | module.hot.accept('./reducers', () => { 45 | const reducers = require('./reducers').default 46 | store.replaceReducer(reducers(store.asyncReducers)) 47 | }) 48 | } 49 | 50 | return store 51 | } 52 | -------------------------------------------------------------------------------- /bin/compile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const webpack = require('webpack') 3 | const debug = require('debug')('app:bin:compile') 4 | const webpackConfig = require('../config/webpack.config') 5 | const project = require('../config/project.config') 6 | 7 | // Wrapper around webpack to promisify its compiler and supply friendly logging 8 | const webpackCompiler = (webpackConfig) => 9 | new Promise((resolve, reject) => { 10 | const compiler = webpack(webpackConfig) 11 | 12 | compiler.run((err, stats) => { 13 | if (err) { 14 | debug('Webpack compiler encountered a fatal error.', err) 15 | return reject(err) 16 | } 17 | 18 | const jsonStats = stats.toJson() 19 | debug('Webpack compile completed.') 20 | debug(stats.toString(project.compiler_stats)) 21 | 22 | if (jsonStats.errors.length > 0) { 23 | debug('Webpack compiler encountered errors.') 24 | debug(jsonStats.errors.join('\n')) 25 | return reject(new Error('Webpack compiler encountered errors')) 26 | } else if (jsonStats.warnings.length > 0) { 27 | debug('Webpack compiler encountered warnings.') 28 | debug(jsonStats.warnings.join('\n')) 29 | } else { 30 | debug('No errors or warnings encountered.') 31 | } 32 | resolve(jsonStats) 33 | }) 34 | }) 35 | 36 | const compile = () => { 37 | debug('Starting compiler.') 38 | return Promise.resolve() 39 | .then(() => webpackCompiler(webpackConfig)) 40 | .then(stats => { 41 | if (stats.warnings.length && project.compiler_fail_on_warning) { 42 | throw new Error('Config set to fail on warning, exiting with status code "1".') 43 | } 44 | debug('Copying static assets to dist folder.') 45 | fs.copySync(project.paths.public(), project.paths.dist()) 46 | }) 47 | .then(() => { 48 | debug('Compilation completed successfully.') 49 | }) 50 | .catch((err) => { 51 | debug('Compiler encountered an error.', err) 52 | process.exit(1) 53 | }) 54 | } 55 | 56 | compile() 57 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import createStore from './store/createStore' 4 | import AppContainer from './containers/AppContainer' 5 | import injectTapEventPlugin from 'react-tap-event-plugin' 6 | injectTapEventPlugin() // http://stackoverflow.com/a/34015469/988941 7 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider' 8 | 9 | // ======================================================== 10 | // Store Instantiation 11 | // ======================================================== 12 | const initialState = window.___INITIAL_STATE__ 13 | const store = createStore(initialState) 14 | 15 | // ======================================================== 16 | // Render Setup 17 | // ======================================================== 18 | const MOUNT_NODE = document.getElementById('root') 19 | 20 | let render = () => { 21 | const routes = require('./routes/index').default(store) 22 | 23 | ReactDOM.render( 24 | 25 | 26 | , 27 | MOUNT_NODE 28 | ) 29 | } 30 | 31 | // This code is excluded from production bundle 32 | if (__DEV__) { 33 | if (module.hot) { 34 | // Development render functions 35 | const renderApp = render 36 | const renderError = (error) => { 37 | const RedBox = require('redbox-react').default 38 | 39 | ReactDOM.render(, MOUNT_NODE) 40 | } 41 | 42 | // Wrap render in try/catch 43 | render = () => { 44 | try { 45 | renderApp() 46 | } catch (error) { 47 | console.error(error) 48 | renderError(error) 49 | } 50 | } 51 | 52 | // Setup hot module replacement 53 | module.hot.accept('./routes/index', () => 54 | setImmediate(() => { 55 | ReactDOM.unmountComponentAtNode(MOUNT_NODE) 56 | render() 57 | }) 58 | ) 59 | } 60 | } 61 | 62 | // ======================================================== 63 | // Go! 64 | // ======================================================== 65 | render() 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Some basic conventions for contributing to this project. 4 | 5 | ### General 6 | 7 | Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork. 8 | 9 | * Non-trivial changes should be discussed in an issue first 10 | * Develop in a topic branch, not master 11 | * Squash your commits 12 | 13 | ### Linting 14 | 15 | Please check your code using `npm run lint` before submitting your pull requests, as the CI build will fail if `eslint` fails. 16 | 17 | ### Commit Message Format 18 | 19 | Each commit message should include a **type**, a **scope** and a **subject**: 20 | 21 | ``` 22 | (): 23 | ``` 24 | 25 | Lines should not exceed 100 characters. This allows the message to be easier to read on github as well as in various git tools and produces a nice, neat commit log ie: 26 | 27 | ``` 28 | #271 feat(standard): add style config and refactor to match 29 | #270 fix(config): only override publicPath when served by webpack 30 | #269 feat(eslint-config-defaults): replace eslint-config-airbnb 31 | #268 feat(config): allow user to configure webpack stats output 32 | ``` 33 | 34 | #### Type 35 | 36 | Must be one of the following: 37 | 38 | * **feat**: A new feature 39 | * **fix**: A bug fix 40 | * **docs**: Documentation only changes 41 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing 42 | semi-colons, etc) 43 | * **refactor**: A code change that neither fixes a bug or adds a feature 44 | * **test**: Adding missing tests 45 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation 46 | generation 47 | 48 | #### Scope 49 | 50 | The scope could be anything specifying place of the commit change. For example `webpack`, 51 | `babel`, `redux` etc... 52 | 53 | #### Subject 54 | 55 | The subject contains succinct description of the change: 56 | 57 | * use the imperative, present tense: "change" not "changed" nor "changes" 58 | * don't capitalize first letter 59 | * no dot (.) at the end 60 | -------------------------------------------------------------------------------- /config/karma.config.js: -------------------------------------------------------------------------------- 1 | const argv = require('yargs').argv 2 | const project = require('./project.config') 3 | const webpackConfig = require('./webpack.config') 4 | const debug = require('debug')('app:config:karma') 5 | 6 | debug('Creating configuration.') 7 | const karmaConfig = { 8 | basePath : '../', // project root in relation to bin/karma.js 9 | files : [ 10 | { 11 | pattern : `./${project.dir_test}/test-bundler.js`, 12 | watched : false, 13 | served : true, 14 | included : true 15 | } 16 | ], 17 | singleRun : !argv.watch, 18 | frameworks : ['mocha'], 19 | reporters : ['mocha'], 20 | preprocessors : { 21 | [`${project.dir_test}/test-bundler.js`] : ['webpack'] 22 | }, 23 | browsers : ['PhantomJS'], 24 | webpack : { 25 | devtool : 'cheap-module-source-map', 26 | resolve : Object.assign({}, webpackConfig.resolve, { 27 | alias : Object.assign({}, webpackConfig.resolve.alias, { 28 | sinon : 'sinon/pkg/sinon.js' 29 | }) 30 | }), 31 | plugins : webpackConfig.plugins, 32 | module : { 33 | noParse : [ 34 | /\/sinon\.js/ 35 | ], 36 | loaders : webpackConfig.module.loaders.concat([ 37 | { 38 | test : /sinon(\\|\/)pkg(\\|\/)sinon\.js/, 39 | loader : 'imports?define=>false,require=>false' 40 | } 41 | ]) 42 | }, 43 | // Enzyme fix, see: 44 | // https://github.com/airbnb/enzyme/issues/47 45 | externals : Object.assign({}, webpackConfig.externals, { 46 | 'react/addons' : true, 47 | 'react/lib/ExecutionEnvironment' : true, 48 | 'react/lib/ReactContext' : 'window' 49 | }), 50 | sassLoader : webpackConfig.sassLoader 51 | }, 52 | webpackMiddleware : { 53 | noInfo : true 54 | }, 55 | coverageReporter : { 56 | reporters : project.coverage_reporters 57 | } 58 | } 59 | 60 | if (project.globals.__COVERAGE__) { 61 | karmaConfig.reporters.push('coverage') 62 | karmaConfig.webpack.module.preLoaders = [{ 63 | test : /\.(js|jsx)$/, 64 | include : new RegExp(project.dir_client), 65 | exclude : /node_modules/, 66 | loader : 'babel', 67 | query : Object.assign({}, project.compiler_babel, { 68 | plugins : (project.compiler_babel.plugins || []).concat('istanbul') 69 | }) 70 | }] 71 | } 72 | 73 | module.exports = (cfg) => cfg.set(karmaConfig) 74 | -------------------------------------------------------------------------------- /src/store/web3Reducer.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3' 2 | import truffleConfig from '../../truffle.js' 3 | 4 | // ------------------------------------ 5 | // Constants 6 | // ------------------------------------ 7 | export const WEB3_CONNECTED = 'WEB3_CONNECTED' 8 | export const WEB3_DISCONNECTED = 'WEB3_DISCONNECTED' 9 | 10 | // ------------------------------------ 11 | // Actions 12 | // ------------------------------------ 13 | export const web3Connect = () => { 14 | return (dispatch, getState) => { 15 | /*eslint-disable */ 16 | let web3Location = `http://${truffleConfig.networks.development.host}:${truffleConfig.networks.development.port}` // This is bad because dev env is hardcoded. :( 17 | 18 | let output = (typeof web3 !== 'undefined') // web3 given by metamask 19 | ? { type: WEB3_CONNECTED, payload: { web3: new Web3(web3.currentProvider), isConnected: true } } 20 | : { type: WEB3_DISCONNECTED, payload: { web3: null, isConnected: false } } // comment out for optional section 21 | // : { type: WEB3_CONNECTED, payload: { web3: new Web3(new Web3.providers.HttpProvider(web3Location)), isConnected: true } } // comment in for optional section 22 | /*eslint-enable */ 23 | dispatch(output) 24 | } 25 | } 26 | 27 | export function web3Connected ({ web3, isConnected }) { 28 | return { 29 | type: WEB3_CONNECTED, 30 | payload: { 31 | web3, 32 | isConnected 33 | } 34 | } 35 | } 36 | 37 | export function web3Disconnected () { 38 | return { 39 | type: WEB3_DISCONNECTED, 40 | payload: { 41 | web3: null, 42 | isConnected: false 43 | } 44 | } 45 | } 46 | 47 | export const actions = { 48 | web3Connect, 49 | web3Connected, 50 | web3Disconnected 51 | } 52 | 53 | // ------------------------------------ 54 | // Action Handlers 55 | // ------------------------------------ 56 | const ACTION_HANDLERS = { 57 | [WEB3_CONNECTED]: (state, action) => { 58 | return action.payload 59 | }, 60 | [WEB3_DISCONNECTED]: (state, action) => { 61 | return action.payload 62 | } 63 | } 64 | 65 | // ------------------------------------ 66 | // Reducer 67 | // ------------------------------------ 68 | const initialState = { isConnected: false } 69 | export default function Web3Reducer (state = initialState, action) { 70 | const handler = ACTION_HANDLERS[action.type] 71 | return handler ? handler(state, action) : state 72 | } 73 | -------------------------------------------------------------------------------- /tests/routes/Counter/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { Counter } from 'routes/Counter/components/Counter' 4 | import { shallow } from 'enzyme' 5 | 6 | describe.skip('(Component) Counter', () => { 7 | let _props, _spies, _wrapper 8 | 9 | beforeEach(() => { 10 | _spies = {} 11 | _props = { 12 | counter : 5, 13 | ...bindActionCreators({ 14 | doubleAsync : (_spies.doubleAsync = sinon.spy()), 15 | increment : (_spies.increment = sinon.spy()) 16 | }, _spies.dispatch = sinon.spy()) 17 | } 18 | _wrapper = shallow() 19 | }) 20 | 21 | it('Should render as a
.', () => { 22 | expect(_wrapper.is('div')).to.equal(true) 23 | }) 24 | 25 | it('Should render with an

that includes Sample Counter text.', () => { 26 | expect(_wrapper.find('h2').text()).to.match(/Counter:/) 27 | }) 28 | 29 | it('Should render props.counter at the end of the sample counter

.', () => { 30 | expect(_wrapper.find('h2').text()).to.match(/5$/) 31 | _wrapper.setProps({ counter: 8 }) 32 | expect(_wrapper.find('h2').text()).to.match(/8$/) 33 | }) 34 | 35 | it('Should render exactly two buttons.', () => { 36 | expect(_wrapper.find('button')).to.have.length(2) 37 | }) 38 | 39 | describe('An increment button...', () => { 40 | let _button 41 | 42 | beforeEach(() => { 43 | _button = _wrapper.find('button').filterWhere(a => a.text() === 'Increment') 44 | }) 45 | 46 | it('has bootstrap classes', () => { 47 | expect(_button.hasClass('btn btn-default')).to.be.true 48 | }) 49 | 50 | it('Should dispatch a `increment` action when clicked', () => { 51 | _spies.dispatch.should.have.not.been.called 52 | 53 | _button.simulate('click') 54 | 55 | _spies.dispatch.should.have.been.called 56 | _spies.increment.should.have.been.called 57 | }) 58 | }) 59 | 60 | describe('A Double (Async) button...', () => { 61 | let _button 62 | 63 | beforeEach(() => { 64 | _button = _wrapper.find('button').filterWhere(a => a.text() === 'Double (Async)') 65 | }) 66 | 67 | it('has bootstrap classes', () => { 68 | expect(_button.hasClass('btn btn-default')).to.be.true 69 | }) 70 | 71 | it('Should dispatch a `doubleAsync` action when clicked', () => { 72 | _spies.dispatch.should.have.not.been.called 73 | 74 | _button.simulate('click') 75 | 76 | _spies.dispatch.should.have.been.called 77 | _spies.doubleAsync.should.have.been.called 78 | }) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /src/components/topBar/topBarComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import AppBar from 'material-ui/AppBar' 3 | import FloatingActionButton from 'material-ui/FloatingActionButton' 4 | import FlatButton from 'material-ui/FlatButton' 5 | import Dialog from 'material-ui/Dialog' 6 | import CloudOff from 'material-ui/svg-icons/file/cloud-off' 7 | import CheckCircle from 'material-ui/svg-icons/action/check-circle' 8 | 9 | const rightIndicator = { 10 | marginRight: 12, 11 | display: 'inline-block' 12 | } 13 | 14 | export class TopBar extends Component { 15 | state = { 16 | open: false 17 | } 18 | 19 | handleOpen = () => { 20 | this.setState({ open: true }) 21 | } 22 | 23 | handleClose = () => { 24 | this.setState({ open: false }) 25 | } 26 | 27 | setDialog = (isConnected) => { 28 | const actions = [ 29 | 34 | ] 35 | let title, body 36 | /*eslint-disable */ 37 | if (isConnected) { 38 | title = 'Success is successful!' 39 | body = (Yay! We are attached to an ethereum node!) 40 | } else { 41 | title = 'Please Connect to an ethereum node' 42 | body = (Unable to connect to etherum, please install MetaMask to use this application.) 43 | } 44 | /*eslint-enable */ 45 | return ( 51 | {body} 52 | ) 53 | } 54 | 55 | render () { 56 | let { openDrawer, title, web3Wrap } = this.props 57 | 58 | let rightIcon = (web3Wrap.isConnected) ? () : () 59 | let dialogShown = this.setDialog(web3Wrap.isConnected) 60 | let rightComponent = ( 61 |
62 | 63 | {rightIcon} 64 | {dialogShown} 65 | 66 |
67 | ) 68 | 69 | return ( 70 | { openDrawer() }} 75 | iconElementRight={rightComponent} 76 | /> 77 | ) 78 | } 79 | } 80 | 81 | TopBar.propTypes = { 82 | openDrawer: React.PropTypes.func.isRequired, 83 | title: React.PropTypes.string, 84 | web3Wrap: React.PropTypes.object.isRequired 85 | } 86 | 87 | export default TopBar 88 | -------------------------------------------------------------------------------- /test/metacoin.js: -------------------------------------------------------------------------------- 1 | var MetaCoin = artifacts.require("./MetaCoin.sol"); 2 | 3 | contract('MetaCoin', function(accounts) { 4 | it("should put 10000 MetaCoin in the first account", function() { 5 | return MetaCoin.deployed().then(function(instance) { 6 | return instance.getBalance.call(accounts[0]); 7 | }).then(function(balance) { 8 | assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account"); 9 | }); 10 | }); 11 | it("should call a function that depends on a linked library", function() { 12 | var meta; 13 | var metaCoinBalance; 14 | var metaCoinEthBalance; 15 | 16 | return MetaCoin.deployed().then(function(instance) { 17 | meta = instance; 18 | return meta.getBalance.call(accounts[0]); 19 | }).then(function(outCoinBalance) { 20 | metaCoinBalance = outCoinBalance.toNumber(); 21 | return meta.getBalanceInEth.call(accounts[0]); 22 | }).then(function(outCoinBalanceEth) { 23 | metaCoinEthBalance = outCoinBalanceEth.toNumber(); 24 | }).then(function() { 25 | assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken"); 26 | }); 27 | }); 28 | it("should send coin correctly", function() { 29 | var meta; 30 | 31 | // Get initial balances of first and second account. 32 | var account_one = accounts[0]; 33 | var account_two = accounts[1]; 34 | 35 | var account_one_starting_balance; 36 | var account_two_starting_balance; 37 | var account_one_ending_balance; 38 | var account_two_ending_balance; 39 | 40 | var amount = 10; 41 | 42 | return MetaCoin.deployed().then(function(instance) { 43 | meta = instance; 44 | return meta.getBalance.call(account_one); 45 | }).then(function(balance) { 46 | account_one_starting_balance = balance.toNumber(); 47 | return meta.getBalance.call(account_two); 48 | }).then(function(balance) { 49 | account_two_starting_balance = balance.toNumber(); 50 | return meta.sendCoin(account_two, amount, {from: account_one}); 51 | }).then(function() { 52 | return meta.getBalance.call(account_one); 53 | }).then(function(balance) { 54 | account_one_ending_balance = balance.toNumber(); 55 | return meta.getBalance.call(account_two); 56 | }).then(function(balance) { 57 | account_two_ending_balance = balance.toNumber(); 58 | 59 | assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender"); 60 | assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver"); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /server/main.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const debug = require('debug')('app:server') 3 | const path = require('path') 4 | const webpack = require('webpack') 5 | const webpackConfig = require('../config/webpack.config') 6 | const project = require('../config/project.config') 7 | const compress = require('compression') 8 | 9 | const app = express() 10 | 11 | // Apply gzip compression 12 | app.use(compress()) 13 | 14 | // ------------------------------------ 15 | // Apply Webpack HMR Middleware 16 | // ------------------------------------ 17 | if (project.env === 'development') { 18 | const compiler = webpack(webpackConfig) 19 | 20 | debug('Enabling webpack dev and HMR middleware') 21 | app.use(require('webpack-dev-middleware')(compiler, { 22 | publicPath : webpackConfig.output.publicPath, 23 | contentBase : project.paths.client(), 24 | hot : true, 25 | quiet : project.compiler_quiet, 26 | noInfo : project.compiler_quiet, 27 | lazy : false, 28 | stats : project.compiler_stats 29 | })) 30 | app.use(require('webpack-hot-middleware')(compiler, { 31 | path: '/__webpack_hmr' 32 | })) 33 | 34 | // Serve static assets from ~/public since Webpack is unaware of 35 | // these files. This middleware doesn't need to be enabled outside 36 | // of development since this directory will be copied into ~/dist 37 | // when the application is compiled. 38 | app.use(express.static(project.paths.public())) 39 | 40 | // This rewrites all routes requests to the root /index.html file 41 | // (ignoring file requests). If you want to implement universal 42 | // rendering, you'll want to remove this middleware. 43 | app.use('*', function (req, res, next) { 44 | const filename = path.join(compiler.outputPath, 'index.html') 45 | compiler.outputFileSystem.readFile(filename, (err, result) => { 46 | if (err) { 47 | return next(err) 48 | } 49 | res.set('content-type', 'text/html') 50 | res.send(result) 51 | res.end() 52 | }) 53 | }) 54 | } else { 55 | debug( 56 | 'Server is being run outside of live development mode, meaning it will ' + 57 | 'only serve the compiled application bundle in ~/dist. Generally you ' + 58 | 'do not need an application server for this and can instead use a web ' + 59 | 'server such as nginx to serve your static files. See the "deployment" ' + 60 | 'section in the README for more information on deployment strategies.' 61 | ) 62 | 63 | // Serving ~/dist by default. Ideally these files should be served by 64 | // the web server and not the app server, but this helps to demo the 65 | // server in production. 66 | app.use(express.static(project.paths.dist())) 67 | } 68 | 69 | module.exports = app 70 | -------------------------------------------------------------------------------- /tests/store/location.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOCATION_CHANGE, 3 | locationChange, 4 | updateLocation, 5 | default as locationReducer 6 | } from 'store/location' 7 | 8 | describe('(Internal Module) Location', () => { 9 | it('Should export a constant LOCATION_CHANGE.', () => { 10 | expect(LOCATION_CHANGE).to.equal('LOCATION_CHANGE') 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(locationReducer).to.be.a('function') 16 | }) 17 | 18 | it('Should initialize with a state of null.', () => { 19 | expect(locationReducer(undefined, {})).to.equal(null) 20 | }) 21 | 22 | it('Should return the previous state if an action was not matched.', () => { 23 | let state = locationReducer(undefined, {}) 24 | expect(state).to.equal(null) 25 | state = locationReducer(state, { type: '@@@@@@@' }) 26 | expect(state).to.equal(null) 27 | 28 | const locationState = { pathname: '/yup' } 29 | state = locationReducer(state, locationChange(locationState)) 30 | expect(state).to.equal(locationState) 31 | state = locationReducer(state, { type: '@@@@@@@' }) 32 | expect(state).to.equal(locationState) 33 | }) 34 | }) 35 | 36 | describe('(Action Creator) locationChange', () => { 37 | it('Should be exported as a function.', () => { 38 | expect(locationChange).to.be.a('function') 39 | }) 40 | 41 | it('Should return an action with type "LOCATION_CHANGE".', () => { 42 | expect(locationChange()).to.have.property('type', LOCATION_CHANGE) 43 | }) 44 | 45 | it('Should assign the first argument to the "payload" property.', () => { 46 | const locationState = { pathname: '/yup' } 47 | expect(locationChange(locationState)).to.have.property('payload', locationState) 48 | }) 49 | 50 | it('Should default the "payload" property to "/" if not provided.', () => { 51 | expect(locationChange()).to.have.property('payload', '/') 52 | }) 53 | }) 54 | 55 | describe('(Specialized Action Creator) updateLocation', () => { 56 | let _globalState 57 | let _dispatchSpy 58 | 59 | beforeEach(() => { 60 | _globalState = { 61 | location : locationReducer(undefined, {}) 62 | } 63 | _dispatchSpy = sinon.spy((action) => { 64 | _globalState = { 65 | ..._globalState, 66 | location : locationReducer(_globalState.location, action) 67 | } 68 | }) 69 | }) 70 | 71 | it('Should be exported as a function.', () => { 72 | expect(updateLocation).to.be.a('function') 73 | }) 74 | 75 | it('Should return a function (is a thunk).', () => { 76 | expect(updateLocation({ dispatch: _dispatchSpy })).to.be.a('function') 77 | }) 78 | 79 | it('Should call dispatch exactly once.', () => { 80 | updateLocation({ dispatch: _dispatchSpy })('/') 81 | expect(_dispatchSpy.should.have.been.calledOnce) 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /src/routes/MetaCoin/modules/metaCoinModule.js: -------------------------------------------------------------------------------- 1 | import MetaCoinArtifact from '../../../../contracts/MetaCoin.sol' 2 | import contract from 'truffle-contract' 3 | const MetaCoin = contract(MetaCoinArtifact) 4 | 5 | // ------------------------------------ 6 | // Constants 7 | // ------------------------------------ 8 | export const SEND_COIN = 'SEND_COIN' 9 | export const GET_BALANCE_IN_ETH = 'GET_BALANCE_IN_ETH' 10 | export const GET_BALANCE = 'GET_BALANCE' 11 | export const SET_BALANCE = 'SET_BALANCE' 12 | 13 | // ------------------------------------ 14 | // helpers 15 | // ------------------------------------ 16 | const getMetaCoin = ({ getState }) => { 17 | MetaCoin.setProvider(getState().web3Wrap.web3.currentProvider) 18 | return MetaCoin.deployed() 19 | } 20 | 21 | const getDefaultAccount = ({ getState }) => { 22 | return getState().web3Wrap.web3.eth.accounts[0] 23 | } 24 | 25 | // ------------------------------------ 26 | // Actions 27 | // ------------------------------------ 28 | export const sendCoin = ({ amount, address }) => { 29 | return (dispatch, getState) => { 30 | return new Promise((resolve) => { 31 | let meta = null 32 | let from = null 33 | getMetaCoin({ getState }) 34 | .then(instance => { 35 | meta = instance 36 | from = getDefaultAccount({ getState }) 37 | return meta.sendCoin(address, amount, { from }) 38 | }) 39 | .then(() => { 40 | return meta.getBalance.call(from, { from }) 41 | }) 42 | .then(value => { 43 | dispatch({ 44 | type: SET_BALANCE, 45 | payload: { value: value.valueOf() } 46 | }) 47 | resolve() 48 | }) 49 | .catch(e => { 50 | console.log(e) 51 | }) 52 | }) 53 | } 54 | } 55 | 56 | export const getBalanceInEth = () => { 57 | return (dispatch, getState) => { 58 | return new Promise((resolve) => { 59 | dispatch({ 60 | type: GET_BALANCE_IN_ETH, 61 | payload: {} 62 | }) 63 | resolve() 64 | }) 65 | } 66 | } 67 | 68 | export const getBalance = ({ account }) => { 69 | return (dispatch, getState) => { 70 | return new Promise((resolve, reject) => { 71 | getMetaCoin({ getState }) 72 | .then(instance => { 73 | return instance 74 | }) 75 | .then(meta => { 76 | return meta.getBalance.call(account, { from: account }) 77 | }) 78 | .then(function (value) { 79 | dispatch({ 80 | type: SET_BALANCE, 81 | payload: { account, value: value.valueOf() } 82 | }) 83 | resolve() 84 | }).catch(function (e) { 85 | console.log(e) 86 | reject() 87 | }) 88 | }) 89 | } 90 | } 91 | 92 | export const actions = { 93 | sendCoin, 94 | getBalanceInEth, 95 | getBalance 96 | } 97 | 98 | // ------------------------------------ 99 | // Action Handlers 100 | // ------------------------------------ 101 | const ACTION_HANDLERS = { 102 | [SET_BALANCE]: (state, action) => { 103 | return Object.assign({}, state, action.payload) 104 | } 105 | } 106 | 107 | // ------------------------------------ 108 | // Reducer 109 | // ------------------------------------ 110 | const initialState = { account: null, value: '0' } 111 | export default function MetaCoinReducer (state = initialState, action) { 112 | const handler = ACTION_HANDLERS[action.type] 113 | return handler ? handler(state, action) : state 114 | } 115 | -------------------------------------------------------------------------------- /tests/routes/Counter/modules/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | COUNTER_INCREMENT, 3 | increment, 4 | doubleAsync, 5 | default as counterReducer 6 | } from 'routes/Counter/modules/counter' 7 | 8 | describe('(Redux Module) Counter', () => { 9 | it('Should export a constant COUNTER_INCREMENT.', () => { 10 | expect(COUNTER_INCREMENT).to.equal('COUNTER_INCREMENT') 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(counterReducer).to.be.a('function') 16 | }) 17 | 18 | it('Should initialize with a state of 0 (Number).', () => { 19 | expect(counterReducer(undefined, {})).to.equal(0) 20 | }) 21 | 22 | it('Should return the previous state if an action was not matched.', () => { 23 | let state = counterReducer(undefined, {}) 24 | expect(state).to.equal(0) 25 | state = counterReducer(state, { type: '@@@@@@@' }) 26 | expect(state).to.equal(0) 27 | state = counterReducer(state, increment(5)) 28 | expect(state).to.equal(5) 29 | state = counterReducer(state, { type: '@@@@@@@' }) 30 | expect(state).to.equal(5) 31 | }) 32 | }) 33 | 34 | describe('(Action Creator) increment', () => { 35 | it('Should be exported as a function.', () => { 36 | expect(increment).to.be.a('function') 37 | }) 38 | 39 | it('Should return an action with type "COUNTER_INCREMENT".', () => { 40 | expect(increment()).to.have.property('type', COUNTER_INCREMENT) 41 | }) 42 | 43 | it('Should assign the first argument to the "payload" property.', () => { 44 | expect(increment(5)).to.have.property('payload', 5) 45 | }) 46 | 47 | it('Should default the "payload" property to 1 if not provided.', () => { 48 | expect(increment()).to.have.property('payload', 1) 49 | }) 50 | }) 51 | 52 | describe('(Action Creator) doubleAsync', () => { 53 | let _globalState 54 | let _dispatchSpy 55 | let _getStateSpy 56 | 57 | beforeEach(() => { 58 | _globalState = { 59 | counter : counterReducer(undefined, {}) 60 | } 61 | _dispatchSpy = sinon.spy((action) => { 62 | _globalState = { 63 | ..._globalState, 64 | counter : counterReducer(_globalState.counter, action) 65 | } 66 | }) 67 | _getStateSpy = sinon.spy(() => { 68 | return _globalState 69 | }) 70 | }) 71 | 72 | it('Should be exported as a function.', () => { 73 | expect(doubleAsync).to.be.a('function') 74 | }) 75 | 76 | it('Should return a function (is a thunk).', () => { 77 | expect(doubleAsync()).to.be.a('function') 78 | }) 79 | 80 | it('Should return a promise from that thunk that gets fulfilled.', () => { 81 | return doubleAsync()(_dispatchSpy, _getStateSpy).should.eventually.be.fulfilled 82 | }) 83 | 84 | it('Should call dispatch and getState exactly once.', () => { 85 | return doubleAsync()(_dispatchSpy, _getStateSpy) 86 | .then(() => { 87 | _dispatchSpy.should.have.been.calledOnce 88 | _getStateSpy.should.have.been.calledOnce 89 | }) 90 | }) 91 | 92 | it('Should produce a state that is double the previous state.', () => { 93 | _globalState = { counter: 2 } 94 | 95 | return doubleAsync()(_dispatchSpy, _getStateSpy) 96 | .then(() => { 97 | _dispatchSpy.should.have.been.calledOnce 98 | _getStateSpy.should.have.been.calledOnce 99 | expect(_globalState.counter).to.equal(4) 100 | return doubleAsync()(_dispatchSpy, _getStateSpy) 101 | }) 102 | .then(() => { 103 | _dispatchSpy.should.have.been.calledTwice 104 | _getStateSpy.should.have.been.calledTwice 105 | expect(_globalState.counter).to.equal(8) 106 | }) 107 | }) 108 | }) 109 | 110 | // NOTE: if you have a more complex state, you will probably want to verify 111 | // that you did not mutate the state. In this case our state is just a number 112 | // (which cannot be mutated). 113 | describe('(Action Handler) COUNTER_INCREMENT', () => { 114 | it('Should increment the state by the action payload\'s "value" property.', () => { 115 | let state = counterReducer(undefined, {}) 116 | expect(state).to.equal(0) 117 | state = counterReducer(state, increment(1)) 118 | expect(state).to.equal(1) 119 | state = counterReducer(state, increment(2)) 120 | expect(state).to.equal(3) 121 | state = counterReducer(state, increment(-3)) 122 | expect(state).to.equal(0) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /config/project.config.js: -------------------------------------------------------------------------------- 1 | /* eslint key-spacing:0 spaced-comment:0 */ 2 | const path = require('path') 3 | const debug = require('debug')('app:config:project') 4 | const argv = require('yargs').argv 5 | const ip = require('ip') 6 | 7 | debug('Creating default configuration.') 8 | // ======================================================== 9 | // Default Configuration 10 | // ======================================================== 11 | const config = { 12 | env : process.env.NODE_ENV || 'development', 13 | 14 | // ---------------------------------- 15 | // Project Structure 16 | // ---------------------------------- 17 | path_base : path.resolve(__dirname, '..'), 18 | dir_client : 'src', 19 | dir_dist : 'dist', 20 | dir_public : 'public', 21 | dir_server : 'server', 22 | dir_test : 'tests', 23 | 24 | // ---------------------------------- 25 | // Server Configuration 26 | // ---------------------------------- 27 | server_host : ip.address(), // use string 'localhost' to prevent exposure on local network 28 | server_port : process.env.PORT || 3000, 29 | 30 | // ---------------------------------- 31 | // Compiler Configuration 32 | // ---------------------------------- 33 | compiler_babel : { 34 | cacheDirectory : true, 35 | plugins : ['transform-runtime'], 36 | presets : ['es2015', 'react', 'stage-0'] 37 | }, 38 | compiler_devtool : 'source-map', 39 | compiler_hash_type : 'hash', 40 | compiler_fail_on_warning : false, 41 | compiler_quiet : false, 42 | compiler_public_path : '/', 43 | compiler_stats : { 44 | chunks : false, 45 | chunkModules : false, 46 | colors : true 47 | }, 48 | compiler_vendors : [ 49 | 'react', 50 | 'react-redux', 51 | 'react-router', 52 | 'redux' 53 | ], 54 | 55 | // ---------------------------------- 56 | // Test Configuration 57 | // ---------------------------------- 58 | coverage_reporters : [ 59 | { type : 'text-summary' }, 60 | { type : 'lcov', dir : 'coverage' } 61 | ] 62 | } 63 | 64 | /************************************************ 65 | ------------------------------------------------- 66 | 67 | All Internal Configuration Below 68 | Edit at Your Own Risk 69 | 70 | ------------------------------------------------- 71 | ************************************************/ 72 | 73 | // ------------------------------------ 74 | // Environment 75 | // ------------------------------------ 76 | // N.B.: globals added here must _also_ be added to .eslintrc 77 | config.globals = { 78 | 'process.env' : { 79 | 'NODE_ENV' : JSON.stringify(config.env) 80 | }, 81 | 'NODE_ENV' : config.env, 82 | '__DEV__' : config.env === 'development', 83 | '__PROD__' : config.env === 'production', 84 | '__TEST__' : config.env === 'test', 85 | '__COVERAGE__' : !argv.watch && config.env === 'test', 86 | '__BASENAME__' : JSON.stringify(process.env.BASENAME || '') 87 | } 88 | 89 | // ------------------------------------ 90 | // Validate Vendor Dependencies 91 | // ------------------------------------ 92 | const pkg = require('../package.json') 93 | 94 | config.compiler_vendors = config.compiler_vendors 95 | .filter((dep) => { 96 | if (pkg.dependencies[dep]) return true 97 | 98 | debug( 99 | `Package "${dep}" was not found as an npm dependency in package.json; ` + 100 | `it won't be included in the webpack vendor bundle. 101 | Consider removing it from \`compiler_vendors\` in ~/config/index.js` 102 | ) 103 | }) 104 | 105 | // ------------------------------------ 106 | // Utilities 107 | // ------------------------------------ 108 | function base () { 109 | const args = [config.path_base].concat([].slice.call(arguments)) 110 | return path.resolve.apply(path, args) 111 | } 112 | 113 | config.paths = { 114 | base : base, 115 | client : base.bind(null, config.dir_client), 116 | public : base.bind(null, config.dir_public), 117 | dist : base.bind(null, config.dir_dist) 118 | } 119 | 120 | // ======================================================== 121 | // Environment Configuration 122 | // ======================================================== 123 | debug(`Looking for environment overrides for NODE_ENV "${config.env}".`) 124 | const environments = require('./environments.config') 125 | const overrides = environments[config.env] 126 | if (overrides) { 127 | debug('Found overrides, applying to default configuration.') 128 | Object.assign(config, overrides(config)) 129 | } else { 130 | debug('No environment overrides found, defaults will be used.') 131 | } 132 | 133 | module.exports = config 134 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-starter-kit", 3 | "version": "3.0.0-alpha.2", 4 | "description": "Get started with React, Redux, and React-Router!", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=4.5.0", 8 | "npm": "^3.0.0" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf dist", 12 | "compile": "better-npm-run compile", 13 | "lint": "eslint bin build config server src tests", 14 | "lint:fix": "npm run lint -- --fix", 15 | "start": "better-npm-run start", 16 | "dev": "better-npm-run dev", 17 | "test": "better-npm-run test", 18 | "test:dev": "npm run test -- --watch", 19 | "deploy": "better-npm-run deploy", 20 | "deploy:dev": "better-npm-run deploy:dev", 21 | "deploy:prod": "better-npm-run deploy:prod", 22 | "codecov": "cat coverage/*/lcov.info | codecov" 23 | }, 24 | "betterScripts": { 25 | "compile": { 26 | "command": "node bin/compile", 27 | "env": { 28 | "DEBUG": "app:*" 29 | } 30 | }, 31 | "dev": { 32 | "command": "nodemon bin/dev-server --ignore dist --ignore coverage --ignore tests --ignore src", 33 | "env": { 34 | "NODE_ENV": "development", 35 | "DEBUG": "app:*" 36 | } 37 | }, 38 | "deploy": { 39 | "command": "npm run lint && npm run test && npm run clean && npm run compile", 40 | "env": { 41 | "DEBUG": "app:*" 42 | } 43 | }, 44 | "deploy:dev": { 45 | "command": "npm run deploy", 46 | "env": { 47 | "NODE_ENV": "development", 48 | "DEBUG": "app:*" 49 | } 50 | }, 51 | "deploy:prod": { 52 | "command": "npm run deploy", 53 | "env": { 54 | "NODE_ENV": "production", 55 | "DEBUG": "app:*" 56 | } 57 | }, 58 | "start": { 59 | "command": "node bin/dev-server", 60 | "env": { 61 | "DEBUG": "app:*" 62 | } 63 | }, 64 | "test": { 65 | "command": "node ./node_modules/karma/bin/karma start config/karma.config", 66 | "env": { 67 | "NODE_ENV": "test", 68 | "DEBUG": "app:*" 69 | } 70 | } 71 | }, 72 | "repository": { 73 | "type": "git", 74 | "url": "git+https://github.com/davezuko/react-redux-starter-kit.git" 75 | }, 76 | "author": "David Zukowski (http://zuko.me)", 77 | "license": "MIT", 78 | "dependencies": { 79 | "babel-core": "^6.17.0", 80 | "babel-loader": "^6.2.5", 81 | "babel-plugin-transform-runtime": "^6.15.0", 82 | "babel-preset-es2015": "^6.14.0", 83 | "babel-preset-react": "^6.11.1", 84 | "babel-preset-stage-0": "^6.3.13", 85 | "babel-runtime": "^6.11.6", 86 | "better-npm-run": "0.0.13", 87 | "classnames": "^2.2.5", 88 | "compression": "^1.6.2", 89 | "css-loader": "^0.26.1", 90 | "cssnano": "^3.7.4", 91 | "debug": "^2.2.0", 92 | "extract-text-webpack-plugin": "^1.0.0", 93 | "file-loader": "^0.9.0", 94 | "flexboxgrid": "^6.3.1", 95 | "fs-extra": "^1.0.0", 96 | "html-webpack-plugin": "^2.22.0", 97 | "imports-loader": "^0.7.0", 98 | "ip": "^1.1.2", 99 | "json-loader": "^0.5.4", 100 | "material-ui": "^0.16.6", 101 | "node-sass": "^4.0.0", 102 | "normalize.css": "^5.0.0", 103 | "postcss-loader": "^1.1.0", 104 | "react": "^15.0.0", 105 | "react-dom": "^15.0.0", 106 | "react-flexbox-grid": "^0.10.2", 107 | "react-pure-grid": "^2.1.1", 108 | "react-redux": "^5.0.1", 109 | "react-router": "^3.0.0", 110 | "react-tap-event-plugin": "^2.0.1", 111 | "redux": "^3.6.0", 112 | "redux-form": "^6.4.3", 113 | "redux-form-material-ui": "^4.1.2", 114 | "redux-thunk": "^2.0.0", 115 | "rimraf": "^2.5.4", 116 | "sass-loader": "^4.0.0", 117 | "style-loader": "^0.13.1", 118 | "truffle-contract": "^1.1.10", 119 | "url-loader": "^0.5.6", 120 | "web3": "^0.18.2", 121 | "webpack": "^1.12.14", 122 | "yargs": "^6.3.0" 123 | }, 124 | "devDependencies": { 125 | "babel-eslint": "^7.1.0", 126 | "babel-plugin-istanbul": "^3.0.0", 127 | "chai": "^3.4.1", 128 | "chai-as-promised": "^6.0.0", 129 | "chai-enzyme": "^0.6.1", 130 | "cheerio": "^0.22.0", 131 | "codecov": "^1.0.1", 132 | "enzyme": "^2.0.0", 133 | "eslint": "^3.0.1", 134 | "eslint-config-standard": "^6.0.0", 135 | "eslint-config-standard-react": "^4.0.0", 136 | "eslint-plugin-babel": "^4.0.0", 137 | "eslint-plugin-promise": "^3.0.0", 138 | "eslint-plugin-react": "^6.0.0", 139 | "eslint-plugin-standard": "^2.0.0", 140 | "express": "^4.14.0", 141 | "karma": "^1.0.0", 142 | "karma-coverage": "^1.0.0", 143 | "karma-mocha": "^1.0.1", 144 | "karma-mocha-reporter": "^2.0.0", 145 | "karma-phantomjs-launcher": "^1.0.2", 146 | "karma-webpack-with-fast-source-maps": "^1.9.2", 147 | "mocha": "^3.0.1", 148 | "nodemon": "^1.10.2", 149 | "phantomjs-prebuilt": "^2.1.12", 150 | "react-addons-test-utils": "^15.0.0", 151 | "redbox-react": "^1.2.10", 152 | "sinon": "^1.17.5", 153 | "sinon-chai": "^2.8.0", 154 | "truffle-solidity-loader": "git+https://github.com/sogoiii/truffle-solidity-loader.git#1f1e213d52f033b6863218307b8968ae68220fe1", 155 | "webpack-dev-middleware": "^1.6.1", 156 | "webpack-hot-middleware": "^2.12.2" 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /walkthrough/UpgradeToTruffle3.md: -------------------------------------------------------------------------------- 1 | ### Integrating truffle 3 into an existing truffle 2 project 2 | 3 | I [created](https://github.com/sogoiii/web3-react-redux-starter-kit) a fork of [react-redux-starter-kit](https://github.com/davezuko/react-redux-starter-kit) and added web3 and truffle to the mix. Recently truffle was upgraded to [version 3](http://truffleframework.com/tutorials/upgrading-from-truffle-2-to-3), and broke the flow. Which is cool because now its nicer to work with. It no longer renders a `.js` file, but rather creates a `json` file and they updated the configuration file. Follow along and see how I updated the [previous repo](https://github.com/sogoiii/web3-react-redux-starter-kit) to use truffle 3. 4 | 5 | --- 6 | 7 | ### Updating the configuration file 8 | 9 | The `rpc` key was removed and a new key `networks` houses all possible environments. To add a staging or local target, simply create a new object. [Looking at the older](https://github.com/sogoiii/web3-react-redux-starter-kit/blob/with_truffle_2/truffle.js) `truffle.js` file, this was an easy update. I removed the `rpc` key and created a `networks.development` object. 10 | 11 | 12 | ```js 13 | // Allows us to use ES6 in our migrations and tests. 14 | require('babel-register') 15 | 16 | module.exports = { 17 | build: 'webpack', //for building with webpack if so desired 18 | networks: { 19 | development: { 20 | host: 'localhost', 21 | port: 8545, 22 | network_id: '*' // Match any network id 23 | }, 24 | production: { 25 | host: 'anotherHost', 26 | port: 8888, 27 | network_id: '*' // Match any network id 28 | } 29 | }, 30 | migrations_directory: './migrations' //this is for truffle-solidity-loader 31 | } 32 | ``` 33 | 34 | In `src/store/web3Reducer.js` I require `truffle.js` and use a few keys. I only had to [update the path of keys](https://github.com/sogoiii/web3-react-redux-starter-kit/commit/a6eb6cf9c1652f5bbec78ab4f7e16ef92f648b38#diff-21d4e70b0fc7dbc01cc6287bc59a066a) to the node. 35 | 36 | 37 | ### Updating truffle-solidity-loader 38 | 39 | The current master branch on [truffle-solidity-loader](https://github.com/ConsenSys/truffle-solidity-loader) does not have my [pull request](https://github.com/ConsenSys/truffle-solidity-loader/pull/10) from [my branch](https://github.com/sogoiii/truffle-solidity-loader/tree/update_truffle_3). But thats ok, npm allows for direct linking into a github repo! I modified the following in `package.json`. 40 | 41 | ```json 42 | { 43 | "devDependencies": { 44 | "truffle-solidity-loader": "git+https://github.com/sogoiii/truffle-solidity-loader.git#1f1e213d52f033b6863218307b8968ae68220fe1", 45 | } 46 | } 47 | ``` 48 | 49 | I kept the old api, but given the changes in truffle we need to tell Webpack which `network` to attach to. When we `import` or `require` in a solidity file, it will return a json object. Therefore, we need to `json-loader`. Webpack will apply loaders right to left, thats why `json-loader` is first. 50 | 51 | 52 | ```js 53 | webpackConfig.module.loaders.push({ 54 | test: /\.sol/, 55 | loaders: ['json-loader', 'truffle-solidity-loader?migrations_directory=' + path.resolve(__dirname, '../migrations') + '&network=development'] 56 | }) 57 | ``` 58 | 59 | As an FYI, if you upgrade to Webpack 2, you can can provide an object instead of query params. 60 | 61 | ```js 62 | { 63 | test: /\.sol/, 64 | use: [ 65 | { loader: 'json-loader' }, 66 | { loader: 'truffle-solidity-loader', 67 | options: { 68 | migrations_directory: path.resolve(__dirname, './migrations'), 69 | network: 'development' 70 | } 71 | } 72 | ] 73 | } 74 | ``` 75 | 76 | ### Using the new Json files 77 | 78 | The changes allow us to `import` solidity files and get back a `json` object. Workign with the new interface is as follows: 79 | 80 | 81 | ```js 82 | import MyContractArtifact from './contracts/MyContract.sol' 83 | import contract from 'truffle-contract' 84 | const MyContract = contract(MyContractArtifact) 85 | MyContract.deployed().then( instance => { 86 | instance.sendCoin(...) 87 | }) 88 | ``` 89 | 90 | Given how I was structured, I was required to make [some changes](https://github.com/sogoiii/web3-react-redux-starter-kit/commit/a6eb6cf9c1652f5bbec78ab4f7e16ef92f648b38#diff-70bec8a6f43a16e6bd44875435adf3b9). I had to update the chain of promises. 91 | 92 | ```js 93 | export const sendCoin = ({ amount, address }) => { 94 | return (dispatch, getState) => { 95 | return new Promise((resolve) => { 96 | let meta = null 97 | let from = null 98 | getMetaCoin({ getState }) 99 | .then(instance => { 100 | meta = instance 101 | from = getDefaultAccount({ getState }) 102 | return meta.sendCoin(address, amount, { from }) 103 | }) 104 | .then(() => { 105 | return meta.getBalance.call(from, { from }) 106 | }) 107 | .then(value => { 108 | dispatch({ 109 | type: SET_BALANCE, 110 | payload: { value: value.valueOf() } 111 | }) 112 | resolve() 113 | }) 114 | .catch(e => { 115 | console.log(e) 116 | }) 117 | }) 118 | } 119 | } 120 | ``` 121 | 122 | I incorporate the promisified `deployed` method and added it to the chain. This forced me to create a few state variables. 123 | 124 | 125 | ## Conclusion 126 | 127 | Overall upgrading to truffle 3 was relatively easy. I referenced `truffle.js` in a file, and so had to change a few keys. Then I had to update any file that included a `.sol` file. Integrating with `truffle-solidity-loader`, I could include it and pass the object into `truffle-contract`. Then update how to get the contract and viola. 128 | 129 | Please refrence the full [migration guide](http://truffleframework.com/tutorials/upgrading-from-truffle-2-to-3) for you migration needs. 130 | -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const argv = require('yargs').argv 2 | const path = require('path') 3 | const webpack = require('webpack') 4 | const cssnano = require('cssnano') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 7 | const project = require('./project.config') 8 | const debug = require('debug')('app:config:webpack') 9 | 10 | const __DEV__ = project.globals.__DEV__ 11 | const __PROD__ = project.globals.__PROD__ 12 | const __TEST__ = project.globals.__TEST__ 13 | 14 | debug('Creating configuration.') 15 | const webpackConfig = { 16 | name : 'client', 17 | target : 'web', 18 | devtool : project.compiler_devtool, 19 | resolve : { 20 | root : project.paths.client(), 21 | extensions : ['', '.js', '.jsx', '.json'], 22 | alias: { 23 | contracts: path.resolve('contracts') 24 | } 25 | }, 26 | module : {} 27 | } 28 | // ------------------------------------ 29 | // Entry Points 30 | // ------------------------------------ 31 | const APP_ENTRY = project.paths.client('main.js') 32 | 33 | webpackConfig.entry = { 34 | app : __DEV__ 35 | ? [APP_ENTRY].concat(`webpack-hot-middleware/client?path=${project.compiler_public_path}__webpack_hmr`) 36 | : [APP_ENTRY], 37 | vendor : project.compiler_vendors 38 | } 39 | 40 | // ------------------------------------ 41 | // Bundle Output 42 | // ------------------------------------ 43 | webpackConfig.output = { 44 | filename : `[name].[${project.compiler_hash_type}].js`, 45 | path : project.paths.dist(), 46 | publicPath : project.compiler_public_path 47 | } 48 | 49 | // ------------------------------------ 50 | // Externals 51 | // ------------------------------------ 52 | webpackConfig.externals = {} 53 | webpackConfig.externals['react/lib/ExecutionEnvironment'] = true 54 | webpackConfig.externals['react/lib/ReactContext'] = true 55 | webpackConfig.externals['react/addons'] = true 56 | 57 | // ------------------------------------ 58 | // Plugins 59 | // ------------------------------------ 60 | webpackConfig.plugins = [ 61 | new webpack.DefinePlugin(project.globals), 62 | new HtmlWebpackPlugin({ 63 | template : project.paths.client('index.html'), 64 | hash : false, 65 | favicon : project.paths.public('favicon.ico'), 66 | filename : 'index.html', 67 | inject : 'body', 68 | minify : { 69 | collapseWhitespace : true 70 | } 71 | }) 72 | ] 73 | 74 | // Ensure that the compiler exits on errors during testing so that 75 | // they do not get skipped and misreported. 76 | if (__TEST__ && !argv.watch) { 77 | webpackConfig.plugins.push(function () { 78 | this.plugin('done', function (stats) { 79 | if (stats.compilation.errors.length) { 80 | // Pretend no assets were generated. This prevents the tests 81 | // from running making it clear that there were warnings. 82 | throw new Error( 83 | stats.compilation.errors.map(err => err.message || err) 84 | ) 85 | } 86 | }) 87 | }) 88 | } 89 | 90 | if (__DEV__) { 91 | debug('Enabling plugins for live development (HMR, NoErrors).') 92 | webpackConfig.plugins.push( 93 | new webpack.HotModuleReplacementPlugin(), 94 | new webpack.NoErrorsPlugin() 95 | ) 96 | } else if (__PROD__) { 97 | debug('Enabling plugins for production (OccurenceOrder, Dedupe & UglifyJS).') 98 | webpackConfig.plugins.push( 99 | new webpack.optimize.OccurrenceOrderPlugin(), 100 | new webpack.optimize.DedupePlugin(), 101 | new webpack.optimize.UglifyJsPlugin({ 102 | compress : { 103 | unused : true, 104 | dead_code : true, 105 | warnings : false 106 | } 107 | }) 108 | ) 109 | } 110 | 111 | // Don't split bundles during testing, since we only want import one bundle 112 | if (!__TEST__) { 113 | webpackConfig.plugins.push( 114 | new webpack.optimize.CommonsChunkPlugin({ 115 | names : ['vendor'] 116 | }) 117 | ) 118 | } 119 | 120 | // ------------------------------------ 121 | // Loaders 122 | // ------------------------------------ 123 | // JavaScript / JSON 124 | webpackConfig.module.loaders = [{ 125 | test : /\.(js|jsx)$/, 126 | exclude : /node_modules/, 127 | loader : 'babel', 128 | query : project.compiler_babel 129 | }, { 130 | test : /\.json$/, 131 | loader : 'json' 132 | }] 133 | 134 | // ------------------------------------ 135 | // Style Loaders 136 | // ------------------------------------ 137 | // We use cssnano with the postcss loader, so we tell 138 | // css-loader not to duplicate minimization. 139 | const BASE_CSS_LOADER = 'css?sourceMap&-minimize' 140 | 141 | webpackConfig.module.loaders.push({ 142 | test : /\.scss$/, 143 | exclude: /flexboxgrid/, // so we have to exclude it 144 | loaders : [ 145 | 'style', 146 | BASE_CSS_LOADER, 147 | 'postcss', 148 | 'sass?sourceMap' 149 | ] 150 | }) 151 | webpackConfig.module.loaders.push({ 152 | test : /\.css$/, 153 | exclude: /flexboxgrid/, // so we have to exclude it 154 | loaders : [ 155 | 'style', 156 | BASE_CSS_LOADER, 157 | 'postcss' 158 | ] 159 | }) 160 | 161 | webpackConfig.module.loaders.push({ 162 | test: /\.css$/, 163 | loader: 'style!css?modules', 164 | include: /flexboxgrid/ 165 | }) 166 | 167 | webpackConfig.sassLoader = { 168 | includePaths : project.paths.client('styles') 169 | } 170 | 171 | webpackConfig.postcss = [ 172 | cssnano({ 173 | autoprefixer : { 174 | add : true, 175 | remove : true, 176 | browsers : ['last 2 versions'] 177 | }, 178 | discardComments : { 179 | removeAll : true 180 | }, 181 | discardUnused : false, 182 | mergeIdents : false, 183 | reduceIdents : false, 184 | safe : true, 185 | sourcemap : true 186 | }) 187 | ] 188 | 189 | // File loaders 190 | /* eslint-disable */ 191 | webpackConfig.module.loaders.push( 192 | { test: /\.woff(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' }, 193 | { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' }, 194 | { test: /\.otf(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' }, 195 | { test: /\.ttf(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' }, 196 | { test: /\.eot(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]' }, 197 | { test: /\.svg(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' }, 198 | { test: /\.(png|jpg)$/, loader: 'url?limit=8192' } 199 | ) 200 | 201 | webpackConfig.module.loaders.push({ 202 | test: /\.sol/, 203 | loaders: ['json-loader', 'truffle-solidity-loader?migrations_directory=' + path.resolve(__dirname, '../migrations') + '&network=development&contracts_build_directory=' + path.resolve(__dirname, '../dist/contracts') ] 204 | }) 205 | 206 | /* eslint-enable */ 207 | 208 | // ------------------------------------ 209 | // Finalize Configuration 210 | // ------------------------------------ 211 | // when we don't know the public path (we know it only when HMR is enabled [in development]) we 212 | // need to use the extractTextPlugin to fix this issue: 213 | // http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809 214 | if (!__DEV__) { 215 | debug('Applying ExtractTextPlugin to CSS loaders.') 216 | webpackConfig.module.loaders.filter((loader) => 217 | loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0])) 218 | ).forEach((loader) => { 219 | const first = loader.loaders[0] 220 | const rest = loader.loaders.slice(1) 221 | loader.loader = ExtractTextPlugin.extract(first, rest.join('!')) 222 | delete loader.loaders 223 | }) 224 | 225 | webpackConfig.plugins.push( 226 | new ExtractTextPlugin('[name].[contenthash].css', { 227 | allChunks : true 228 | }) 229 | ) 230 | } 231 | 232 | module.exports = webpackConfig 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web3 React Redux Starter Kit 2 | 3 | ## NOTE: 4 | Look at the walkthrough directory to see how web3 was integrated into the original repo. 5 | 6 | 1. [Blog Post 1](https://medium.com/@angellopozo/a-web3-redux-react-starter-kit-walkthrough-1fff0e9c3c36#.hkqj1eqy5): A Web3-redux-react-starter-kit Walkthrough 7 | 2. [Blog Post 2](https://medium.com/@angellopozo/upgrading-to-truffle-3-from-an-existing-truffle-2-project-c0f030b681d3#.g4x4a8lku): Upgrading to truffle 3 from an existing truffle 2 project 8 | 9 | [![Join the chat at https://gitter.im/davezuko/react-redux-starter-kit](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/davezuko/react-redux-starter-kit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 10 | [![Build Status](https://travis-ci.org/davezuko/react-redux-starter-kit.svg?branch=master)](https://travis-ci.org/davezuko/react-redux-starter-kit?branch=master) 11 | [![dependencies](https://david-dm.org/davezuko/react-redux-starter-kit.svg)](https://david-dm.org/davezuko/react-redux-starter-kit) 12 | [![devDependency Status](https://david-dm.org/davezuko/react-redux-starter-kit/dev-status.svg)](https://david-dm.org/davezuko/react-redux-starter-kit#info=devDependencies) 13 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 14 | 15 | This starter kit is designed to get you up and running with a bunch of awesome new front-end technologies, all on top of a configurable, feature-rich webpack build system that's already setup to provide hot reloading, CSS preprocessing with Sass, unit testing, code coverage reports, bundle splitting, and more. 16 | 17 | The primary goal of this project is to remain as **unopinionated** as possible. Its purpose is not to dictate your project structure or to demonstrate a complete sample application, but to provide a set of tools intended to make front-end development robust, easy, and, most importantly, fun. Check out the full feature list below! 18 | 19 | Finally, This project wouldn't be possible without the help of our many contributors, so [thank you](#thank-you) for all of your help. 20 | 21 | ## Table of Contents 22 | 1. [Features](#features) 23 | 1. [Requirements](#requirements) 24 | 1. [Getting Started](#getting-started) 25 | 1. [Application Structure](#application-structure) 26 | 1. [Development](#development) 27 | 1. [Developer Tools](#developer-tools) 28 | 1. [Routing](#routing) 29 | 1. [Testing](#testing) 30 | 1. [Deployment](#deployment) 31 | 1. [Build System](#build-system) 32 | 1. [Configuration](#configuration) 33 | 1. [Globals](#globals) 34 | 1. [Styles](#styles) 35 | 1. [Server](#server) 36 | 1. [Production Optimization](#production-optimization) 37 | 1. [Learning Resources](#learning-resources) 38 | 1. [FAQ](#troubleshooting) 39 | 1. [Thank You](#thank-you) 40 | 41 | ## Features 42 | * [react](https://github.com/facebook/react) 43 | * [redux](https://github.com/rackt/redux) 44 | * [react-router](https://github.com/rackt/react-router) 45 | * [webpack](https://github.com/webpack/webpack) 46 | * [babel](https://github.com/babel/babel) 47 | * [express](https://github.com/expressjs/express) 48 | * [karma](https://github.com/karma-runner/karma) 49 | * [eslint](http://eslint.org) 50 | 51 | ## Requirements 52 | * node `^4.5.0` 53 | * yarn `^0.17.0` or npm `^3.0.0` 54 | 55 | ## Getting Started 56 | 57 | After confirming that your development environment meets the specified [requirements](#requirements), you can create a new project based on `react-redux-starter-kit` by doing the following: 58 | 59 | ### Install from source 60 | 61 | First, clone the project: 62 | 63 | ```bash 64 | $ git clone https://github.com/davezuko/react-redux-starter-kit.git 65 | $ cd 66 | ``` 67 | 68 | Then install dependencies and check to see it works. It is recommended that you use [Yarn](https://yarnpkg.com/) for deterministic installs, but `npm install` will work just as well. 69 | 70 | ```bash 71 | $ yarn install # Install project dependencies 72 | $ yarn start # Compile and launch (same as `npm start`) 73 | ``` 74 | If everything works, you should see the following: 75 | 76 | 77 | 78 | While developing, you will probably rely mostly on `npm start`; however, there are additional scripts at your disposal: 79 | 80 | |`npm run