diff --git a/packages/fether-react/package.json b/packages/fether-react/package.json index 99370cc896a7610c0ad3879b46c6868db2172c67..6b68b0cbe4894eba24b0b10053ed4fbce9eb3a15 100644 --- a/packages/fether-react/package.json +++ b/packages/fether-react/package.json @@ -38,8 +38,10 @@ "@parity/light.js": "https://github.com/parity-js/light.js#9646ce15d9dd9c4cf11776ddd613d5bd86016f94", "@parity/shared": "^3.0.2", "bignumber.js": "^4.1.0", + "debounce-promise": "^3.1.0", "debug": "^3.1.0", "fether-ui": "^0.1.0", + "final-form": "^4.8.3", "is-electron": "^2.1.0", "light-hoc": "^0.1.0", "lodash": "^4.17.10", @@ -48,10 +50,12 @@ "react": "^16.3.2", "react-blockies": "^1.3.0", "react-dom": "^16.3.2", + "react-final-form": "^3.6.4", "react-markdown": "^3.3.4", "react-router-dom": "^4.2.2", "react-scripts": "1.1.4", "react-tooltip": "^3.6.1", + "recompose": "^0.27.1", "rxjs": "^6.2.0" }, "devDependencies": { diff --git a/packages/fether-react/src/App/App.js b/packages/fether-react/src/App/App.js index fc8a44f541c2680df740e9766da6b81a92a8f059..a624480ff69a9a0ac595a7c64f97bbf696f216a3 100644 --- a/packages/fether-react/src/App/App.js +++ b/packages/fether-react/src/App/App.js @@ -29,6 +29,13 @@ const Router = @inject('healthStore', 'onboardingStore') @observer class App extends Component { + componentDidCatch () { + if (process.env.NODE_ENV !== 'development') { + // Redirect to '/' on errors + window.location.href = '/'; + } + } + render () { return ( diff --git a/packages/fether-react/src/Send/Send.js b/packages/fether-react/src/Send/Send.js index f460e2724f0bd28f221bbef50c06739cb7408c07..231e073f77ac819f6ace059080f961fff0d8630d 100644 --- a/packages/fether-react/src/Send/Send.js +++ b/packages/fether-react/src/Send/Send.js @@ -4,32 +4,20 @@ // SPDX-License-Identifier: BSD-3-Clause import React, { Component } from 'react'; -import { inject, observer } from 'mobx-react'; import { Route, Redirect, Switch } from 'react-router-dom'; import Sent from './Sent'; import Signer from './Signer'; import TxForm from './TxForm'; -@inject('sendStore') -@observer class Send extends Component { render () { - const { - sendStore: { tokenAddress } - } = this.props; - - // We only show then Send components if we have already selected a token to - // send. - if (!tokenAddress) { - return ; - } - return ( - - - + + + + ); } diff --git a/packages/fether-react/src/Send/Signer/Signer.js b/packages/fether-react/src/Send/Signer/Signer.js index c91324f215b41abed11d39b66fd1f35b99d9da29..338de44700b121f892389af930d3d73c34346c74 100644 --- a/packages/fether-react/src/Send/Signer/Signer.js +++ b/packages/fether-react/src/Send/Signer/Signer.js @@ -4,65 +4,41 @@ // SPDX-License-Identifier: BSD-3-Clause import React, { Component } from 'react'; -import { findDOMNode } from 'react-dom'; -import { FormField, Header } from 'fether-ui'; +import { Field, Form } from 'react-final-form'; +import { Form as FetherForm, Header } from 'fether-ui'; import { inject, observer } from 'mobx-react'; -import { Link } from 'react-router-dom'; -import ReactTooltip from 'react-tooltip'; +import { Link, Redirect } from 'react-router-dom'; +import { withProps } from 'recompose'; import TokenBalance from '../../Tokens/TokensList/TokenBalance'; @inject('sendStore', 'tokensStore') +@withProps(({ match: { params: { tokenAddress } }, tokensStore }) => ({ + token: tokensStore.tokens[tokenAddress] +})) @observer class Signer extends Component { - state = { - error: null, - isSending: false, - password: '' - }; - - handleAccept = event => { - const { history, sendStore } = this.props; - const { password } = this.state; - - event.preventDefault(); - - this.setState({ isSending: true }, () => { - sendStore - .send(password) - .then(() => history.push('/send/sent')) - .catch(error => { - this.setState({ error, isSending: false }, () => - ReactTooltip.show(findDOMNode(this.tooltip)) - ); - }); - }); - }; - - handleCancel = () => { - const { history } = this.props; - history.goBack(); - }; - - handleChangePassword = ({ target: { value } }) => { - this.setState({ error: null, password: value }); - }; - - /** - * TODO All this tooltips refs etc should go inside a React validation - * library. - */ - handleTooltipRef = ref => { - this.tooltip = ref; + handleAccept = values => { + const { history, sendStore, token } = this.props; + + return sendStore + .send(token, values.password) + .then(() => history.push(`/send/${token.address}/sent`)) + .catch(error => ({ + password: error.text + })); }; render () { const { - sendStore: { tokenAddress, tx }, - tokensStore + history, + sendStore: { tx }, + token } = this.props; - const { error, isSending, password } = this.state; - const token = tokensStore.tokens[tokenAddress]; + + if (!tx || !token) { + return ; + } return (
@@ -72,7 +48,7 @@ class Signer extends Component { Close } - title={

Send {token.name}

} + title={token &&

Send {token.name}

} />
@@ -80,64 +56,63 @@ class Signer extends Component { -
- -
- {tx.amount} {token.symbol} -
-
-
- -
{tx.to}
-
+ + +
, -
-
-

Enter your password to confirm this transaction.

-
- -
- -
- - -
+
( + +
+

Enter your password to confirm this transaction.

+
+ + + + + + )} + /> ]} onClick={null} token={token} />
- ); } diff --git a/packages/fether-react/src/Send/TxForm/TxForm.js b/packages/fether-react/src/Send/TxForm/TxForm.js index a93aca0bf46432feb3f07671f2bfd2eb743d9203..6a65f3941869d9904f278147d90d7d20367069e4 100644 --- a/packages/fether-react/src/Send/TxForm/TxForm.js +++ b/packages/fether-react/src/Send/TxForm/TxForm.js @@ -4,13 +4,15 @@ // SPDX-License-Identifier: BSD-3-Clause import React, { Component } from 'react'; -import debounce from 'lodash/debounce'; -import { FormField, Header } from 'fether-ui'; +import debounce from 'debounce-promise'; +import { estimateGas } from '../../utils/estimateGas'; +import { Field, Form } from 'react-final-form'; +import { Form as FetherForm, Header } from 'fether-ui'; import { fromWei, toWei } from '@parity/api/lib/util/wei'; import { inject, observer } from 'mobx-react'; import { isAddress } from '@parity/api/lib/util/address'; import { Link } from 'react-router-dom'; -import ReactTooltip from 'react-tooltip'; +import { withProps } from 'recompose'; import TokenBalance from '../../Tokens/TokensList/TokenBalance'; import withBalance from '../../utils/withBalance'; @@ -18,120 +20,24 @@ import withBalance from '../../utils/withBalance'; const MAX_GAS_PRICE = 40; // In Gwei const MIN_GAS_PRICE = 3; // Safelow gas price from GasStation, in Gwei -@inject('sendStore', 'tokensStore') -@withBalance( - ({ sendStore: { tokenAddress }, tokensStore }) => - tokensStore.tokens[tokenAddress] -) +@inject('parityStore', 'sendStore', 'tokensStore') +@withProps(({ match: { params: { tokenAddress } }, tokensStore }) => ({ + token: tokensStore.tokens[tokenAddress] +})) +@withBalance @observer class Send extends Component { - state = { - amount: '', // In Ether or in token - gasPrice: 4, // in Gwei - to: '', - estimating: false, // Currently estimating gasPrice - ...this.props.sendStore.tx - }; - - static getDerivedStateFromProps (nextProps, prevState) { - const { - balance, - sendStore: { estimated } - } = nextProps; - - // Calculate the maxAount - return { - maxAmount: - balance && estimated - ? +fromWei( - toWei(balance).minus( - estimated.mul(toWei(prevState.gasPrice, 'shannon')) - ) - ) - : 0.01 - }; - } - - componentDidMount () { - this.handleEstimateGasPrice(); - } - - estimateGas = debounce( - () => - this.props.sendStore - .estimateGas() - .then(() => this.setState({ estimating: false })) - .catch(() => this.setState({ estimating: false })), - 1000 - ); - - handleChangeAmount = ({ target: { value } }) => - this.setState({ amount: value }, this.handleEstimateGasPrice); - - handleChangeGasPrice = ({ target: { value } }) => - this.setState({ gasPrice: value }, this.handleEstimateGasPrice); - - handleChangeTo = ({ target: { value } }) => { - this.setState({ to: value }, this.handleEstimateGasPrice); - }; - - handleEstimateGasPrice = () => { - if (this.hasError()) { - return; - } - - const { amount, gasPrice, to } = this.state; - this.props.sendStore.setTx({ amount, gasPrice, to }); - this.setState({ estimating: true }, this.estimateGas); - }; - - handleMax = () => - this.setState( - { amount: this.state.maxAmount }, - this.handleEstimateGasPrice - ); - - handleSubmit = event => { - event.preventDefault(); - const { history } = this.props; - history.push('/send/signer'); - }; - - /** - * Get form errors. - * - * TODO Use a React form library to do this? - */ - hasError = () => { - const { amount, maxAmount, to } = this.state; - if (!amount || isNaN(amount)) { - return 'Please enter a valid amount'; - } - - if (amount < 0) { - return 'Please enter a positive amount '; - } - - if (amount > maxAmount) { - return "You don't have enough balance"; - } - - if (!isAddress(to)) { - return 'Please enter a valid Ethereum address'; - } - - return null; + handleSubmit = values => { + const { history, sendStore, token } = this.props; + sendStore.setTx(values); + history.push(`/send/${token.address}/signer`); }; render () { const { - sendStore: { tokenAddress }, - tokensStore + sendStore: { tx }, + token } = this.props; - const { amount, estimating, gasPrice, maxAmount, to } = this.state; - - const token = tokensStore.tokens[tokenAddress]; - const error = this.hasError(); return (
@@ -141,7 +47,7 @@ class Send extends Component { Close } - title={

Send {token.name}

} + title={token &&

Send {token.name}

} />
@@ -149,104 +55,122 @@ class Send extends Component { -
- - - -
- } - label='Amount' - /> - - ( +
+
+ + + - } - label='To' - /> - - - - -
- } - label='Gas' - /> - - - + + + + + + )} + /> ]} - onClick={null} + onClick={null} // To disable cursor:pointer on card // TODO Can this be done better? token={token} /> - ); } + + /** + * Estimate gas amount, and validate that the user has enough balance to make + * the tx. + */ + validateAmount = debounce(async values => { + try { + const { balance, parityStore, token } = this.props; + const amount = +values.amount; + + if (!amount || isNaN(amount)) { + return { amount: 'Please enter a valid amount' }; + } else if (amount < 0) { + return { amount: 'Please enter a positive amount ' }; + } else if (balance && balance.lt(amount)) { + return { amount: `You don't have enough ${token.symbol} balance` }; + } + + if (token.address !== 'ETH') { + // No need to estimate gas for tokens. + // TODO Make sure that user has enough ETH balance + return; + } + + const estimated = await estimateGas(values, token, parityStore.api); + + if (!balance || isNaN(estimated)) { + throw new Error('No "balance" or "estimated" value.'); + } + // Calculate the max amount the user can send + const maxAmount = +fromWei( + toWei(balance).minus(estimated.mul(toWei(values.gasPrice, 'shannon'))) + ); + + if (amount > maxAmount) { + return { amount: "You don't have enough ETH balance" }; + } + } catch (err) { + return { + amount: 'Failed estimating balance, please try again' + }; + } + }, 1000); + + validateForm = values => { + const errors = {}; + if (!isAddress(values.to)) { + errors.to = 'Please enter a valid Ethereum address'; + } + + return Object.keys(errors).length ? errors : this.validateAmount(values); + }; } export default Send; diff --git a/packages/fether-react/src/Tokens/TokensList/TokenBalance/TokenBalance.js b/packages/fether-react/src/Tokens/TokensList/TokenBalance/TokenBalance.js index 3517bda5a7ffd04ec1fc248a751590f7e7b4880d..08ea0a5fc9b1c30bf7a22b9ec55b1b9364fb9c68 100644 --- a/packages/fether-react/src/Tokens/TokensList/TokenBalance/TokenBalance.js +++ b/packages/fether-react/src/Tokens/TokensList/TokenBalance/TokenBalance.js @@ -11,12 +11,12 @@ import { withRouter } from 'react-router-dom'; import withBalance from '../../../utils/withBalance'; -@withBalance() +@withBalance @inject('sendStore') @withRouter class TokenBalance extends Component { static propTypes = { - token: PropTypes.object.isRequired + token: PropTypes.object }; handleClick = () => { @@ -24,8 +24,8 @@ class TokenBalance extends Component { if (!token.address) { return; } - sendStore.setTokenAddress(token.address); - history.push('/send'); + sendStore.clear(); + history.push(`/send/${token.address}`); }; render () { diff --git a/packages/fether-react/src/assets/sass/shared/_form.scss b/packages/fether-react/src/assets/sass/shared/_form.scss index e03077b07be3d8a62a6c7bb68e8cb59a47c030a0..c2e35db93243ccbbbbf064485706d1b16e7277e9 100644 --- a/packages/fether-react/src/assets/sass/shared/_form.scss +++ b/packages/fether-react/src/assets/sass/shared/_form.scss @@ -14,6 +14,8 @@ } .form_field_value { + background-color: transparent; + border: none; font-size: ms(-1); font-weight: 400; font-family: $mono; @@ -21,6 +23,8 @@ opacity: 0.75; margin: 0.25rem 0.5rem 0; padding-bottom: 0.5rem; + padding-left: 0; + padding-right: 0; overflow: hidden; word-wrap: break-word; } diff --git a/packages/fether-react/src/stores/parityStore.js b/packages/fether-react/src/stores/parityStore.js index 365267ab5badce8b56fc56f8c707b9d3684151cd..0c026a06d1330a4150c6c8438daa58f91fe6fdc6 100644 --- a/packages/fether-react/src/stores/parityStore.js +++ b/packages/fether-react/src/stores/parityStore.js @@ -27,6 +27,7 @@ export class ParityStore { // Retrieve token from localStorage const token = store.get(LS_KEY); if (token) { + debug('Got token from localStorage.'); this.setToken(token); } @@ -99,6 +100,7 @@ export class ParityStore { } // If `parity signer new-token` has successfully given us a token back, // then we submit it + debug('Successfully received new token.'); this.setToken(token); }); }; @@ -137,7 +139,6 @@ export class ParityStore { return; } - debug(`Setting token in localStorage.`); this.token = token; // If we receive a new token, then we try to connect to the Api with this diff --git a/packages/fether-react/src/stores/sendStore.js b/packages/fether-react/src/stores/sendStore.js index 2bfc036e3a153d288b2936dff4f926d7b8a6e83b..950dbd8fa2a6e33a12ad12aa1e162be1e677f04b 100644 --- a/packages/fether-react/src/stores/sendStore.js +++ b/packages/fether-react/src/stores/sendStore.js @@ -3,26 +3,17 @@ // // SPDX-License-Identifier: BSD-3-Clause -import abi from '@parity/shared/lib/contracts/abi/eip20'; import { action, computed, observable } from 'mobx'; -import { BigNumber } from 'bignumber.js'; -import { blockNumber$, makeContract$, post$ } from '@parity/light.js'; -import memoize from 'lodash/memoize'; -import noop from 'lodash/noop'; -import { toWei } from '@parity/api/lib/util/wei'; +import { blockNumber$, post$ } from '@parity/light.js'; +import { contractForToken, txForErc20, txForEth } from '../utils/estimateGas'; import Debug from '../utils/debug'; import parityStore from './parityStore'; -import tokensStore from './tokensStore'; const debug = Debug('sendStore'); -const GAS_MULT_FACTOR = 1.25; // Since estimateGas is not always accurate, we add a 33% factor for buffer. -const DEFAULT_GAS = new BigNumber(21000 * GAS_MULT_FACTOR); // Default gas amount export class SendStore { @observable blockNumber; // Current block number, used to calculate tx confirmations. - @observable estimated = DEFAULT_GAS; // Estimated gas amount for this transaction. - @observable tokenAddress; // 'ETH', or the token contract address tx = {}; // The actual tx we are sending. No need to be observable. @observable txStatus; // Status of the tx, see wiki for details. @@ -55,74 +46,18 @@ export class SendStore { return this.blockNumber - +this.txStatus.confirmed.blockNumber; } - /** - * If it's a token, then return the makeContract$ object. - */ - @computed - get contract () { - if (this.tokenAddress === 'ETH') { - return null; - } - return makeContract$(this.tokenAddress, abi); - } - - /** - * Estimate the amount of gas for our transaction. - */ - estimateGas = () => { - if (!this.tx || !Object.keys(this.tx).length) { - return Promise.reject(new Error('Tx not set in sendStore.')); - } - - if (this.tokenAddress === 'ETH') { - return this.estimateGasForEth(this.txForEth); - } else { - return this.estimateGasForErc20(this.txForErc20); - } - }; - - /** - * Estimate gas to transfer in ERC20 contract. Expensive function, so we - * memoize it. - */ - estimateGasForErc20 = memoize( - txForErc20 => - this.contract.contractObject.instance.transfer - .estimateGas(txForErc20.options, txForErc20.args) - .then(this.setEstimated) - .catch(noop), - JSON.stringify - ); - - /** - * Estimate gas to transfer to an ETH address. Expensive function, so we - * memoize it. - */ - estimateGasForEth = memoize( - txForEth => - parityStore.api.eth - .estimateGas(txForEth) - .then(this.setEstimated) - .catch(noop), - JSON.stringify - ); - /** * Create a transaction. */ - send = password => { + send = (token, password) => { + const tx = + token.address === 'ETH' ? txForEth(this.tx) : txForErc20(this.tx, token); const send$ = - this.tokenAddress === 'ETH' - ? post$(this.txForEth) - : this.contract.transfer$( - ...this.txForErc20.args, - this.txForErc20.options - ); + token.address === 'ETH' + ? post$(tx) + : contractForToken(token.address).transfer$(...tx.args, tx.options); - debug( - 'Sending tx.', - this.tokenAddress === 'ETH' ? this.txForEth : this.txForErc20 - ); + debug('Sending tx.', tx); return new Promise((resolve, reject) => { send$.subscribe(txStatus => { @@ -138,54 +73,11 @@ export class SendStore { }); }; - /** - * This.tx is a user-friendly tx object. We convert it now as it can be - * passed to makeContract$(...). - */ - @computed - get txForErc20 () { - return { - args: [ - this.tx.to, - new BigNumber(this.tx.amount).mul( - new BigNumber(10).pow(tokensStore.tokens[this.tokenAddress].decimals) - ) - ], - options: { - gasPrice: toWei(this.tx.gasPrice, 'shannon') // shannon == gwei - } - }; - } - - /** - * This.tx is a user-friendly tx object. We convert it now as it can be - * passed to post$(tx). - */ - @computed - get txForEth () { - return { - gasPrice: toWei(this.tx.gasPrice, 'shannon'), // shannon == gwei - to: this.tx.to, - value: toWei(this.tx.amount.toString()) - }; - } - @action setBlockNumber = blockNumber => { this.blockNumber = blockNumber; }; - @action - setEstimated = estimated => { - this.estimated = estimated.mul(GAS_MULT_FACTOR); - debug('Estimated gas,', +estimated, ', with buffer,', +this.estimated); - }; - - @action - setTokenAddress = tokenAddress => { - this.tokenAddress = tokenAddress; - }; - @action setTx = tx => { this.tx = tx; diff --git a/packages/fether-react/src/stores/sendStore.spec.js b/packages/fether-react/src/stores/sendStore.spec.js index 7f3c63dbd7e462c917fc12eb93a89395bb51878d..c0d8113cd350bfa1f1daf7d0c07f08cbe2e060cd 100644 --- a/packages/fether-react/src/stores/sendStore.spec.js +++ b/packages/fether-react/src/stores/sendStore.spec.js @@ -5,8 +5,6 @@ /* eslint-env jest */ -import abi from '@parity/shared/lib/contracts/abi/eip20'; -import BigNumber from 'bignumber.js'; import lightJs from '@parity/light.js'; // Mocked import parityStore from './parityStore'; @@ -21,11 +19,6 @@ jest.mock('@parity/light.js', () => ({ })) })), makeContract$: jest.fn(() => ({ - contractObject: { - instance: { - transfer: { estimateGas: jest.fn(() => Promise.resolve(123)) } - } - }, transfer$: jest.fn(() => ({ subscribe: jest.fn() })) })), post$: jest.fn(() => ({ @@ -38,9 +31,6 @@ jest.mock('@parity/light.js', () => ({ jest.mock('./parityStore', () => ({ api: { - eth: { - estimateGas: jest.fn(() => Promise.resolve(123)) - }, signer: { confirmRequest: jest.fn(() => Promise.resolve(true)) } @@ -60,6 +50,15 @@ const mockTx = { to: '0x123' }; +const mockErc20Token = { + address: 'foo', + decimals: 18 +}; + +const mockEthToken = { + address: 'ETH' +}; + let sendStore; // Will hold the newly created instance of SendStore in each test beforeEach(() => { sendStore = new SendStore(); @@ -113,149 +112,34 @@ describe('@computed confirmations', () => { }); }); -describe('@computed contract', () => { - test('should create a contract with correct token address if the current token Erc20', () => { - sendStore.setTokenAddress('foo'); - sendStore.contract; // eslint-disable-line - expect(lightJs.makeContract$).toHaveBeenCalledWith('foo', abi); - }); - - test('should return null if the current token is ETH', () => { - sendStore.setTokenAddress('ETH'); - expect(sendStore.contract).toBe(null); - }); -}); - -describe('method estimateGas', () => { - test('should reject and not estimate if no tx is set', () => { - sendStore.estimateGasForErc20 = jest.fn(); - sendStore.estimateGasForEth = jest.fn(); - expect(sendStore.estimateGas()).rejects.toHaveProperty( - 'message', - 'Tx not set in sendStore.' - ); - expect(sendStore.estimateGasForErc20).not.toHaveBeenCalled(); - expect(sendStore.estimateGasForEth).not.toHaveBeenCalled(); - }); - - test('should call estimateGasForErc20 if the current token is Erc20', () => { - sendStore.estimateGasForErc20 = jest.fn(() => 'estimateGasForErc20'); - sendStore.setTokenAddress('foo'); - sendStore.setTx(mockTx); - expect(sendStore.estimateGas()).toBe('estimateGasForErc20'); - expect(sendStore.estimateGasForErc20).toHaveBeenCalled(); - }); - - test('should call estimateGasForEth if the current token is ETH', () => { - sendStore.estimateGasForEth = jest.fn(() => 'estimateGasForEth'); - sendStore.setTokenAddress('ETH'); - sendStore.setTx(mockTx); - expect(sendStore.estimateGas()).toBe('estimateGasForEth'); - expect(sendStore.estimateGasForEth).toHaveBeenCalled(); - }); -}); - -describe('method estimateGasForErc20', () => { - beforeEach(() => { - sendStore.setTokenAddress('foo'); - }); - - test.skip('should call the transfer method on the contract', () => { - sendStore.estimateGasForErc20(mockTx); - expect( - sendStore.contract.contractObject.instance.transfer.estimateGas - ).toHaveBeenCalledWith(mockTx); - }); - - test('should memoize result', () => { - const a = sendStore.estimateGasForErc20(mockTx); - const b = sendStore.estimateGasForErc20(mockTx); - expect(a).toBe(b); - }); -}); - -describe('method estimateGasForEth', () => { - beforeEach(() => { - sendStore.setTokenAddress('ETH'); - }); - - test('should call api.eth.estimateGas', () => { - sendStore.estimateGasForEth(mockTx); - expect(parityStore.api.eth.estimateGas).toHaveBeenCalledWith(mockTx); - }); - - test('should memoize result', () => { - const a = sendStore.estimateGasForEth(mockTx); - const b = sendStore.estimateGasForEth(mockTx); - expect(a).toBe(b); - }); -}); - describe('method send', () => { beforeEach(() => { sendStore.setTx(mockTx); }); test.skip('should call transfer$ if the token is Erc20 and subscribe to it', () => { - sendStore.setTokenAddress('foo'); - sendStore.send(); - expect(sendStore.contract.transfer$).toHaveBeenCalledWith( - sendStore.txForErc20 - ); + sendStore.send(mockErc20Token); + expect(sendStore.contract.transfer$).toHaveBeenCalled(); }); test('should call post$ if the token is ETH and subscribe to it', () => { - sendStore.setTokenAddress('ETH'); - sendStore.send(); - expect(lightJs.post$).toHaveBeenCalledWith(sendStore.txForEth); + sendStore.send(mockEthToken); + expect(lightJs.post$).toHaveBeenCalled(); }); test('should update txStatus', () => { sendStore.setTxStatus = jest.fn(); - sendStore.setTokenAddress('ETH'); - sendStore.send(); + sendStore.send(mockEthToken); expect(sendStore.setTxStatus).toHaveBeenCalledWith({ estimating: true }); }); test('should call acceptRequest when txStatus is requested', () => { sendStore.acceptRequest = jest.fn(() => Promise.resolve(true)); - sendStore.setTokenAddress('ETH'); - sendStore.send('foo'); + sendStore.send(mockEthToken, 'foo'); expect(sendStore.acceptRequest).toHaveBeenCalledWith(1, 'foo'); }); }); -describe('setter setEstimated', () => { - test('should add a 1.25 factor', () => { - sendStore.setEstimated(new BigNumber(2)); - expect(sendStore.estimated).toEqual(new BigNumber(2 * 1.25)); - }); -}); - -describe('@computed txForErc20', () => { - test('should return correct value', () => { - sendStore.setTokenAddress('foo'); - sendStore.setTx(mockTx); - expect(sendStore.txForErc20).toEqual({ - args: ['0x123', new BigNumber('10000000000000000')], - options: { gasPrice: new BigNumber('4000000000') } - }); - }); -}); - -describe('@computed txForEth', () => { - test('should return correct value', () => { - sendStore.setTokenAddress('foo'); - sendStore.setTx(mockTx); - expect(sendStore.txForEth).toEqual({ - gasPrice: new BigNumber('4000000000'), - to: '0x123', - value: new BigNumber('10000000000000000') - }); - }); -}); - storeTests.setterTest(SendStore, 'blockNumber'); -storeTests.setterTest(SendStore, 'tokenAddress'); storeTests.setterTest(SendStore, 'tx'); storeTests.setterTest(SendStore, 'txStatus'); diff --git a/packages/fether-react/src/utils/estimateGas.js b/packages/fether-react/src/utils/estimateGas.js new file mode 100644 index 0000000000000000000000000000000000000000..72ad7ec5d095b81f5fa0605d1ddb3c62d281f432 --- /dev/null +++ b/packages/fether-react/src/utils/estimateGas.js @@ -0,0 +1,97 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: BSD-3-Clause + +import abi from '@parity/shared/lib/contracts/abi/eip20'; +import BigNumber from 'bignumber.js'; +import { makeContract$ } from '@parity/light.js'; +import memoize from 'lodash/memoize'; +import { toWei } from '@parity/api/lib/util/wei'; + +import Debug from './debug'; + +const debug = Debug('estimateGas'); +const GAS_MULT_FACTOR = 1.25; // Since estimateGas is not always accurate, we add a 33% factor for buffer. + +export const contractForToken = memoize(tokenAddress => + makeContract$(tokenAddress, abi) +); + +/** + * Estimate the amount of gas for our transaction. + */ +export const estimateGas = (tx, token, api) => { + if (!tx || !Object.keys(tx).length) { + return Promise.reject(new Error('Tx not set in sendStore.')); + } + + if (token.address === 'ETH') { + return estimateGasForEth(txForEth(tx), api).then(addBuffer); + } else { + return estimateGasForErc20(txForErc20(tx, token), token).then(addBuffer); + } +}; + +/** + * Estimate gas to transfer in ERC20 contract. Expensive function, so we + * memoize it. + */ +const estimateGasForErc20 = memoize((txForErc20, token) => { + debug(`Estimating gas for tx on token contract.`, token, txForErc20); + return contractForToken( + token.address + ).contractObject.instance.transfer.estimateGas( + txForErc20.options, + txForErc20.args + ); +}, JSON.stringify); + +/** + * Estimate gas to transfer to an ETH address. Expensive function, so we + * memoize it. + */ +const estimateGasForEth = memoize((txForEth, api) => { + debug(`Estimating gas for tx.`, txForEth); + return api.eth.estimateGas(txForEth); +}, JSON.stringify); + +/** + * Add some extra gas buffer just to be sure user has enough balance. + * + * @param {BigNumber} estimated - The estimated gas price returned by + * estimateGas. + */ +const addBuffer = estimated => { + const withBuffer = estimated.mul(GAS_MULT_FACTOR); + debug(`Estimated gas ${+estimated}, with buffer ${+withBuffer}.`); + return withBuffer; +}; + +/** + * This.tx is a user-friendly tx object. We convert it now as it can be + * passed to makeContract$.transfer(...). + */ +export const txForErc20 = (tx, token) => { + return { + args: [ + tx.to, + new BigNumber(tx.amount).mul(new BigNumber(10).pow(token.decimals)) + ], + options: { + gasPrice: toWei(tx.gasPrice, 'shannon') // shannon == gwei + } + }; +}; + +/** + * This.tx is a user-friendly tx object. We convert it now as it can be + * passed to post$(tx). + */ +export const txForEth = tx => { + return { + gasPrice: toWei(tx.gasPrice, 'shannon'), // shannon == gwei + to: tx.to, + value: toWei(tx.amount.toString()) + }; +}; diff --git a/packages/fether-react/src/utils/withBalance.js b/packages/fether-react/src/utils/withBalance.js index e852cadbda20526242773bf4f3aad7cf6abf92f9..c4442e731e3a63ba0c27d7ea9a2d3041117a5ec5 100644 --- a/packages/fether-react/src/utils/withBalance.js +++ b/packages/fether-react/src/utils/withBalance.js @@ -16,35 +16,35 @@ import { fromWei } from '@parity/api/lib/util/wei'; import light from 'light-hoc'; /** - * A HOC on light.js to get the current balance. + * A HOC on light.js to get the current balance. The inner component needs to + * have a `token` field in its props. * * @example - * @withBalance() + * @withBalance * class MyComponent extends React.Component{ * * } */ -export default (propsSelector = ({ token }) => token) => - light({ - balance: ownProps => { - // Find our token object in the props - const token = propsSelector(ownProps); +export default light({ + balance: ownProps => { + // Find our token object in the props + const { token } = ownProps; - if (!token.address) { - return empty(); - } - return token.address === 'ETH' - ? myBalance$().pipe( - map(value => (isNullOrLoading(value) ? null : value)), // Transform loading state to null - map(value => value && fromWei(value)) - ) - : defaultAccount$().pipe( - filter(x => x), - switchMap(defaultAccount => - makeContract$(token.address, abi).balanceOf$(defaultAccount) - ), - map(value => (isNullOrLoading(value) ? null : value)), // Transform loading state to null - map(value => value && value.div(10 ** token.decimals)) - ); + if (!token || !token.address) { + return empty(); } - }); + return token.address === 'ETH' + ? myBalance$().pipe( + map(value => (isNullOrLoading(value) ? null : value)), // Transform loading state to null + map(value => value && fromWei(value)) + ) + : defaultAccount$().pipe( + filter(x => x), + switchMap(defaultAccount => + makeContract$(token.address, abi).balanceOf$(defaultAccount) + ), + map(value => (isNullOrLoading(value) ? null : value)), // Transform loading state to null + map(value => value && value.div(10 ** token.decimals)) + ); + } +}); diff --git a/packages/fether-ui/package.json b/packages/fether-ui/package.json index 14ee73769a72fa6ff310fa3a58f20530452749c9..9d676770c8ce5416e6e0c3892da5877a2213d7f4 100644 --- a/packages/fether-ui/package.json +++ b/packages/fether-ui/package.json @@ -33,7 +33,9 @@ "dependencies": { "react-blockies": "^1.3.0", "react-content-loader": "^3.1.2", - "react-tooltip": "^3.6.1" + "react-tooltip": "^3.6.1", + "semantic-ui-css": "^2.3.2", + "semantic-ui-react": "^0.81.3" }, "devDependencies": { "@babel/cli": "^7.0.0-beta.49", diff --git a/packages/fether-ui/src/AccountCard/AccountCard.js b/packages/fether-ui/src/AccountCard/AccountCard.js index f569788d51fd1ea6d56aed268e1ed44a54e554d7..65083b19fa92feceb50653d196fac82a993a5638 100644 --- a/packages/fether-ui/src/AccountCard/AccountCard.js +++ b/packages/fether-ui/src/AccountCard/AccountCard.js @@ -6,13 +6,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Address from './Address'; -import Avatar from './Avatar'; -import Card from '../Card'; -import Information from './Information'; -import Name from './Name'; +import { Address } from './Address'; +import { Avatar } from './Avatar'; +import { Card } from '../Card'; +import { Information } from './Information'; +import { Name } from './Name'; -const AccountCard = ({ address, name, shortAddress, ...otherProps }) => ( +export const AccountCard = ({ address, name, shortAddress, ...otherProps }) => (
@@ -34,5 +34,3 @@ AccountCard.propTypes = { name: PropTypes.string, shortAddress: PropTypes.bool }; - -export default AccountCard; diff --git a/packages/fether-ui/src/AccountCard/Address/Address.js b/packages/fether-ui/src/AccountCard/Address/Address.js index d1279565807a61f6376b7e89ed7481d170d0902d..9c1c5edb955f5bfe548d50c2b9739f86fa288e59 100644 --- a/packages/fether-ui/src/AccountCard/Address/Address.js +++ b/packages/fether-ui/src/AccountCard/Address/Address.js @@ -6,10 +6,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import AddressShort from '../../AddressShort'; -import Placeholder from '../../Placeholder'; +import { AddressShort } from '../../AddressShort'; +import { Placeholder } from '../../Placeholder'; -const Address = ({ address, short, ...otherProps }) => ( +export const Address = ({ address, short, ...otherProps }) => (
{address ? ( short ? ( @@ -31,5 +31,3 @@ Address.propTypes = { name: PropTypes.string, short: PropTypes.bool }; - -export default Address; diff --git a/packages/fether-ui/src/AccountCard/Address/index.js b/packages/fether-ui/src/AccountCard/Address/index.js index 32a089646b45bb33ab5d357bf66e609b68e0341a..88179d97c8e913f92ae42a931fc14d99cff22707 100644 --- a/packages/fether-ui/src/AccountCard/Address/index.js +++ b/packages/fether-ui/src/AccountCard/Address/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import Address from './Address'; - -export default Address; +export * from './Address'; diff --git a/packages/fether-ui/src/AccountCard/Avatar/Avatar.js b/packages/fether-ui/src/AccountCard/Avatar/Avatar.js index 6b089966fa4ad061795c1ee0a5e00b2a27c48217..1912517f2318fcec8898632e0acd16d9ec621933 100644 --- a/packages/fether-ui/src/AccountCard/Avatar/Avatar.js +++ b/packages/fether-ui/src/AccountCard/Avatar/Avatar.js @@ -7,9 +7,9 @@ import React from 'react'; import Blockies from 'react-blockies'; import PropTypes from 'prop-types'; -import Placeholder from '../../Placeholder'; +import { Placeholder } from '../../Placeholder'; -const Avatar = ({ address, ...otherProps }) => ( +export const Avatar = ({ address, ...otherProps }) => (
{address ? ( @@ -22,5 +22,3 @@ const Avatar = ({ address, ...otherProps }) => ( Avatar.propTypes = { address: PropTypes.string }; - -export default Avatar; diff --git a/packages/fether-ui/src/AccountCard/Avatar/index.js b/packages/fether-ui/src/AccountCard/Avatar/index.js index 7118470af4180ac03ba115888a427f4e24101806..82cb46a95728471cb96cad5d4c17ecb0919a9c9e 100644 --- a/packages/fether-ui/src/AccountCard/Avatar/index.js +++ b/packages/fether-ui/src/AccountCard/Avatar/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import Avatar from './Avatar'; - -export default Avatar; +export * from './Avatar'; diff --git a/packages/fether-ui/src/AccountCard/Information/Information.js b/packages/fether-ui/src/AccountCard/Information/Information.js index ff9bdfefdcc431454131448cde5b4021ce671c5e..e2d41c5034a4fa92438dadf25cadc95daa486a98 100644 --- a/packages/fether-ui/src/AccountCard/Information/Information.js +++ b/packages/fether-ui/src/AccountCard/Information/Information.js @@ -7,7 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -const Information = ({ children, ...otherProps }) => ( +export const Information = ({ children, ...otherProps }) => (
{children}
@@ -16,5 +16,3 @@ const Information = ({ children, ...otherProps }) => ( Information.propTypes = { children: PropTypes.node }; - -export default Information; diff --git a/packages/fether-ui/src/AccountCard/Information/index.js b/packages/fether-ui/src/AccountCard/Information/index.js index 916846dc74cd021e0d23951a0f6d6e17c0e664ce..77e73b929a2e99b01cab1fa6d3ca4ebb441088b5 100644 --- a/packages/fether-ui/src/AccountCard/Information/index.js +++ b/packages/fether-ui/src/AccountCard/Information/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import Information from './Information'; - -export default Information; +export * from './Information'; diff --git a/packages/fether-ui/src/AccountCard/Name/Name.js b/packages/fether-ui/src/AccountCard/Name/Name.js index 6fec49b0dc842799aa3770835f167b366588debf..40049c2a0fd6b950a295054ce990d7e40833996c 100644 --- a/packages/fether-ui/src/AccountCard/Name/Name.js +++ b/packages/fether-ui/src/AccountCard/Name/Name.js @@ -6,9 +6,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Placeholder from '../../Placeholder'; +import { Placeholder } from '../../Placeholder'; -const Name = ({ name, ...otherProps }) => ( +export const Name = ({ name, ...otherProps }) => (
{name || }
@@ -17,5 +17,3 @@ const Name = ({ name, ...otherProps }) => ( Name.propTypes = { name: PropTypes.string }; - -export default Name; diff --git a/packages/fether-ui/src/AccountCard/Name/index.js b/packages/fether-ui/src/AccountCard/Name/index.js index 091ba90e20479b00f354088c8c34d7ae36840871..ec7fc7843bda9a89efd19333801ca5f28894b296 100644 --- a/packages/fether-ui/src/AccountCard/Name/index.js +++ b/packages/fether-ui/src/AccountCard/Name/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import Name from './Name'; - -export default Name; +export * from './Name'; diff --git a/packages/fether-ui/src/AccountCard/index.js b/packages/fether-ui/src/AccountCard/index.js index 82cb08c39e0662503c144fbbff6863b2deb5331e..509a607afe3a1863b3154980fe46f974d4453ce3 100644 --- a/packages/fether-ui/src/AccountCard/index.js +++ b/packages/fether-ui/src/AccountCard/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import AccountCard from './AccountCard'; - -export default AccountCard; +export * from './AccountCard'; diff --git a/packages/fether-ui/src/AccountHeader/AccountHeader.js b/packages/fether-ui/src/AccountHeader/AccountHeader.js index 9f60c356931199ee61c22edadf16fffa4825ebe8..d77ac6d88331274fc23086b72df304da3161b9a0 100644 --- a/packages/fether-ui/src/AccountHeader/AccountHeader.js +++ b/packages/fether-ui/src/AccountHeader/AccountHeader.js @@ -7,8 +7,8 @@ import React from 'react'; import Blockies from 'react-blockies'; import PropTypes from 'prop-types'; -import ClickToCopy from '../ClickToCopy'; -import Header from '../Header'; +import { ClickToCopy } from '../ClickToCopy'; +import { Header } from '../Header'; const NormalContainer = ({ children }) => (

{children}

@@ -19,7 +19,12 @@ const CopyContainer = ({ address, children, ...otherProps }) => ( ); -const AccountHeader = ({ address, copyAddress, name, ...otherProps }) => { +export const AccountHeader = ({ + address, + copyAddress, + name, + ...otherProps +}) => { const Container = copyAddress ? CopyContainer : NormalContainer; return ( @@ -45,5 +50,3 @@ AccountHeader.propTypes = { address: PropTypes.string, name: PropTypes.string }; - -export default AccountHeader; diff --git a/packages/fether-ui/src/AccountHeader/index.js b/packages/fether-ui/src/AccountHeader/index.js index 6091361371bce0958d0e487ee6afb6ef7e999c7a..b24ac61cadfcc3a3e260ecdc6a215cfdbc0cf09d 100644 --- a/packages/fether-ui/src/AccountHeader/index.js +++ b/packages/fether-ui/src/AccountHeader/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import AccountHeader from './AccountHeader'; - -export default AccountHeader; +export * from './AccountHeader'; diff --git a/packages/fether-ui/src/AddressShort/AddressShort.js b/packages/fether-ui/src/AddressShort/AddressShort.js index f3f4bb8bbb65b980cd1dffc759785d3d7fd543bf..8fc363bb3376ec036bd13b6b487ef65e5374106b 100644 --- a/packages/fether-ui/src/AddressShort/AddressShort.js +++ b/packages/fether-ui/src/AddressShort/AddressShort.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -const AddressShort = ({ address, as: T = 'span', ...otherProps }) => ( +export const AddressShort = ({ address, as: T = 'span', ...otherProps }) => ( {address.slice(0, 6)}..{address.slice(-4)} @@ -15,5 +15,3 @@ const AddressShort = ({ address, as: T = 'span', ...otherProps }) => ( AddressShort.propTypes = { address: PropTypes.string.isRequired }; - -export default AddressShort; diff --git a/packages/fether-ui/src/AddressShort/index.js b/packages/fether-ui/src/AddressShort/index.js index bf2aed3a96cafe66bccc762279534a8a5ae86168..c46d6bad821893d72bba605bbc9ee8ec0ac1c972 100644 --- a/packages/fether-ui/src/AddressShort/index.js +++ b/packages/fether-ui/src/AddressShort/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import AddressShort from './AddressShort'; - -export default AddressShort; +export * from './AddressShort'; diff --git a/packages/fether-ui/src/Card/Card.js b/packages/fether-ui/src/Card/Card.js index 963299ef37573c4443387d826f34927cfb3e9f1f..5119e6ea5abc6d746bf04b7fb5815dfca03fe8d7 100644 --- a/packages/fether-ui/src/Card/Card.js +++ b/packages/fether-ui/src/Card/Card.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -const Card = ({ children, className, drawers, onClick }) => ( +export const Card = ({ children, className, drawers, onClick }) => (
( +
+ + } + /> + + {children} +
+); diff --git a/packages/fether-ui/src/FormField/index.js b/packages/fether-ui/src/Form/Field/index.js similarity index 67% rename from packages/fether-ui/src/FormField/index.js rename to packages/fether-ui/src/Form/Field/index.js index 8a6066dbf05faee70f6ea078e1bc025a531beb49..1a046ee28ad7b641bd00ee1386a9b16063a1d2cd 100644 --- a/packages/fether-ui/src/FormField/index.js +++ b/packages/fether-ui/src/Form/Field/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import FormField from './FormField'; - -export default FormField; +export * from './Field'; diff --git a/packages/fether-ui/src/Form/Form.js b/packages/fether-ui/src/Form/Form.js new file mode 100644 index 0000000000000000000000000000000000000000..23344af4c93b1c9c66fce5c607158f51a3a082c9 --- /dev/null +++ b/packages/fether-ui/src/Form/Form.js @@ -0,0 +1,12 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: BSD-3-Clause + +import { Field } from './Field'; +import { Slider } from './Slider'; + +export const Form = { + Field, + Slider +}; diff --git a/packages/fether-ui/src/Form/Slider/Slider.js b/packages/fether-ui/src/Form/Slider/Slider.js new file mode 100644 index 0000000000000000000000000000000000000000..3e665b7d5cb8c1375f832e71b35c63756db5455c --- /dev/null +++ b/packages/fether-ui/src/Form/Slider/Slider.js @@ -0,0 +1,24 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: BSD-3-Clause + +import React from 'react'; + +import { Field } from '../Field'; + +export const Slider = ({ + centerText, + input, + leftText, + rightText, + ...otherProps +}) => ( + + + +); diff --git a/packages/fether-ui/src/Form/Slider/index.js b/packages/fether-ui/src/Form/Slider/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ac776dd209cdc4ff0d47977fd3fb41386c97b7fe --- /dev/null +++ b/packages/fether-ui/src/Form/Slider/index.js @@ -0,0 +1,6 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: BSD-3-Clause + +export * from './Slider'; diff --git a/packages/fether-ui/src/Form/index.js b/packages/fether-ui/src/Form/index.js new file mode 100644 index 0000000000000000000000000000000000000000..45c4b207bf97cd174195eb7920627bd3da6d03a0 --- /dev/null +++ b/packages/fether-ui/src/Form/index.js @@ -0,0 +1,6 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: BSD-3-Clause + +export * from './Form'; diff --git a/packages/fether-ui/src/FormField/FormField.js b/packages/fether-ui/src/FormField/FormField.js deleted file mode 100644 index 32d37df23fcf28cc8f936dfdb3709c63a3677b12..0000000000000000000000000000000000000000 --- a/packages/fether-ui/src/FormField/FormField.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2018 Parity Technologies (UK) Ltd. -// This file is part of Parity. -// -// SPDX-License-Identifier: BSD-3-Clause - -import React from 'react'; -import PropTypes from 'prop-types'; - -const FormField = ({ - className, - input: inputComponent, - label, - ...otherProps -}) => ( -
- - {inputComponent || } -
-); - -FormField.propTypes = { - className: PropTypes.string, - input: PropTypes.node, - label: PropTypes.string -}; - -export default FormField; diff --git a/packages/fether-ui/src/Header/Header.js b/packages/fether-ui/src/Header/Header.js index b889c3526974c4b11a8b3651e1619abbf6b08788..c04f4b2db3dd970a69acf78deb2654c69cc093ec 100644 --- a/packages/fether-ui/src/Header/Header.js +++ b/packages/fether-ui/src/Header/Header.js @@ -6,9 +6,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Placeholder from '../Placeholder'; +import { Placeholder } from '../Placeholder'; -const Header = ({ left, right, title }) => ( +export const Header = ({ left, right, title }) => (
{left}
@@ -23,5 +23,3 @@ Header.propTypes = { right: PropTypes.node, title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]) }; - -export default Header; diff --git a/packages/fether-ui/src/Header/index.js b/packages/fether-ui/src/Header/index.js index 95cd638571b139801b8aee53b44d610178acd40a..704bf96704321bc54f80f4be1a4340d4586453cd 100644 --- a/packages/fether-ui/src/Header/index.js +++ b/packages/fether-ui/src/Header/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import Header from './Header'; - -export default Header; +export * from './Header'; diff --git a/packages/fether-ui/src/Placeholder/Placeholder.js b/packages/fether-ui/src/Placeholder/Placeholder.js index 0543faf638400ab61e4890eb0d6c3fcefdaaa58d..9fc8940d1df5ea0e65c40a2bc6b030d61f6ff31a 100644 --- a/packages/fether-ui/src/Placeholder/Placeholder.js +++ b/packages/fether-ui/src/Placeholder/Placeholder.js @@ -7,7 +7,7 @@ import React from 'react'; import ContentLoader from 'react-content-loader'; import PropTypes from 'prop-types'; -const Placeholder = ({ height, width, ...otherProps }) => ( +export const Placeholder = ({ height, width, ...otherProps }) => (
- {token.logo ? ( + {token && token.logo ? ( {token.symbol} ) : ( )}
- {token.name ? token.name : } + {token && token.name ? ( + token.name + ) : ( + + )}
{balance ? ( @@ -35,7 +39,7 @@ const TokenCard = ({ ) : showBalance ? ( ) : null} - {token.symbol} + {token && token.symbol}
{children}
@@ -53,7 +57,7 @@ TokenCard.propTypes = { logo: PropTypes.string, name: PropTypes.string, symbol: PropTypes.string - }).isRequired + }) }; export default TokenCard; diff --git a/packages/fether-ui/src/TokenCard/index.js b/packages/fether-ui/src/TokenCard/index.js index 1134ad9cdce408700e1e43e1d3ad31d05b49be98..f0b51b2aafcec741d5fc3d93023088069faa7a6c 100644 --- a/packages/fether-ui/src/TokenCard/index.js +++ b/packages/fether-ui/src/TokenCard/index.js @@ -3,6 +3,4 @@ // // SPDX-License-Identifier: BSD-3-Clause -import TokenCard from './TokenCard'; - -export default TokenCard; +export * from './TokenCard'; diff --git a/packages/fether-ui/src/index.js b/packages/fether-ui/src/index.js index 32124296c1f10f9b89011101cca5c245870275d0..2d1c5e9f5c8e8d14a583346f17a4685230a65dbb 100644 --- a/packages/fether-ui/src/index.js +++ b/packages/fether-ui/src/index.js @@ -3,22 +3,11 @@ // // SPDX-License-Identifier: BSD-3-Clause -import AccountCard from './AccountCard'; -import AccountHeader from './AccountHeader'; -import AddressShort from './AddressShort'; -import Card from './Card'; -import FormField from './FormField'; -import Header from './Header'; -import Placeholder from './Placeholder'; -import TokenCard from './TokenCard'; - -export { - AccountCard, - AccountHeader, - AddressShort, - Card, - FormField, - Header, - Placeholder, - TokenCard -}; +export * from './AccountCard'; +export * from './AccountHeader'; +export * from './AddressShort'; +export * from './Card'; +export * from './Form'; +export * from './Header'; +export * from './Placeholder'; +export * from './TokenCard'; diff --git a/packages/parity-electron/src/runParity.js b/packages/parity-electron/src/runParity.js index cd2f277a1f4a3a75ada6ca0ac2c5caee8b268374..0e328cc1e0a52feb5f747637ab640e3ab1231d33 100644 --- a/packages/parity-electron/src/runParity.js +++ b/packages/parity-electron/src/runParity.js @@ -47,7 +47,7 @@ export const runParity = async (additionalFlags, onParityError) => { await fsChmod(parityPath, '755'); } catch (e) {} - let logLastLine; // Always contains last line of the Parity logs + let logLastLine = ''; // Always contains last line of the Parity logs // Run an instance of parity with the correct args const args = [...parityArgv(), ...additionalFlags]; diff --git a/yarn.lock b/yarn.lock index 90f7dd459b7db5c6477e6a27b126b7732d335527..b8d3e188387b8b96f4d0ef3d5e41f58a68e3e961 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2919,6 +2919,10 @@ chalk@~0.4.0: has-color "~0.1.0" strip-ansi "~0.1.0" +change-emitter@^0.1.2: + version "0.1.6" + resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" + character-entities-legacy@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c" @@ -3870,6 +3874,10 @@ dateformat@^3.0.0, dateformat@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" +debounce-promise@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debounce-promise/-/debounce-promise-3.1.0.tgz#25035f4b45017bd51a7bef8b3bd9f6401dc47423" + debug-log@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" @@ -5190,7 +5198,7 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.16: +fbjs@^0.8.1, fbjs@^0.8.16: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" dependencies: @@ -5276,6 +5284,10 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +final-form@^4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.8.3.tgz#86a03da6cd6459ed8fe3737dbd9dc87ed40c11d7" + finalhandler@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" @@ -5993,7 +6005,7 @@ hoist-non-react-statics@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" -hoist-non-react-statics@^2.5.0: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" @@ -7053,6 +7065,10 @@ jest@20.0.4: dependencies: jest-cli "^20.0.4" +jquery@x.*: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" + js-base64@^2.1.8, js-base64@^2.1.9: version "2.4.5" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.5.tgz#e293cd3c7c82f070d700fc7a1ca0a2e69f101f92" @@ -7284,6 +7300,10 @@ keccak@^1.0.2: nan "^2.2.1" safe-buffer "^5.1.0" +keyboard-key@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/keyboard-key/-/keyboard-key-1.0.1.tgz#a946294fe59ad5431c63a3ea269f023e51fac6aa" + keyv@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" @@ -9552,6 +9572,10 @@ react-error-overlay@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" +react-final-form@^3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-3.6.4.tgz#1ca37935c2af0bc659a53b293dd84a75d2381548" + react-lifecycles-compat@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -9795,6 +9819,17 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recompose@^0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.27.1.tgz#1a49e931f183634516633bbb4f4edbfd3f38a7ba" + dependencies: + babel-runtime "^6.26.0" + change-emitter "^0.1.2" + fbjs "^0.8.1" + hoist-non-react-statics "^2.3.1" + react-lifecycles-compat "^3.0.2" + symbol-observable "^1.0.4" + recursive-readdir@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99" @@ -10353,6 +10388,23 @@ selfsigned@^1.9.1: dependencies: node-forge "0.7.5" +semantic-ui-css@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/semantic-ui-css/-/semantic-ui-css-2.3.2.tgz#f84ffaf7559c17401db43e2b44b7b4caaab54836" + dependencies: + jquery x.* + +semantic-ui-react@^0.81.3: + version "0.81.3" + resolved "https://registry.yarnpkg.com/semantic-ui-react/-/semantic-ui-react-0.81.3.tgz#ad98917c44cda4b316ee841d67dc071e16e93e9c" + dependencies: + "@babel/runtime" "^7.0.0-beta.49" + classnames "^2.2.5" + keyboard-key "^1.0.1" + lodash "^4.17.10" + prop-types "^15.6.1" + shallowequal "^1.0.2" + semistandard@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/semistandard/-/semistandard-12.0.1.tgz#82190c720b01cf68e3051e0985578f0b1d596683" @@ -10474,6 +10526,10 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +shallowequal@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -11082,7 +11138,7 @@ symbol-observable@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" -symbol-observable@^1.0.2, symbol-observable@^1.1.0: +symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"