Add visual indicator when displaying a cached balance (#5854)

feature/default_network_editable
Dan J Miller 6 years ago committed by Whymarrh Whitby
parent df3169d1c7
commit 02bdbbbc3e
  1. 3
      app/_locales/en/messages.json
  2. 1
      development/states/confirm-sig-requests.json
  3. 1
      development/states/currency-localization.json
  4. 1
      development/states/send-edit.json
  5. 1
      development/states/send-new-ui.json
  6. 1
      development/states/tx-list-items.json
  7. 1
      test/data/mock-state.json
  8. 1
      test/unit/ui/app/selectors.spec.js
  9. 5
      ui/app/components/currency-display/currency-display.component.js
  10. 24
      ui/app/components/send/account-list-item/account-list-item.component.js
  11. 4
      ui/app/components/send/account-list-item/account-list-item.container.js
  12. 1
      ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
  13. 4
      ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
  14. 1
      ui/app/components/send/tests/send-selectors-test-data.js
  15. 6
      ui/app/components/tooltip-v2.js
  16. 18
      ui/app/components/transaction-view-balance/index.scss
  17. 23
      ui/app/components/transaction-view-balance/transaction-view-balance.component.js
  18. 2
      ui/app/components/transaction-view-balance/transaction-view-balance.container.js
  19. 1
      ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js
  20. 29
      ui/app/css/itcss/components/account-dropdown.scss
  21. 1
      ui/app/css/itcss/settings/variables.scss
  22. 19
      ui/app/selectors.js

@ -139,6 +139,9 @@
"balance": { "balance": {
"message": "Balance" "message": "Balance"
}, },
"balanceOutdated": {
"message": "Balance may be outdated"
},
"balances": { "balances": {
"message": "Token balance(s)" "message": "Token balance(s)"
}, },

@ -23,6 +23,7 @@
"name": "Send Account 4" "name": "Send Account 4"
} }
}, },
"cachedBalances": {},
"unapprovedTxs": {}, "unapprovedTxs": {},
"currentCurrency": "USD", "currentCurrency": "USD",
"conversionRate": 1200.88200327, "conversionRate": 1200.88200327,

@ -23,6 +23,7 @@
"name": "Send Account 4" "name": "Send Account 4"
} }
}, },
"cachedBalances": {},
"unapprovedTxs": {}, "unapprovedTxs": {},
"currentCurrency": "USD", "currentCurrency": "USD",
"conversionRate": 19855, "conversionRate": 19855,

@ -23,6 +23,7 @@
"name": "Send Account 4" "name": "Send Account 4"
} }
}, },
"cachedBalances": {},
"assetImages": {}, "assetImages": {},
"unapprovedTxs": {}, "unapprovedTxs": {},
"currentCurrency": "USD", "currentCurrency": "USD",

@ -23,6 +23,7 @@
"name": "Send Account 4" "name": "Send Account 4"
} }
}, },
"cachedBalances": {},
"unapprovedTxs": {}, "unapprovedTxs": {},
"currentCurrency": "USD", "currentCurrency": "USD",
"conversionRate": 1200.88200327, "conversionRate": 1200.88200327,

@ -23,6 +23,7 @@
"name": "Send Account 4" "name": "Send Account 4"
} }
}, },
"cachedBalances": {},
"currentCurrency": "USD", "currentCurrency": "USD",
"conversionRate": 1200.88200327, "conversionRate": 1200.88200327,
"conversionDate": 1489013762, "conversionDate": 1489013762,

@ -11,6 +11,7 @@
"name": "Test Account 2" "name": "Test Account 2"
} }
}, },
"cachedBalances": {},
"unapprovedTxs": { "unapprovedTxs": {
"8393540981007587": { "8393540981007587": {
"id": 8393540981007587, "id": 8393540981007587,

@ -19,6 +19,7 @@ describe('Selectors', function () {
'address': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', 'address': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
}, },
}, },
cachedBalances: {},
}, },
} }
}) })

