diff --git a/common/actions/wallet.js b/common/actions/wallet.js index 0d0ee1d9..aa6ca64f 100644 --- a/common/actions/wallet.js +++ b/common/actions/wallet.js @@ -85,9 +85,27 @@ export function setTokenBalances(payload: { }; } +/*** Broadcast Tx ***/ +export type BroadcastTxRequestedAction = { + type: 'WALLET_BROADCAST_TX_REQUESTED', + payload: { + signedTx: string + } +}; + +export function broadcastTx(signedTx: string): BroadcastTxRequestedAction { + return { + type: 'WALLET_BROADCAST_TX_REQUESTED', + payload: { + signedTx + } + }; +} + /*** Union Type ***/ export type WalletAction = | UnlockPrivateKeyAction | SetWalletAction | SetBalanceAction - | SetTokenBalancesAction; + | SetTokenBalancesAction + | BroadcastTxRequestedAction; diff --git a/common/components/BalanceSidebar/BalanceSidebar.jsx b/common/components/BalanceSidebar/BalanceSidebar.jsx index 8340c244..f29bfdd8 100644 --- a/common/components/BalanceSidebar/BalanceSidebar.jsx +++ b/common/components/BalanceSidebar/BalanceSidebar.jsx @@ -14,6 +14,7 @@ import { formatNumber } from 'utils/formatters'; import { Identicon } from 'components/ui'; import translate from 'translations'; import * as customTokenActions from 'actions/customTokens'; +import { showNotification } from 'actions/notifications'; type Props = { wallet: BaseWallet, @@ -21,6 +22,7 @@ type Props = { network: NetworkConfig, tokenBalances: TokenBalance[], rates: { [string]: number }, + showNotification: Function, addCustomToken: typeof customTokenActions.addCustomToken, removeCustomToken: typeof customTokenActions.removeCustomToken }; @@ -39,8 +41,7 @@ export class BalanceSidebar extends React.Component { this.setState({ address: addr }); }) .catch(err => { - //TODO: communicate error in UI - console.log(err); + this.props.showNotification('danger', err); }); } @@ -130,35 +131,35 @@ export class BalanceSidebar extends React.Component { {rates['REP'] &&
  • - {formatNumber(balance.times(rates['REP']))} + {formatNumber(balance.times(rates['REP']), 2)} {' '} REP
  • } {rates['EUR'] &&
  • - €{formatNumber(balance.times(rates['EUR']))} + €{formatNumber(balance.times(rates['EUR']), 2)} {' EUR'}
  • } {rates['USD'] &&
  • - ${formatNumber(balance.times(rates['USD']))} + ${formatNumber(balance.times(rates['USD']), 2)} {' USD'}
  • } {rates['GBP'] &&
  • - £{formatNumber(balance.times(rates['GBP']))} + £{formatNumber(balance.times(rates['GBP']), 2)} {' GBP'}
  • } {rates['CHF'] &&
  • - {formatNumber(balance.times(rates['CHF']))} + {formatNumber(balance.times(rates['CHF']), 2)} {' '} CHF
  • } @@ -191,4 +192,7 @@ function mapStateToProps(state: State) { }; } -export default connect(mapStateToProps, customTokenActions)(BalanceSidebar); +export default connect(mapStateToProps, { + ...customTokenActions, + showNotification +})(BalanceSidebar); diff --git a/common/components/ExtendedNotifications/TransactionSucceeded.js b/common/components/ExtendedNotifications/TransactionSucceeded.js new file mode 100644 index 00000000..98ba7f0f --- /dev/null +++ b/common/components/ExtendedNotifications/TransactionSucceeded.js @@ -0,0 +1,29 @@ +import React from 'react'; +import bityConfig from 'config/bity'; +import translate from 'translations'; +export type TransactionSucceededProps = { + txHash: string +}; + +const TransactionSucceeded = ({ txHash }: TransactionSucceededProps) => { + // const checkTxLink = `https://www.myetherwallet.com?txHash=${txHash}/#check-tx-status`; + const txHashLink = bityConfig.ethExplorer.replace('[[txHash]]', txHash); + + return ( +
    +

    + {translate('SUCCESS_3', true) + txHash} +

    + + Verify Transaction + +
    + ); +}; + +export default TransactionSucceeded; diff --git a/common/components/WalletDecrypt/index.jsx b/common/components/WalletDecrypt/index.jsx index deb4056c..33d3cdff 100644 --- a/common/components/WalletDecrypt/index.jsx +++ b/common/components/WalletDecrypt/index.jsx @@ -165,7 +165,7 @@ export class WalletDecrypt extends Component { onUnlock = (payload: any) => { this.props.dispatch( - WALLETS[this.state.selectedWalletKey].unlock(payload || this.state.value) + WALLETS[this.state.selectedWalletKey].unlock(this.state.value || payload) ); }; } diff --git a/common/components/ui/Modal.jsx b/common/components/ui/Modal.jsx index 5f32364a..473eb32c 100644 --- a/common/components/ui/Modal.jsx +++ b/common/components/ui/Modal.jsx @@ -21,6 +21,7 @@ type Props = { onClick?: () => void }[], handleClose: () => void, + disableButtons?: boolean, children: any }; @@ -65,8 +66,10 @@ export default class Modal extends Component { } }; - _renderButtons() { - return this.props.buttons.map((btn, idx) => { + _renderButtons = () => { + const { disableButtons, buttons } = this.props; + + return buttons.map((btn, idx) => { let btnClass = 'Modal-footer-btn btn'; if (btn.type) { @@ -78,13 +81,13 @@ export default class Modal extends Component { className={btnClass} onClick={btn.onClick} key={idx} - disabled={btn.disabled} + disabled={disableButtons || btn.disabled} > {btn.text} ); }); - } + }; render() { const { isOpen, title, children, buttons, handleClose } = this.props; diff --git a/common/components/ui/SimpleButton.jsx b/common/components/ui/SimpleButton.jsx index be5b336d..08daa490 100644 --- a/common/components/ui/SimpleButton.jsx +++ b/common/components/ui/SimpleButton.jsx @@ -5,9 +5,7 @@ import type { Element } from 'react'; const DEFAULT_BUTTON_TYPE = 'primary'; const DEFAULT_BUTTON_SIZE = 'lg'; -const Spinner = () => { - return ; -}; +import Spinner from './Spinner'; type ButtonType = | 'default' diff --git a/common/components/ui/Spinner.jsx b/common/components/ui/Spinner.jsx new file mode 100644 index 00000000..5a77ca9b --- /dev/null +++ b/common/components/ui/Spinner.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +type size = 'lg' | '2x' | '3x' | '4x' | '5x'; + +type SpinnerProps = { + size?: size +}; + +const Spinner = ({ size = 'fa-' }: SpinnerProps) => { + return ; +}; + +export default Spinner; diff --git a/common/containers/Tabs/SendTransaction/components/AddressField.jsx b/common/containers/Tabs/SendTransaction/components/AddressField.jsx index fd534bd9..cbab4a6d 100644 --- a/common/containers/Tabs/SendTransaction/components/AddressField.jsx +++ b/common/containers/Tabs/SendTransaction/components/AddressField.jsx @@ -26,12 +26,15 @@ export class AddressField extends React.Component { return (
    - + ↳ - - {ensAddress} - + {ensAddress}

    }
    diff --git a/common/containers/Tabs/SendTransaction/components/ConfirmationModal.jsx b/common/containers/Tabs/SendTransaction/components/ConfirmationModal.jsx index 196280cf..90a312d5 100644 --- a/common/containers/Tabs/SendTransaction/components/ConfirmationModal.jsx +++ b/common/containers/Tabs/SendTransaction/components/ConfirmationModal.jsx @@ -11,27 +11,31 @@ import ERC20 from 'libs/erc20'; import { getTransactionFields } from 'libs/transaction'; import { getTokens } from 'selectors/wallet'; import { getNetworkConfig, getLanguageSelection } from 'selectors/config'; +import { getTxFromState } from 'selectors/wallet'; import type { NodeConfig } from 'config/data'; import type { Token, NetworkConfig } from 'config/data'; - import Modal from 'components/ui/Modal'; import Identicon from 'components/ui/Identicon'; +import Spinner from 'components/ui/Spinner'; +import type { BroadcastStatusTransaction } from 'libs/transaction'; type Props = { - signedTransaction: string, + signedTx: string, transaction: EthTx, wallet: BaseWallet, node: NodeConfig, token: ?Token, network: NetworkConfig, onConfirm: (string, EthTx) => void, - onCancel: () => void, - lang: string + onClose: () => void, + lang: string, + broadCastStatusTx: BroadcastStatusTransaction }; type State = { fromAddress: string, - timeToRead: number + timeToRead: number, + hasBroadCasted: boolean }; class ConfirmationModal extends React.Component { @@ -43,7 +47,8 @@ class ConfirmationModal extends React.Component { this.state = { fromAddress: '', - timeToRead: 5 + timeToRead: 5, + hasBroadCasted: false }; } @@ -54,6 +59,15 @@ class ConfirmationModal extends React.Component { } } + componentDidUpdate() { + if ( + this.state.hasBroadCasted && + !this.props.broadCastStatusTx.isBroadcasting + ) { + this.props.onClose(); + } + } + // Count down 5 seconds before allowing them to confirm readTimer = 0; componentDidMount() { @@ -72,10 +86,10 @@ class ConfirmationModal extends React.Component { clearInterval(this.readTimer); } - _setWalletAddress(wallet: BaseWallet) { - wallet.getAddress().then(fromAddress => { - this.setState({ fromAddress }); - }); + async _setWalletAddress(wallet: BaseWallet) { + // TODO move getAddress to saga + const fromAddress = await wallet.getAddress(); + this.setState({ fromAddress }); } _decodeTransaction() { @@ -102,15 +116,15 @@ class ConfirmationModal extends React.Component { }; } - _confirm() { + _confirm = () => { if (this.state.timeToRead < 1) { - const { signedTransaction, transaction } = this.props; - this.props.onConfirm(signedTransaction, transaction); + this.props.onConfirm(this.props.signedTx); + this.setState({ hasBroadCasted: true }); } - } + }; render() { - const { node, token, network, onCancel } = this.props; + const { node, token, network, onClose, broadCastStatusTx } = this.props; const { fromAddress, timeToRead } = this.state; const { toAddress, value, gasPrice, data } = this._decodeTransaction(); @@ -120,98 +134,114 @@ class ConfirmationModal extends React.Component { text: buttonPrefix + translateRaw('SENDModal_Yes'), type: 'primary', disabled: timeToRead > 0, - onClick: this._confirm() + onClick: this._confirm }, { text: translateRaw('SENDModal_No'), type: 'default', - onClick: onCancel + onClick: onClose } ]; const symbol = token ? token.symbol : network.unit; + const isBroadcasting = + broadCastStatusTx && broadCastStatusTx.isBroadcasting; + return ( -
    -
    -
    - -
    -
    -
    -
    - {value} {symbol} -
    -
    -
    - -
    -
    + { +
    + {isBroadcasting + ?
    + +
    + :
    +
    +
    + +
    +
    +
    +
    + {value} {symbol} +
    +
    +
    + +
    +
    -
      -
    • - You are sending from {fromAddress} -
    • -
    • - You are sending to {toAddress} -
    • -
    • - You are sending{' '} - - {value} {symbol} - {' '} - with a gas price of {gasPrice} gwei -
    • -
    • - You are interacting with the {node.network}{' '} - network provided by {node.service} -
    • - {!token && -
    • - {data - ? - You are sending the following data:{' '} -