diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/.env.dev b/.env.dev index b512e134..56c17b40 100644 --- a/.env.dev +++ b/.env.dev @@ -1,6 +1,4 @@ HTTP_PROVIDER="https://kovan.infura.io" KEEN_PROJECT_ID="5a8ad8f146e0fb00016bc353" KEEN_WRITE_KEY="7A8EFD6315E74E131701296C580E9A05D04A4630D3030B4B0FF8AFC8CA343F5611FD1593A189114AC0F43F5596F2AEB82B19973202D7BF6ABC0BF1450AF6F77A19290EE6E5F9D23265637AD8B0FAA83B31C42FA174AE09BAE3D6BD33D651F139" -KEEN_READ_KEY="8A667A700E42CF4EFD62599F49CF9A224B9200BC626EED814E11D28B585102D9B980D45E720F1AF1F49CC332D640B7BE77DB0DC8160D22B3244ED59A102429AE31DEA6C8C4AB56476130B43FA8EB17FF10FE18DEB2D4BA4E33250FC871BE5578" -DAY_TOKEN_ADDRESS={"1":"0xe814aee960a85208c3db542c53e7d4a6c8d5f60f","3":"0x7941bc77E1d6BD4628467b6cD3650F20F745dB06","42":"0x5a6b5c6387196bd4ea264f627792af9d09096876"} -DAY_FAUCET_ADDRESS={"3":"0xfc5c1dc438411dce1cee4971fa333ecd3c3fa7d3","42":"0x3baebd8b6839f8ae0c88fc15b9d8d7b641d06731"} \ No newline at end of file +KEEN_READ_KEY="8A667A700E42CF4EFD62599F49CF9A224B9200BC626EED814E11D28B585102D9B980D45E720F1AF1F49CC332D640B7BE77DB0DC8160D22B3244ED59A102429AE31DEA6C8C4AB56476130B43FA8EB17FF10FE18DEB2D4BA4E33250FC871BE5578" \ No newline at end of file diff --git a/.env.prod b/.env.prod index b512e134..56c17b40 100644 --- a/.env.prod +++ b/.env.prod @@ -1,6 +1,4 @@ HTTP_PROVIDER="https://kovan.infura.io" KEEN_PROJECT_ID="5a8ad8f146e0fb00016bc353" KEEN_WRITE_KEY="7A8EFD6315E74E131701296C580E9A05D04A4630D3030B4B0FF8AFC8CA343F5611FD1593A189114AC0F43F5596F2AEB82B19973202D7BF6ABC0BF1450AF6F77A19290EE6E5F9D23265637AD8B0FAA83B31C42FA174AE09BAE3D6BD33D651F139" -KEEN_READ_KEY="8A667A700E42CF4EFD62599F49CF9A224B9200BC626EED814E11D28B585102D9B980D45E720F1AF1F49CC332D640B7BE77DB0DC8160D22B3244ED59A102429AE31DEA6C8C4AB56476130B43FA8EB17FF10FE18DEB2D4BA4E33250FC871BE5578" -DAY_TOKEN_ADDRESS={"1":"0xe814aee960a85208c3db542c53e7d4a6c8d5f60f","3":"0x7941bc77E1d6BD4628467b6cD3650F20F745dB06","42":"0x5a6b5c6387196bd4ea264f627792af9d09096876"} -DAY_FAUCET_ADDRESS={"3":"0xfc5c1dc438411dce1cee4971fa333ecd3c3fa7d3","42":"0x3baebd8b6839f8ae0c88fc15b9d8d7b641d06731"} \ No newline at end of file +KEEN_READ_KEY="8A667A700E42CF4EFD62599F49CF9A224B9200BC626EED814E11D28B585102D9B980D45E720F1AF1F49CC332D640B7BE77DB0DC8160D22B3244ED59A102429AE31DEA6C8C4AB56476130B43FA8EB17FF10FE18DEB2D4BA4E33250FC871BE5578" \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index ac8e31f3..10cfbd45 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,8 @@ "keyword-spacing": "error", "no-extra-semi": "error", "semi": ["error", "always"], - "quotes": ["error", "single", { "allowTemplateLiterals": true }] + "quotes": ["error", "single", { "allowTemplateLiterals": true }], + "jsx-quotes": ["error", "prefer-double"] }, "plugins": ["jest"] } diff --git a/.gitignore b/.gitignore index 93a59754..89791fec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules out/ -.DS_Store \ No newline at end of file +.DS_Store +yarn-error.log \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..95099133 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 100, + "singleQuote": true, + "useTabs": false, + "semi": true, + "tabWidth": 2, + "trailingComma": "none" +} \ No newline at end of file diff --git a/README.md b/README.md index c1e4c7e7..263f5810 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ A DApp that interacts with the Ethereum Alarm Clock. 3. `npm run dev` - Run the dev server 4. Check `localhost:8080` in your browser +## Docker +Useful in case a developer would like to test a feature in an isolated environment. +1. Build containers - `npm run docker-build` +2. Wait for the containers to finish building and starting. +3. Visit `localhost:8080` on your browser. If it still not running, check logs with `docker logs ethalarmclockdapp_dapp_1`. +4. Once the DApp is running, point your MetaMask provider to `http://localhost:9545` and import an account with the following private key: `c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3` (default Ganache account). +5. You are now running a fully dockerized environment! +6. (Optional) If you need test DAY tokens, you can get some from the faucet. + ## Debugging Having issues with the project? Try these: - Try cleaning the project `npm run clean` then running `npm run dev` diff --git a/__tests__/Header.unit.test.js b/__tests__/Header.unit.test.js index 31689a2e..2b0708ec 100644 --- a/__tests__/Header.unit.test.js +++ b/__tests__/Header.unit.test.js @@ -19,10 +19,16 @@ describe('Header', () => { }); const keenStore = {}; + const eacService = { + getActiveContracts: () => { + return {}; + } + }; const injectables = { keenStore, - web3Service + web3Service, + eacService }; let mockedRender = renderer.create( diff --git a/__tests__/__snapshots__/Header.unit.test.js.snap b/__tests__/__snapshots__/Header.unit.test.js.snap index 79d2bb38..591771a0 100644 --- a/__tests__/__snapshots__/Header.unit.test.js.snap +++ b/__tests__/__snapshots__/Header.unit.test.js.snap @@ -56,6 +56,79 @@ exports[`Header displays current block number 1`] = ` 5361200 +
+ + + +   + +
+
+
+
+
+
+
+ Ethereum Alarm Clock contracts +
+
+
+
+
+
+ Schedulers +
+
+
+
+
+
+ Libraries +
+
+
+
+
+
+
+
- - {Titles[type]} {this.props.msg} +
+
+
+ {Titles[type]} + {this.props.msg} +
+ {callToAction && ( +
{callToAction}
+ )} +
+
{close &&
); } @@ -22,7 +32,9 @@ class Alert extends Component { Alert.propTypes = { msg: PropTypes.string, - type: PropTypes.string + type: PropTypes.string, + close: PropTypes.any, + action: PropTypes.any }; export default Alert; diff --git a/app/components/Common/AwaitingMining.js b/app/components/Common/AwaitingMining.js index 2521bfd2..ce9be30c 100644 --- a/app/components/Common/AwaitingMining.js +++ b/app/components/Common/AwaitingMining.js @@ -32,13 +32,15 @@ class AwaitingMining extends Component { transactionHash: '', newContract: '', deploying: false, - minning: false, + minning: false }; } async loadUp() { const { web3Service } = this.props; - const { web3Service: { web3 } } = this.props; + const { + web3Service: { web3 } + } = this.props; const { hash, type } = this.props.match.params; let unmined = true; let unconfirmed = true; @@ -81,7 +83,7 @@ class AwaitingMining extends Component { if (mineDestinations[type].logEventTypes && mineDestinations[type].logEventHex) { const log = await web3Service.fetchLog(transactionHash, mineDestinations[type].logEventHex); - const data = log.data.substring(2);//truncate data for decoding + const data = log.data.substring(2); //truncate data for decoding const args = Coder.decodeParams(mineDestinations[type].logEventTypes, data); let newSate = {}; newSate[mineDestinations[type].prop] = args[mineDestinations[type].nextParameterPosition]; @@ -110,37 +112,39 @@ class AwaitingMining extends Component { } render() { - const { web3Service: { explorer } } = this.props; - const { transactionHash,newContract } = this.state; + const { + web3Service: { explorer } + } = this.props; + const { transactionHash, newContract } = this.state; return (
- {this.state.deploying && -

Deploying

- } - {this.state.minning && -

Awaiting Mining

- } - {!this.state.deploying && !this.state.minning && -

...

- } -
-
+ {this.state.deploying &&

Deploying

} + {this.state.minning &&

Awaiting Mining

} + {!this.state.deploying && !this.state.minning &&

...

} +
+
- {this.state.transactionHash && + {this.state.transactionHash && (

Transaction Hash:
- {this.state.transactionHash} + + {' '} + {this.state.transactionHash}{' '} +

- } - {this.state.newContract && + )} + {this.state.newContract && (

Contract Address:
- {this.state.newContract} + + {' '} + {this.state.newContract}{' '} +

- } + )}
@@ -155,5 +159,4 @@ AwaitingMining.propTypes = { history: PropTypes.object.isRequired }; - export default AwaitingMining; diff --git a/app/components/Common/Faucet.js b/app/components/Common/Faucet.js index da45c8a2..1a50220b 100644 --- a/app/components/Common/Faucet.js +++ b/app/components/Common/Faucet.js @@ -3,11 +3,10 @@ import PropTypes from 'prop-types'; import { inject, observer } from 'mobx-react'; import Bb from 'bluebird'; import { BeatLoader } from 'react-spinners'; -import dayFaucetABI from '../../abi/dayFaucetABI'; import { showNotification } from '../../services/notification'; import MetamaskComponent from '../Common/MetamaskComponent'; -const Eth = 1e+18; +const Eth = 1e18; @inject('web3Service') @observer @@ -27,7 +26,7 @@ class Faucet extends MetamaskComponent { lastUsed: '' }; - instance = {} + instance = {}; get networkHasFaucet() { return Boolean(this.state.faucetAddress && this.state.faucetAddress != '0x0'); @@ -40,10 +39,16 @@ class Faucet extends MetamaskComponent { } get isEligible() { - return this.isWeb3Usable && this.state.loaded && this.networkHasFaucet && this.state.defaultAccount && this.waitTimeLeft == 0; + return ( + this.isWeb3Usable && + this.state.loaded && + this.networkHasFaucet && + this.state.defaultAccount && + this.waitTimeLeft == 0 + ); } - get printWaitTime () { + get printWaitTime() { let waitMins = 0; let waitSecs = 0; const Min = 60 * 1000; @@ -86,39 +91,64 @@ class Faucet extends MetamaskComponent { await web3Service.awaitInitialized(); const { accounts } = web3Service; - this.setState({ defaultAccount: accounts[0], faucetAddress: JSON.parse(process.env.DAY_FAUCET_ADDRESS)[web3Service.netId] }); + this.setState({ + defaultAccount: accounts[0], + faucetAddress: web3Service.network.dayFaucetAddress + }); if (!this.isWeb3Usable || !this.state.faucetAddress) { return; } - const { web3Service: { web3 } } = this.props; - this.instance = web3.eth.contract(dayFaucetABI).at(this.state.faucetAddress); + const { + web3Service: { web3 } + } = this.props; + + const faucetAbi = web3Service.network.dayFaucetAbi; + this.instance = web3.eth.contract(faucetAbi).at(this.state.faucetAddress); + this.setState({ - faucetBalance: Number( await Bb.fromCallback(callback => this.instance.getTokensBalance(callback))), - lastUsed: Number( await Bb.fromCallback(callback => this.instance.lastRequest(this.state.defaultAccount, callback))), + faucetBalance: Number( + await Bb.fromCallback(callback => this.instance.getTokensBalance(callback)) + ), + lastUsed: Number( + await Bb.fromCallback(callback => + this.instance.lastRequest(this.state.defaultAccount, callback) + ) + ), waitTime: Number(await Bb.fromCallback(callback => this.instance.waitTime(callback))), - allowedTokens: Number( await Bb.fromCallback(callback => this.instance.allowedTokens(callback))) + allowedTokens: Number( + await Bb.fromCallback(callback => this.instance.allowedTokens(callback)) + ) }); this.setWaitTime(); this.setState({ loaded: true }); } - useFaucet = async (event) => { + useFaucet = async event => { const { target } = event; - const { web3Service: { explorer } } = this.props; + const { + web3Service: { explorer } + } = this.props; target.disabled = true; let transaction; try { - transaction = await Bb.fromCallback(callback => this.instance.useFaucet({ from: this.state.defaultAccount }, callback)); - showNotification(`Transaction successful \r\n ${transaction}`, 'success'); + transaction = await Bb.fromCallback(callback => + this.instance.useFaucet({ from: this.state.defaultAccount }, callback) + ); + showNotification( + `Transaction successful \r\n ${transaction}`, + 'success' + ); await this.restartInterval(); } catch (e) { - showNotification(`Transaction Failed !!!`); + showNotification(`The transaction was unsuccessful.`); } - } + }; async componentDidMount() { super.componentDidMount(); @@ -133,67 +163,99 @@ class Faucet extends MetamaskComponent { render() { const { web3Service } = this.props; + const explorer = web3Service.explorer; - return ( -
-

DAY Token Faucet

-
-
-
+ const hrefProps = { + target: '_blank', + rel: 'noopener noreferrer' + }; + const faucetAddressProps = hrefProps; + const yourAddressProps = hrefProps; + + if (explorer) { + faucetAddressProps.href = explorer + 'address/' + this.state.faucetAddress; + yourAddressProps.href = explorer + 'address/' + this.state.defaultAccount; + } + + return ( +
+

DAY Token Faucet

+
+
+

Get test DAY tokens on a testnet of your choice.

-
-
-
-
+
+
+
+
Testnet Network
-
- {web3Service.network ? web3Service.network : } +
+ {(this.state.loaded || web3Service.network) ? web3Service.network.name : }
-
-
+
+
Faucet Address
-
- {this.state.faucetAddress ? { this.state.faucetAddress } : } +
+ {(this.state.loaded || this.state.faucetAddress) ? ( + + {this.state.faucetAddress} + + ) : ( + + )}
-
-
+
+
Faucet Balance
-
- {this.state.faucetBalance > 0 ? this.state.faucetBalance / Eth : } +
+ {(this.state.loaded || (this.state.faucetAddress && typeof this.state.faucetBalance !== 'undefined')) ? ( + this.state.faucetBalance > 0 ? this.state.faucetBalance/ Eth : 0 + ) : ( + + )}
-
-
+
+
Your Wallet Address
-
-
-
+
+
Remaining Wait Time
-
- {this.state.lastUsed ? this.printWaitTime : } +
+ {(this.state.loaded || (this.state.defaultAccount && typeof this.state.lastUsed !== 'undefined')) ? this.printWaitTime : }
-
-
@@ -207,4 +269,4 @@ Faucet.propTypes = { web3Service: PropTypes.any }; -export default Faucet; \ No newline at end of file +export default Faucet; diff --git a/app/components/Common/MetamaskComponent.js b/app/components/Common/MetamaskComponent.js index 899120e0..375776cf 100644 --- a/app/components/Common/MetamaskComponent.js +++ b/app/components/Common/MetamaskComponent.js @@ -7,15 +7,12 @@ import Cookies from 'js-cookie'; @observer class MetamaskComponent extends Component { - state = { accounts: '' - } + }; showAlert(args) { - return ( - - ); + return ; } get isWeb3Viewable() { @@ -24,10 +21,15 @@ class MetamaskComponent extends Component { get isWeb3Usable() { const { web3Service } = this.props; - return (web3Service.web3.isConnected() && typeof web3Service.accounts !== 'undefined' && web3Service.accounts !== null && web3Service.accounts.length > 0); + return ( + web3Service.web3.isConnected() && + typeof web3Service.accounts !== 'undefined' && + web3Service.accounts !== null && + web3Service.accounts.length > 0 + ); } - runNotifications () { + runNotifications() { const { web3Service } = this.props; /* * Detects if the Metamask state (installed/not installed) @@ -45,8 +47,17 @@ class MetamaskComponent extends Component { showNotification(`Metamask connected`, 'success'); showNotification(`You are connected to ${web3Service.network}`, 'info'); } else { - showNotification(`Metamask is not installed`, undefined, undefined, undefined, false); - showNotification(`Metamask is required to use this Dapp https://metamask.io`, 'warning'); + showNotification( + `Metamask is not installed`, + undefined, + undefined, + undefined, + false + ); + showNotification( + `Metamask is required to use this Dapp https://metamask.io`, + 'warning' + ); } Cookies.set('metamaskInstalled', metamaskInstalled, { expires: 30 }); } @@ -62,22 +73,27 @@ class MetamaskComponent extends Component { if (metamaskInstalled && hasChangedMetamaskUnlocked) { if (!metamaskUnlocked) { - showNotification(`Kindly unlock your account or add new accounts to use this Dapp`, 'warning'); + showNotification( + `Kindly unlock your account or add new accounts to use this Dapp`, + 'warning' + ); } Cookies.set('metamaskUnlocked', metamaskUnlocked, { expires: 30 }); } } - async scoutUpdates () { + async scoutUpdates() { const SCOUT_TIMEOUT = 1000; const { web3Service } = this.props; await web3Service.getAccountUpdates(); - const accountsChanged = this.state.accounts.length !== web3Service.accounts.length || (this.state.accounts.length > 0 && this.state.accounts[0] !== web3Service.accounts[0]); + const accountsChanged = + this.state.accounts.length !== web3Service.accounts.length || + (this.state.accounts.length > 0 && this.state.accounts[0] !== web3Service.accounts[0]); if (accountsChanged) { this.setState({ accounts: web3Service.accounts }); showNotification(`Accounts updated`, 'info', 4000); } - this.timeout = setTimeout ( () => this.scoutUpdates(), SCOUT_TIMEOUT ); + this.timeout = setTimeout(() => this.scoutUpdates(), SCOUT_TIMEOUT); this.runNotifications(); } @@ -100,7 +116,7 @@ class MetamaskComponent extends Component { MetamaskComponent.propTypes = { web3Service: PropTypes.object, - history: PropTypes.any, + history: PropTypes.any }; -export default MetamaskComponent; \ No newline at end of file +export default MetamaskComponent; diff --git a/app/components/Header/Header.js b/app/components/Header/Header.js index 88fb6676..a696a3a0 100644 --- a/app/components/Header/Header.js +++ b/app/components/Header/Header.js @@ -3,13 +3,15 @@ import PropTypes from 'prop-types'; import { observer,inject } from 'mobx-react'; @inject('web3Service') +@inject('eacService') @inject('keenStore') @observer class Header extends Component { constructor(props) { super(props); this.state = { - blocknumber: '' + blocknumber: '', + eacContracts: {} }; this.getCurrentBlock = this.getCurrentBlock.bind(this); } @@ -21,6 +23,12 @@ class Header extends Component { componentDidMount() { // Check every 10 seconds if the block number changed this.interval = setInterval(this.getCurrentBlock, 10000); + this.fetchEacContracts(); + } + + async fetchEacContracts() { + const eacContracts = await this.props.eacService.getActiveContracts(); + this.setState({ eacContracts }); } getCurrentBlock() { @@ -36,6 +44,7 @@ class Header extends Component { } render() { + const { web3Service } = this.props; return ( +
{this.props.updateSearchState(true);}}> @@ -73,6 +143,7 @@ class Header extends Component { Header.propTypes = { updateSearchState: PropTypes.any, web3Service: PropTypes.any, + eacService: PropTypes.any, keenStore: PropTypes.any }; diff --git a/app/components/ScheduleWizard/AbstractSetting.js b/app/components/ScheduleWizard/AbstractSetting.js index 371867bd..16999cbc 100644 --- a/app/components/ScheduleWizard/AbstractSetting.js +++ b/app/components/ScheduleWizard/AbstractSetting.js @@ -5,22 +5,21 @@ import PropTypes from 'prop-types'; @observer class AbstractSetting extends Component { - - constructor (props) { + constructor(props) { super(props); this.onChange = this.onChange.bind(this); } - validators = {} + validators = {}; - integerValidator (min,minError){ + integerValidator(min, minError) { const { _validations } = this.props; if (min) { - minError = minError || `Value / amount shall be greater or equal to minimum value of ${min}`; + minError = minError || `Value / amount should be greater or equal to minimum value of ${min}`; } return { - validator: (value)=> { + validator: value => { if (!new RegExp('^\\d+$').test(value)) return 1; if (min) { if (Number(value) < Number(min)) { @@ -31,21 +30,18 @@ class AbstractSetting extends Component { } return 0; }, - errors: [ - _validations.Errors.numeric, - minError || _validations.Errors.minimum_numeric - ] + errors: [_validations.Errors.numeric, minError || _validations.Errors.minimum_numeric] }; } - decimalValidator(min, minError){ + decimalValidator(min, minError) { const { _validations } = this.props; + minError = minError || _validations.Errors.minimum_decimal; return { - validator: (value)=> { + validator: value => { if (!new RegExp('^\\d+\\.?\\d*$').test(value)) return 1; if (min) { - minError = minError || `Value / amount shall be greater or equal to minimum value of ${min}`; - if (Number(value) < Number(min) ) { + if (Number(value) < Number(min)) { return 2; } } else if (!(Number(value) > 0)) { @@ -53,32 +49,51 @@ class AbstractSetting extends Component { } return 0; }, + errors: [_validations.Errors.numeric, minError] + }; + } + + integerMinMaxValidator(min, max, minError, maxError) { + const { _validations } = this.props; + min = min || 1; + minError = minError || `Value / amount should be greater or equal to minimum value of ${min}`; + maxError = max + ? maxError || `Value / amount should be less or equal to maximum value of ${max}` + : null; + return { + validator: value => { + if (!new RegExp('^\\d+\\.?\\d*$').test(value)) return 1; + if (Number(value) < Number(min)) { + return 2; + } + if (max && Number(value) > Number(max)) { + return 3; + } + return 0; + }, errors: [ _validations.Errors.numeric, - minError || _validations.Errors.minimum_decimal + minError || _validations.Errors.minimum_numeric, + maxError || `Value / amount should be less or equal to maximum value of ${max}` ] }; } - booleanValidator (){ + booleanValidator() { return { - validator: (value)=> { + validator: value => { if (!value && value !== true) return 1; return 0; }, - errors: [ - 'Kindly indicate Value' - ] + errors: ['Kindly indicate Value'] }; } - ethereumAddressValidator(){ - return { - validator: (value,web3)=>web3.isAddress(value)?0:1, - errors: [ - 'Kindly provide valid address' - ] - }; + ethereumAddressValidator() { + return { + validator: (value, web3) => (web3.isAddress(value) ? 0 : 1), + errors: ['Kindly provide valid address'] + }; } getValidations() { @@ -86,35 +101,36 @@ class AbstractSetting extends Component { } @action - validate = (property) => () => { - const { props: { scheduleStore,web3Service },_validationsErrors } = this; + validate = property => () => { + const { + props: { scheduleStore, web3Service }, + _validationsErrors + } = this; const { _validations } = this; - const { validator,errors } = this.validators[property]; + const { validator, errors } = this.validators[property]; const value = scheduleStore[property]; let Web3; - if (web3Service){ + if (web3Service) { const { web3 } = web3Service; Web3 = web3; } - const errorState = validator(value,Web3); - if (errorState == 0){ + const errorState = validator(value, Web3); + if (errorState == 0) { _validations[property] = true; _validationsErrors[property] = ''; - } - else { + } else { _validations[property] = false; - _validationsErrors[property] = errors[errorState-1]; + _validationsErrors[property] = errors[errorState - 1]; } return _validations[property]; - } + }; - onChange = (name) => (event)=> { + onChange = name => event => { const { target } = event; const { scheduleStore } = this.props; scheduleStore[name] = target.value; this.validate(name)(event); - } - + }; } AbstractSetting.propTypes = { diff --git a/app/components/ScheduleWizard/ConfirmSettings.js b/app/components/ScheduleWizard/ConfirmSettings.js index 0138a1e0..c500d0ca 100644 --- a/app/components/ScheduleWizard/ConfirmSettings.js +++ b/app/components/ScheduleWizard/ConfirmSettings.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { inject,observer } from 'mobx-react'; +import { action } from 'mobx'; +import { inject, observer } from 'mobx-react'; import Alert from '../Common/Alert'; @inject('scheduleStore') @@ -8,23 +9,25 @@ import Alert from '../Common/Alert'; @inject('web3Service') @observer class ConfirmSettings extends Component { - - constructor(props){ + constructor(props) { super(props); - this.infoSettingsValidations = this.infoSettingsValidations.bind(this); - this.bountySettingsValidations = this.bountySettingsValidations.bind(this); - this.timeSettingsValidations = this.timeSettingsValidations.bind(this); - this.blockComponentValidations = this.blockComponentValidations.bind(this); + this.state = { + errors: {} + }; } totalCost() { - const { scheduleStore, eacService,web3Service: { web3 } } = this.props; + const { + scheduleStore, + eacService, + web3Service: { web3 } + } = this.props; let { gasAmount, amountToSend, gasPrice, fee, timeBounty } = scheduleStore; amountToSend = web3.toWei(amountToSend, 'ether'); gasPrice = web3.toWei(gasPrice, 'gwei'); fee = web3.toWei(fee, 'ether'); - timeBounty = web3.toWei(timeBounty,'ether'); + timeBounty = web3.toWei(timeBounty, 'ether'); const endowment = eacService.calcEndowment(gasAmount, amountToSend, gasPrice, fee, timeBounty); @@ -33,22 +36,24 @@ class ConfirmSettings extends Component { get executionWindow() { const { scheduleStore, isCustomWindow } = this.props; - if (scheduleStore.isUsingTime){ - return `${(isCustomWindow ? scheduleStore.customWindow : scheduleStore.executionWindow)} mins`; + if (scheduleStore.isUsingTime) { + return `${isCustomWindow ? scheduleStore.customWindow : scheduleStore.executionWindow} mins`; } - return `${scheduleStore.blockSize} blocks`; + return `${scheduleStore.blockSize} blocks`; } - blockOrTime(){ + blockOrTime() { const { scheduleStore } = this.props; - if (scheduleStore.isUsingTime){ + if (scheduleStore.isUsingTime) { return scheduleStore.transactionTzTime; } return scheduleStore.blockNumber ? scheduleStore.blockNumber : '-'; } web3Error() { - return !this.props.isWeb3Usable ? : null; + return !this.props.isWeb3Usable ? ( + + ) : null; } infoSettingsValidations() { @@ -69,52 +74,149 @@ class ConfirmSettings extends Component { return !scheduleStore.isUsingTime && !this.props.blockTabValidator ? 'block' : null; } + @action tabValidations() { - const errors = []; - this.infoSettingsValidations() ? errors.push(this.infoSettingsValidations()) : null; - this.bountySettingsValidations() ? errors.push(this.bountySettingsValidations()) : null; - this.timeSettingsValidations() ? errors.push(this.timeSettingsValidations()) : null; - this.blockComponentValidations() ? errors.push(this.blockComponentValidations()) : null; - return errors.length > 0 ? : null; + let errors = {}; + errors.info = this.infoSettingsValidations() ? true : false; + errors.time = this.timeSettingsValidations() ? true : false; + errors.bounty = this.bountySettingsValidations() ? true : false; + errors.block = this.blockComponentValidations() ? true : false; + + if (this._mounted && JSON.stringify(this.state.errors) !== JSON.stringify(errors)) { + this.setState(Object.assign(this.state.errors, errors)); + } + } + + componentDidMount() { + this._mounted = true; + this.tabValidations(); + } + + componentDidUpdate() { + this.tabValidations(); + } + + componentWillUnmount() { + this._mounted = false; } render() { const { scheduleStore } = this.props; const emptyFieldSign = '-'; + let errMsg = []; + Object.keys(this.state.errors).map(section => { + this.state.errors[section] ? errMsg.push(section) : null; + }); return (

Summary

{this.web3Error()} - {this.tabValidations()} + {errMsg.length > 0 && } + {scheduleStore.isTokenTransfer && ( + + )}
-
- - - - +
+ + + - + - - + + + {scheduleStore.isTokenTransfer && ( + + + + + )} - - + + {!scheduleStore.isTokenTransfer && ( + + )} + {scheduleStore.isTokenTransfer && ( + + )} - + {!scheduleStore.isTokenTransfer && ( + + )} + {scheduleStore.isTokenTransfer && ( + + )} - + - +
To Address{scheduleStore.toAddress ? { scheduleStore.toAddress } : emptyFieldSign} + + {!scheduleStore.isTokenTransfer ? 'To Address' : 'Token Address'} + + + {scheduleStore.toAddress ? ( + + {scheduleStore.toAddress} + + ) : ( + emptyFieldSign + )} +
To address + + {scheduleStore.receiverAddress} + +
Amount to Send{scheduleStore.amountToSend ? scheduleStore.amountToSend + ' ETH' : emptyFieldSign} + Amount to Send + + {scheduleStore.amountToSend + ? scheduleStore.amountToSend + ' ETH' + : emptyFieldSign} + + {scheduleStore.tokenToSend + ? scheduleStore.tokenToSend + ' ' + scheduleStore.tokenSymbol + : emptyFieldSign} +
Data{scheduleStore.yourData ? scheduleStore.yourData : emptyFieldSign} + {scheduleStore.yourData ? scheduleStore.yourData : emptyFieldSign} + + {scheduleStore.tokenData} +
{scheduleStore.isUsingTime ? 'Time' : 'Block Number'} + {scheduleStore.isUsingTime ? 'Time' : 'Block Number'} + {this.blockOrTime()}
Window Size{this.executionWindow || emptyFieldSign} + {this.executionWindow || emptyFieldSign} +
@@ -124,40 +226,51 @@ class ConfirmSettings extends Component { - + - + - + - + - + - +
Gas Amount{scheduleStore.gasAmount ? scheduleStore.gasAmount : emptyFieldSign} + {scheduleStore.gasAmount ? scheduleStore.gasAmount : emptyFieldSign} +
Gas Price{scheduleStore.gasPrice ? scheduleStore.gasPrice + ' Gwei' : emptyFieldSign} + {scheduleStore.gasPrice ? scheduleStore.gasPrice + ' Gwei' : emptyFieldSign} +
Fee{scheduleStore.fee ? scheduleStore.fee : emptyFieldSign} + {scheduleStore.fee ? scheduleStore.fee : emptyFieldSign} +
Time Bounty{scheduleStore.timeBounty ? scheduleStore.timeBounty + ' ETH' : emptyFieldSign} + {scheduleStore.timeBounty ? scheduleStore.timeBounty + ' ETH' : emptyFieldSign} +
Deposit{scheduleStore.deposit ? scheduleStore.deposit + ' ETH' : emptyFieldSign} + {scheduleStore.deposit ? scheduleStore.deposit + ' ETH' : emptyFieldSign} +
-
-

Total amount: { this.totalCost() } ETH

+

+ Total amount: {this.totalCost()} ETH +

- ); - } + ); } +} ConfirmSettings.propTypes = { scheduleStore: PropTypes.any, diff --git a/app/components/ScheduleWizard/InfoSettings.js b/app/components/ScheduleWizard/InfoSettings.js index 1653e131..c335bfa5 100644 --- a/app/components/ScheduleWizard/InfoSettings.js +++ b/app/components/ScheduleWizard/InfoSettings.js @@ -1,141 +1,455 @@ import React from 'react'; import { inject, observer } from 'mobx-react'; import Bb from 'bluebird'; +import Switch from 'react-switch'; import AbstractSetting from './AbstractSetting'; +import Alert from '../Common/Alert'; @inject('scheduleStore') @inject('web3Service') @observer class InfoSettings extends AbstractSetting { - constructor (props) { - super(props); - - this.state = { - minGas: 21000 - }; - const { _validations,_validationsErrors } = this.props; - this._validations = _validations.InfoSettings; - this._validationsErrors = _validationsErrors.InfoSettings; - - this.toggleYourData = this.toggleYourData.bind(this); - this.onChangeCheck = this.onChangeCheck.bind(this); - } + constructor(props) { + super(props); - validators = { - toAddress: this.ethereumAddressValidator(), - gasAmount: '', - amountToSend: this.decimalValidator(), - gasPrice: this.integerValidator(), - yourData: { - validator: value => typeof value === 'string'?0:1, - errors: [ - 'Kindly provide valid input Data' - ] - } + this.state = { + account: '', + minGas: 21000, + token: {} + }; + const { _validations, _validationsErrors } = this.props; + this._validations = _validations.InfoSettings; + this._validationsErrors = _validationsErrors.InfoSettings; + + this.toggleField = this.toggleField.bind(this); + + this.onChangeCheck = this.onChangeCheck.bind(this); + } + + validators = { + toAddress: this.ethereumAddressValidator(), + gasAmount: '', + amountToSend: this.decimalValidator(), + gasPrice: this.integerValidator(), + yourData: { + validator: value => (typeof value === 'string' ? 0 : 1), + errors: ['Kindly provide valid input Data'] + }, + receiverAddress: this.ethereumAddressValidator(), + tokenToSend: this.decimalValidator() + }; + + revalidateGasAmount() { + const { scheduleStore } = this.props; + const minimumGas = + this.state.minGas > scheduleStore.gasAmount ? this.state.minGas : scheduleStore.gasAmount; + this.onChange('gasAmount')({ target: { value: minimumGas } }); + this.forceUpdate(); + } + + checkAmountValidation() { + const { scheduleStore } = this.props; + if (!scheduleStore.isTokenTransfer && scheduleStore.amountToSend !== '') { + this.validate('amountToSend')(); } + if (scheduleStore.isTokenTransfer && scheduleStore.tokenToSend !== '') { + this.validate('tokenToSend')(); + } + } - async calculateMinimumGas () { - const { web3Service: { web3 },scheduleStore } = this.props; - const isAddress = this.ethereumAddressValidator().validator; - const minEstimate = 21000; - let estimate; - if (isAddress(scheduleStore.toAddress, web3) && scheduleStore.useData ) { - estimate = await Bb.fromCallback(callback => - web3.eth.estimateGas({ + async calculateMinimumGas() { + const { + web3Service: { web3 }, + scheduleStore + } = this.props; + const isAddress = this.ethereumAddressValidator().validator; + const minEstimate = 21000; + let estimate; + if (isAddress(scheduleStore.toAddress, web3) === 0 && scheduleStore.useData) { + estimate = await Bb.fromCallback(callback => + web3.eth.estimateGas( + { to: scheduleStore.toAddress, data: scheduleStore.yourData - }, callback) + }, + callback + ) + ); + } + estimate = Number(estimate) > minEstimate ? Number(estimate) : minEstimate; + this.setState({ minGas: estimate }); + this.revalidateGasAmount(); + return estimate; + } + + async calculateTokenTransferMinimumGasandData() { + const { + web3Service, + web3Service: { web3 }, + scheduleStore + } = this.props; + const isAddress = this.ethereumAddressValidator().validator; + const minEstimate = 21000; + let estimate; + scheduleStore.tokenData = '0x'; + if ( + isAddress(scheduleStore.toAddress, web3) === 0 && + isAddress(scheduleStore.receiverAddress, web3) == 0 + ) { + try { + estimate = await web3Service.estimateTokenTransfer( + scheduleStore.toAddress, + scheduleStore.receiverAddress, + scheduleStore.tokenToSend * 10 ** this.state.token.decimals ); + estimate = estimate + 20000; + scheduleStore.tokenData = await web3Service.getTokenTransferData( + scheduleStore.toAddress, + scheduleStore.receiverAddress, + scheduleStore.tokenToSend * 10 ** this.state.token.decimals + ); + } catch (e) { + scheduleStore.tokenData = ''; + return; } - estimate = Number(estimate) > minEstimate ? Number(estimate) : minEstimate; - this.setState({ minGas:estimate }); - return estimate; } + estimate = Number(estimate) > minEstimate ? Number(estimate) : minEstimate; + this.setState({ minGas: estimate }); + this.revalidateGasAmount(); + return estimate; + } + + async checkAccountUpdate() { + if (!this._mounted) { + return; + } + const { + web3Service, + web3Service: { web3 }, + scheduleStore + } = this.props; + const isAddress = this.ethereumAddressValidator().validator; + if (!web3Service.accounts || web3Service.accounts[0] === this.state.account) { + return; + } + this.setState({ account: web3Service.accounts[0] }); + if (scheduleStore.isTokenTransfer && isAddress(scheduleStore.toAddress, web3) === 0) { + await this.getTokenDetails(true); + await this.calculateTokenTransferMinimumGasandData(); + } + } + + async getTokenDetails(onlyBalance = false) { + const { web3Service, scheduleStore } = this.props; + if (!onlyBalance) { + const tokenDetails = await web3Service.fetchTokenDetails(scheduleStore.toAddress); + this.setState({ token: tokenDetails }); + scheduleStore.tokenSymbol = tokenDetails.symbol; + } + let _balance = await web3Service.fetchTokenBalance(scheduleStore.toAddress); + _balance = _balance == '-' ? _balance : Number(_balance / 10 ** this.state.token.decimals); + const balance = new RegExp('^\\d+\\.?\\d{8,}$').test(_balance) ? _balance.toFixed(8) : _balance; + this.setState({ token: Object.assign(this.state.token, { balance }) }); + this.validators.tokenToSend = this.integerMinMaxValidator( + 1 / 10 ** this.state.token.decimals, + balance + ); + this.checkAmountValidation(); + this.forceUpdate(); + } - toggleYourData(){ - const { scheduleStore } = this.props; - scheduleStore.useData = !scheduleStore.useData; + toggleField = property => () => { + const { scheduleStore } = this.props; + scheduleStore[property] = !scheduleStore[property]; + if (scheduleStore.isTokenTransfer) { + this.tokenChangeCheck('toAddress'); + } else { + this.checkAmountValidation(); + this.calculateMinimumGas(); + } + }; + + async tokenChangeCheck(property) { + const { + scheduleStore, + web3Service: { web3 } + } = this.props; + const isAddress = this.ethereumAddressValidator().validator; + if (isAddress(scheduleStore.toAddress, web3) !== 0) { + this.setState({ token: {} }); + scheduleStore.tokenSymbol = ''; + this.validators.tokenToSend = this.decimalValidator(); + return; + } + if (property == 'toAddress') { + await this.getTokenDetails(); } + await this.calculateTokenTransferMinimumGasandData(); + this.forceUpdate(); + } - onChangeCheck = (property) => async(event) => { - let { target: { value } } = event; + onChangeCheck = property => async event => { + let { + target: { value } + } = event; + const { scheduleStore } = this.props; + this.onChange(property)({ target: { value: value } }); + + if (scheduleStore.isTokenTransfer) { + await this.tokenChangeCheck(property); + } else { await this.calculateMinimumGas(); + } + + this.forceUpdate(); + }; + + componentDidMount() { + this._mounted = true; + this.checkAccountUpdate(); + this.updateInterval = setInterval(() => this.checkAccountUpdate(), 2000); + } - this.onChange(property)({ target: { value: value } }); - this.forceUpdate(); + componentWillUnmount() { + if (this.updateInterval) { + clearInterval(this.updateInterval); } + this._mounted = false; + } - render() { - const { scheduleStore } = this.props; - const { _validations,_validationsErrors } = this; - this.validators.gasAmount = this.integerValidator(this.state.minGas); + render() { + const { scheduleStore } = this.props; + const { _validations, _validationsErrors } = this; + this.validators.gasAmount = this.integerValidator(this.state.minGas); - return ( -
-
-

Information

-
+ return ( +
+
+

Information

+
+
+ {scheduleStore.isTokenTransfer && + (scheduleStore.tokenToSend && scheduleStore.toAddress && scheduleStore.receiverAddress) && + (_validations.tokenToSend && _validations.toAddress && _validations.receiverAddress) && + !scheduleStore.tokenData && ( + + )} +
+
+
+ + Token transfer +
-
-
-
- - + {scheduleStore.isTokenTransfer && ( +
+
+
+
+ + {this.state.token.name} +
+
+ + {this.state.token.decimals} +
+
+ + {this.state.token.balance} +
+
- {!_validations.toAddress && - - }
+ )} +
+
+
+
+ + +
+ {!_validations.toAddress && ( + + )} +
+
+
+ + +
+ {!_validations.receiverAddress && ( + + )}
-
+
+ +
+ {scheduleStore.isTokenTransfer && (
-
+
- +
- {!_validations.amountToSend && - - } + {!_validations.tokenToSend && ( + + )}
+ )} + {!scheduleStore.isTokenTransfer && (
-
- - +
+ +
- {!_validations.gasAmount && - + {!_validations.amountToSend && ( + + )} +
+ )} +
+
+ +
-
-
- - -
- {!_validations.gasPrice && - - } + {!_validations.gasAmount && ( + + )} +
+
+
+ +
+ {!_validations.gasPrice && ( + + )}
+
+ {!scheduleStore.isTokenTransfer && (
- +
- {scheduleStore.useData && + )} + {!scheduleStore.isTokenTransfer && + scheduleStore.useData && (
-
+
- +
- {!_validations.yourData && + {!_validations.yourData && ( - } + )}
- } -
- ); - } + )} +
+ ); + } } export default InfoSettings; diff --git a/app/components/ScheduleWizard/ScheduleRoute.js b/app/components/ScheduleWizard/ScheduleRoute.js index 071e9bd0..ade15061 100644 --- a/app/components/ScheduleWizard/ScheduleRoute.js +++ b/app/components/ScheduleWizard/ScheduleRoute.js @@ -4,7 +4,6 @@ import { inject, observer } from 'mobx-react'; import MetamaskComponent from '../Common/MetamaskComponent'; import ScheduleWizard from './ScheduleWizard'; - @inject('web3Service') @observer export class ScheduleRoute extends MetamaskComponent { diff --git a/app/components/ScheduleWizard/ScheduleWizard.js b/app/components/ScheduleWizard/ScheduleWizard.js index dd227f33..5bd8119d 100644 --- a/app/components/ScheduleWizard/ScheduleWizard.js +++ b/app/components/ScheduleWizard/ScheduleWizard.js @@ -13,7 +13,7 @@ import { showNotification } from '../../services/notification'; @inject('transactionStore') @observer class ScheduleWizard extends Component { - constructor(props){ + constructor(props) { super(props); this.state = {}; this.scheduleTransaction = this.scheduleTransaction.bind(this); @@ -26,11 +26,11 @@ class ScheduleWizard extends Component { transactionDate: true, transactionTime: true, executionWindow: true, - customWindow: true, + customWindow: true }, BlockComponent: { blockNumber: true, - blockSize: true, + blockSize: true } }, BountySettings: { @@ -43,7 +43,9 @@ class ScheduleWizard extends Component { gasAmount: true, amountToSend: true, gasPrice: true, - yourData: true + yourData: true, + receiverAddress: true, + tokenToSend: true }, ConfirmSettings: { timeZone: true, @@ -62,14 +64,15 @@ class ScheduleWizard extends Component { gasAmount: true, amountToSend: true, gasPrice: true, - yourData: true, + yourData: true }, - Errors:{ + Errors: { numeric: 'Please enter valid value/amount', - minimum_numeric: 'Value/amount shall be greater or equal to minimum value of 1', - minimum_decimal: 'Value/amount shall be greater or equal to minimum value of 0.0000000000000000001 ' + minimum_numeric: 'Value/amount should be greater or equal to minimum value of 1', + minimum_decimal: + 'Value/amount should be greater or equal to minimum value of 0.0000000000000000001 ' } - } + }; _validationsErrors = { TimeSettings: { @@ -78,11 +81,11 @@ class ScheduleWizard extends Component { transactionDate: '', transactionTime: '', executionWindow: '', - customWindow: '', + customWindow: '' }, BlockComponent: { blockNumber: '', - blockSize: '', + blockSize: '' } }, BountySettings: { @@ -97,46 +100,88 @@ class ScheduleWizard extends Component { gasPrice: '', yourData: '' } - } + }; - get isCustomWindow () { + get isCustomWindow() { const { scheduleStore } = this.props; return scheduleStore.customWindow && this._validations.TimeSettings.TimeComponent.customWindow; } get TimeComponentValidations() { const { scheduleStore } = this.props; - const _timeZone = Boolean(scheduleStore.timeZone) && this._validations.TimeSettings.TimeComponent.timeZone; - const _transactionDate = Boolean(scheduleStore.transactionDate) && this._validations.TimeSettings.TimeComponent.transactionDate; - const _transactionTime = Boolean(scheduleStore.transactionTime) && this._validations.TimeSettings.TimeComponent.transactionTime; - const _executionWindow = (Boolean(scheduleStore.customWindow) && this._validations.TimeSettings.TimeComponent.customWindow) || (Boolean(scheduleStore.executionWindow) && this._validations.TimeSettings.TimeComponent.executionWindow); + const _timeZone = + Boolean(scheduleStore.timeZone) && this._validations.TimeSettings.TimeComponent.timeZone; + const _transactionDate = + Boolean(scheduleStore.transactionDate) && + this._validations.TimeSettings.TimeComponent.transactionDate; + const _transactionTime = + Boolean(scheduleStore.transactionTime) && + this._validations.TimeSettings.TimeComponent.transactionTime; + const _executionWindow = + (Boolean(scheduleStore.customWindow) && + this._validations.TimeSettings.TimeComponent.customWindow) || + (Boolean(scheduleStore.executionWindow) && + this._validations.TimeSettings.TimeComponent.executionWindow); return _timeZone && _transactionDate && _transactionTime && _executionWindow; } get blockComponentValidations() { const { scheduleStore } = this.props; - const _blockNumber = Boolean(scheduleStore.blockNumber) && this._validations.TimeSettings.BlockComponent.blockNumber; - const _blockSize = Boolean(scheduleStore.blockSize) && this._validations.TimeSettings.BlockComponent.blockSize; + const _blockNumber = + Boolean(scheduleStore.blockNumber) && + this._validations.TimeSettings.BlockComponent.blockNumber; + const _blockSize = + Boolean(scheduleStore.blockSize) && this._validations.TimeSettings.BlockComponent.blockSize; return _blockNumber && _blockSize; } get bountySettingsValidation() { const { scheduleStore } = this.props; - const _timeBounty = Boolean(scheduleStore.timeBounty) && this._validations.BountySettings.timeBounty; - const _deposit = !scheduleStore.requireDeposit || (Boolean(scheduleStore.deposit) && this._validations.BountySettings.deposit); + const _timeBounty = + Boolean(scheduleStore.timeBounty) && this._validations.BountySettings.timeBounty; + const _deposit = + !scheduleStore.requireDeposit || + (Boolean(scheduleStore.deposit) && this._validations.BountySettings.deposit); return _timeBounty && _deposit; } get infoSettingsValidations() { const { scheduleStore } = this.props; const _addr = Boolean(scheduleStore.toAddress) && this._validations.InfoSettings.toAddress; + const _receiverAddress = + !scheduleStore.isTokenTransfer || + (Boolean(scheduleStore.receiverAddress) && this._validations.InfoSettings.receiverAddress); + const _amountToSend = + !scheduleStore.isTokenTransfer && + Boolean(scheduleStore.amountToSend) && + this._validations.InfoSettings.amountToSend; + const _tokenToSend = + scheduleStore.isTokenTransfer && + Boolean(scheduleStore.tokenToSend) && + this._validations.InfoSettings.tokenToSend; const _gasAmount = Boolean(scheduleStore.gasAmount) && this._validations.InfoSettings.gasAmount; - const _amountToSend = Boolean(scheduleStore.amountToSend) && this._validations.InfoSettings.amountToSend; const _gasPrice = Boolean(scheduleStore.gasPrice) && this._validations.InfoSettings.gasPrice; - const _yourData = !scheduleStore.useData || (Boolean(scheduleStore.yourData) && this._validations.yourData); - return _addr && _gasAmount && _amountToSend && _gasPrice && _yourData; + const _yourData = + (!scheduleStore.useData && !scheduleStore.isTokenTransfer) || + (Boolean(scheduleStore.yourData) && this._validations.yourData); + const _tokenData = scheduleStore.isTokenTransfer && Boolean(scheduleStore.tokenData); + return ( + _addr && + _receiverAddress && + _gasAmount && + (_amountToSend || _tokenToSend) && + _gasPrice && + (_yourData || _tokenData) + ); } get scheduleDisabled() { const { scheduleStore } = this.props; - const validations = !this.bountySettingsValidation || !this.props.isWeb3Usable || !this.infoSettingsValidations || !((scheduleStore.isUsingTime && this.TimeComponentValidations) || this.blockComponentValidations); + const validations = + !this.bountySettingsValidation || + !this.props.isWeb3Usable || + !this.infoSettingsValidations || + !( + (scheduleStore.isUsingTime && this.TimeComponentValidations) || + this.blockComponentValidations + ); return validations; } @@ -145,30 +190,50 @@ class ScheduleWizard extends Component { const originalBodyCss = document.body.className; document.body.className += ' fade-me'; - const { scheduleStore, transactionStore, web3Service: { web3 } , history } = this.props; + const { + scheduleStore, + transactionStore, + web3Service: { web3 }, + history + } = this.props; let executionTime, executionWindow; if (scheduleStore.isUsingTime) { executionTime = scheduleStore.transactionTimestamp; - executionWindow = this.isCustomWindow ? scheduleStore.customWindow : scheduleStore.executionWindow; + executionWindow = this.isCustomWindow + ? scheduleStore.customWindow + : scheduleStore.executionWindow; executionWindow = executionWindow * 60; } else { executionTime = scheduleStore.blockNumber; executionWindow = scheduleStore.blockSize; } - let { toAddress, yourData, gasAmount, amountToSend, gasPrice, fee, timeBounty, deposit, isUsingTime } = scheduleStore; + let { + toAddress, + yourData, + tokenData, + gasAmount, + amountToSend, + gasPrice, + fee, + timeBounty, + deposit, + isUsingTime, + isTokenTransfer + } = scheduleStore; amountToSend = web3.toWei(amountToSend, 'ether'); gasPrice = web3.toWei(gasPrice, 'gwei'); fee = web3.toWei(fee, 'ether'); timeBounty = web3.toWei(timeBounty, 'ether'); deposit = web3.toWei(deposit, 'ether'); + const data = isTokenTransfer ? tokenData : yourData; try { const scheduled = await transactionStore.schedule( toAddress, - yourData, + data, gasAmount, amountToSend, executionWindow, @@ -188,43 +253,55 @@ class ScheduleWizard extends Component { } } catch (error) { showNotification('Transaction cancelled by the user.', 'danger', 4000); + document.body.className = originalBodyCss; + this.scheduleBtn.innerHTML = 'Schedule'; } - - if (this.scheduleBtn) this.scheduleBtn.innerHTML = 'Schedule'; - document.body.className = originalBodyCss; } componentDidMount() { const { jQuery } = window; jQuery('#scheduleWizard').bootstrapWizard({ - onTabShow: function (tab, navigation, index) { + onTabShow: function(tab, navigation, index) { var $total = navigation.find('li').length; var $current = index + 1; - // If it's the last tab then hide the last button and show the finish instead - if ($current >= $total) { - jQuery('#scheduleWizard').find('.pager .next').hide(); - jQuery('#scheduleWizard').find('.pager .finish').show(); - jQuery('#scheduleWizard').find('.pager .finish').removeClass('disabled'); - } else { - jQuery('#scheduleWizard').find('.pager .next').show(); - jQuery('#scheduleWizard').find('.pager .finish').hide(); - } + // If it's the last tab then hide the last button and show the finish instead + if ($current >= $total) { + jQuery('#scheduleWizard') + .find('.pager .next') + .hide(); + jQuery('#scheduleWizard') + .find('.pager .finish') + .show(); + jQuery('#scheduleWizard') + .find('.pager .finish') + .removeClass('disabled'); + } else { + jQuery('#scheduleWizard') + .find('.pager .next') + .show(); + jQuery('#scheduleWizard') + .find('.pager .finish') + .hide(); } - }); + } + }); } render() { - const _validationProps = { _validations:this._validations,_validationsErrors:this._validationsErrors }; + const _validationProps = { + _validations: this._validations, + _validationsErrors: this._validationsErrors + }; return (
+ {(!this.props.timeNodeStore.logs || this.props.timeNodeStore.logs.length === 0) && +

+ No logs yet. Kindly start your TimeNode to begin. +

+ } {this.props.timeNodeStore.logs.map((log, index) => { return (

diff --git a/app/components/TimeNode/TimeNodeProve.js b/app/components/TimeNode/TimeNodeProve.js index e87d5443..b0ddb213 100644 --- a/app/components/TimeNode/TimeNodeProve.js +++ b/app/components/TimeNode/TimeNodeProve.js @@ -6,7 +6,6 @@ import PoweredByEAC from '../Common/PoweredByEAC'; @inject('timeNodeStore') @observer class TimeNodeProve extends Component { - constructor(props) { super(props); this.verifyDayTokens = this.verifyDayTokens.bind(this); @@ -49,63 +48,96 @@ class TimeNodeProve extends Component { const myAddress = this.props.timeNodeStore.getMyAddress(); return ( -

-
+
+

Sign to prove DAY ownership

-
-
-

The TimeNode functionality requires DAY tokens as a proof of ownership. By signing the TimeNode address using your DAY token account, you provide us with the necessary information to determine your DAY token balance.

-

Please note that the signing process will not give us any control over your DAY tokens.

+
+ -
-
+
+