From 389bc28a83e3421267c3c9b7ad49e162d6d75442 Mon Sep 17 00:00:00 2001 From: Barry Gitarts Date: Fri, 1 Jun 2018 10:25:59 -0400 Subject: [PATCH 1/5] add initial redux scafold --- app/dapp.js | 2 +- app/index.html | 2 +- app/index.js | 15 +++++++++++++++ app/reducers/accounts.js | 19 +++++++++++++++++++ app/reducers/rootReducer.js | 8 ++++++++ app/store/configureStore.js | 11 +++++++++++ embark.json | 1 + package.json | 6 +++++- 8 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 app/index.js create mode 100644 app/reducers/accounts.js create mode 100644 app/reducers/rootReducer.js create mode 100644 app/store/configureStore.js diff --git a/app/dapp.js b/app/dapp.js index 381f900..ca9825c 100644 --- a/app/dapp.js +++ b/app/dapp.js @@ -44,4 +44,4 @@ class App extends React.Component { } } -ReactDOM.render(, document.getElementById('app')); +export default App; diff --git a/app/index.html b/app/index.html index 1fb8607..43438ee 100644 --- a/app/index.html +++ b/app/index.html @@ -7,6 +7,6 @@
- + diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..0316ccd --- /dev/null +++ b/app/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { render } from 'react-dom'; +import { Provider } from 'react-redux'; +import configureStore from './store/configureStore'; +import App from './dapp'; +import './dapp.css'; + +const store = configureStore(); + +render( + + + , + document.getElementById('app') +); diff --git a/app/reducers/accounts.js b/app/reducers/accounts.js new file mode 100644 index 0000000..89012a6 --- /dev/null +++ b/app/reducers/accounts.js @@ -0,0 +1,19 @@ +import { createTypes, actionCreator } from 'redux-action-creator' + +export const types = createTypes([ + 'RECEIVE_ACCOUNTS' +], 'ACCOUNTS') +export const actions = { + receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'accounts') +} + +export default function accounts(state = [], action) { + switch (action.type) { + case types.RECEIVE_ACCOUNTS: + return { ...state, ...action.accounts } + default: + return state; + } +} + +export const getAccounts = state => state.accounts; diff --git a/app/reducers/rootReducer.js b/app/reducers/rootReducer.js new file mode 100644 index 0000000..244ee3e --- /dev/null +++ b/app/reducers/rootReducer.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import accounts from './accounts' + +const rootReducer = combineReducers({ + accounts +}); + +export default rootReducer; diff --git a/app/store/configureStore.js b/app/store/configureStore.js new file mode 100644 index 0000000..b44170c --- /dev/null +++ b/app/store/configureStore.js @@ -0,0 +1,11 @@ +import { createStore, applyMiddleware } from 'redux'; +import rootReducer from '../reducers/rootReducer'; +import thunk from 'redux-thunk'; + +export default function configureStore() { + return createStore( + rootReducer, + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), + applyMiddleware(thunk) + ); +} diff --git a/embark.json b/embark.json index 2f73ec7..13e7e73 100644 --- a/embark.json +++ b/embark.json @@ -2,6 +2,7 @@ "contracts": ["contracts/**"], "app": { "js/dapp.js": ["app/dapp.js"], + "js/index.js": ["app/index.js"], "index.html": "app/index.html", "images/": ["app/images/**"] }, diff --git a/package.json b/package.json index c3a0548..33f8d11 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,11 @@ "react": "^16.3.2", "react-blockies": "^1.3.0", "react-bootstrap": "^0.32.1", - "react-dom": "^16.3.2" + "react-dom": "^16.3.2", + "react-redux": "^5.0.7", + "redux": "^4.0.0", + "redux-action-creator": "^2.3.0", + "redux-thunk": "^2.3.0" }, "devDependencies": { "babel-plugin-transform-object-rest-spread": "^6.26.0", From 6bb91818100c8b230f8bb1d6f031b2c6a79704f2 Mon Sep 17 00:00:00 2001 From: Barry Gitarts Date: Fri, 1 Jun 2018 12:45:11 -0400 Subject: [PATCH 2/5] add reducer state and actions --- app/actions/accounts.js | 18 ++++++++++++++++++ app/index.js | 5 +++-- app/reducers/accounts.js | 15 +++++++++++---- app/store/configureStore.js | 14 +++++++------- app/store/init.js | 12 ++++++++++++ 5 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 app/actions/accounts.js create mode 100644 app/store/init.js diff --git a/app/actions/accounts.js b/app/actions/accounts.js new file mode 100644 index 0000000..a018c48 --- /dev/null +++ b/app/actions/accounts.js @@ -0,0 +1,18 @@ +import { actions as accountActions } from '../reducers/accounts' + +const { receiveAccounts } = accountActions +export const fetchAndDispatchAccountsWithBalances = (web3, dispatch) => { + web3.eth.getAccounts((err, addresses) => { + if (addresses) { + const defaultAccount = web3.eth.defaultAccount || addresses[0] + const accounts = addresses.map(async address => { + const balance = await web3.eth.getBalance(address, 'latest') + return { address, balance } + }) + console.log('accounts page', Promise.all(accounts), accounts.length) + Promise.all(accounts).then(accounts => { + dispatch(receiveAccounts(defaultAccount, accounts)) + }) + } + }) +} diff --git a/app/index.js b/app/index.js index 0316ccd..71ad7c2 100644 --- a/app/index.js +++ b/app/index.js @@ -1,11 +1,12 @@ import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; -import configureStore from './store/configureStore'; +import store from './store/configureStore'; import App from './dapp'; +import init from './store/init' import './dapp.css'; -const store = configureStore(); +init(); render( diff --git a/app/reducers/accounts.js b/app/reducers/accounts.js index 89012a6..8c06e66 100644 --- a/app/reducers/accounts.js +++ b/app/reducers/accounts.js @@ -4,16 +4,23 @@ export const types = createTypes([ 'RECEIVE_ACCOUNTS' ], 'ACCOUNTS') export const actions = { - receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'accounts') + receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'defaultAccount','accounts') } -export default function accounts(state = [], action) { +export default function(state = { loading: true, accounts: [] }, action) { switch (action.type) { case types.RECEIVE_ACCOUNTS: - return { ...state, ...action.accounts } + const { defaultAccount, accounts } = action.payload + return { + ...state, + loading: false, + defaultAccount, + accounts + } default: return state; } } -export const getAccounts = state => state.accounts; +export const getAccounts = state => state.accounts.accounts; +export const accountsIsLoading = state => state.accounts.loading; diff --git a/app/store/configureStore.js b/app/store/configureStore.js index b44170c..d5ee806 100644 --- a/app/store/configureStore.js +++ b/app/store/configureStore.js @@ -2,10 +2,10 @@ import { createStore, applyMiddleware } from 'redux'; import rootReducer from '../reducers/rootReducer'; import thunk from 'redux-thunk'; -export default function configureStore() { - return createStore( - rootReducer, - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), - applyMiddleware(thunk) - ); -} +const store = createStore( + rootReducer, + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), + applyMiddleware(thunk) +); + +export default store; diff --git a/app/store/init.js b/app/store/init.js new file mode 100644 index 0000000..b7fabd0 --- /dev/null +++ b/app/store/init.js @@ -0,0 +1,12 @@ +import web3 from "Embark/web3" +import EmbarkJS from 'Embark/EmbarkJS' +import store from './configureStore' +import { fetchAndDispatchAccountsWithBalances } from '../actions/accounts' + +const dispatch = action => store.dispatch(action) + +export default () => { + __embarkContext.execWhenReady(async () => { + fetchAndDispatchAccountsWithBalances(web3, dispatch) + }) +} From 3477b7b24c214a9fce36fb69b1f249831242340b Mon Sep 17 00:00:00 2001 From: Barry Gitarts Date: Fri, 1 Jun 2018 16:06:27 -0400 Subject: [PATCH 3/5] add dispatch and cleanup to accounts list --- .eslintrc.json | 10 ++- app/actions/accounts.js | 1 - app/components/accountlist.js | 162 ++++++++++++---------------------- app/reducers/accounts.js | 15 +++- package.json | 1 + 5 files changed, 79 insertions(+), 110 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 44cbe88..f8ab647 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,9 @@ { - "extends": "airbnb" -} \ No newline at end of file + "extends": "airbnb", + "plugins": [ + "react" + ], + "rules": { + "react/prop-types": 0 + } +} diff --git a/app/actions/accounts.js b/app/actions/accounts.js index a018c48..4f731c8 100644 --- a/app/actions/accounts.js +++ b/app/actions/accounts.js @@ -9,7 +9,6 @@ export const fetchAndDispatchAccountsWithBalances = (web3, dispatch) => { const balance = await web3.eth.getBalance(address, 'latest') return { address, balance } }) - console.log('accounts page', Promise.all(accounts), accounts.length) Promise.all(accounts).then(accounts => { dispatch(receiveAccounts(defaultAccount, accounts)) }) diff --git a/app/components/accountlist.js b/app/components/accountlist.js index fbd3cc7..adcd873 100644 --- a/app/components/accountlist.js +++ b/app/components/accountlist.js @@ -1,112 +1,66 @@ -import web3 from "Embark/web3" -import EmbarkJS from 'Embark/EmbarkJS'; +import web3 from 'Embark/web3' import React from 'react'; -import { Nav, MenuItem , NavDropdown} from 'react-bootstrap'; +import { connect } from 'react-redux'; +import { Nav, MenuItem, NavDropdown } from 'react-bootstrap'; import Blockies from 'react-blockies'; - +import { string, bool, func, arrayOf, shape } from 'prop-types'; +import { getAccounts, getDefaultAccount, accountsIsLoading, actions as accountActions } from '../reducers/accounts'; import './accountlist.css'; -class AccList extends React.Component { - - constructor(props) { - super(props); - this.state = { - classNameNavDropdown: props.classNameNavDropdown, - defaultAccount: "0x0000000000000000000000000000000000000000", - addresses: [], - balances: [] - } - __embarkContext.execWhenReady(() => { - this.load() - }); - } - - - load() { - web3.eth.getAccounts((err, addresses) => { - if (addresses) { - var defaultAccount = web3.eth.defaultAccount; - if(!defaultAccount){ - web3.eth.defaultAccount = addresses[0]; - } - - var balances = []; - balances.length == addresses.length; - addresses.forEach((address, index) => { - web3.eth.getBalance(address, 'latest', (err, balance) => { - balances[index] = balance; - if(index+1 == balances.length){ - this.setState({ - balances: balances - }); - } - }) - }) - this.setState({ - defaultAccount: defaultAccount, - addresses: addresses - }); - - } else { - console.log("No addresses available."); - } - - }) - } - setDefaultAccount(index) { - var defaultAcc = this.state.addresses[index]; - if(defaultAcc){ - web3.eth.defaultAccount = defaultAcc; - this.setState({defaultAccount: defaultAcc }); - } else { - console.log("invalid account") - } - } - - render() { - - var accsTitle; - var accsList = []; - if (this.state.addresses) { - accsTitle = this.state.defaultAccount; - this.state.addresses.forEach( - (name, index) => { - accsList.push( - this.setDefaultAccount(index) }> -
-
- -
-
- {name} -
-
- Ξ {this.state.balances[index] / (10**18)} -
-
-
); - } - ) - } - - return ( - -
-
- +const AccList = ({ + accounts, defaultAccount, changeAccount, isLoading, classNameNavDropdown, +}) => ( + + {!isLoading ? +
+
+ +
+
+ +
+
+ :
Loading...
} +
+); - } +AccList.propTypes = { + accounts: arrayOf(shape({ address: string, balance: string })).isRequired, + defaultAccount: string, + changeAccount: func.isRequired, + isLoading: bool.isRequired, + classNameNavDropdown: string +} - export default AccList; \ No newline at end of file +const mapStateToProps = state => ({ + accounts: getAccounts(state), + defaultAccount: getDefaultAccount(state), + isLoading: accountsIsLoading(state), +}); + +const mapDispatchToProps = dispatch => ({ + changeAccount(address) { + web3.eth.defaultAccount = address; + dispatch(accountActions.updateDefaultAccount(address)); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AccList); diff --git a/app/reducers/accounts.js b/app/reducers/accounts.js index 8c06e66..4e7795d 100644 --- a/app/reducers/accounts.js +++ b/app/reducers/accounts.js @@ -1,15 +1,17 @@ import { createTypes, actionCreator } from 'redux-action-creator' export const types = createTypes([ - 'RECEIVE_ACCOUNTS' + 'RECEIVE_ACCOUNTS', + 'UPDATE_DEFAULT_ACCOUNT' ], 'ACCOUNTS') export const actions = { - receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'defaultAccount','accounts') + receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'defaultAccount','accounts'), + updateDefaultAccount: actionCreator(types.UPDATE_DEFAULT_ACCOUNT, 'defaultAccount') } export default function(state = { loading: true, accounts: [] }, action) { switch (action.type) { - case types.RECEIVE_ACCOUNTS: + case types.RECEIVE_ACCOUNTS: { const { defaultAccount, accounts } = action.payload return { ...state, @@ -17,10 +19,17 @@ export default function(state = { loading: true, accounts: [] }, action) { defaultAccount, accounts } + } + case types.UPDATE_DEFAULT_ACCOUNT: { + const { defaultAccount } = action.payload + return { ...state, defaultAccount } + } default: return state; } } +export const getAccountState = state => state.acounts; export const getAccounts = state => state.accounts.accounts; +export const getDefaultAccount = state => state.accounts.defaultAccount; export const accountsIsLoading = state => state.accounts.loading; diff --git a/package.json b/package.json index 33f8d11..18b3c28 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "homepage": "https://github.com/status-im/contracts#readme", "dependencies": { + "prop-types": "^15.6.1", "react": "^16.3.2", "react-blockies": "^1.3.0", "react-bootstrap": "^0.32.1", From af17134f3e4b20d957591a44094c3c32791d5b86 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Tue, 5 Jun 2018 11:45:35 -0300 Subject: [PATCH 4/5] small fix --- app/components/testtoken.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/components/testtoken.js b/app/components/testtoken.js index 4f4b19a..14639db 100644 --- a/app/components/testtoken.js +++ b/app/components/testtoken.js @@ -26,9 +26,8 @@ class TestTokenUI extends React.Component { TestToken.methods.mint(value).send({from: web3.eth.defaultAccount}); } else { TestToken.mint(value); - this._addToLog("#blockchain", "TestToken.mint(" + value + ")"); } - this._addToLog(TestToken.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})"); + console.log(TestToken.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})"); } render(){ From cb41a904c5e0fc745052560846c39e0f603eea3a Mon Sep 17 00:00:00 2001 From: Barry Gitarts Date: Tue, 5 Jun 2018 13:19:21 -0400 Subject: [PATCH 5/5] add update ERC20 account balance add reselect - memoizes from the state tree --- app/actions/accounts.js | 4 +- app/components/erc20token.js | 111 +++++++++++++++++++---------------- app/components/testtoken.js | 19 ++++-- app/reducers/accounts.js | 23 +++++++- package.json | 3 +- 5 files changed, 100 insertions(+), 60 deletions(-) diff --git a/app/actions/accounts.js b/app/actions/accounts.js index 4f731c8..6e5323a 100644 --- a/app/actions/accounts.js +++ b/app/actions/accounts.js @@ -1,3 +1,4 @@ +import ERC20Token from 'Embark/contracts/ERC20Token'; import { actions as accountActions } from '../reducers/accounts' const { receiveAccounts } = accountActions @@ -7,7 +8,8 @@ export const fetchAndDispatchAccountsWithBalances = (web3, dispatch) => { const defaultAccount = web3.eth.defaultAccount || addresses[0] const accounts = addresses.map(async address => { const balance = await web3.eth.getBalance(address, 'latest') - return { address, balance } + const ERC20TokenBalance = await ERC20Token.methods.balanceOf(address).call() + return { address, balance, ERC20TokenBalance } }) Promise.all(accounts).then(accounts => { dispatch(receiveAccounts(defaultAccount, accounts)) diff --git a/app/components/erc20token.js b/app/components/erc20token.js index 0b318b3..173b706 100644 --- a/app/components/erc20token.js +++ b/app/components/erc20token.js @@ -1,7 +1,9 @@ import EmbarkJS from 'Embark/EmbarkJS'; import ERC20Token from 'Embark/contracts/ERC20Token'; import React from 'react'; +import { connect } from 'react-redux'; import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; +import { getCurrentAccount, accountsIsLoading } from '../reducers/accounts'; class ERC20TokenUI extends React.Component { @@ -67,59 +69,64 @@ class ERC20TokenUI extends React.Component { console.log(txt); } - render(){ - return ( - -

Read your account token balance

-
- - Your test token balance is {this.state.accountBalance} - - -
- -

Read account token balance

-
- - - - - -
+ render() { + const { account, isLoading } = this.props; + return ( + +

Read your account token balance

+
+ + {!isLoading && Your test token balance is {account.ERC20TokenBalance}} + +
+ +

Read account token balance

+
+ + + + + +
-

Transfer/Approve token balance

-
- - - - - - -
- +

Transfer/Approve token balance

+
+ + + + + + +
+
- ); - } + ); + } } - export default ERC20TokenUI; \ No newline at end of file +const mapStateToProps = state => ({ + account: getCurrentAccount(state), + isLoading: accountsIsLoading(state), +}); + +export default connect(mapStateToProps)(ERC20TokenUI); diff --git a/app/components/testtoken.js b/app/components/testtoken.js index 14639db..0670abc 100644 --- a/app/components/testtoken.js +++ b/app/components/testtoken.js @@ -3,6 +3,8 @@ import TestToken from 'Embark/contracts/TestToken'; import React from 'react'; import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap'; import ERC20TokenUI from './erc20token'; +import { connect } from 'react-redux'; +import { actions as accountActions } from '../reducers/accounts'; class TestTokenUI extends React.Component { @@ -17,15 +19,18 @@ class TestTokenUI extends React.Component { this.setState({amountToMint: e.target.value}); } - mint(e){ + mint(e){ + const { addToBalance } = this.props; e.preventDefault(); var value = parseInt(this.state.amountToMint, 10); if (EmbarkJS.isNewWeb3()) { - TestToken.methods.mint(value).send({from: web3.eth.defaultAccount}); + TestToken.methods.mint(value).send({from: web3.eth.defaultAccount}) + .then(r => { addToBalance(value) }); } else { - TestToken.mint(value); + TestToken.mint(value).send({from: web3.eth.defaultAccount}) + .then(r => { addToBalance(value) }); } console.log(TestToken.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})"); } @@ -50,4 +55,10 @@ class TestTokenUI extends React.Component { } } - export default TestTokenUI; \ No newline at end of file +const mapDispatchToProps = dispatch => ({ + addToBalance(amount) { + dispatch(accountActions.addToErc20TokenBalance(amount)); + }, +}); + +export default connect(null, mapDispatchToProps)(TestTokenUI); diff --git a/app/reducers/accounts.js b/app/reducers/accounts.js index 4e7795d..4ca8dc9 100644 --- a/app/reducers/accounts.js +++ b/app/reducers/accounts.js @@ -1,12 +1,15 @@ import { createTypes, actionCreator } from 'redux-action-creator' +import { createSelector } from 'reselect' export const types = createTypes([ 'RECEIVE_ACCOUNTS', - 'UPDATE_DEFAULT_ACCOUNT' + 'UPDATE_DEFAULT_ACCOUNT', + 'ADD_TO_ERC20_TOKEN_BALANCE' ], 'ACCOUNTS') export const actions = { receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'defaultAccount','accounts'), - updateDefaultAccount: actionCreator(types.UPDATE_DEFAULT_ACCOUNT, 'defaultAccount') + updateDefaultAccount: actionCreator(types.UPDATE_DEFAULT_ACCOUNT, 'defaultAccount'), + addToErc20TokenBalance: actionCreator(types.ADD_TO_ERC20_TOKEN_BALANCE, 'amount') } export default function(state = { loading: true, accounts: [] }, action) { @@ -24,6 +27,17 @@ export default function(state = { loading: true, accounts: [] }, action) { const { defaultAccount } = action.payload return { ...state, defaultAccount } } + case types.ADD_TO_ERC20_TOKEN_BALANCE: { + const currentAccount = { ...getCurrentAccount({accounts: state}) } + currentAccount.ERC20TokenBalance = Number(currentAccount.ERC20TokenBalance) + Number(action.payload.amount) + const accounts = [ ...state.accounts ] + const idx = accounts.findIndex(a => a.address === currentAccount.address) + accounts[idx] = currentAccount + return { + ...state, + accounts + } + } default: return state; } @@ -33,3 +47,8 @@ export const getAccountState = state => state.acounts; export const getAccounts = state => state.accounts.accounts; export const getDefaultAccount = state => state.accounts.defaultAccount; export const accountsIsLoading = state => state.accounts.loading; +export const getCurrentAccount = createSelector( + getDefaultAccount, + getAccounts, + (defaultAccount, accounts) => accounts.find(a => a.address === defaultAccount) +) diff --git a/package.json b/package.json index 18b3c28..2f8ae8c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "react-redux": "^5.0.7", "redux": "^4.0.0", "redux-action-creator": "^2.3.0", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "reselect": "^3.0.1" }, "devDependencies": { "babel-plugin-transform-object-rest-spread": "^6.26.0",