fix: clean up balance summaries (#4984)

* fix: clean up balance summaries

* fix: nits

* fix: off-chain balances

* fix: only show token under details

* fix: consolidate formatting
This commit is contained in:
Zach Pomerantz 2022-10-25 13:46:25 -07:00 committed by GitHub
parent f15ac091b4
commit c871e55d82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 143 deletions

@ -1,10 +1,13 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { formatToDecimal } from 'analytics/utils'
import CurrencyLogo from 'components/CurrencyLogo'
import { validateUrlChainParam } from 'graphql/data/util'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import { useParams } from 'react-router-dom'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { StyledInternalLink } from 'theme'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
@ -29,11 +32,11 @@ const BalancesCard = styled.div`
display: flex;
}
`
const TotalBalanceSection = styled.div`
const BalanceSection = styled.div`
height: fit-content;
width: 100%;
`
const TotalBalance = styled.div`
const BalanceRow = styled.div`
align-items: center;
display: flex;
flex-direction: row;
@ -42,99 +45,52 @@ const TotalBalance = styled.div`
line-height: 28px;
margin-top: 12px;
`
const TotalBalanceItem = styled.div`
const BalanceItem = styled.div`
display: flex;
`
const BalanceRowLink = styled(StyledInternalLink)`
const BalanceLink = styled(StyledInternalLink)`
color: unset;
`
function BalanceRow({ currency, formattedBalance, usdValue, href }: BalanceRowData) {
const content = (
<TotalBalance key={currency.wrapped.address}>
<TotalBalanceItem>
<CurrencyLogo currency={currency} />
&nbsp;{formattedBalance} {currency?.symbol}
</TotalBalanceItem>
<TotalBalanceItem>{formatDollar({ num: usdValue === 0 ? undefined : usdValue, isPrice: true })}</TotalBalanceItem>
</TotalBalance>
export function useFormatBalance(balance: CurrencyAmount<Currency> | undefined) {
return useMemo(
() => (balance ? formatToDecimal(balance, Math.min(balance.currency.decimals, 2)) : undefined),
[balance]
)
if (href) {
return <BalanceRowLink to={href}>{content}</BalanceRowLink>
}
return content
}
interface BalanceRowData {
currency: Currency
formattedBalance: number
usdValue: number | undefined
href?: string
}
export interface BalanceSummaryProps {
tokenAmount: CurrencyAmount<Token> | undefined
nativeCurrencyAmount: CurrencyAmount<Currency> | undefined
isNative: boolean
export function useFormatUsdValue(usdValue: CurrencyAmount<Token> | null) {
return useMemo(() => {
const float = usdValue ? currencyAmountToPreciseFloat(usdValue) : undefined
if (!float) return undefined
return formatDollar({ num: float, isPrice: true })
}, [usdValue])
}
export default function BalanceSummary({ tokenAmount, nativeCurrencyAmount, isNative }: BalanceSummaryProps) {
const balanceUsdValue = useStablecoinValue(tokenAmount)
const nativeBalanceUsdValue = useStablecoinValue(nativeCurrencyAmount)
const { chainName } = useParams<{ chainName?: string }>()
const pageChainName = validateUrlChainParam(chainName).toLowerCase()
const tokenIsWrappedNative =
tokenAmount &&
nativeCurrencyAmount &&
tokenAmount.currency.address.toLowerCase() === nativeCurrencyAmount.currency.wrapped.address.toLowerCase()
if (
(!tokenAmount && !nativeCurrencyAmount) ||
(!tokenAmount && !tokenIsWrappedNative && !isNative) ||
(!isNative && !tokenIsWrappedNative && tokenAmount?.equalTo(0)) ||
(isNative && tokenAmount?.equalTo(0) && nativeCurrencyAmount?.equalTo(0))
) {
return null
}
const showNative = tokenIsWrappedNative || isNative
const currencies = []
if (tokenAmount) {
const tokenData: BalanceRowData = {
currency: tokenAmount.currency,
formattedBalance: formatToDecimal(tokenAmount, Math.min(tokenAmount.currency.decimals, 2)),
usdValue: balanceUsdValue ? currencyAmountToPreciseFloat(balanceUsdValue) : undefined,
}
if (isNative) {
tokenData.href = `/tokens/${pageChainName}/${tokenAmount.currency.address}`
}
currencies.push(tokenData)
}
if (showNative && nativeCurrencyAmount) {
const nativeData: BalanceRowData = {
currency: nativeCurrencyAmount.currency,
formattedBalance: formatToDecimal(nativeCurrencyAmount, Math.min(nativeCurrencyAmount.currency.decimals, 2)),
usdValue: nativeBalanceUsdValue ? currencyAmountToPreciseFloat(nativeBalanceUsdValue) : undefined,
}
if (isNative) {
currencies.unshift(nativeData)
} else {
nativeData.href = `/tokens/${pageChainName}/NATIVE`
currencies.push(nativeData)
}
}
export default function BalanceSummary({ token }: { token: Currency }) {
const { account } = useWeb3React()
const balance = useCurrencyBalance(account, token)
const formattedBalance = useFormatBalance(balance)
const usdValue = useStablecoinValue(balance)
const formattedUsdValue = useFormatUsdValue(usdValue)
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
if (!account || !balance) return null
return (
<BalancesCard>
<TotalBalanceSection>
<BalanceSection>
<Trans>Your balance</Trans>
{currencies.map((props, i) => (
<BalanceRow {...props} key={props.currency.wrapped.address + i} />
))}
</TotalBalanceSection>
<BalanceLink to={`/tokens/${chain}/${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
<BalanceRow>
<BalanceItem>
<CurrencyLogo currency={token} />
&nbsp;{formattedBalance} {token.symbol}
</BalanceItem>
<BalanceItem>{formattedUsdValue}</BalanceItem>
</BalanceRow>
</BalanceLink>
</BalanceSection>
</BalancesCard>
)
}