@ -17,10 +17,11 @@ export default class CurrencyDisplay extends PureComponent {
value: PropTypes.string, value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideLabel: PropTypes.bool, hideLabel: PropTypes.bool,
hideTitle: PropTypes.bool,
} }
render () { render () {
const { className, displayValue, prefix, prefixComponent, style, suffix } = this.props const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props
const text = `${prefix || ''}${displayValue}` const text = `${prefix || ''}${displayValue}`
const title = `${text} ${suffix}` const title = `${text} ${suffix}`
@ -28,7 +29,7 @@ export default class CurrencyDisplay extends PureComponent {
<div <div
className={classnames('currency-display-component', className)} className={classnames('currency-display-component', className)}
style={style} style={style}
title={title} title={!hideTitle && title || null}
> >
{ prefixComponent } { prefixComponent }
<span className="currency-display-component__text">{ text }</span> <span className="currency-display-component__text">{ text }</span>

@ -1,9 +1,11 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames'
import { checksumAddress } from '../../../util' import { checksumAddress } from '../../../util'
import Identicon from '../../identicon' import Identicon from '../../identicon'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common' import { PRIMARY, SECONDARY } from '../../../constants/common'
import Tooltip from '../../tooltip-v2'
export default class AccountListItem extends Component { export default class AccountListItem extends Component {
@ -16,6 +18,7 @@ export default class AccountListItem extends Component {
displayBalance: PropTypes.bool, displayBalance: PropTypes.bool,
handleClick: PropTypes.func, handleClick: PropTypes.func,
icon: PropTypes.node, icon: PropTypes.node,
balanceIsCached: PropTypes.bool,
}; };
static contextTypes = { static contextTypes = {
@ -30,6 +33,7 @@ export default class AccountListItem extends Component {
displayBalance = true, displayBalance = true,
handleClick, handleClick,
icon = null, icon = null,
balanceIsCached,
} = this.props } = this.props
const { name, address, balance } = account || {} const { name, address, balance } = account || {}
@ -58,16 +62,34 @@ export default class AccountListItem extends Component {
{ {
displayBalance && ( displayBalance && (
<div className="account-list-item__account-balances"> <Tooltip
position="left"
title={this.context.t('balanceOutdated')}
disabled={!balanceIsCached}
style={{
left: '-20px !important',
}}
>
<div className={classnames('account-list-item__account-balances', {
'account-list-item__cached-balances': balanceIsCached,
})}>
<div className="account-list-item__primary-cached-container">
<UserPreferencedCurrencyDisplay <UserPreferencedCurrencyDisplay
type={PRIMARY} type={PRIMARY}
value={balance} value={balance}
hideTitle={true}
/> />
{
balanceIsCached ? <span className="account-list-item__cached-star">*</span> : null
}
</div>
<UserPreferencedCurrencyDisplay <UserPreferencedCurrencyDisplay
type={SECONDARY} type={SECONDARY}
value={balance} value={balance}
hideTitle={true}
/> />
</div> </div>
</Tooltip>
) )
} }

@ -4,6 +4,9 @@ import {
getCurrentCurrency, getCurrentCurrency,
getNativeCurrency, getNativeCurrency,
} from '../send.selectors.js' } from '../send.selectors.js'
import {
isBalanceCached,
} from '../../../selectors'
import AccountListItem from './account-list-item.component' import AccountListItem from './account-list-item.component'
export default connect(mapStateToProps)(AccountListItem) export default connect(mapStateToProps)(AccountListItem)
@ -13,5 +16,6 @@ function mapStateToProps (state) {
conversionRate: getConversionRate(state), conversionRate: getConversionRate(state),
currentCurrency: getCurrentCurrency(state), currentCurrency: getCurrentCurrency(state),
nativeCurrency: getNativeCurrency(state), nativeCurrency: getNativeCurrency(state),
balanceIsCached: isBalanceCached(state),
} }
} }

@ -121,6 +121,7 @@ describe('AccountListItem Component', function () {
{ {
type: 'PRIMARY', type: 'PRIMARY',
value: 'mockBalance', value: 'mockBalance',
hideTitle: true,
} }
) )
}) })

@ -15,6 +15,9 @@ proxyquire('../account-list-item.container.js', {
getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`, getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`,
getNativeCurrency: (s) => `mockNativeCurrency:${s}`, getNativeCurrency: (s) => `mockNativeCurrency:${s}`,
}, },
'../../../selectors.js': {
isBalanceCached: (s) => `mockBalanceIsCached:${s}`,
},
}) })
describe('account-list-item container', () => { describe('account-list-item container', () => {
@ -26,6 +29,7 @@ describe('account-list-item container', () => {
conversionRate: 'mockConversionRate:mockState', conversionRate: 'mockConversionRate:mockState',
currentCurrency: 'mockCurrentCurrency:mockState', currentCurrency: 'mockCurrentCurrency:mockState',
nativeCurrency: 'mockNativeCurrency:mockState', nativeCurrency: 'mockNativeCurrency:mockState',
balanceIsCached: 'mockBalanceIsCached:mockState',
}) })
}) })

@ -22,6 +22,7 @@ module.exports = {
'name': 'Send Account 4', 'name': 'Send Account 4',
}, },
}, },
'cachedBalances': {},
'currentBlockGasLimit': '0x4c1878', 'currentBlockGasLimit': '0x4c1878',
'currentCurrency': 'USD', 'currentCurrency': 'USD',
'conversionRate': 1200.88200327, 'conversionRate': 1200.88200327,

