Skip to content

Commit

Permalink
feat: ux warnings (#3220)
Browse files Browse the repository at this point in the history
* chore: mv Toolbar to a directory

* refactor: clean up Toolbar

* refactor: simplify Toolbar Caption

* feat: warn on price impact in Summary

* refactor: add computeRealizedPriceImpact util
  • Loading branch information
zzmp authored Feb 2, 2022
1 parent 800b5e0 commit e19e849
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 184 deletions.
19 changes: 12 additions & 7 deletions src/lib/components/Swap/Summary/Details.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { t } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_MEDIUM } from 'constants/misc'
import { useAtom } from 'jotai'
import { integratorFeeAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import { Color, ThemedText } from 'lib/theme'
import { useMemo } from 'react'
import { currencyId } from 'utils/currencyId'
import { computeRealizedLPFeePercent } from 'utils/prices'
import { computeRealizedPriceImpact } from 'utils/prices'

import Row from '../../Row'

Expand Down Expand Up @@ -36,21 +37,25 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
const { inputAmount, outputAmount } = trade
const inputCurrency = inputAmount.currency
const outputCurrency = outputAmount.currency
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])

const integrator = window.location.hostname
const [integratorFee] = useAtom(integratorFeeAtom)

const priceImpact = useMemo(() => {
const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
return trade.priceImpact.subtract(realizedLpFeePercent)
}, [trade])

