improvement(advanced): always show advanced (#890)
* always show advanced * fix test * always show the price, less jitter * show tokens in price field * fix the dropdown sticking around when switching between swap/send * lint * fix ios scrolling the modal body into the viewport bug * don't use react-spring for simple slide animation * safer price impact constants
This commit is contained in:
parent
aac7268dc8
commit
2c4f4092d8
@ -37,7 +37,6 @@ describe('Swap', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
|
||||
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
|
||||
cy.get('#show-advanced').click()
|
||||
cy.get('#swap-button').click()
|
||||
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
|
||||
})
|
||||
|
@ -18,6 +18,7 @@ const StyledDialogOverlay = styled(({ mobile, ...rest }) => <AnimatedDialogOverl
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
${({ mobile }) =>
|
||||
mobile &&
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Trade, TradeType } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { ChevronUp, ChevronRight } from 'react-feather'
|
||||
import { Text, Flex } from 'rebass'
|
||||
import { ChevronRight } from 'react-feather'
|
||||
import { Flex } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { CursorPointer, TYPE } from '../../theme'
|
||||
import { TYPE } from '../../theme'
|
||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { SectionBreak } from './styleds'
|
||||
@ -67,29 +67,21 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
|
||||
|
||||
export interface AdvancedSwapDetailsProps {
|
||||
trade?: Trade
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export function AdvancedSwapDetails({ trade, onDismiss }: AdvancedSwapDetailsProps) {
|
||||
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
|
||||
const showRoute = trade?.route?.path?.length > 2
|
||||
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
<CursorPointer>
|
||||
<RowBetween onClick={onDismiss} padding={'8px 20px'}>
|
||||
<Text fontSize={16} color={theme.text2} fontWeight={500} style={{ userSelect: 'none' }}>
|
||||
Hide Advanced
|
||||
</Text>
|
||||
<ChevronUp color={theme.text2} />
|
||||
</RowBetween>
|
||||
</CursorPointer>
|
||||
<SectionBreak />
|
||||
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
|
||||
{trade?.route?.path?.length > 2 && <SectionBreak />}
|
||||
{trade?.route?.path?.length > 2 && (
|
||||
<AutoColumn style={{ padding: '0 20px' }}>
|
||||
{showRoute && <SectionBreak />}
|
||||
{showRoute && (
|
||||
<AutoColumn style={{ padding: '0 24px' }}>
|
||||
<RowFixed>
|
||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||
Route
|
||||
|
@ -1,35 +1,30 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { ChevronDown } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { CursorPointer } from '../../theme'
|
||||
import { RowBetween } from '../Row'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
|
||||
import { AdvancedDropdown } from './styleds'
|
||||
|
||||
export default function AdvancedSwapDetailsDropdown({
|
||||
showAdvanced,
|
||||
setShowAdvanced,
|
||||
...rest
|
||||
}: Omit<AdvancedSwapDetailsProps, 'onDismiss'> & {
|
||||
showAdvanced: boolean
|
||||
setShowAdvanced: (showAdvanced: boolean) => void
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
|
||||
padding-top: calc(16px + 2rem);
|
||||
padding-bottom: 20px;
|
||||
margin-top: -2rem;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
background-color: ${({ theme }) => theme.advancedBG};
|
||||
z-index: -1;
|
||||
|
||||
transform: ${({ show }) => (show ? 'translateY(0%)' : 'translateY(-100%)')};
|
||||
transition: transform 300ms ease-in-out;
|
||||
`
|
||||
|
||||
export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
|
||||
const lastTrade = useLast(trade)
|
||||
|
||||
return (
|
||||
<AdvancedDropdown>
|
||||
{showAdvanced ? (
|
||||
<AdvancedSwapDetails {...rest} onDismiss={() => setShowAdvanced(false)} />
|
||||
) : (
|
||||
<CursorPointer>
|
||||
<RowBetween onClick={() => setShowAdvanced(true)} padding={'8px 20px'} id="show-advanced">
|
||||
<Text fontSize={16} fontWeight={500} style={{ userSelect: 'none' }}>
|
||||
Show Advanced
|
||||
</Text>
|
||||
<ChevronDown color={theme.text2} />
|
||||
</RowBetween>
|
||||
</CursorPointer>
|
||||
)}
|
||||
</AdvancedDropdown>
|
||||
<AdvancedDetailsFooter show={Boolean(trade)}>
|
||||
<AdvancedSwapDetails {...rest} trade={lastTrade} />
|
||||
</AdvancedDetailsFooter>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Trade } from '@uniswap/sdk'
|
||||
import { Price, Token } from '@uniswap/sdk'
|
||||
import { useContext } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@ -7,20 +7,19 @@ import { ThemeContext } from 'styled-components'
|
||||
import { StyledBalanceMaxMini } from './styleds'
|
||||
|
||||
interface TradePriceProps {
|
||||
trade?: Trade
|
||||
price?: Price
|
||||
inputToken?: Token
|
||||
outputToken?: Token
|
||||
showInverted: boolean
|
||||
setShowInverted: (showInverted: boolean) => void
|
||||
}
|
||||
|
||||
export default function TradePrice({ trade, showInverted, setShowInverted }: TradePriceProps) {
|
||||
export default function TradePrice({ price, inputToken, outputToken, showInverted, setShowInverted }: TradePriceProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const inputToken = trade?.inputAmount?.token
|
||||
const outputToken = trade?.outputAmount?.token
|
||||
|
||||
const price = showInverted
|
||||
? trade?.executionPrice?.toSignificant(6)
|
||||
: trade?.executionPrice?.invert()?.toSignificant(6)
|
||||
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
|
||||
|
||||
const show = Boolean(inputToken && outputToken)
|
||||
const label = showInverted
|
||||
? `${outputToken?.symbol} per ${inputToken?.symbol}`
|
||||
: `${inputToken?.symbol} per ${outputToken?.symbol}`
|
||||
@ -32,10 +31,16 @@ export default function TradePrice({ trade, showInverted, setShowInverted }: Tra
|
||||
color={theme.text2}
|
||||
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
||||
>
|
||||
{price && `${price} ${label}`}
|
||||
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
|
||||
<Repeat size={14} />
|
||||
</StyledBalanceMaxMini>
|
||||
{show ? (
|
||||
<>
|
||||
{formattedPrice ?? '-'} {label}
|
||||
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
|
||||
<Repeat size={14} />
|
||||
</StyledBalanceMaxMini>
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
@ -21,19 +21,6 @@ export const ArrowWrapper = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
export const AdvancedDropdown = styled.div`
|
||||
padding-top: calc(10px + 2rem);
|
||||
padding-bottom: 10px;
|
||||
margin-top: -2rem;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
background-color: ${({ theme }) => theme.advancedBG};
|
||||
z-index: -1;
|
||||
`
|
||||
|
||||
export const SectionBreak = styled.div`
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
@ -71,7 +58,7 @@ export const StyledNumerical = styled(NumericalInput)`
|
||||
color: ${({ theme }) => theme.text4};
|
||||
}
|
||||
`
|
||||
export const StyledBalanceMaxMini = styled.button<{ active?: boolean }>`
|
||||
export const StyledBalanceMaxMini = styled.button`
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
|
@ -151,13 +151,12 @@ export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000))
|
||||
export const BIPS_BASE = JSBI.BigInt(10000)
|
||||
// used for warning states
|
||||
export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BIPS_BASE) // 1%
|
||||
export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5%
|
||||
export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10%
|
||||
// for non expert mode disable swaps above this
|
||||
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(5000), BIPS_BASE) // 50%
|
||||
|
||||
export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(300), BIPS_BASE) // 3%
|
||||
export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5%
|
||||
// if the price slippage exceeds this number, force the user to type 'confirm' to execute
|
||||
export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(2500), BIPS_BASE) // 25%
|
||||
export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10%
|
||||
// for non expert mode disable swaps above this
|
||||
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BIPS_BASE) // 15%
|
||||
|
||||
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
||||
export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH
|
||||
|
13
src/hooks/useLast.ts
Normal file
13
src/hooks/useLast.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Returns the last truthy value of type T
|
||||
* @param value changing value
|
||||
*/
|
||||
export default function useLast<T>(value: T | undefined | null): T | null | undefined {
|
||||
const [last, setLast] = useState<T | null | undefined>(value)
|
||||
useEffect(() => {
|
||||
setLast(last => value ?? last)
|
||||
}, [value])
|
||||
return last
|
||||
}
|
@ -270,8 +270,8 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
|
||||
|
||||
const PriceBar = () => {
|
||||
return (
|
||||
<AutoColumn gap="md" justify="space-between">
|
||||
<AutoRow justify="space-between">
|
||||
<AutoColumn gap="md">
|
||||
<AutoRow justify="space-around" gap="4px">
|
||||
<AutoColumn justify="center">
|
||||
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
|
||||
|
@ -10,11 +10,9 @@ import Card, { BlueCard, GreyCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
|
||||
import { AutoRow, RowBetween } from '../../components/Row'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||
import FormattedPriceImpact from '../../components/swap/FormattedPriceImpact'
|
||||
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
||||
import { ArrowWrapper, BottomGrouping, Dots, InputGroup, StyledNumerical, Wrapper } from '../../components/swap/styleds'
|
||||
import TradePrice from '../../components/swap/TradePrice'
|
||||
@ -98,7 +96,6 @@ export default function Send() {
|
||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
|
||||
// modal and loading
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
@ -476,14 +473,20 @@ export default function Send() {
|
||||
}}
|
||||
/>
|
||||
</AutoColumn>
|
||||
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
|
||||
{sendingWithSwap && (
|
||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||
<AutoColumn gap="4px">
|
||||
<RowBetween align="center">
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
||||
Price
|
||||
</Text>
|
||||
<TradePrice showInverted={showInverted} setShowInverted={setShowInverted} trade={bestTrade} />
|
||||
<TradePrice
|
||||
inputToken={tokens[Field.INPUT]}
|
||||
outputToken={tokens[Field.OUTPUT]}
|
||||
price={bestTrade?.executionPrice}
|
||||
showInverted={showInverted}
|
||||
setShowInverted={setShowInverted}
|
||||
/>
|
||||
</RowBetween>
|
||||
|
||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||
@ -500,20 +503,6 @@ export default function Send() {
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
)}
|
||||
{bestTrade && severity > 1 && (
|
||||
<RowBetween>
|
||||
<TYPE.main
|
||||
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
||||
fontSize={14}
|
||||
>
|
||||
Price Impact
|
||||
</TYPE.main>
|
||||
<RowFixed>
|
||||
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
|
||||
<QuestionHelper text="The difference between the market price and estimated price due to trade size." />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</Card>
|
||||
)}
|
||||
@ -588,9 +577,7 @@ export default function Send() {
|
||||
</Wrapper>
|
||||
</AppBody>
|
||||
|
||||
{bestTrade && (
|
||||
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
||||
)}
|
||||
<AdvancedSwapDetailsDropdown trade={bestTrade} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -9,11 +9,9 @@ import Card, { GreyCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import QuestionHelper from '../../components/QuestionHelper'
|
||||
import { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { RowBetween } from '../../components/Row'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||
import FormattedPriceImpact from '../../components/swap/FormattedPriceImpact'
|
||||
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds'
|
||||
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
||||
import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
||||
@ -84,7 +82,6 @@ export default function Swap() {
|
||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
|
||||
// modal and loading
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
@ -284,44 +281,33 @@ export default function Swap() {
|
||||
/>
|
||||
</>
|
||||
|
||||
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
|
||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||
<AutoColumn gap="4px">
|
||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||
<AutoColumn gap="4px">
|
||||
<RowBetween align="center">
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
||||
Price
|
||||
</Text>
|
||||
<TradePrice
|
||||
inputToken={tokens[Field.INPUT]}
|
||||
outputToken={tokens[Field.OUTPUT]}
|
||||
price={bestTrade?.executionPrice}
|
||||
showInverted={showInverted}
|
||||
setShowInverted={setShowInverted}
|
||||
/>
|
||||
</RowBetween>
|
||||
|
||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||
<RowBetween align="center">
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
||||
Price
|
||||
</Text>
|
||||
<TradePrice trade={bestTrade} showInverted={showInverted} setShowInverted={setShowInverted} />
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
Slippage Tolerance
|
||||
</ClickableText>
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
{allowedSlippage ? allowedSlippage / 100 : '-'}%
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
|
||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||
<RowBetween align="center">
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
Slippage Tolerance
|
||||
</ClickableText>
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
{allowedSlippage ? allowedSlippage / 100 : '-'}%
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
)}
|
||||
|
||||
{bestTrade && priceImpactSeverity > 1 && (
|
||||
<RowBetween>
|
||||
<TYPE.main
|
||||
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
|
||||
fontSize={14}
|
||||
>
|
||||
Price Impact
|
||||
</TYPE.main>
|
||||
<RowFixed>
|
||||
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
|
||||
<QuestionHelper text="The difference between the market price and estimated price due to trade size." />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</Card>
|
||||
)}
|
||||
)}
|
||||
</AutoColumn>
|
||||
</Card>
|
||||
</AutoColumn>
|
||||
<BottomGrouping>
|
||||
{!account ? (
|
||||
@ -385,9 +371,7 @@ export default function Swap() {
|
||||
</Wrapper>
|
||||
</AppBody>
|
||||
|
||||
{bestTrade && (
|
||||
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
|
||||
)}
|
||||
<AdvancedSwapDetailsDropdown trade={bestTrade} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user