Commit 7dafb0cf authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

Remove UI (#52)

* use token0 as base in all calculations

* refactor

* fix price order

* fix existing position ticks

* remove empty space

* basic remove page
Co-authored-by: default avatarNoah Zinsmeister <noahwz@gmail.com>
parent da33ec9d
......@@ -226,6 +226,7 @@ export const ButtonText = styled(Base)`
padding: 0;
width: fit-content;
background: none;
text-decoration: none;
&:focus {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
text-decoration: underline;
......
......@@ -19,7 +19,7 @@ const StyledRangeInput = styled.input<{ size: number }>`
-webkit-appearance: none;
height: ${({ size }) => size}px;
width: ${({ size }) => size}px;
background-color: #565a69;
background-color: ${({ theme }) => theme.blue1};
border-radius: 100%;
border: none;
transform: translateY(-50%);
......@@ -62,7 +62,7 @@ const StyledRangeInput = styled.input<{ size: number }>`
}
&::-webkit-slider-runnable-track {
background: linear-gradient(90deg, ${({ theme }) => theme.bg5}, ${({ theme }) => theme.bg3});
background: linear-gradient(90deg, ${({ theme }) => theme.blue1}, ${({ theme }) => theme.blue2});
height: 2px;
}
......@@ -96,7 +96,15 @@ interface InputSliderProps {
size?: number
}
export default function Slider({ value, onChange, min = 0, step = 1, max = 100, size = 28 }: InputSliderProps) {
export default function Slider({
value,
onChange,
min = 0,
step = 1,
max = 100,
size = 28,
...rest
}: InputSliderProps) {
const changeCallback = useCallback(
(e) => {
onChange(parseInt(e.target.value))
......@@ -107,9 +115,10 @@ export default function Slider({ value, onChange, min = 0, step = 1, max = 100,
return (
<StyledRangeInput
size={size}
{...rest}
type="range"
value={value}
style={{ width: '90%', marginLeft: 15, marginRight: 15, padding: '15px 0' }}
style={{ padding: '15px 0' }}
onChange={changeCallback}
aria-labelledby="input slider"
step={step}
......
......@@ -17,6 +17,7 @@ import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
const Wrapper = styled.div`
width: 100%;
padding: 1rem;
`
const Section = styled(AutoColumn)<{ inline?: boolean }>`
padding: ${({ inline }) => (inline ? '0' : '24px')};
......
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, TokenAmount, Percent, ETHER } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useMemo, useState } from 'react'
import { WETH9 } from '@uniswap/sdk-core'
import { Link2, AlertTriangle } from 'react-feather'
import ReactGA from 'react-ga'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
......@@ -225,17 +226,8 @@ export default function AddLiquidity({
setAttemptingTxn(false)
addTransaction(response, {
summary: noLiquidity
? 'Create Pool + '
: '' + 'Add ' + !depositADisabled
? parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currencies[Field.CURRENCY_A]?.symbol +
!outOfRange
: ''
? ' and '
: '' + !depositBDisabled
? parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) + ' ' + currencies[Field.CURRENCY_B]?.symbol
: '',
? `Create pool and add ${currencyA?.symbol}/${currencyB?.symbol} V3 liquidity`
: `Add ${currencyA?.symbol}/${currencyB?.symbol} V3 liquidity`,
})
setTxHash(response.hash)
ReactGA.event({
......@@ -273,13 +265,17 @@ export default function AddLiquidity({
const handleCurrencyASelect = useCallback(
(currencyA: Currency) => {
const newCurrencyIdA = currencyId(currencyA)
//switch order if same selected
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else if (chainId && newCurrencyIdA === WETH9[chainId]?.address && currencyIdB === 'ETH') {
// prevent eth / weth
history.push(`/add/${newCurrencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB ?? 'ETH'}`)
}
},
[currencyIdB, history, currencyIdA]
[currencyIdB, chainId, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currencyB: Currency) => {
......@@ -290,11 +286,14 @@ export default function AddLiquidity({
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else if (chainId && newCurrencyIdB === WETH9[chainId]?.address && currencyIdA === 'ETH') {
// prevent eth / weth
history.push(`/add/${newCurrencyIdB}`)
} else {
history.push(`/add/${currencyIdA ?? 'ETH'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
[currencyIdA, chainId, currencyIdB, history]
)
const handleFeePoolSelect = useCallback(
......
......@@ -26,6 +26,7 @@ export const RangeBadge = styled(Card)<{ inRange?: boolean }>`
font-weight: 500;
border-radius: 8px;
padding: 4px 6px;
color: ${({ theme }) => theme.black};
background-color: ${({ inRange, theme }) => (inRange ? theme.green1 : theme.yellow2)};
`
......
......@@ -48,17 +48,17 @@ const ResponsiveGrid = styled.div`
display: grid;
grid-gap: 1em;
grid-template-columns: 1.5fr repeat(3, 1fr);
grid-template-columns: 1.5fr repeat(2, 1fr);
@media screen and (max-width: 900px) {
grid-template-columns: 1.5fr repeat(3, 1fr);
grid-template-columns: 1.5fr repeat(2, 1fr);
& :nth-child(4) {
display: none;
}
}
@media screen and (max-width: 700px) {
grid-template-columns: 20px 1.5fr repeat(3, 1fr);
grid-template-columns: 20px 1.5fr repeat(2, 1fr);
& :nth-child(4) {
display: none;
}
......@@ -288,9 +288,8 @@ export function PositionPage({
<AutoColumn gap="lg">
<ResponsiveGrid>
<Label>Tokens</Label>
<Label end={true}>Current</Label>
<Label end={true}>Amount Deposited</Label>
<Label end={true}>Fees</Label>
<Label end={true}>USD Value</Label>
</ResponsiveGrid>
<ResponsiveGrid>
<RowFixed>
......@@ -299,7 +298,6 @@ export function PositionPage({
</RowFixed>
<Label end={true}>{position?.amount0.toSignificant(4)}</Label>
<Label end={true}>{feeValue0 ? formatTokenAmount(feeValue0, 4) : '-'}</Label>
<Label end={true}>-</Label>
</ResponsiveGrid>
<ResponsiveGrid>
<RowFixed>
......@@ -308,7 +306,6 @@ export function PositionPage({
</RowFixed>
<Label end={true}>{position?.amount1.toSignificant(4)}</Label>
<Label end={true}>{feeValue1 ? formatTokenAmount(feeValue1, 4) : '-'}</Label>
<Label end={true}>-</Label>
</ResponsiveGrid>
</AutoColumn>
</DarkCard>
......@@ -322,13 +319,13 @@ export function PositionPage({
<RowFixed>
<TYPE.label>{price0Lower?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency1?.symbol} / {currency0?.symbol}
{currency0?.symbol} / {currency1?.symbol}
</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label>{price1Lower?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency0?.symbol} / {currency1?.symbol}
{currency1?.symbol} / {currency0?.symbol}
</TYPE.label>
</RowFixed>
<DarkBadge>
......@@ -346,13 +343,13 @@ export function PositionPage({
<RowFixed>
<TYPE.label>{price0Upper?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency1?.symbol} / {currency0?.symbol}
{currency0?.symbol} / {currency1?.symbol}
</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label>{price1Upper?.toSignificant(4)}</TYPE.label>
<TYPE.label ml="10px">
{currency0?.symbol} / {currency1?.symbol}
{currency1?.symbol} / {currency0?.symbol}
</TYPE.label>
</RowFixed>
<DarkBadge>
......
......@@ -138,7 +138,7 @@ export default function Pool() {
<AutoColumn gap="lg" style={{ width: '100%' }}>
<TitleRow style={{ marginTop: '1rem' }} padding={'0'}>
<HideSmall>
<TYPE.mediumHeader>{t('Pool Overview')}</TYPE.mediumHeader>
<TYPE.mediumHeader>Your Positions</TYPE.mediumHeader>
</HideSmall>
<ButtonRow>
<Menu
......
import React, { useCallback, useMemo } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { RouteComponentProps } from 'react-router'
import { Redirect } from 'react-router-dom'
import { Redirect, RouteComponentProps, Link } from 'react-router-dom'
import AppBody from '../AppBody'
import { BigNumber } from '@ethersproject/bignumber'
import useDebouncedChangeHandler from 'hooks/useDebouncedChangeHandler'
import { useBurnV3ActionHandlers, useBurnV3State, useDerivedV3BurnInfo } from 'state/burn/v3/hooks'
import Slider from 'components/Slider'
import { RowBetween, RowFixed } from 'components/Row'
import { MaxButton } from 'pages/Pool/styleds'
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
import TransactionConfirmationModal from '../../components/TransactionConfirmationModal'
import { AutoColumn } from 'components/Column'
import { ButtonConfirmed } from 'components/Button'
import { ButtonConfirmed, ButtonText } from 'components/Button'
import { LightCard } from 'components/Card'
import { Text } from 'rebass'
import CurrencyLogo from 'components/CurrencyLogo'
......@@ -24,9 +23,22 @@ import { useActiveWeb3React } from 'hooks'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from 'state/transactions/hooks'
import { WETH9 } from '@uniswap/sdk-core'
import { TYPE } from 'theme'
import styled from 'styled-components'
import { Wrapper, SmallMaxButton } from './styled'
import Loader from 'components/Loader'
import { useToken } from 'hooks/Tokens'
import { unwrappedToken } from 'utils/wrappedCurrency'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import { RangeBadge } from 'pages/AddLiquidity/styled'
import { Break } from 'components/earn/styled'
export const UINT128MAX = BigNumber.from(2).pow(128).sub(1)
const UnstyledLink = styled(Link)`
text-decoration: none;
`
// redirect invalid tokenIds
export default function RemoveLiquidityV3({
location,
......@@ -48,16 +60,21 @@ export default function RemoveLiquidityV3({
return <Remove tokenId={parsedTokenId} />
}
function Remove({ tokenId }: { tokenId: BigNumber }) {
const position = useV3PositionFromTokenId(tokenId)
const { position } = useV3PositionFromTokenId(tokenId)
const { account, chainId } = useActiveWeb3React()
// currencies from position
const token0 = useToken(position?.token0)
const token1 = useToken(position?.token1)
const currency0 = token0 ? unwrappedToken(token0) : undefined
const currency1 = token1 ? unwrappedToken(token1) : undefined
// burn state
const { percent } = useBurnV3State()
const { liquidity, liquidityValue0, liquidityValue1, feeValue0, feeValue1, error } = useDerivedV3BurnInfo(
position?.position
const { liquidity, liquidityValue0, liquidityValue1, feeValue0, feeValue1, outOfRange, error } = useDerivedV3BurnInfo(
position
)
const { onPercentSelect } = useBurnV3ActionHandlers()
......@@ -67,9 +84,14 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [showConfirm, setShowConfirm] = useState(false)
const [attemptingTxn, setAttemptingTxn] = useState(false)
const [txnHash, setTxnHash] = useState<string | undefined>()
const addTransaction = useTransactionAdder()
const positionManager = useV3NFTPositionManagerContract()
const burn = useCallback(() => {
setShowConfirm(true)
setAttemptingTxn(true)
if (
!liquidity ||
!positionManager ||
......@@ -80,8 +102,10 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
!chainId ||
!feeValue0 ||
!feeValue1
)
) {
setShowConfirm(false)
return
}
const data = []
......@@ -155,12 +179,15 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
action: 'RemoveV3',
label: [liquidityValue0.token.symbol, liquidityValue1.token.symbol].join('/'),
})
setTxnHash(response.hash)
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${liquidityValue0.token.symbol}/${liquidityValue1.token.symbol} V3 liquidity`,
})
})
.catch((error) => {
setShowConfirm(false)
setAttemptingTxn(false)
console.error(error)
})
}, [
......@@ -178,28 +205,78 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
feeValue1,
])
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txnHash) {
onPercentSelectForSlider(0)
}
setAttemptingTxn(false)
setTxnHash('')
}, [onPercentSelectForSlider, txnHash])
const pendingText = `Removing ${liquidityValue0?.toSignificant(6)} ${
currency0?.symbol
} and ${liquidityValue1?.toSignificant(6)} ${currency1?.symbol}`
return (
<AutoColumn>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txnHash ?? ''}
content={() => <div />}
pendingText={pendingText}
/>
<AutoRow marginBottom="20px">
<UnstyledLink to="pool">
<ButtonText opacity={'0.4'}>Pool</ButtonText>
</UnstyledLink>
<TYPE.label margin="0 10px" opacity={'0.4'}>
{' > '}
</TYPE.label>
<TYPE.label>{liquidity?.eq(0) ? 'Collect Fees' : 'Remove Liquidity'}</TYPE.label>
</AutoRow>
<AppBody>
<>
<Slider value={percentForSlider} onChange={onPercentSelectForSlider} />
<Wrapper>
{position ? (
<AutoColumn gap="lg">
<RowBetween>
<RowFixed>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} margin={true} />
<TYPE.label ml="10px" fontSize="20px">{`${currency0?.symbol}/${currency1?.symbol}`}</TYPE.label>
</RowFixed>
<RangeBadge inRange={!outOfRange}>{outOfRange ? 'Out of range' : 'In Range'}</RangeBadge>
</RowBetween>
<LightCard>
<AutoColumn gap="md">
<TYPE.main fontWeight={400}>Withdrawl Amount</TYPE.main>
<RowBetween>
<MaxButton onClick={() => onPercentSelect(25)} width="20%">
<TYPE.label fontSize="40px">{percentForSlider}%</TYPE.label>
<AutoRow gap="4px" justify="flex-end">
<SmallMaxButton onClick={() => onPercentSelect(25)} width="20%">
25%
</MaxButton>
<MaxButton onClick={() => onPercentSelect(50)} width="20%">
</SmallMaxButton>
<SmallMaxButton onClick={() => onPercentSelect(50)} width="20%">
50%
</MaxButton>
<MaxButton onClick={() => onPercentSelect(75)} width="20%">
</SmallMaxButton>
<SmallMaxButton onClick={() => onPercentSelect(75)} width="20%">
75%
</MaxButton>
<MaxButton onClick={() => onPercentSelect(100)} width="20%">
</SmallMaxButton>
<SmallMaxButton onClick={() => onPercentSelect(100)} width="20%">
Max
</MaxButton>
</SmallMaxButton>
</AutoRow>
</RowBetween>
<RowBetween my="1rem">
<Slider value={percentForSlider} onChange={onPercentSelectForSlider} />
</AutoColumn>
</LightCard>
<LightCard>
<AutoColumn gap="md">
<RowBetween>
<Text fontSize={16} fontWeight={500}>
Pooled {liquidityValue0?.token?.symbol}:
Pooled {currency0?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
......@@ -208,9 +285,9 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue0?.token} />
</RowFixed>
</RowBetween>
<RowBetween mb="1rem">
<RowBetween>
<Text fontSize={16} fontWeight={500}>
Pooled {liquidityValue1?.token?.symbol}:
Pooled {currency1?.symbol}:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
......@@ -219,10 +296,10 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={liquidityValue1?.token} />
</RowFixed>
</RowBetween>
<RowBetween my="1rem">
<Break />
<RowBetween>
<Text fontSize={16} fontWeight={500}>
{feeValue0?.token?.symbol} Fees:
{currency0?.symbol} Fees:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
......@@ -231,9 +308,9 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue0?.token} />
</RowFixed>
</RowBetween>
<RowBetween mb="1rem">
<RowBetween>
<Text fontSize={16} fontWeight={500}>
{feeValue1?.token?.symbol} Fees:
{currency1?.symbol} Fees:
</Text>
<RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
......@@ -242,17 +319,21 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={feeValue1?.token} />
</RowFixed>
</RowBetween>
<LightCard>
<div style={{ display: 'flex', marginTop: '1rem' }}>
</AutoColumn>
</LightCard>
<div style={{ display: 'flex' }}>
<AutoColumn gap="12px" style={{ flex: '1' }}>
<ButtonConfirmed confirmed={false} disabled={!liquidity} onClick={burn}>
{error ?? liquidity?.eq(0) ? 'Collect' : 'Burn'}
{error ?? liquidity?.eq(0) ? 'Collect' : 'Remove Liquidity'}
</ButtonConfirmed>
</AutoColumn>
</div>
</LightCard>
</>
</AutoColumn>
) : (
<Loader />
)}
</Wrapper>
</AppBody>
</AutoColumn>
)
}
import { MaxButton } from 'pages/Pool/styleds'
import styled from 'styled-components'
export const Wrapper = styled.div`
position: relative;
padding: 20px;
min-width: 460px;
`
export const SmallMaxButton = styled(MaxButton)`
font-size: 12px;
`
......@@ -89,14 +89,7 @@ export default function Swap({ history }: RouteComponentProps) {
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const {
v2Trade,
currencyBalances,
parsedAmount,
currencies,
v3Route,
inputError: swapInputError,
} = useDerivedSwapInfo()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
......
......@@ -24,6 +24,7 @@ export function useDerivedV3BurnInfo(
liquidityValue1?: TokenAmount
feeValue0?: TokenAmount
feeValue1?: TokenAmount
outOfRange: boolean
error?: string
} {
const { account } = useActiveWeb3React()
......@@ -52,6 +53,9 @@ export function useDerivedV3BurnInfo(
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, position)
const outOfRange =
pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false
let error: string | undefined
if (!account) {
error = 'Connect Wallet'
......@@ -65,6 +69,7 @@ export function useDerivedV3BurnInfo(
liquidityValue1,
feeValue0,
feeValue1,
outOfRange,
error,
}
}
......
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