const details = useMemo(() => {
// @TODO(ianlapham): Check that provider fee is even a valid list item
return [
// [t`Liquidity provider fee`, `${swap.lpFee} ${inputSymbol}`],
[t`${integrator} fee`, integratorFee && `${integratorFee} ${currencyId(inputCurrency)}`],
[t`Price impact`, `${priceImpact.toFixed(2)}%`],
[
t`Price impact`,
`${priceImpact.toFixed(2)}%`,
priceImpact >= ALLOWED_PRICE_IMPACT_HIGH
? 'error'
: priceImpact >= ALLOWED_PRICE_IMPACT_MEDIUM
? 'warning'
: undefined,
],
trade.tradeType === TradeType.EXACT_INPUT
? [t`Maximum sent`, `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${inputCurrency.symbol}`]
: [],
Expand Down
17 changes: 12 additions & 5 deletions src/lib/components/Swap/Summary/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_MEDIUM } from 'constants/misc'
import { useAtomValue } from 'jotai/utils'
import { IconButton } from 'lib/components/Button'
import useScrollbar from 'lib/hooks/useScrollbar'
Expand All @@ -9,6 +10,7 @@ import { MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import { Field, independentFieldAtom } from 'lib/state/swap'
import styled, { ThemedText } from 'lib/theme'
import { useMemo, useState } from 'react'
import { computeRealizedPriceImpact } from 'utils/prices'
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'

import ActionButton from '../../ActionButton'
Expand Down Expand Up @@ -83,14 +85,19 @@ interface SummaryDialogProps {
}

export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDialogProps) {
const { inputAmount, outputAmount } = trade
const { inputAmount, outputAmount, executionPrice } = trade
const inputCurrency = inputAmount.currency
const outputCurrency = outputAmount.currency
const price = trade.executionPrice
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])

const independentField = useAtomValue(independentFieldAtom)

const slippageWarning = useMemo(() => allowedSlippage.greaterThan(MIN_HIGH_SLIPPAGE), [allowedSlippage])
const warning = useMemo(() => {
if (priceImpact >= ALLOWED_PRICE_IMPACT_HIGH) return 'error'
if (priceImpact >= ALLOWED_PRICE_IMPACT_MEDIUM) return 'warning'
if (allowedSlippage.greaterThan(MIN_HIGH_SLIPPAGE)) return 'warning'
return
}, [allowedSlippage, priceImpact])

const [confirmedTrade, setConfirmedTrade] = useState(trade)
const doesTradeDiffer = useMemo(
Expand All @@ -114,13 +121,13 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
<SummaryColumn gap={0.75} flex justify="center">
<Summary input={inputAmount} output={outputAmount} usdc={true} />
<ThemedText.Caption>
1 {inputCurrency.symbol} = {price?.toSignificant(6)} {outputCurrency.symbol}
1 {inputCurrency.symbol} = {executionPrice?.toSignificant(6)} {outputCurrency.symbol}
</ThemedText.Caption>
</SummaryColumn>
<Rule />
<Row>
<Row gap={0.5}>
{slippageWarning ? <AlertTriangle color="warning" /> : <Info color="secondary" />}
{warning ? <AlertTriangle color={warning} /> : <Info color="secondary" />}
<ThemedText.Subhead2 color="secondary">
<Trans>Swap details</Trans>
</ThemedText.Subhead2>
Expand Down
172 changes: 0 additions & 172 deletions src/lib/components/Swap/Toolbar.tsx

This file was deleted.

81 changes: 81 additions & 0 deletions src/lib/components/Swap/Toolbar/Caption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core'
import useUSDCPrice from 'hooks/useUSDCPrice'
import { AlertTriangle, Icon, Info, Spinner } from 'lib/icons'
import { ThemedText } from 'lib/theme'
import { ReactNode, useMemo, useState } from 'react'
import { InterfaceTrade } from 'state/routing/types'

import { TextButton } from '../../Button'
import Row from '../../Row'
import RoutingTooltip from './RoutingTooltip'

interface CaptionProps {
icon?: Icon
caption: ReactNode
}

function Caption({ icon: Icon = AlertTriangle, caption }: CaptionProps) {
return (
<>
<Icon color="secondary" />
{caption}
</>
)
}

export function ConnectWallet() {
return <Caption caption={<Trans>Connect wallet to swap</Trans>} />
}
export function UnsupportedNetwork() {
return <Caption caption={<Trans>Unsupported network - switch to another network to swap</Trans>} />
}
export function InsufficientBalance({ currency }: { currency: Currency }) {
return <Caption caption={<Trans>Insufficient {currency?.symbol} balance</Trans>} />
}
export function InsufficientLiquidity() {
return <Caption caption={<Trans>Insufficient liquidity in the pool for your trade</Trans>} />
}
export function Empty() {
return <Caption icon={Info} caption={<Trans>Enter an amount</Trans>} />
}
export function LoadingTrade() {
return <Caption icon={Spinner} caption={<Trans>Fetching best price…</Trans>} />
}

export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
const [flip, setFlip] = useState(true)
const { inputAmount, outputAmount, executionPrice } = trade
const fiatValueInput = useUSDCPrice(inputAmount.currency)
const fiatValueOutput = useUSDCPrice(outputAmount.currency)

const ratio = useMemo(() => {
const [a, b] = flip ? [outputAmount, inputAmount] : [inputAmount, outputAmount]
const priceString = (!flip ? executionPrice : executionPrice?.invert())?.toSignificant(6)

const ratio = `1 ${a.currency.symbol} = ${priceString}} ${b.currency.symbol}`
const usdc = !flip
? fiatValueInput
? ` ($${fiatValueInput.toSignificant(2)})`
: null
: fiatValueOutput
? ` ($${fiatValueOutput.toSignificant(2)})`
: null

return (
<Row gap={0.25} style={{ userSelect: 'text' }}>
{ratio}
{usdc && <ThemedText.Caption color="secondary">{usdc}</ThemedText.Caption>}
</Row>
)
}, [executionPrice, fiatValueInput, fiatValueOutput, flip, inputAmount, outputAmount])

return (
<>
<RoutingTooltip />
<TextButton color="primary" onClick={() => setFlip(!flip)}>
{ratio}
</TextButton>
</>
)
}
14 changes: 14 additions & 0 deletions src/lib/components/Swap/Toolbar/RoutingTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Info } from 'lib/icons'

export default function RoutingTooltip() {
return <Info color="secondary" />
/* TODO(zzmp): Implement post-beta launch.
return (
<Tooltip icon={Info} placement="bottom">
<ThemeProvider>
<ThemedText.Subhead2>TODO: Routing Tooltip</ThemedText.Subhead2>
</ThemeProvider>
</Tooltip>
)
*/
}
Loading

0 comments on commit e19e849

Please sign in to comment.