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:
parent
f15ac091b4
commit
c871e55d82
@ -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} />
|
||||
{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} />
|
||||
{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)}
|
||||
|
Loading…
Reference in New Issue
Block a user