@ -20,6 +20,7 @@ export default class Tooltip extends PureComponent {
arrow: PropTypes.bool, arrow: PropTypes.bool,
children: PropTypes.node, children: PropTypes.node,
containerClassName: PropTypes.string, containerClassName: PropTypes.string,
disabled: PropTypes.bool,
onHidden: PropTypes.func, onHidden: PropTypes.func,
position: PropTypes.oneOf([ position: PropTypes.oneOf([
'top', 'top',
@ -33,10 +34,11 @@ export default class Tooltip extends PureComponent {
title: PropTypes.string, title: PropTypes.string,
trigger: PropTypes.any, trigger: PropTypes.any,
wrapperClassName: PropTypes.string, wrapperClassName: PropTypes.string,
style: PropTypes.object,
} }
render () { render () {
const {arrow, children, containerClassName, position, size, title, trigger, onHidden, wrapperClassName } = this.props const {arrow, children, containerClassName, disabled, position, size, title, trigger, onHidden, wrapperClassName, style } = this.props
if (!title) { if (!title) {
return ( return (
@ -50,6 +52,7 @@ export default class Tooltip extends PureComponent {
<div className={wrapperClassName}> <div className={wrapperClassName}>
<ReactTippy <ReactTippy
className={containerClassName} className={containerClassName}
disabled={disabled}
title={title} title={title}
position={position} position={position}
trigger={trigger} trigger={trigger}
@ -57,6 +60,7 @@ export default class Tooltip extends PureComponent {
size={size} size={size}
arrow={arrow} arrow={arrow}
onHidden={onHidden} onHidden={onHidden}
style={style}
> >
{children} {children}
</ReactTippy> </ReactTippy>

@ -17,6 +17,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
position: relative;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
align-items: center; align-items: center;
@ -26,6 +27,10 @@
} }
} }
&__primary-container {
display: flex;
}
&__primary-balance { &__primary-balance {
font-size: 1.5rem; font-size: 1.5rem;
@ -36,6 +41,19 @@
} }
} }
&__cached-star {
margin-left: 4px;
}
&__cached-balance, &__cached-star {
color: $web-orange;
}
&__cached-secondary-balance {
color: rgba(220, 153, 18, 0.6901960784313725);
font-size: 1.15rem;
}
&__secondary-balance { &__secondary-balance {
font-size: 1.15rem; font-size: 1.15rem;
color: #a0a0a0; color: #a0a0a0;

@ -1,11 +1,13 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames'
import Button from '../button' import Button from '../button'
import Identicon from '../identicon' import Identicon from '../identicon'
import TokenBalance from '../token-balance' import TokenBalance from '../token-balance'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { SEND_ROUTE } from '../../routes' import { SEND_ROUTE } from '../../routes'
import { PRIMARY, SECONDARY } from '../../constants/common' import { PRIMARY, SECONDARY } from '../../constants/common'
import Tooltip from '../tooltip-v2'
export default class TransactionViewBalance extends PureComponent { export default class TransactionViewBalance extends PureComponent {
static contextTypes = { static contextTypes = {
@ -19,10 +21,11 @@ export default class TransactionViewBalance extends PureComponent {
network: PropTypes.string, network: PropTypes.string,
balance: PropTypes.string, balance: PropTypes.string,
assetImage: PropTypes.string, assetImage: PropTypes.string,
balanceIsCached: PropTypes.bool,
} }
renderBalance () { renderBalance () {
const { selectedToken, balance } = this.props const { selectedToken, balance, balanceIsCached } = this.props
return selectedToken return selectedToken
? ( ? (
@ -34,20 +37,34 @@ export default class TransactionViewBalance extends PureComponent {
/> />
</div> </div>
) : ( ) : (
<Tooltip position="top" title={this.context.t('balanceOutdated')} disabled={!balanceIsCached}>
<div className="transaction-view-balance__balance"> <div className="transaction-view-balance__balance">
<div className="transaction-view-balance__primary-container">
<UserPreferencedCurrencyDisplay <UserPreferencedCurrencyDisplay
className="transaction-view-balance__primary-balance" className={classnames('transaction-view-balance__primary-balance', {
'transaction-view-balance__cached-balance': balanceIsCached,
})}
value={balance} value={balance}
type={PRIMARY} type={PRIMARY}
ethNumberOfDecimals={4} ethNumberOfDecimals={4}
hideTitle={true}
/> />
{
balanceIsCached ? <span className="transaction-view-balance__cached-star">*</span> : null
}
</div>
<UserPreferencedCurrencyDisplay <UserPreferencedCurrencyDisplay
className="transaction-view-balance__secondary-balance" className={classnames({
'transaction-view-balance__cached-secondary-balance': balanceIsCached,
'transaction-view-balance__secondary-balance': !balanceIsCached,
})}
value={balance} value={balance}
type={SECONDARY} type={SECONDARY}
ethNumberOfDecimals={4} ethNumberOfDecimals={4}
hideTitle={true}
/> />
</div> </div>
</Tooltip>
) )
} }

@ -8,6 +8,7 @@ import {
getNativeCurrency, getNativeCurrency,
getSelectedTokenAssetImage, getSelectedTokenAssetImage,
getMetaMaskAccounts, getMetaMaskAccounts,
isBalanceCached,
} from '../../selectors' } from '../../selectors'
import { showModal } from '../../actions' import { showModal } from '../../actions'
@ -24,6 +25,7 @@ const mapStateToProps = state => {
balance, balance,
nativeCurrency: getNativeCurrency(state), nativeCurrency: getNativeCurrency(state),
assetImage: getSelectedTokenAssetImage(state), assetImage: getSelectedTokenAssetImage(state),
balanceIsCached: isBalanceCached(state),
} }
} }

@ -10,6 +10,7 @@ export default class UserPreferencedCurrencyDisplay extends PureComponent {
value: PropTypes.string, value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideLabel: PropTypes.bool, hideLabel: PropTypes.bool,
hideTitle: PropTypes.bool,
style: PropTypes.object, style: PropTypes.object,
showEthLogo: PropTypes.bool, showEthLogo: PropTypes.bool,
ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

@ -24,6 +24,10 @@
position: relative; position: relative;
} }
&__tooltip-wrapper {
left: -10px;
}
&__account-balances { &__account-balances {
height: auto; height: auto;
border: none; border: none;
@ -34,6 +38,24 @@
position: relative; position: relative;
} }
&__primary-cached-container {
display: flex;
}
&__cached-star {
margin-left: 4px;
}
&__cached-balances {
div:first-of-type {
color: $web-orange;
}
div:last-of-type {
color: rgba(220, 153, 18, 0.6901960784313725)
}
}
&__account-name { &__account-name {
font-size: 16px; font-size: 16px;
margin-left: 8px; margin-left: 8px;
@ -52,6 +74,13 @@
font-size: 12px; font-size: 12px;
} }
&__balance-flag {
position: absolute;
top: 3px;
left: -8px;
color: $curious-blue;
}
&__account-primary-balance { &__account-primary-balance {
color: $scorpion; color: $scorpion;
border: none; border: none;

@ -59,6 +59,7 @@ $oslo-gray: #8C8E94;
$polar: #fafcfe; $polar: #fafcfe;
$blizzard-blue: #bfdef3; $blizzard-blue: #bfdef3;
$mischka: #dddee9; $mischka: #dddee9;
$web-orange: #f2a202;
/* /*
Z-Indicies Z-Indicies

@ -37,6 +37,7 @@ const selectors = {
getMetaMaskAccounts, getMetaMaskAccounts,
getCurrentEthBalance, getCurrentEthBalance,
getNetworkIdentifier, getNetworkIdentifier,
isBalanceCached,
} }
module.exports = selectors module.exports = selectors
@ -62,7 +63,7 @@ function getSelectedIdentity (state) {
function getMetaMaskAccounts (state) { function getMetaMaskAccounts (state) {
const currentAccounts = state.metamask.accounts const currentAccounts = state.metamask.accounts
const cachedBalances = state.metamask.cachedBalances const cachedBalances = state.metamask.cachedBalances[state.metamask.network]
const selectedAccounts = {} const selectedAccounts = {}
Object.keys(currentAccounts).forEach(accountID => { Object.keys(currentAccounts).forEach(accountID => {
@ -70,7 +71,7 @@ function getMetaMaskAccounts (state) {
if (account && account.balance === null || account.balance === undefined) { if (account && account.balance === null || account.balance === undefined) {
selectedAccounts[accountID] = { selectedAccounts[accountID] = {
...account, ...account,
balance: cachedBalances[accountID], balance: cachedBalances && cachedBalances[accountID],
} }
} else { } else {
selectedAccounts[accountID] = account selectedAccounts[accountID] = account
@ -79,6 +80,20 @@ function getMetaMaskAccounts (state) {
return selectedAccounts return selectedAccounts
} }
function isBalanceCached (state) {
const selectedAccountBalance = state.metamask.accounts[getSelectedAddress(state)].balance
const cachedBalance = getSelectedAccountCachedBalance(state)
return Boolean(!selectedAccountBalance && cachedBalance)
}
function getSelectedAccountCachedBalance (state) {
const cachedBalances = state.metamask.cachedBalances[state.metamask.network]
const selectedAddress = getSelectedAddress(state)
return cachedBalances && cachedBalances[selectedAddress]
}
function getSelectedAccount (state) { function getSelectedAccount (state) {
const accounts = getMetaMaskAccounts(state) const accounts = getMetaMaskAccounts(state)
const selectedAddress = getSelectedAddress(state) const selectedAddress = getSelectedAddress(state)

Loading…
Cancel
Save