diff options
author | Alexander Tseung <alextsg@gmail.com> | 2018-07-31 13:03:20 +0800 |
---|---|---|
committer | Alexander Tseung <alextsg@gmail.com> | 2018-08-24 07:44:44 +0800 |
commit | 5ee40675b9f986a9ff2e5d15a271d7de2145d0e9 (patch) | |
tree | 80f4b3e0a88a5621724a05efeb320596e0bcedad /ui/app/components | |
parent | d733bd34fbd356bca640b3a50582208c0284be40 (diff) | |
download | tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.gz tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.bz2 tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.lz tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.xz tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.zst tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.zip |
Refactor transactions list views. Add redesign components
Diffstat (limited to 'ui/app/components')
26 files changed, 689 insertions, 197 deletions
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 35d38e2a3..261d917f6 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -1,23 +1,35 @@ +@import './app-header/index'; + @import './button-group/index'; -@import './export-text-container/index'; +@import './confirm-page-container/index'; -@import './selected-account/index'; +@import './export-text-container/index'; @import './info-box/index'; -@import './network-display/index'; +@import './menu-bar/index'; -@import './confirm-page-container/index'; +@import './modals/index'; + +@import './network-display/index'; @import './page-container/index'; @import './pages/index'; -@import './modals/index'; +@import './selected-account/index'; @import './sender-to-recipient/index'; @import './tabs/index'; -@import './app-header/index'; +@import './token-view/index'; + +@import './token-view-balance/index'; + +@import './transaction-list/index'; + +@import './transaction-list-item/index'; + +@import './transaction-status/index'; diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js index d494977cd..2c44b6094 100644 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js +++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js @@ -12,12 +12,12 @@ import { CONFIRM_TOKEN_METHOD_PATH, SIGNATURE_REQUEST_PATH, } from '../../../routes' -import { isConfirmDeployContract } from './confirm-transaction-switch.util' +import { isConfirmDeployContract } from '../../../helpers/transactions.util' import { TOKEN_METHOD_TRANSFER, TOKEN_METHOD_APPROVE, TOKEN_METHOD_TRANSFER_FROM, -} from './confirm-transaction-switch.constants' +} from '../../../constants/transactions' export default class ConfirmTransactionSwitch extends Component { static propTypes = { diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js deleted file mode 100644 index 9db4a2f96..000000000 --- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js +++ /dev/null @@ -1,3 +0,0 @@ -export const TOKEN_METHOD_TRANSFER = 'transfer' -export const TOKEN_METHOD_APPROVE = 'approve' -export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js index 20ba44484..dae9790de 100644 --- a/ui/app/components/pages/home/home.component.js +++ b/ui/app/components/pages/home/home.component.js @@ -4,6 +4,7 @@ import Media from 'react-media' import { Redirect } from 'react-router-dom' import WalletView from '../../wallet-view' import TxView from '../../tx-view' +import TokenView from '../../token-view' import { INITIALIZE_BACKUP_PHRASE_ROUTE, RESTORE_VAULT_ROUTE, @@ -14,28 +15,17 @@ import { export default class Home extends PureComponent { static propTypes = { history: PropTypes.object, - unapprovedTxs: PropTypes.object, - unapprovedMsgCount: PropTypes.number, - unapprovedPersonalMsgCount: PropTypes.number, - unapprovedTypedMessagesCount: PropTypes.number, noActiveNotices: PropTypes.bool, lostAccounts: PropTypes.array, forgottenPassword: PropTypes.bool, seedWords: PropTypes.string, + unconfirmedTransactionsCount: PropTypes.number, } componentDidMount () { - const { - history, - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, - } = this.props + const { history, unconfirmedTransactionsCount = 0 } = this.props - // unapprovedTxs and unapproved messages - if (Object.keys(unapprovedTxs).length || - unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } } @@ -69,7 +59,8 @@ export default class Home extends PureComponent { query="(min-width: 576px)" render={() => <WalletView />} /> - <TxView /> + <TokenView /> + {/* <TxView /> */} </div> </div> ) diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js index 96a45a69b..b0e34f832 100644 --- a/ui/app/components/pages/home/home.container.js +++ b/ui/app/components/pages/home/home.container.js @@ -2,14 +2,11 @@ import Home from './home.component' import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' +import { unconfirmedTransactionsCountSelector } from '../../../selectors/confirm-transaction' const mapStateToProps = state => { const { metamask, appState } = state const { - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, noActiveNotices, lostAccounts, seedWords, @@ -17,14 +14,11 @@ const mapStateToProps = state => { const { forgottenPassword } = appState return { - unapprovedTxs, - unapprovedMsgCount, - unapprovedPersonalMsgCount, - unapprovedTypedMessagesCount, noActiveNotices, lostAccounts, forgottenPassword, seedWords, + unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), } } diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js deleted file mode 100644 index 99ca7335c..000000000 --- a/ui/app/components/token-balance.js +++ /dev/null @@ -1,120 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const TokenTracker = require('eth-token-tracker') -const connect = require('react-redux').connect -const selectors = require('../selectors') -const log = require('loglevel') - -function mapStateToProps (state) { - return { - userAddress: selectors.getSelectedAddress(state), - } -} - -module.exports = connect(mapStateToProps)(TokenBalance) - - -inherits(TokenBalance, Component) -function TokenBalance () { - this.state = { - string: '', - symbol: '', - isLoading: true, - error: null, - } - Component.call(this) -} - -TokenBalance.prototype.render = function () { - const state = this.state - const { symbol, string, isLoading } = state - const { balanceOnly } = this.props - - return isLoading - ? h('span', '') - : h('span.token-balance', [ - h('span.hide-text-overflow.token-balance__amount', string), - !balanceOnly && h('span.token-balance__symbol', symbol), - ]) -} - -TokenBalance.prototype.componentDidMount = function () { - this.createFreshTokenTracker() -} - -TokenBalance.prototype.createFreshTokenTracker = function () { - if (this.tracker) { - // Clean up old trackers when refreshing: - this.tracker.stop() - this.tracker.removeListener('update', this.balanceUpdater) - this.tracker.removeListener('error', this.showError) - } - - if (!global.ethereumProvider) return - const { userAddress, token } = this.props - - this.tracker = new TokenTracker({ - userAddress, - provider: global.ethereumProvider, - tokens: [token], - pollingInterval: 8000, - }) - - - // Set up listener instances for cleaning up - this.balanceUpdater = this.updateBalance.bind(this) - this.showError = error => { - this.setState({ error, isLoading: false }) - } - this.tracker.on('update', this.balanceUpdater) - this.tracker.on('error', this.showError) - - this.tracker.updateBalances() - .then(() => { - this.updateBalance(this.tracker.serialize()) - }) - .catch((reason) => { - log.error(`Problem updating balances`, reason) - this.setState({ isLoading: false }) - }) -} - -TokenBalance.prototype.componentDidUpdate = function (nextProps) { - const { - userAddress: oldAddress, - token: { address: oldTokenAddress }, - } = this.props - const { - userAddress: newAddress, - token: { address: newTokenAddress }, - } = nextProps - - if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) return - if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) return - - this.setState({ isLoading: true }) - this.createFreshTokenTracker() -} - -TokenBalance.prototype.updateBalance = function (tokens = []) { - if (!this.tracker.running) { - return - } - - const [{ string, symbol }] = tokens - - this.setState({ - string, - symbol, - isLoading: false, - }) -} - -TokenBalance.prototype.componentWillUnmount = function () { - if (!this.tracker) return - this.tracker.stop() - this.tracker.removeListener('update', this.balanceUpdater) - this.tracker.removeListener('error', this.showError) -} - diff --git a/ui/app/components/token-view-balance/index.js b/ui/app/components/token-view-balance/index.js new file mode 100644 index 000000000..e0509096a --- /dev/null +++ b/ui/app/components/token-view-balance/index.js @@ -0,0 +1 @@ +export { default } from './token-view-balance.container' diff --git a/ui/app/components/token-view-balance/index.scss b/ui/app/components/token-view-balance/index.scss new file mode 100644 index 000000000..6a89e125b --- /dev/null +++ b/ui/app/components/token-view-balance/index.scss @@ -0,0 +1,66 @@ +.token-view-balance { + display: flex; + justify-content: space-between; + align-items: center; + flex: 1; + height: 54px; + + &__balance { + margin-left: 12px; + display: flex; + flex-direction: column; + + @media screen and (max-width: $break-small) { + align-items: center; + margin: 16px 0; + } + } + + &__primary-balance { + font-size: 1.5rem; + + @media screen and (max-width: $break-small) { + margin-bottom: 12px; + font-size: 1.75rem; + } + } + + &__secondary-balance { + font-size: 1.15rem; + color: #a0a0a0; + } + + &__balance-container { + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (max-width: $break-small) { + flex-direction: column; + } + } + + &__buttons { + display: flex; + flex-direction: row; + + @media screen and (max-width: $break-small) { + margin-bottom: 16px; + } + } + + &__button { + min-width: initial; + width: 100px; + + &:not(:last-child) { + margin-right: 12px; + } + } + + @media screen and (max-width: $break-small) { + flex-direction: column; + height: initial + } +} diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js new file mode 100644 index 000000000..6b8140a22 --- /dev/null +++ b/ui/app/components/token-view-balance/token-view-balance.component.js @@ -0,0 +1,92 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Button from '../button' +import Identicon from '../identicon' +import TokenBalance from '../token-balance' +import { SEND_ROUTE } from '../../routes' +import { formatCurrency } from '../../helpers/confirm-transaction/util' + +export default class TokenViewBalance extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + showDepositModal: PropTypes.func, + selectedToken: PropTypes.object, + history: PropTypes.object, + network: PropTypes.string, + ethBalance: PropTypes.string, + fiatBalance: PropTypes.string, + currentCurrency: PropTypes.string, + } + + renderBalance () { + const { selectedToken, ethBalance, fiatBalance, currentCurrency } = this.props + const formattedFiatBalance = formatCurrency(fiatBalance, currentCurrency) + + return selectedToken + ? ( + <TokenBalance + token={selectedToken} + withSymbol + className="token-view-balance__primary-balance" + /> + ) : ( + <div className="token-view-balance__balance"> + <div className="token-view-balance__primary-balance"> + { `${ethBalance} ETH` } + </div> + <div className="token-view-balance__secondary-balance"> + { formattedFiatBalance } + </div> + </div> + ) + } + + renderButtons () { + const { t } = this.context + const { selectedToken, showDepositModal, history } = this.props + + return ( + <div className="token-view-balance__buttons"> + { + !selectedToken && ( + <Button + type="primary" + className="token-view-balance__button" + onClick={() => showDepositModal()} + > + { t('deposit') } + </Button> + ) + } + <Button + type="primary" + className="token-view-balance__button" + onClick={() => history.push(SEND_ROUTE)} + > + { t('send') } + </Button> + </div> + ) + } + + render () { + const { network, selectedToken } = this.props + + return ( + <div className="token-view-balance"> + <div className="token-view-balance__balance-container"> + <Identicon + diameter={50} + address={selectedToken && selectedToken.address} + network={network} + /> + { this.renderBalance() } + </div> + { this.renderButtons() } + </div> + ) + } +} diff --git a/ui/app/components/token-view-balance/token-view-balance.container.js b/ui/app/components/token-view-balance/token-view-balance.container.js new file mode 100644 index 000000000..692e6e32f --- /dev/null +++ b/ui/app/components/token-view-balance/token-view-balance.container.js @@ -0,0 +1,42 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TokenViewBalance from './token-view-balance.component' +import { getSelectedToken, getSelectedAddress } from '../../selectors' +import { showModal } from '../../actions' +import { getValueFromWeiHex } from '../../helpers/confirm-transaction/util' + +const mapStateToProps = state => { + const selectedAddress = getSelectedAddress(state) + const { metamask } = state + const { network, accounts, currentCurrency, conversionRate } = metamask + const account = accounts[selectedAddress] + const { balance: value } = account + + const ethBalance = getValueFromWeiHex({ + value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 3, + }) + + const fiatBalance = getValueFromWeiHex({ + value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2, + }) + + return { + selectedToken: getSelectedToken(state), + network, + ethBalance, + fiatBalance, + currentCurrency, + } +} + +const mapDispatchToProps = dispatch => { + return { + showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TokenViewBalance) diff --git a/ui/app/components/token-view/index.js b/ui/app/components/token-view/index.js new file mode 100644 index 000000000..f49cb034f --- /dev/null +++ b/ui/app/components/token-view/index.js @@ -0,0 +1 @@ +export { default } from './token-view.component' diff --git a/ui/app/components/token-view/index.scss b/ui/app/components/token-view/index.scss new file mode 100644 index 000000000..438147ad9 --- /dev/null +++ b/ui/app/components/token-view/index.scss @@ -0,0 +1,27 @@ +.token-view { + flex: 1 1 66.5%; + background: $white; + min-width: 0; + display: flex; + flex-direction: column; + + &__balance-wrapper { + @media screen and (max-width: $break-small) { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + flex: 0 0 auto; + padding-top: 16px; + } + + @media screen and (min-width: $break-large) { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 2.3em 2.37em .8em; + flex: 0 0 auto; + } + } +} diff --git a/ui/app/components/token-view/token-view.component.js b/ui/app/components/token-view/token-view.component.js new file mode 100644 index 000000000..3e1a4a0c3 --- /dev/null +++ b/ui/app/components/token-view/token-view.component.js @@ -0,0 +1,28 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Media from 'react-media' +import MenuBar from '../menu-bar' +import TokenViewBalance from '../token-view-balance' +// import TransactionList from '../tx-list' +import TransactionList from '../transaction-list' + +export default class TokenView extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + render () { + return ( + <div className="token-view"> + <Media + query="(max-width: 575px)" + render={() => <MenuBar />} + /> + <div className="token-view__balance-wrapper"> + <TokenViewBalance /> + </div> + <TransactionList /> + </div> + ) + } +} diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/transaction-action/index.js new file mode 100644 index 000000000..5882443b6 --- /dev/null +++ b/ui/app/components/transaction-action/index.js @@ -0,0 +1 @@ +export { default } from './transaction-action.container' diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js new file mode 100644 index 000000000..b608615d0 --- /dev/null +++ b/ui/app/components/transaction-action/transaction-action.component.js @@ -0,0 +1,52 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { getTransactionActionKey } from '../../helpers/transactions.util' + +export default class TransactionAction extends PureComponent { + static contextTypes = { + tOrDefault: PropTypes.func, + } + + static propTypes = { + className: PropTypes.string, + transaction: PropTypes.object, + methodData: PropTypes.object, + } + + state = { + transactionAction: '', + } + + componentDidMount () { + this.getTransactionAction() + } + + componentDidUpdate () { + this.getTransactionAction() + } + + getTransactionAction () { + const { transactionAction } = this.state + const { transaction, methodData } = this.props + const { data, isFetching } = methodData + + if (isFetching || transactionAction) { + return + } + + const actionKey = getTransactionActionKey(transaction, data) + const action = actionKey && this.context.tOrDefault(actionKey) + this.setState({ transactionAction: action }) + } + + render () { + const { className } = this.props + const { transactionAction } = this.state + + return ( + <div className={className}> + { transactionAction || '--' } + </div> + ) + } +} diff --git a/ui/app/components/transaction-action/transaction-action.container.js b/ui/app/components/transaction-action/transaction-action.container.js new file mode 100644 index 000000000..56efbdc26 --- /dev/null +++ b/ui/app/components/transaction-action/transaction-action.container.js @@ -0,0 +1,4 @@ +import withMethodData from '../../higher-order-components/with-method-data' +import TransactionAction from './transaction-action.component' + +export default withMethodData(TransactionAction) diff --git a/ui/app/components/transaction-list-item/index.js b/ui/app/components/transaction-list-item/index.js new file mode 100644 index 000000000..697cc55e9 --- /dev/null +++ b/ui/app/components/transaction-list-item/index.js @@ -0,0 +1 @@ +export { default } from './transaction-list-item.container' diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss new file mode 100644 index 000000000..8a3973f92 --- /dev/null +++ b/ui/app/components/transaction-list-item/index.scss @@ -0,0 +1,71 @@ +.transaction-list-item { + box-sizing: border-box; + height: 74px; + padding: 0 21px; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 1px solid $geyser; + cursor: pointer; + + @media screen and (max-width: $break-small) { + padding: 0 12px; + } + + &__identicon-wrapper { + padding-top: 2px; + } + + &__action-block { + padding: 0 8px 0 12px; + width: 180px; + + @media screen and (max-width: $break-small) { + padding: 0 8px; + width: 160px; + } + } + + &__action { + text-transform: capitalize; + padding-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + @media screen and (max-width: $break-small) { + padding-bottom: 0; + font-size: .875rem; + } + } + + &__nonce { + font-size: .75rem; + color: #5e6064; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__transaction-amounts { + flex: 1; + } + + &__primary-transaction-amount { + text-align: end; + + @media screen and (max-width: $break-small) { + font-size: .75rem; + } + } + + &__secondary-transaction-amount { + text-align: end; + font-size: .75rem; + color: #5e6064; + } + + &:hover { + background: rgba($alto, .2); + } +} diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js new file mode 100644 index 000000000..e334cd938 --- /dev/null +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -0,0 +1,82 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Media from 'react-media' +import Identicon from '../identicon' +import TransactionStatus from '../transaction-status' +import TransactionAction from '../transaction-action' +import { formatDate } from '../../util' +import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' +import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' +import { UNAPPROVED_STATUS } from '../../constants/transactions' +import { hexToDecimal } from '../../helpers/conversions.util' + +export default class TransactionListItem extends PureComponent { + static propTypes = { + history: PropTypes.object, + methodData: PropTypes.object, + transaction: PropTypes.object, + ethTransactionAmount: PropTypes.string, + fiatDisplayValue: PropTypes.string, + } + + handleClick = () => { + const { transaction, history } = this.props + const { id, status, hash, metamaskNetworkId } = transaction + + if (status === UNAPPROVED_STATUS) { + history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`) + } else if (hash) { + const prefix = prefixForNetwork(metamaskNetworkId) + const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}` + global.platform.openWindow({ url: etherscanUrl }) + } + } + + render () { + const { + transaction, + ethTransactionAmount, + fiatDisplayValue, + } = this.props + const { txParams = {} } = transaction + const nonce = hexToDecimal(txParams.nonce) + + return ( + <div + className="transaction-list-item" + onClick={this.handleClick} + > + <div className="transaction-list-item__identicon-wrapper"> + <Media query="(max-width: 575px)"> + { + matches => ( + <Identicon + address={txParams.to} + diameter={matches ? 26 : 34} + /> + ) + } + </Media> + </div> + <div className="transaction-list-item__action-block"> + <TransactionAction + transaction={transaction} + className="transaction-list-item__action" + /> + <div className="transaction-list-item__nonce"> + { `#${nonce} - ${formatDate(transaction.time)}` } + </div> + </div> + <TransactionStatus status={transaction.status} /> + <div className="transaction-list-item__transaction-amounts"> + <div className="transaction-list-item__primary-transaction-amount"> + { `-${fiatDisplayValue}` } + </div> + <div className="transaction-list-item__secondary-transaction-amount"> + { `-${ethTransactionAmount} ETH` } + </div> + </div> + </div> + ) + } +} diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js new file mode 100644 index 000000000..bc47f20aa --- /dev/null +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -0,0 +1,28 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TransactionListItem from './transaction-list-item.component' +import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util' +import { formatCurrency } from '../../helpers/confirm-transaction/util' + +const mapStateToProps = (state, ownProps) => { + const { metamask } = state + const { currentCurrency, conversionRate } = metamask + const { transaction: { txParams: { value } = {} } = {} } = ownProps + const ethTransactionAmount = getEthFromWeiHex({ value, conversionRate }) + const fiatTransactionAmount = getValueFromWeiHex({ + value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2, + }) + const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency) + const fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}` + + return { + ethTransactionAmount, + fiatDisplayValue, + } +} + +export default compose( + withRouter, + connect(mapStateToProps), +)(TransactionListItem) diff --git a/ui/app/components/transaction-list/index.js b/ui/app/components/transaction-list/index.js new file mode 100644 index 000000000..688994367 --- /dev/null +++ b/ui/app/components/transaction-list/index.js @@ -0,0 +1 @@ +export { default } from './transaction-list.container' diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss new file mode 100644 index 000000000..f6f209831 --- /dev/null +++ b/ui/app/components/transaction-list/index.scss @@ -0,0 +1,40 @@ +.transaction-list { + display: flex; + flex-direction: column; + flex: 1; + overflow-y: hidden; + + &__header { + flex: 0 0 auto; + font-size: .875rem; + color: $dusty-gray; + border-bottom: 1px solid $geyser; + padding: 16px 0 8px 20px; + + @media screen and (max-width: $break-small) { + padding: 8px 0 8px 16px; + } + } + + &__transactions { + flex: 1; + overflow-y: auto; + } + + &__pending-transactions { + margin-bottom: 16px; + } + + &__empty { + flex: 1; + display: grid; + grid-template-rows: 35% 1fr; + } + + &__empty-text { + grid-row-start: 2; + display: flex; + justify-content: center; + color: $silver; + } +} diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js new file mode 100644 index 000000000..63d171127 --- /dev/null +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -0,0 +1,90 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import TransactionListItem from '../transaction-list-item' + +export default class TransactionList extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static defaultProps = { + pendingTransactions: [], + completedTransactions: [], + } + + static propTypes = { + pendingTransactions: PropTypes.array, + completedTransactions: PropTypes.array, + } + + renderTransactions () { + const { t } = this.context + const { pendingTransactions, completedTransactions } = this.props + + return ( + <div className="transaction-list__transactions"> + { + pendingTransactions.length > 0 && ( + <div className="transaction-list__pending-transactions"> + <div className="transaction-list__header"> + { `${t('pending')} (${pendingTransactions.length})` } + </div> + { + pendingTransactions.map(transaction => { + return ( + <TransactionListItem + transaction={transaction} + key={transaction.id} + /> + ) + }) + } + </div> + ) + } + <div className="transaction-list__completed-transactions"> + <div className="transaction-list__header"> + { t('history') } + </div> + { + completedTransactions.length > 0 + ? ( + completedTransactions.map(transaction => { + return ( + <TransactionListItem + transaction={transaction} + key={transaction.id} + /> + ) + }) + ) + : this.renderEmpty() + } + </div> + </div> + ) + } + + renderEmpty () { + return ( + <div className="transaction-list__empty"> + <div className="transaction-list__empty-text"> + { this.context.t('noTransactions') } + </div> + </div> + ) + } + + render () { + return ( + <div className="transaction-list"> + { + this.renderTransactions() + // pendingTransactions.length + completedTransactions.length > 0 + // ? this.renderTransactions() + // : this.renderEmpty() + } + </div> + ) + } +} diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js new file mode 100644 index 000000000..b1c2c04c9 --- /dev/null +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TransactionList from './transaction-list.component' +import { + pendingTransactionsSelector, + completedTransactionsSelector, +} from '../../selectors/transactions' + +const mapStateToProps = state => { + return { + pendingTransactions: pendingTransactionsSelector(state), + completedTransactions: completedTransactionsSelector(state), + } +} + +export default compose( + withRouter, + connect(mapStateToProps) +)(TransactionList) diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss index dd9bf5877..03a378b4e 100644 --- a/ui/app/components/transaction-status/index.scss +++ b/ui/app/components/transaction-status/index.scss @@ -10,6 +10,12 @@ justify-content: center; align-items: center; + @media screen and (max-width: $break-small) { + height: 24px; + width: 74px; + font-size: .5rem; + } + &--confirmed { background-color: #eafad7; color: #609a1c; diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 654090da6..aa540249f 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -15,6 +15,9 @@ const Tooltip = require('./tooltip') const TxList = require('./tx-list') const SelectedAccount = require('./selected-account') +import Media from 'react-media' +import MenuBar from './menu-bar' + module.exports = compose( withRouter, connect(mapStateToProps, mapDispatchToProps) @@ -104,49 +107,11 @@ TxView.prototype.renderButtons = function () { } TxView.prototype.render = function () { - const { hideSidebar, isMascara, showSidebar, sidebarOpen } = this.props - const { t } = this.context - - return h('div.tx-view.flex-column', { - style: {}, - }, [ - - h('div.flex-row.phone-visible', { - style: { - justifyContent: 'center', - alignItems: 'center', - flex: '0 0 auto', - marginBottom: '16px', - padding: '5px', - borderBottom: '1px solid #e5e5e5', - }, - }, [ - - h(Tooltip, { - title: t('menu'), - position: 'bottom', - }, [ - h('div.fa.fa-bars', { - style: { - fontSize: '1.3em', - cursor: 'pointer', - padding: '10px', - }, - onClick: () => sidebarOpen ? hideSidebar() : showSidebar(), - }), - ]), - - h(SelectedAccount), - - !isMascara && h(Tooltip, { - title: t('openInTab'), - position: 'bottom', - }, [ - h('div.open-in-browser', { - onClick: () => global.platform.openExtensionInBrowser(), - }, [h('img', { src: 'images/popout.svg' })]), - ]), - ]), + return h('div.tx-view.flex-column', [ + h(Media, { + query: '(max-width: 575px)', + render: () => h(MenuBar), + }), this.renderHeroBalance(), |