@ -1,11 +1,14 @@
import { Trans } from '@lingui/macro'
import { formatToDecimal } from 'analytics/utils'
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import styled from 'styled-components/macro'
import { StyledInternalLink } from 'theme'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
import { BalanceSummaryProps } from './BalanceSummary'
import { useFormatBalance, useFormatUsdValue } from './BalanceSummary'
const Wrapper = styled.div`
align-content: center;
@ -41,7 +44,7 @@ const BalanceValue = styled.div`
display: flex;
gap: 8px;
`
const BalanceTotal = styled.div`
const Balance = styled.div`
align-items: center;
display: flex;
flex-direction: row;
@ -78,51 +81,28 @@ const SwapButton = styled(StyledInternalLink)`
max-width: 100vw;
`
export default function MobileBalanceSummaryFooter({
tokenAmount,
nativeCurrencyAmount,
isNative,
tokenAddress,
}: BalanceSummaryProps & { tokenAddress: string }) {
const balanceUsdValue = useStablecoinValue(tokenAmount)
const nativeBalanceUsdValue = useStablecoinValue(nativeCurrencyAmount)
const formattedBalance = tokenAmount
? formatToDecimal(tokenAmount, Math.min(tokenAmount.currency.decimals, 2))
: undefined
const balanceUsd = balanceUsdValue ? currencyAmountToPreciseFloat(balanceUsdValue) : undefined
const formattedNativeBalance = nativeCurrencyAmount
? formatToDecimal(nativeCurrencyAmount, Math.min(nativeCurrencyAmount.currency.decimals, 2))
: undefined
const nativeBalanceUsd = nativeBalanceUsdValue ? currencyAmountToPreciseFloat(nativeBalanceUsdValue) : undefined
export default function MobileBalanceSummaryFooter({ token }: { token: Currency }) {
const { account } = useWeb3React()
const balance = useCurrencyBalance(account, token)
const formattedBalance = useFormatBalance(balance)
const usdValue = useStablecoinValue(balance)
const formattedUsdValue = useFormatUsdValue(usdValue)
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
return (
<Wrapper>
{Boolean(formattedBalance !== undefined && !isNative && tokenAmount?.greaterThan(0)) && (
{Boolean(account && balance) && (
<BalanceInfo>
<Trans>Your {tokenAmount?.currency?.symbol} balance</Trans>
<BalanceTotal>
<Trans>Your {token.symbol} balance</Trans>
<Balance>
<BalanceValue>
{formattedBalance} {tokenAmount?.currency?.symbol}
{formattedBalance} {token.symbol}
</BalanceValue>
<FiatValue>{formatDollar({ num: balanceUsd, isPrice: true })}</FiatValue>
</BalanceTotal>
<FiatValue>{formattedUsdValue}</FiatValue>
</Balance>
</BalanceInfo>
)}
{Boolean(isNative && nativeCurrencyAmount?.greaterThan(0)) && (
<BalanceInfo>
<Trans>Your {nativeCurrencyAmount?.currency?.symbol} balance</Trans>
<BalanceTotal>
<BalanceValue>
{formattedNativeBalance} {nativeCurrencyAmount?.currency?.symbol}
</BalanceValue>
<FiatValue>{formatDollar({ num: nativeBalanceUsd, isPrice: true })}</FiatValue>
</BalanceTotal>
</BalanceInfo>
)}
<SwapButton to={`/swap?outputCurrency=${tokenAddress}`}>
<SwapButton to={`/swap?chainName=${chain}&outputCurrency=${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
<Trans>Swap</Trans>
</SwapButton>
</Wrapper>

@ -117,6 +117,7 @@ export function useCurrencyBalances(
[currencies]
)
const { chainId } = useWeb3React()
const tokenBalances = useTokenBalances(account, tokens)
const containsETH: boolean = useMemo(() => currencies?.some((currency) => currency?.isNative) ?? false, [currencies])
const ethBalance = useNativeCurrencyBalances(useMemo(() => (containsETH ? [account] : []), [containsETH, account]))
@ -124,12 +125,12 @@ export function useCurrencyBalances(
return useMemo(
() =>
currencies?.map((currency) => {
if (!account || !currency) return undefined
if (!account || !currency || currency.chainId !== chainId) return undefined
if (currency.isToken) return tokenBalances[currency.address]
if (currency.isNative) return ethBalance[account]
return undefined
}) ?? [],
[account, currencies, ethBalance, tokenBalances]
[account, chainId, currencies, ethBalance, tokenBalances]
)
}

@ -1,5 +1,4 @@
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
import { filterTimeAtom } from 'components/Tokens/state'
@ -28,14 +27,12 @@ import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { useAtomValue } from 'jotai/utils'
import { useTokenFromQuery } from 'lib/hooks/useCurrency'
import useCurrencyBalance, { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
import { useCallback, useState, useTransition } from 'react'
import { ArrowLeft } from 'react-feather'
import { useNavigate, useParams } from 'react-router-dom'
export default function TokenDetails() {
const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>()
const { account } = useWeb3React()
const chain = validateUrlChainParam(chainName)
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const nativeCurrency = nativeOnChain(pageChainId)
@ -48,10 +45,6 @@ export default function TokenDetails() {
)
const queryToken = useTokenFromQuery(isNative ? undefined : { ...tokenQueryData, chainId: pageChainId })
const token = isNative ? nativeCurrency : queryToken
const tokenQueryAddress = isNative ? nativeCurrency.wrapped.address : tokenAddress
const nativeCurrencyBalance = useCurrencyBalance(account, nativeCurrency)
const tokenBalance = useTokenBalance(account, token?.wrapped)
const tokenWarning = tokenAddress ? checkWarning(tokenAddress) : null
const isBlockedToken = tokenWarning?.canProceed === false
@ -141,21 +134,14 @@ export default function TokenDetails() {
onReviewSwapClick={onReviewSwapClick}
/>
{tokenWarning && <TokenSafetyMessage tokenAddress={tokenAddress ?? ''} warning={tokenWarning} />}
<BalanceSummary tokenAmount={tokenBalance} nativeCurrencyAmount={nativeCurrencyBalance} isNative={isNative} />
{token && <BalanceSummary token={token} />}
</RightPanel>
{token && <MobileBalanceSummaryFooter token={token} />}
{tokenQueryAddress && (
<MobileBalanceSummaryFooter
tokenAmount={tokenBalance}
tokenAddress={tokenQueryAddress}
nativeCurrencyAmount={nativeCurrencyBalance}
isNative={isNative}
/>
)}
{tokenQueryAddress && (
{tokenAddress && (
<TokenSafetyModal
isOpen={isBlockedToken || !!continueSwap}
tokenAddress={tokenQueryAddress}
tokenAddress={tokenAddress}
onContinue={() => onResolveSwap(true)}
onBlocked={() => navigate(-1)}
onCancel={() => onResolveSwap(false)}