diff --git a/test/e2e/address-book.spec.js b/test/e2e/address-book.spec.js index 72f194fa27a6..6d21eed6fef6 100644 --- a/test/e2e/address-book.spec.js +++ b/test/e2e/address-book.spec.js @@ -151,7 +151,7 @@ describe('MetaMask', function () { }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount')) + const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) await driver.wait(until.elementTextMatches(balance, /25\s*ETH/)) await driver.delay(regularDelayMs) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index a38af5ec32f3..3a2677405310 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -207,7 +207,7 @@ describe('MetaMask', function () { }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount')) + const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) await driver.wait(until.elementTextMatches(balance, /100\s*ETH/)) await driver.delay(regularDelayMs) }) diff --git a/test/e2e/threebox.spec.js b/test/e2e/threebox.spec.js index 54f7f01cd15c..33b5b1ff7628 100644 --- a/test/e2e/threebox.spec.js +++ b/test/e2e/threebox.spec.js @@ -96,7 +96,7 @@ describe('MetaMask', function () { }) it('balance renders', async function () { - const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount')) + const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) await driver.wait(until.elementTextMatches(balance, /25\s*ETH/)) await driver.delay(regularDelayMs) }) @@ -202,7 +202,7 @@ describe('MetaMask', function () { }) it('balance renders', async function () { - const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount')) + const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading')) await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/)) await driver2.delay(regularDelayMs) }) diff --git a/ui/app/components/app/asset-list-item/asset-list-item.js b/ui/app/components/app/asset-list-item/asset-list-item.js index 021ba39ab287..ffd2028a657e 100644 --- a/ui/app/components/app/asset-list-item/asset-list-item.js +++ b/ui/app/components/app/asset-list-item/asset-list-item.js @@ -2,9 +2,9 @@ import React from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' import Identicon from '../../ui/identicon' +import ListItem from '../../ui/list-item' const AssetListItem = ({ - children, className, 'data-testid': dataTestId, iconClassName, @@ -12,32 +12,31 @@ const AssetListItem = ({ tokenAddress, tokenImage, warning, + primary, + secondary, }) => { return ( -
- -
- { children } -
- { warning } - -
+ icon={( + + )} + rightContent={} + /> ) } AssetListItem.propTypes = { - children: PropTypes.node.isRequired, className: PropTypes.string, 'data-testid': PropTypes.string, iconClassName: PropTypes.string, @@ -45,6 +44,8 @@ AssetListItem.propTypes = { tokenAddress: PropTypes.string, tokenImage: PropTypes.string, warning: PropTypes.node, + primary: PropTypes.string, + secondary: PropTypes.string, } AssetListItem.defaultProps = { diff --git a/ui/app/components/app/asset-list-item/asset-list-item.scss b/ui/app/components/app/asset-list-item/asset-list-item.scss index 83ddcc370a8e..4cb68b6be85f 100644 --- a/ui/app/components/app/asset-list-item/asset-list-item.scss +++ b/ui/app/components/app/asset-list-item/asset-list-item.scss @@ -1,26 +1,10 @@ .asset-list-item { - &__container { - display: flex; - padding: 24px 16px; - align-items: center; - border-top: 1px solid $mercury; - border-bottom: 1px solid $mercury; - cursor: pointer; - - &:hover { - background-color: $Grey-000; - } - } - - &__balance { - display: flex; - flex-direction: column; - margin-left: 15px; - flex: 1; - min-width: 0; - } - &__chevron-right { color: $Grey-500; } + + .list-item__subheading { + margin-top: 6px; + font-size: 14px; + } } diff --git a/ui/app/components/app/asset-list/asset-list.js b/ui/app/components/app/asset-list/asset-list.js index c3317cc5cf91..39958fb2a6b0 100644 --- a/ui/app/components/app/asset-list/asset-list.js +++ b/ui/app/components/app/asset-list/asset-list.js @@ -6,11 +6,11 @@ import AddTokenButton from '../add-token-button' import TokenList from '../token-list' import { ADD_TOKEN_ROUTE } from '../../../helpers/constants/routes' import AssetListItem from '../asset-list-item' -import CurrencyDisplay from '../../ui/currency-display' import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' import { useMetricEvent } from '../../../hooks/useMetricEvent' import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency' import { getCurrentAccountWithSendEtherInfo, getNativeCurrency, getShouldShowFiat } from '../../../selectors' +import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay' const AssetList = ({ onClickAsset }) => { const history = useHistory() @@ -41,29 +41,24 @@ const AssetList = ({ onClickAsset }) => { numberOfDecimals: secondaryNumberOfDecimals, } = useUserPreferencedCurrency(SECONDARY, { ethNumberOfDecimals: 4 }) + const [primaryCurrencyDisplay] = useCurrencyDisplay( + selectedAccountBalance, + { numberOfDecimals: primaryNumberOfDecimals, currency: primaryCurrency } + ) + + const [secondaryCurrencyDisplay] = useCurrencyDisplay( + selectedAccountBalance, + { numberOfDecimals: secondaryNumberOfDecimals, currency: secondaryCurrency } + ) + return ( <> onClickAsset(nativeCurrency)} data-testid="wallet-balance" - > - - { - showFiat && ( - - ) - } - + primary={primaryCurrencyDisplay} + secondary={showFiat && secondaryCurrencyDisplay} + /> { onClickAsset(tokenAddress) diff --git a/ui/app/components/app/asset-list/asset-list.scss b/ui/app/components/app/asset-list/asset-list.scss deleted file mode 100644 index 1fb806da88bc..000000000000 --- a/ui/app/components/app/asset-list/asset-list.scss +++ /dev/null @@ -1,13 +0,0 @@ -.asset-list { - &__primary-amount { - color: $Black-100; - font-size: 16px; - height: 16px; - } - - &__secondary-amount { - color: $Grey-500; - margin-top: 6px; - font-size: 14px; - } -} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 2f2aa89a9f45..6fbe4389bdc0 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -6,8 +6,6 @@ @import 'app-header/index'; -@import 'asset-list/asset-list'; - @import 'asset-list-item/asset-list-item'; @import '../ui/breadcrumbs/index'; diff --git a/ui/app/components/app/token-cell/index.js b/ui/app/components/app/token-cell/index.js index f0f3bcb4aaab..c33e36adb2c4 100644 --- a/ui/app/components/app/token-cell/index.js +++ b/ui/app/components/app/token-cell/index.js @@ -1 +1 @@ -export { default } from './token-cell.container' +export { default } from './token-cell' diff --git a/ui/app/components/app/token-cell/token-cell.component.js b/ui/app/components/app/token-cell/token-cell.component.js deleted file mode 100644 index a7088324ce22..000000000000 --- a/ui/app/components/app/token-cell/token-cell.component.js +++ /dev/null @@ -1,111 +0,0 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util' -import Tooltip from '../../ui/tooltip-v2' -import { I18nContext } from '../../../contexts/i18n' -import AssetListItem from '../asset-list-item' - -export default class TokenCell extends Component { - static contextType = I18nContext - - static propTypes = { - address: PropTypes.string, - outdatedBalance: PropTypes.bool, - symbol: PropTypes.string, - string: PropTypes.string, - contractExchangeRates: PropTypes.object, - conversionRate: PropTypes.number, - currentCurrency: PropTypes.string, - image: PropTypes.string, - onClick: PropTypes.func.isRequired, - userAddress: PropTypes.string.isRequired, - } - - static defaultProps = { - outdatedBalance: false, - } - - render () { - const t = this.context - const { - address, - symbol, - string, - contractExchangeRates, - conversionRate, - onClick, - currentCurrency, - image, - outdatedBalance, - userAddress, - } = this.props - let currentTokenToFiatRate - let currentTokenInFiat - let formattedFiat = '' - - if (contractExchangeRates[address]) { - currentTokenToFiatRate = multiplyCurrencies( - contractExchangeRates[address], - conversionRate - ) - currentTokenInFiat = conversionUtil(string, { - fromNumericBase: 'dec', - fromCurrency: symbol, - toCurrency: currentCurrency.toUpperCase(), - numberOfDecimals: 2, - conversionRate: currentTokenToFiatRate, - }) - formattedFiat = currentTokenInFiat.toString() === '0' - ? '' - : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` - } - - const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol - - const warning = outdatedBalance - ? ( - - { t('troubleTokenBalances') } - - { t('here') } - - - )} - > - - - ) - : null - - return ( - -
-
{string || 0}
-
{symbol}
- {showFiat && ( -
- {formattedFiat} -
- )} -
-
- ) - } -} diff --git a/ui/app/components/app/token-cell/token-cell.container.js b/ui/app/components/app/token-cell/token-cell.container.js deleted file mode 100644 index af5a1373f51e..000000000000 --- a/ui/app/components/app/token-cell/token-cell.container.js +++ /dev/null @@ -1,14 +0,0 @@ -import { connect } from 'react-redux' -import TokenCell from './token-cell.component' -import { getSelectedAddress } from '../../../selectors' - -function mapStateToProps (state) { - return { - contractExchangeRates: state.metamask.contractExchangeRates, - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - userAddress: getSelectedAddress(state), - } -} - -export default connect(mapStateToProps)(TokenCell) diff --git a/ui/app/components/app/token-cell/token-cell.js b/ui/app/components/app/token-cell/token-cell.js new file mode 100644 index 000000000000..2408f429064c --- /dev/null +++ b/ui/app/components/app/token-cell/token-cell.js @@ -0,0 +1,98 @@ +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' +import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util' +import Tooltip from '../../ui/tooltip-v2' +import AssetListItem from '../asset-list-item' +import { useSelector } from 'react-redux' +import { getTokenExchangeRates, getConversionRate, getCurrentCurrency, getSelectedAddress } from '../../../selectors' +import { useI18nContext } from '../../../hooks/useI18nContext' +import InfoIcon from '../../ui/icon/info-icon.component' +import { formatCurrency } from '../../../helpers/utils/confirm-tx.util' + +export default function TokenCell ({ address, outdatedBalance, symbol, string, image, onClick }) { + const contractExchangeRates = useSelector(getTokenExchangeRates) + const conversionRate = useSelector(getConversionRate) + const currentCurrency = useSelector(getCurrentCurrency) + const userAddress = useSelector(getSelectedAddress) + const t = useI18nContext() + + let currentTokenToFiatRate + let currentTokenInFiat + let formattedFiat = '' + + + // if the conversionRate is 0 eg: currently unknown + // or the contract exchange rate is currently unknown + // the effective currentTokenToFiatRate is 0 and erroneous. + // Skipping this entire block will result in fiat not being + // shown to the user, instead of a fiat value of 0 for a non-zero + // token amount. + if (conversionRate > 0 && contractExchangeRates[address]) { + currentTokenToFiatRate = multiplyCurrencies( + contractExchangeRates[address], + conversionRate + ) + currentTokenInFiat = conversionUtil(string, { + fromNumericBase: 'dec', + fromCurrency: symbol, + toCurrency: currentCurrency.toUpperCase(), + numberOfDecimals: 2, + conversionRate: currentTokenToFiatRate, + }) + formattedFiat = `${formatCurrency(currentTokenInFiat, currentCurrency)} ${currentCurrency.toUpperCase()}` + } + + const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol + + const warning = outdatedBalance + ? ( + + { t('troubleTokenBalances') } + + { t('here') } + + + )} + > + + + ) + : null + + return ( + + + ) +} + +TokenCell.propTypes = { + address: PropTypes.string, + outdatedBalance: PropTypes.bool, + symbol: PropTypes.string, + string: PropTypes.string, + image: PropTypes.string, + onClick: PropTypes.func.isRequired, +} + +TokenCell.defaultProps = { + outdatedBalance: false, +} diff --git a/ui/app/components/app/token-cell/token-cell.scss b/ui/app/components/app/token-cell/token-cell.scss index e14aa8d5520a..38adfcb56690 100644 --- a/ui/app/components/app/token-cell/token-cell.scss +++ b/ui/app/components/app/token-cell/token-cell.scss @@ -1,95 +1,5 @@ -$wallet-balance-breakpoint: 890px; -$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})"; - .token-cell { - position: relative; - - &__token-balance { - margin-right: 4px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - max-width: 100%; - } - - &__token-balance, &__token-symbol { - font-size: 16px; - flex: 0 0 auto; - color: $Black-100; - } - - &__fiat-amount { - margin-top: 6px; - font-size: 14px; - width: 100%; - text-transform: uppercase; + &--outdated .list-item__heading { color: $Grey-500; } - - &--outdated &__icon { - opacity: 0.5 - } - &--outdated &__balance-wrapper { - opacity: 0.5 - } - - &__balance-wrapper { - flex: 1; - flex-flow: row wrap; - display: flex; - min-width: 0; - } - - &__outdated-icon { - color: $warning-yellow; - display: block; - padding: 0 10px; - } - - &__outdated-tooltip { - width: 260px; - } -} - -.token-menu-dropdown { - width: 80%; - position: absolute; - top: 52px; - right: 25px; - z-index: 2000; - - @media #{$wallet-balance-breakpoint-range} { - right: 18px; - } - - &__close-area { - position: fixed; - top: 0; - left: 0; - z-index: 2100; - width: 100%; - height: 100%; - cursor: default; - } - - &__container { - padding: 16px; - z-index: 2200; - position: relative; - } - - &__options { - display: flex; - flex-direction: column; - justify-content: center; - } - - &__option { - color: $white; - font-family: Roboto; - font-size: 16px; - line-height: 21px; - text-align: center; - } } diff --git a/ui/app/components/app/token-cell/token-cell.test.js b/ui/app/components/app/token-cell/token-cell.test.js index 3d7136d790a6..fba52b602295 100644 --- a/ui/app/components/app/token-cell/token-cell.test.js +++ b/ui/app/components/app/token-cell/token-cell.test.js @@ -59,16 +59,12 @@ describe('Token Cell', function () { assert.equal(wrapper.find(Identicon).prop('image'), './test-image') }) - it('renders token balance', function () { - assert.equal(wrapper.find('.token-cell__token-balance').text(), '5.000') - }) - - it('renders token symbol', function () { - assert.equal(wrapper.find('.token-cell__token-symbol').text(), 'TEST') + it('renders token balance and symbol', function () { + assert.equal(wrapper.find('.list-item__heading').text(), '5.000 TEST ') }) it('renders converted fiat amount', function () { - assert.equal(wrapper.find('.token-cell__fiat-amount').text(), '0.52 USD') + assert.equal(wrapper.find('.list-item__subheading').text(), '$0.52 USD') }) it('calls onClick when clicked', function () { diff --git a/ui/app/components/ui/list-item/index.scss b/ui/app/components/ui/list-item/index.scss index 987c01c9a2d9..6696bb5ada06 100644 --- a/ui/app/components/ui/list-item/index.scss +++ b/ui/app/components/ui/list-item/index.scss @@ -18,7 +18,7 @@ } &__col { - align-self: flex-start; + align-self: center; &-main { flex-grow: 1; } @@ -32,7 +32,6 @@ &-wrap { display: inline-block; position: absolute; - top: 2px; width: 16px; height: 16px; margin-left: 8px; diff --git a/ui/app/components/ui/list-item/list-item.component.js b/ui/app/components/ui/list-item/list-item.component.js index 278b4cd10b36..07bdb153582f 100644 --- a/ui/app/components/ui/list-item/list-item.component.js +++ b/ui/app/components/ui/list-item/list-item.component.js @@ -2,11 +2,22 @@ import React from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -export default function ListItem ({ title, subtitle, onClick, subtitleStatus, children, titleIcon, icon, rightContent, className }) { +export default function ListItem ({ + title, + subtitle, + onClick, + subtitleStatus, + children, + titleIcon, + icon, + rightContent, + className, + 'data-testid': dataTestId, +}) { const primaryClassName = classnames('list-item', className) return ( -
+
{icon && (
{icon} @@ -48,4 +59,5 @@ ListItem.propTypes = { rightContent: PropTypes.node, className: PropTypes.string, onClick: PropTypes.func, + 'data-testid': PropTypes.string, }