Commit 303fa152 authored by Mike Grabowski's avatar Mike Grabowski Committed by GitHub

feat: show affordance in swap UI when we can't fetch usd quote (#6622)

* initial commit:

* add todo to linear
parent d180aef3
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format' import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import { useMemo } from 'react' import { useMemo } from 'react'
import styled, { useTheme } from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { ThemedText } from '../../theme' import { warningSeverity } from 'utils/prices'
import { warningSeverity } from '../../utils/prices'
const FiatLoadingBubble = styled(LoadingBubble)` const FiatLoadingBubble = styled(LoadingBubble)`
border-radius: 4px; border-radius: 4px;
...@@ -21,36 +19,40 @@ export function FiatValue({ ...@@ -21,36 +19,40 @@ export function FiatValue({
fiatValue, fiatValue,
priceImpact, priceImpact,
}: { }: {
fiatValue?: { data?: number; isLoading: boolean } fiatValue: { data?: number; isLoading: boolean }
priceImpact?: Percent priceImpact?: Percent
}) { }) {
const theme = useTheme()
const priceImpactColor = useMemo(() => { const priceImpactColor = useMemo(() => {
if (!priceImpact) return undefined if (!priceImpact) return undefined
if (priceImpact.lessThan('0')) return theme.accentSuccess if (priceImpact.lessThan('0')) return 'accentSuccess'
const severity = warningSeverity(priceImpact) const severity = warningSeverity(priceImpact)
if (severity < 1) return theme.textTertiary if (severity < 1) return 'textTertiary'
if (severity < 3) return theme.deprecated_yellow1 if (severity < 3) return 'deprecated_yellow1'
return theme.accentFailure return 'accentFailure'
}, [priceImpact, theme.accentSuccess, theme.accentFailure, theme.textTertiary, theme.deprecated_yellow1]) }, [priceImpact])
if (fiatValue.isLoading) {
return <FiatLoadingBubble />
}
return ( return (
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}> <Row gap="sm">
{fiatValue?.isLoading ? ( <ThemedText.BodySmall>
<FiatLoadingBubble /> {fiatValue.data ? (
) : ( formatNumber(fiatValue.data, NumberType.FiatTokenPrice)
<div> ) : (
{fiatValue?.data ? formatNumber(fiatValue.data, NumberType.FiatTokenPrice) : undefined} <MouseoverTooltip text={<Trans>Not enough liquidity to show accurate USD value.</Trans>}>-</MouseoverTooltip>
{priceImpact && ( )}
<span style={{ color: priceImpactColor }}> </ThemedText.BodySmall>
{' '} {priceImpact && (
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}> <ThemedText.BodySmall color={priceImpactColor}>
(<Trans>{formatPriceImpact(priceImpact)}</Trans>) <MouseoverTooltip
</MouseoverTooltip> text={<Trans>The estimated difference between the USD values of input and output amounts.</Trans>}
</span> >
)} (<Trans>{formatPriceImpact(priceImpact)}</Trans>)
</div> </MouseoverTooltip>
</ThemedText.BodySmall>
)} )}
</ThemedText.DeprecatedBody> </Row>
) )
} }
...@@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps { ...@@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps {
pair?: Pair | null pair?: Pair | null
hideInput?: boolean hideInput?: boolean
otherCurrency?: Currency | null otherCurrency?: Currency | null
fiatValue: { data?: number; isLoading: boolean } fiatValue?: { data?: number; isLoading: boolean }
priceImpact?: Percent priceImpact?: Percent
id: string id: string
showCommonBases?: boolean showCommonBases?: boolean
...@@ -308,7 +308,7 @@ export default function SwapCurrencyInputPanel({ ...@@ -308,7 +308,7 @@ export default function SwapCurrencyInputPanel({
<FiatRow> <FiatRow>
<RowBetween> <RowBetween>
<LoadingOpacityContainer $loading={loading}> <LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} /> {fiatValue && <FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />}
</LoadingOpacityContainer> </LoadingOpacityContainer>
{account ? ( {account ? (
<RowFixed style={{ height: '17px' }}> <RowFixed style={{ height: '17px' }}>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics' import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk' import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
...@@ -183,7 +183,6 @@ interface CurrencyInputPanelProps { ...@@ -183,7 +183,6 @@ interface CurrencyInputPanelProps {
hideInput?: boolean hideInput?: boolean
otherCurrency?: Currency | null otherCurrency?: Currency | null
fiatValue?: { data?: number; isLoading: boolean } fiatValue?: { data?: number; isLoading: boolean }
priceImpact?: Percent
id: string id: string
showCommonBases?: boolean showCommonBases?: boolean
showCurrencyAmount?: boolean showCurrencyAmount?: boolean
...@@ -207,7 +206,6 @@ export default function CurrencyInputPanel({ ...@@ -207,7 +206,6 @@ export default function CurrencyInputPanel({
disableNonToken, disableNonToken,
renderBalance, renderBalance,
fiatValue, fiatValue,
priceImpact,
hideBalance = false, hideBalance = false,
pair = null, // used for double token logo pair = null, // used for double token logo
hideInput = false, hideInput = false,
...@@ -293,7 +291,7 @@ export default function CurrencyInputPanel({ ...@@ -293,7 +291,7 @@ export default function CurrencyInputPanel({
<FiatRow> <FiatRow>
<RowBetween> <RowBetween>
<LoadingOpacityContainer $loading={loading}> <LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} /> {fiatValue && <FiatValue fiatValue={fiatValue} />}
</LoadingOpacityContainer> </LoadingOpacityContainer>
{account ? ( {account ? (
<RowFixed style={{ height: '17px' }}> <RowFixed style={{ height: '17px' }}>
......
...@@ -50,6 +50,9 @@ function useETHValue(currencyAmount?: CurrencyAmount<Currency>): { ...@@ -50,6 +50,9 @@ function useETHValue(currencyAmount?: CurrencyAmount<Currency>): {
return { data: price.quote(currencyAmount), isLoading: false } return { data: price.quote(currencyAmount), isLoading: false }
} }
// TODO(WEB-2095): This hook should early return `null` when `currencyAmount` is undefined. Otherwise,
// it is not possible to differentiate between a loading state and a state where `currencyAmount`
// is undefined
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): { export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): {
data?: number data?: number
isLoading: boolean isLoading: boolean
......
...@@ -287,8 +287,11 @@ export function Swap({ ...@@ -287,8 +287,11 @@ export function Swap({
}, },
[independentField, parsedAmount, showWrap, trade] [independentField, parsedAmount, showWrap, trade]
) )
const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT]) const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT])
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT]) const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT])
const showFiatValueInput = Boolean(parsedAmounts[Field.INPUT])
const showFiatValueOutput = Boolean(parsedAmounts[Field.OUTPUT])
const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo( const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
() => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.LOADING === tradeState && Boolean(trade)], () => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.LOADING === tradeState && Boolean(trade)],
...@@ -580,10 +583,10 @@ export function Swap({ ...@@ -580,10 +583,10 @@ export function Swap({
currency={currencies[Field.INPUT] ?? null} currency={currencies[Field.INPUT] ?? null}
onUserInput={handleTypeInput} onUserInput={handleTypeInput}
onMax={handleMaxInput} onMax={handleMaxInput}
fiatValue={fiatValueInput} fiatValue={showFiatValueInput ? fiatValueInput : undefined}
onCurrencySelect={handleInputSelect} onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]} otherCurrency={currencies[Field.OUTPUT]}
showCommonBases={true} showCommonBases
id={InterfaceSectionName.CURRENCY_INPUT_PANEL} id={InterfaceSectionName.CURRENCY_INPUT_PANEL}
loading={independentField === Field.OUTPUT && routeIsSyncing} loading={independentField === Field.OUTPUT && routeIsSyncing}
/> />
...@@ -621,12 +624,12 @@ export function Swap({ ...@@ -621,12 +624,12 @@ export function Swap({
label={independentField === Field.INPUT && !showWrap ? <Trans>To (at least)</Trans> : <Trans>To</Trans>} label={independentField === Field.INPUT && !showWrap ? <Trans>To (at least)</Trans> : <Trans>To</Trans>}
showMaxButton={false} showMaxButton={false}
hideBalance={false} hideBalance={false}
fiatValue={fiatValueOutput} fiatValue={showFiatValueOutput ? fiatValueOutput : undefined}
priceImpact={stablecoinPriceImpact} priceImpact={stablecoinPriceImpact}
currency={currencies[Field.OUTPUT] ?? null} currency={currencies[Field.OUTPUT] ?? null}
onCurrencySelect={handleOutputSelect} onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]} otherCurrency={currencies[Field.INPUT]}
showCommonBases={true} showCommonBases
id={InterfaceSectionName.CURRENCY_OUTPUT_PANEL} id={InterfaceSectionName.CURRENCY_OUTPUT_PANEL}
loading={independentField === Field.INPUT && routeIsSyncing} loading={independentField === Field.INPUT && routeIsSyncing}
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment