Position styles (#55)
* Better position list layout WIP * Position list updates * add badge data and current price hover * merge cleanup * fix missing library * position page improvements * Clean up position page and overview * layout and color updates * Clean up page * Clean up position page * clean up errors * Add icons * Merge main * Position styles tweaks Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
This commit is contained in:
parent
93d33947da
commit
1619386ab4
@ -89,7 +89,7 @@
|
|||||||
"toleranceExplanation": "Lowering this limit decreases your risk of frontrunning. However, this makes more likely that your transaction will fail due to normal price movements.",
|
"toleranceExplanation": "Lowering this limit decreases your risk of frontrunning. However, this makes more likely that your transaction will fail due to normal price movements.",
|
||||||
"tokenSearchPlaceholder": "Search name or paste address",
|
"tokenSearchPlaceholder": "Search name or paste address",
|
||||||
"selectFee": "Select Fee",
|
"selectFee": "Select Fee",
|
||||||
"selectLiquidityRange": "Select Liquidity Range",
|
"selectLiquidityRange": "Select Price Range",
|
||||||
"selectPool": "Select Fee Tier",
|
"selectPool": "Select Fee Tier",
|
||||||
"depositAmounts": "Deposit Amounts",
|
"depositAmounts": "Deposit Amounts",
|
||||||
"fee": "fee",
|
"fee": "fee",
|
||||||
|
63
src/components/Badge/RangeBadge.tsx
Normal file
63
src/components/Badge/RangeBadge.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Badge, { BadgeVariant } from 'components/Badge'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { MouseoverTooltip } from '../../components/Tooltip'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { AlertCircle } from 'react-feather'
|
||||||
|
|
||||||
|
const BadgeWrapper = styled.div`
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`
|
||||||
|
|
||||||
|
const BadgeText = styled.div`
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ActiveDot = styled.span`
|
||||||
|
background-color: ${({ theme }) => theme.success};
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
margin-right: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const DarkBadge = styled.div`
|
||||||
|
width: fit-content;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: ${({ theme }) => theme.bg0};
|
||||||
|
padding: 4px 6px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function RangeBadge({ inRange }: { inRange?: boolean }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BadgeWrapper>
|
||||||
|
{inRange ? (
|
||||||
|
<MouseoverTooltip
|
||||||
|
text={`The price of this pair is within your selected range. Your positions is earning fees.`}
|
||||||
|
>
|
||||||
|
<Badge variant={BadgeVariant.DEFAULT}>
|
||||||
|
<ActiveDot />
|
||||||
|
<BadgeText>{t('In range')}</BadgeText>
|
||||||
|
</Badge>
|
||||||
|
</MouseoverTooltip>
|
||||||
|
) : (
|
||||||
|
<MouseoverTooltip
|
||||||
|
text={`The price of this pair is outside of your selected range. Your positions is not earning fees.`}
|
||||||
|
>
|
||||||
|
<Badge variant={BadgeVariant.WARNING}>
|
||||||
|
<AlertCircle width={14} height={14} />
|
||||||
|
|
||||||
|
<BadgeText>{t('Out of range')}</BadgeText>
|
||||||
|
</Badge>
|
||||||
|
</MouseoverTooltip>
|
||||||
|
)}
|
||||||
|
</BadgeWrapper>
|
||||||
|
)
|
||||||
|
}
|
@ -37,6 +37,10 @@ const Base = styled(RebassButton)<{
|
|||||||
> * {
|
> * {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const ButtonPrimary = styled(Base)`
|
export const ButtonPrimary = styled(Base)`
|
||||||
|
@ -9,19 +9,17 @@ const DesktopHeader = styled.div`
|
|||||||
display: none;
|
display: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
opacity: 0.6;
|
padding: 8px 8px 8px 8px;
|
||||||
padding: 8px 8px 0 8px;
|
|
||||||
|
|
||||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 0 8px 0;
|
|
||||||
& > div:first-child {
|
display: grid;
|
||||||
flex: 1 1 auto;
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
& > div:last-child {
|
||||||
& > div:not(:first-child) {
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
min-width: 18%;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -45,10 +43,11 @@ export default function PositionList({ positions }: PositionListProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DesktopHeader>
|
<DesktopHeader>
|
||||||
<div>{t('Position')}</div>
|
<div>
|
||||||
<div>{t('Range')}</div>
|
{t('Your positions')}
|
||||||
<div>{t('Liquidity')}</div>
|
{positions && ' (' + positions.length + ')'}
|
||||||
<div>{t('Fees Earned')}</div>
|
</div>
|
||||||
|
<div>{t('Price range')}</div>
|
||||||
</DesktopHeader>
|
</DesktopHeader>
|
||||||
<MobileHeader>Your positions</MobileHeader>
|
<MobileHeader>Your positions</MobileHeader>
|
||||||
{positions.map((p) => {
|
{positions.map((p) => {
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { Position } from '@uniswap/v3-sdk'
|
import { Position } from '@uniswap/v3-sdk'
|
||||||
import Badge, { BadgeVariant } from 'components/Badge'
|
import Badge, { BadgeVariant } from 'components/Badge'
|
||||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||||
import { usePool } from 'hooks/usePools'
|
import { usePool } from 'hooks/usePools'
|
||||||
import { useToken } from 'hooks/Tokens'
|
import { useToken } from 'hooks/Tokens'
|
||||||
import { AlertTriangle } from 'react-feather'
|
import { AlertCircle } from 'react-feather'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { MEDIA_WIDTHS } from 'theme'
|
import { MEDIA_WIDTHS } from 'theme'
|
||||||
import { PositionDetails } from 'types/position'
|
import { PositionDetails } from 'types/position'
|
||||||
import { TokenAmount, WETH9, Price, Token, Percent } from '@uniswap/sdk-core'
|
import { WETH9, Price, Token, Percent } from '@uniswap/sdk-core'
|
||||||
import { formatPrice, formatTokenAmount } from 'utils/formatTokenAmount'
|
import { formatPrice } from 'utils/formatTokenAmount'
|
||||||
import Loader from 'components/Loader'
|
import Loader from 'components/Loader'
|
||||||
import { unwrappedToken } from 'utils/wrappedCurrency'
|
import { unwrappedToken } from 'utils/wrappedCurrency'
|
||||||
import { useV3PositionFees } from 'hooks/useV3PositionFees'
|
|
||||||
import { DAI, USDC, USDT, WBTC } from '../../constants'
|
import { DAI, USDC, USDT, WBTC } from '../../constants'
|
||||||
|
import { MouseoverTooltip } from '../Tooltip'
|
||||||
|
import { RowFixed } from 'components/Row'
|
||||||
|
|
||||||
const ActiveDot = styled.span`
|
const ActiveDot = styled.span`
|
||||||
background-color: ${({ theme }) => theme.success};
|
background-color: ${({ theme }) => theme.success};
|
||||||
@ -28,29 +29,29 @@ const Row = styled(Link)`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
justify-content: space-between;
|
||||||
color: ${({ theme }) => theme.text1};
|
color: ${({ theme }) => theme.text1};
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
padding: 8px;
|
padding: 16px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
background-color: ${({ theme }) => theme.bg1};
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
}
|
}
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
margin: 8px 0 0 0;
|
margin: 8px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > div:not(:first-child) {
|
& > div:not(:first-child) {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
min-width: 18%;
|
}
|
||||||
|
:hover {
|
||||||
|
background-color: ${({ theme }) => theme.bg2};
|
||||||
}
|
}
|
||||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
:hover {
|
|
||||||
background-color: ${({ theme }) => theme.bg1};
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
const BadgeText = styled.div`
|
const BadgeText = styled.div`
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -60,63 +61,44 @@ const BadgeWrapper = styled.div`
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
`
|
`
|
||||||
const DataLineItem = styled.div`
|
const DataLineItem = styled.div`
|
||||||
text-align: right;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const RangeLineItem = styled(DataLineItem)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-self: flex-end;
|
||||||
|
`
|
||||||
|
|
||||||
const DoubleArrow = styled.span`
|
const DoubleArrow = styled.span`
|
||||||
color: ${({ theme }) => theme.text3};
|
color: ${({ theme }) => theme.text3};
|
||||||
`
|
`
|
||||||
const RangeData = styled.div`
|
|
||||||
display: flex;
|
const RangeText = styled.span`
|
||||||
flex-direction: column;
|
background-color: ${({ theme }) => theme.bg2};
|
||||||
width: 100%;
|
padding: 0.25rem 0.5rem;
|
||||||
& > div {
|
border-radius: 8px;
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
|
||||||
display: block;
|
|
||||||
& > div {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
const AmountData = styled.div`
|
|
||||||
display: none;
|
const ExtentsText = styled.span`
|
||||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
color: ${({ theme }) => theme.text3};
|
||||||
display: block;
|
font-size: 14px;
|
||||||
}
|
margin-right: 4px;
|
||||||
`
|
|
||||||
const FeeData = styled.div`
|
|
||||||
display: none;
|
|
||||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const LabelData = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const PrimaryPositionIdData = styled.div`
|
const PrimaryPositionIdData = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 0 12px 0;
|
|
||||||
> * {
|
> * {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const DataText = styled.div`
|
const DataText = styled.div`
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
|
font-size: 18px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface PositionListItemProps {
|
export interface PositionListItemProps {
|
||||||
@ -207,31 +189,33 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
|||||||
return undefined
|
return undefined
|
||||||
}, [liquidity, pool, tickLower, tickUpper])
|
}, [liquidity, pool, tickLower, tickUpper])
|
||||||
|
|
||||||
// liquidity amounts in tokens
|
|
||||||
const amount0: TokenAmount | undefined = position?.amount0
|
|
||||||
const amount1: TokenAmount | undefined = position?.amount1
|
|
||||||
const formattedAmount0 = formatTokenAmount(amount0, 4)
|
|
||||||
const formattedAmount1 = formatTokenAmount(amount1, 4)
|
|
||||||
|
|
||||||
// prices
|
// prices
|
||||||
const { priceLower, priceUpper, base } = getPriceOrderingFromPositionForUI(position)
|
let { priceLower, priceUpper, base, quote } = getPriceOrderingFromPositionForUI(position)
|
||||||
const inverted = token1 ? base?.equals(token1) : undefined
|
const inverted = token1 ? base?.equals(token1) : undefined
|
||||||
const currencyQuote = inverted ? currency0 : currency1
|
const currencyQuote = inverted ? currency0 : currency1
|
||||||
const currencyBase = inverted ? currency1 : currency0
|
const currencyBase = inverted ? currency1 : currency0
|
||||||
|
|
||||||
// fees
|
|
||||||
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails)
|
|
||||||
|
|
||||||
// check if price is within range
|
// check if price is within range
|
||||||
const outOfRange: boolean = pool ? pool.tickCurrent < tickLower || pool.tickCurrent >= tickUpper : false
|
const outOfRange: boolean = pool ? pool.tickCurrent < tickLower || pool.tickCurrent >= tickUpper : false
|
||||||
|
|
||||||
const positionSummaryLink = '/pool/' + positionDetails.tokenId
|
const positionSummaryLink = '/pool/' + positionDetails.tokenId
|
||||||
|
|
||||||
|
const [manuallyInverted, setManuallyInverted] = useState(true)
|
||||||
|
if (manuallyInverted) {
|
||||||
|
;[priceLower, priceUpper, base, quote] = [priceUpper?.invert(), priceLower?.invert(), quote, base]
|
||||||
|
}
|
||||||
|
|
||||||
|
const quotePrice = useMemo(() => {
|
||||||
|
return manuallyInverted
|
||||||
|
? position?.pool.priceOf(position?.pool.token0)
|
||||||
|
: position?.pool.priceOf(position?.pool.token1)
|
||||||
|
}, [manuallyInverted, position?.pool])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row to={positionSummaryLink}>
|
<Row to={positionSummaryLink}>
|
||||||
<LabelData>
|
<RowFixed>
|
||||||
<PrimaryPositionIdData>
|
<PrimaryPositionIdData>
|
||||||
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={16} margin />
|
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={18} margin />
|
||||||
<DataText>
|
<DataText>
|
||||||
{currencyQuote?.symbol} / {currencyBase?.symbol}
|
{currencyQuote?.symbol} / {currencyBase?.symbol}
|
||||||
</DataText>
|
</DataText>
|
||||||
@ -242,61 +226,63 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
|||||||
</PrimaryPositionIdData>
|
</PrimaryPositionIdData>
|
||||||
<BadgeWrapper>
|
<BadgeWrapper>
|
||||||
{outOfRange ? (
|
{outOfRange ? (
|
||||||
<Badge variant={BadgeVariant.WARNING}>
|
<MouseoverTooltip
|
||||||
<AlertTriangle width={14} height={14} style={{ marginRight: '4px' }} />
|
text={`The price of this pair is outside of your selected range. Your positions is not earning fees. Current price: ${quotePrice?.toSignificant(
|
||||||
|
6
|
||||||
<BadgeText>{t('Out of range')}</BadgeText>
|
)} ${manuallyInverted ? currencyQuote?.symbol : currencyBase?.symbol} / ${
|
||||||
</Badge>
|
manuallyInverted ? currencyBase?.symbol : currencyQuote?.symbol
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Badge variant={BadgeVariant.WARNING}>
|
||||||
|
<AlertCircle width={14} height={14} style={{ marginRight: '' }} />
|
||||||
|
|
||||||
|
<BadgeText>{t('Out of range')}</BadgeText>
|
||||||
|
</Badge>
|
||||||
|
</MouseoverTooltip>
|
||||||
) : (
|
) : (
|
||||||
<Badge variant={BadgeVariant.DEFAULT}>
|
<MouseoverTooltip
|
||||||
<ActiveDot />
|
text={`The price of this pair is within your selected range. Your positions is earning fees. Current price: ${quotePrice?.toSignificant(
|
||||||
<BadgeText>{t('Active')}</BadgeText>
|
6
|
||||||
</Badge>
|
)} ${manuallyInverted ? currencyQuote?.symbol : currencyBase?.symbol} / ${
|
||||||
|
manuallyInverted ? currencyBase?.symbol : currencyQuote?.symbol
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Badge variant={BadgeVariant.DEFAULT}>
|
||||||
|
<ActiveDot />
|
||||||
|
<BadgeText>{t('In range')}</BadgeText>
|
||||||
|
</Badge>
|
||||||
|
</MouseoverTooltip>
|
||||||
)}
|
)}
|
||||||
</BadgeWrapper>
|
</BadgeWrapper>
|
||||||
</LabelData>
|
</RowFixed>
|
||||||
<RangeData>
|
|
||||||
{priceLower && priceUpper ? (
|
{priceLower && priceUpper ? (
|
||||||
<>
|
<>
|
||||||
<DataLineItem>
|
{' '}
|
||||||
{formatPrice(priceLower, 4)} <DoubleArrow>↔</DoubleArrow> {formatPrice(priceUpper, 4)}{' '}
|
<RangeLineItem
|
||||||
{currencyQuote?.symbol}
|
onClick={(e) => {
|
||||||
/
|
e.stopPropagation()
|
||||||
{currencyBase?.symbol}
|
setManuallyInverted(!manuallyInverted)
|
||||||
</DataLineItem>
|
}}
|
||||||
</>
|
>
|
||||||
) : (
|
<span>
|
||||||
<Loader />
|
<RangeText>
|
||||||
)}
|
<ExtentsText>Min: </ExtentsText>
|
||||||
</RangeData>
|
{formatPrice(priceLower, 4)} {manuallyInverted ? currencyQuote?.symbol : currencyBase?.symbol} {' / '}{' '}
|
||||||
<AmountData>
|
{manuallyInverted ? currencyBase?.symbol : currencyQuote?.symbol}
|
||||||
{formattedAmount0 && formattedAmount1 ? (
|
</RangeText>{' '}
|
||||||
<>
|
<DoubleArrow>⟷</DoubleArrow>{' '}
|
||||||
<DataLineItem>
|
<RangeText>
|
||||||
{inverted ? formattedAmount0 : formattedAmount1} {currencyQuote?.symbol}
|
<ExtentsText>Max:</ExtentsText>
|
||||||
</DataLineItem>
|
{formatPrice(priceUpper, 4)} {manuallyInverted ? currencyQuote?.symbol : currencyBase?.symbol} {' / '}{' '}
|
||||||
<DataLineItem>
|
{manuallyInverted ? currencyBase?.symbol : currencyQuote?.symbol}
|
||||||
{inverted ? formattedAmount1 : formattedAmount0} {currencyBase?.symbol}
|
</RangeText>{' '}
|
||||||
</DataLineItem>
|
</span>
|
||||||
</>
|
</RangeLineItem>
|
||||||
) : (
|
</>
|
||||||
<Loader />
|
) : (
|
||||||
)}
|
<Loader />
|
||||||
</AmountData>
|
)}
|
||||||
<FeeData>
|
|
||||||
{feeValue0 && feeValue1 ? (
|
|
||||||
<>
|
|
||||||
<DataLineItem>
|
|
||||||
{formatTokenAmount(inverted ? feeValue0 : feeValue1, 4)} {currencyQuote?.symbol}
|
|
||||||
</DataLineItem>
|
|
||||||
<DataLineItem>
|
|
||||||
{formatTokenAmount(inverted ? feeValue1 : feeValue0, 4)} {currencyBase?.symbol}
|
|
||||||
</DataLineItem>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Loader />
|
|
||||||
)}
|
|
||||||
</FeeData>
|
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,15 @@ import { Currency } from '@uniswap/sdk-core'
|
|||||||
import { ToggleElement, ToggleWrapper } from 'components/Toggle/MultiToggle'
|
import { ToggleElement, ToggleWrapper } from 'components/Toggle/MultiToggle'
|
||||||
import { useActiveWeb3React } from 'hooks'
|
import { useActiveWeb3React } from 'hooks'
|
||||||
import { wrappedCurrency } from 'utils/wrappedCurrency'
|
import { wrappedCurrency } from 'utils/wrappedCurrency'
|
||||||
|
import Switch from '../../assets/svg/switch.svg'
|
||||||
|
import { useDarkModeManager } from '../../state/user/hooks'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
const StyledSwitchIcon = styled.img<{ darkMode: boolean }>`
|
||||||
|
margin: 0 4px;
|
||||||
|
opacity: 0.4;
|
||||||
|
filter: ${({ darkMode }) => (darkMode ? 'invert(0)' : 'invert(1)')};
|
||||||
|
`
|
||||||
|
|
||||||
// the order of displayed base currencies from left to right is always in sort order
|
// the order of displayed base currencies from left to right is always in sort order
|
||||||
// currencyA is treated as the preferred base currency
|
// currencyA is treated as the preferred base currency
|
||||||
@ -22,14 +31,21 @@ export default function RateToggle({
|
|||||||
|
|
||||||
const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
|
const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
|
||||||
|
|
||||||
|
const [darkMode] = useDarkModeManager()
|
||||||
|
|
||||||
return tokenA && tokenB ? (
|
return tokenA && tokenB ? (
|
||||||
<ToggleWrapper width="fit-content">
|
<div style={{ width: 'fit-content', display: 'flex', alignItems: 'center' }}>
|
||||||
<ToggleElement isActive={isSorted} fontSize="12px" onClick={handleRateToggle}>
|
<ToggleWrapper width="fit-content">
|
||||||
{isSorted ? currencyA.symbol : currencyB.symbol}
|
<ToggleElement isActive={isSorted} fontSize="12px" onClick={handleRateToggle}>
|
||||||
</ToggleElement>
|
{isSorted ? currencyA.symbol : currencyB.symbol} {' price'}
|
||||||
<ToggleElement isActive={!isSorted} fontSize="12px" onClick={handleRateToggle}>
|
</ToggleElement>
|
||||||
{isSorted ? currencyB.symbol : currencyA.symbol}
|
<StyledSwitchIcon onClick={handleRateToggle} width={'16px'} src={Switch} alt="logo" darkMode={darkMode} />
|
||||||
</ToggleElement>
|
|
||||||
</ToggleWrapper>
|
<ToggleElement isActive={!isSorted} fontSize="12px" onClick={handleRateToggle}>
|
||||||
|
{isSorted ? currencyB.symbol : currencyA.symbol}
|
||||||
|
{' price'}
|
||||||
|
</ToggleElement>
|
||||||
|
</ToggleWrapper>
|
||||||
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@ import styled from 'styled-components'
|
|||||||
export const ToggleWrapper = styled.button<{ width?: string }>`
|
export const ToggleWrapper = styled.button<{ width?: string }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: ${({ width }) => width ?? '100%'}
|
width: ${({ width }) => width ?? '100%'};
|
||||||
padding: 1px;
|
padding: 2px;
|
||||||
background: ${({ theme }) => theme.bg0};
|
background: ${({ theme }) => theme.bg1};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: ${({ theme }) => '2px solid ' + theme.bg2};
|
border: ${({ theme }) => '1px solid ' + theme.bg1};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
`
|
`
|
||||||
@ -21,7 +21,7 @@ export const ToggleElement = styled.span<{ isActive?: boolean; fontSize?: string
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: ${({ theme, isActive }) => (isActive ? theme.bg2 : 'none')};
|
background: ${({ theme, isActive }) => (isActive ? theme.bg0 : 'none')};
|
||||||
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
|
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
|
||||||
font-size: ${({ fontSize }) => fontSize ?? '1rem'};
|
font-size: ${({ fontSize }) => fontSize ?? '1rem'};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -32,6 +32,15 @@ export const ToggleElement = styled.span<{ isActive?: boolean; fontSize?: string
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const ToggleText = styled.div`
|
||||||
|
color: ${({ theme }) => theme.text3};
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0 0 0 4px;
|
||||||
|
`
|
||||||
|
|
||||||
export interface ToggleProps {
|
export interface ToggleProps {
|
||||||
options: string[]
|
options: string[]
|
||||||
activeIndex: number
|
activeIndex: number
|
||||||
|
73
src/pages/Pool/CTACards.tsx
Normal file
73
src/pages/Pool/CTACards.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { TYPE } from 'theme'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ExternalLink } from '../../theme'
|
||||||
|
|
||||||
|
const CTASection = styled.section`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const CTA1 = styled(ExternalLink)`
|
||||||
|
background-color: ${({ theme }) => theme.bg1};
|
||||||
|
padding: 32px;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 220px;
|
||||||
|
border: 1px solid ${({ theme }) => theme.bg4};
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: ${({ theme }) => theme.text1};
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
border: 1px solid ${({ theme }) => theme.bg5};
|
||||||
|
background-color: ${({ theme }) => theme.bg2};
|
||||||
|
text-decoration: none;
|
||||||
|
* {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function CTACards() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CTASection>
|
||||||
|
<CTA1 href={''}>
|
||||||
|
<span>
|
||||||
|
<TYPE.largeHeader fontWeight={400} style={{ alignItems: 'center', display: 'flex', marginBottom: '24px' }}>
|
||||||
|
{t('What’s new in V3 Liquidity Pools?')}
|
||||||
|
</TYPE.largeHeader>
|
||||||
|
<TYPE.body fontWeight={300} style={{ alignItems: 'center', display: 'flex' }}>
|
||||||
|
{t(
|
||||||
|
'Learn all about concentrated liquidity and get informed about how to choose ranges that make sense for you.'
|
||||||
|
)}
|
||||||
|
</TYPE.body>
|
||||||
|
</span>
|
||||||
|
<TYPE.largeHeader fontWeight={400} style={{ alignItems: 'center', display: 'flex' }}>
|
||||||
|
{t('↗')}
|
||||||
|
</TYPE.largeHeader>
|
||||||
|
</CTA1>
|
||||||
|
<CTA1 href={''}>
|
||||||
|
<span>
|
||||||
|
<TYPE.largeHeader fontWeight={400} style={{ alignItems: 'center', display: 'flex', marginBottom: '24px' }}>
|
||||||
|
{t('Top pools')}
|
||||||
|
</TYPE.largeHeader>
|
||||||
|
<TYPE.body fontWeight={300} style={{ alignItems: 'center', display: 'flex' }}>
|
||||||
|
{t('Explore the top pools with Uniswap Analytics.')}
|
||||||
|
</TYPE.body>
|
||||||
|
</span>
|
||||||
|
<TYPE.largeHeader fontWeight={400} style={{ alignItems: 'center', display: 'flex' }}>
|
||||||
|
{t('↗')}
|
||||||
|
</TYPE.largeHeader>
|
||||||
|
</CTA1>
|
||||||
|
</CTASection>
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
|
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
|
||||||
|
|
||||||
import { PoolState, usePool } from 'hooks/usePools'
|
import { PoolState, usePool } from 'hooks/usePools'
|
||||||
import { useToken } from 'hooks/Tokens'
|
import { useToken } from 'hooks/Tokens'
|
||||||
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
|
import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
|
||||||
@ -9,15 +10,14 @@ import { usePositionTokenURI } from '../../hooks/usePositionTokenURI'
|
|||||||
import { LoadingRows } from './styleds'
|
import { LoadingRows } from './styleds'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { AutoColumn } from 'components/Column'
|
import { AutoColumn } from 'components/Column'
|
||||||
import Row, { RowBetween, RowFixed } from 'components/Row'
|
import { RowBetween, RowFixed } from 'components/Row'
|
||||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||||
import { ButtonText, TYPE } from 'theme'
|
import { TYPE } from 'theme'
|
||||||
import Badge, { BadgeVariant } from 'components/Badge'
|
import Badge from 'components/Badge'
|
||||||
import { calculateGasMargin } from 'utils'
|
import { calculateGasMargin } from 'utils'
|
||||||
import { ButtonConfirmed, ButtonPrimary } from 'components/Button'
|
import { ButtonConfirmed, ButtonPrimary, ButtonGray } from 'components/Button'
|
||||||
import { DarkCard, DarkGreyCard } from 'components/Card'
|
import { DarkCard, LightCard } from 'components/Card'
|
||||||
import CurrencyLogo from 'components/CurrencyLogo'
|
import CurrencyLogo from 'components/CurrencyLogo'
|
||||||
import { AlertTriangle, ToggleLeft, ToggleRight } from 'react-feather'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { currencyId } from 'utils/currencyId'
|
import { currencyId } from 'utils/currencyId'
|
||||||
import { formatTokenAmount } from 'utils/formatTokenAmount'
|
import { formatTokenAmount } from 'utils/formatTokenAmount'
|
||||||
@ -31,44 +31,24 @@ import ReactGA from 'react-ga'
|
|||||||
import { TransactionResponse } from '@ethersproject/providers'
|
import { TransactionResponse } from '@ethersproject/providers'
|
||||||
import { Dots } from 'components/swap/styleds'
|
import { Dots } from 'components/swap/styleds'
|
||||||
import { getPriceOrderingFromPositionForUI } from '../../components/PositionListItem'
|
import { getPriceOrderingFromPositionForUI } from '../../components/PositionListItem'
|
||||||
|
|
||||||
|
import useTheme from '../../hooks/useTheme'
|
||||||
|
import { MinusCircle, PlusCircle } from 'react-feather'
|
||||||
|
|
||||||
|
import RateToggle from '../../components/RateToggle'
|
||||||
import { useSingleCallResult } from 'state/multicall/hooks'
|
import { useSingleCallResult } from 'state/multicall/hooks'
|
||||||
|
|
||||||
|
import RangeBadge from '../../components/Badge/RangeBadge'
|
||||||
|
|
||||||
const PageWrapper = styled.div`
|
const PageWrapper = styled.div`
|
||||||
min-width: 800px;
|
min-width: 800px;
|
||||||
`
|
max-width: 960px;
|
||||||
|
|
||||||
const BadgeWrapper = styled.div`
|
|
||||||
font-size: 14px;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const BadgeText = styled.div`
|
const BadgeText = styled.div`
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
`
|
`
|
||||||
const ResponsiveGrid = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 1em;
|
|
||||||
|
|
||||||
grid-template-columns: 1.5fr repeat(2, 1fr);
|
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
|
||||||
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(2, 1fr);
|
|
||||||
& :nth-child(4) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
& :nth-child(5) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// responsive text
|
// responsive text
|
||||||
// disable the warning because we don't use the end prop, we just want to filter it out
|
// disable the warning because we don't use the end prop, we just want to filter it out
|
||||||
@ -80,14 +60,6 @@ const Label = styled(({ end, ...props }) => <TYPE.label {...props} />)<{ end?: b
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ActiveDot = styled.span`
|
|
||||||
background-color: ${({ theme }) => theme.success};
|
|
||||||
border-radius: 50%;
|
|
||||||
height: 8px;
|
|
||||||
width: 8px;
|
|
||||||
margin-right: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export const DarkBadge = styled.div`
|
export const DarkBadge = styled.div`
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -95,6 +67,37 @@ export const DarkBadge = styled.div`
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const ExtentsText = styled.span`
|
||||||
|
color: ${({ theme }) => theme.text3};
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const HoverText = styled(TYPE.main)`
|
||||||
|
text-decoration: none;
|
||||||
|
color: ${({ theme }) => theme.text3};
|
||||||
|
:hover {
|
||||||
|
color: ${({ theme }) => theme.text1};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const DoubleArrow = styled.span`
|
||||||
|
color: ${({ theme }) => theme.text3};
|
||||||
|
margin: 0 1rem;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ResponsiveButtonPrimary = styled(ButtonPrimary)`
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
width: fit-content;
|
||||||
|
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 49%;
|
||||||
|
`};
|
||||||
|
`
|
||||||
|
|
||||||
function CurrentPriceCard({
|
function CurrentPriceCard({
|
||||||
inverted,
|
inverted,
|
||||||
pool,
|
pool,
|
||||||
@ -106,22 +109,20 @@ function CurrentPriceCard({
|
|||||||
currencyQuote?: Currency
|
currencyQuote?: Currency
|
||||||
currencyBase?: Currency
|
currencyBase?: Currency
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
if (!pool || !currencyQuote || !currencyBase) {
|
if (!pool || !currencyQuote || !currencyBase) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DarkGreyCard width="32%">
|
<LightCard padding="12px ">
|
||||||
<AutoColumn gap="sm" justify="flex-start">
|
<AutoColumn gap="md" justify="center">
|
||||||
<TYPE.main>Current</TYPE.main>
|
<ExtentsText>{t('Current price')}</ExtentsText>
|
||||||
<RowFixed>
|
<TYPE.label textAlign="center">
|
||||||
<TYPE.label>
|
{(inverted ? pool.token1Price : pool.token0Price).toSignificant(4)} {currencyQuote?.symbol}
|
||||||
{(inverted ? pool.token1Price : pool.token0Price).toSignificant(4)} {currencyQuote?.symbol} / 1{' '}
|
</TYPE.label>
|
||||||
{currencyBase?.symbol}
|
|
||||||
</TYPE.label>
|
|
||||||
</RowFixed>
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</DarkGreyCard>
|
</LightCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +133,7 @@ export function PositionPage({
|
|||||||
}: RouteComponentProps<{ tokenId?: string }>) {
|
}: RouteComponentProps<{ tokenId?: string }>) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { chainId, account, library } = useActiveWeb3React()
|
const { chainId, account, library } = useActiveWeb3React()
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined
|
const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined
|
||||||
const { loading, position: positionDetails } = useV3PositionFromTokenId(parsedTokenId)
|
const { loading, position: positionDetails } = useV3PositionFromTokenId(parsedTokenId)
|
||||||
@ -171,8 +173,10 @@ export function PositionPage({
|
|||||||
pool && typeof tickLower === 'number' && typeof tickUpper === 'number'
|
pool && typeof tickLower === 'number' && typeof tickUpper === 'number'
|
||||||
? pool.tickCurrent >= tickLower && pool.tickCurrent < tickUpper
|
? pool.tickCurrent >= tickLower && pool.tickCurrent < tickUpper
|
||||||
: false
|
: false
|
||||||
const below = pool && typeof tickLower === 'number' ? pool.tickCurrent < tickLower : false
|
|
||||||
const above = pool && typeof tickUpper === 'number' ? pool.tickCurrent >= tickUpper : false
|
// keep will need to be able to draw the range visualization
|
||||||
|
// const below = pool && typeof tickLower === 'number' ? pool.tickCurrent < tickLower : false
|
||||||
|
// const above = pool && typeof tickUpper === 'number' ? pool.tickCurrent >= tickUpper : false
|
||||||
|
|
||||||
// fees
|
// fees
|
||||||
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails)
|
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails)
|
||||||
@ -254,204 +258,240 @@ export function PositionPage({
|
|||||||
</LoadingRows>
|
</LoadingRows>
|
||||||
) : (
|
) : (
|
||||||
<PageWrapper>
|
<PageWrapper>
|
||||||
<AutoColumn gap="lg">
|
<AutoColumn gap="md">
|
||||||
<AutoColumn gap="sm">
|
<AutoColumn gap="sm">
|
||||||
|
<Link style={{ textDecoration: 'none', width: 'fit-content', marginBottom: '0.5rem' }} to="/pool">
|
||||||
|
<HoverText>{'← Back to overview'}</HoverText>
|
||||||
|
</Link>
|
||||||
<RowBetween>
|
<RowBetween>
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={20} margin={true} />
|
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={24} margin={true} />
|
||||||
<TYPE.label fontSize={'20px'} mr="10px">
|
<TYPE.label fontSize={'24px'} mr="10px">
|
||||||
{currencyQuote?.symbol} / {currencyBase?.symbol}
|
{currencyQuote?.symbol} / {currencyBase?.symbol}
|
||||||
</TYPE.label>
|
</TYPE.label>
|
||||||
<Badge>
|
<Badge style={{ marginRight: '8px' }}>
|
||||||
<BadgeText>{new Percent(feeAmount, 1_000_000).toSignificant()}%</BadgeText>
|
<BadgeText>{new Percent(feeAmount, 1_000_000).toSignificant()}%</BadgeText>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<RangeBadge inRange={inRange} />
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
|
|
||||||
{ownsNFT && (
|
{ownsNFT && (
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
{feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0) || !!collectMigrationHash ? (
|
|
||||||
<ButtonConfirmed
|
|
||||||
disabled={collecting || !!collectMigrationHash}
|
|
||||||
confirmed={!!collectMigrationHash && !isCollectPending}
|
|
||||||
mr="15px"
|
|
||||||
width="175px"
|
|
||||||
padding="8px"
|
|
||||||
style={{ borderRadius: '12px' }}
|
|
||||||
onClick={collect}
|
|
||||||
>
|
|
||||||
{!!collectMigrationHash && !isCollectPending ? (
|
|
||||||
'Collected'
|
|
||||||
) : isCollectPending || collecting ? (
|
|
||||||
<Dots>Collecting</Dots>
|
|
||||||
) : (
|
|
||||||
'Collect fees'
|
|
||||||
)}
|
|
||||||
</ButtonConfirmed>
|
|
||||||
) : null}
|
|
||||||
{currency0 && currency1 && feeAmount && tokenId ? (
|
{currency0 && currency1 && feeAmount && tokenId ? (
|
||||||
<Link to={`/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeAmount}/${tokenId}`}>
|
<ButtonGray
|
||||||
<ButtonPrimary mr="15px" width="175px" padding="8px" borderRadius="12px">
|
as={Link}
|
||||||
Add liquidity
|
to={`/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeAmount}/${tokenId}`}
|
||||||
</ButtonPrimary>
|
width="fit-content"
|
||||||
</Link>
|
padding="6px 8px"
|
||||||
|
borderRadius="12px"
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
|
>
|
||||||
|
<PlusCircle size={16} style={{ marginRight: '8px' }} />{' '}
|
||||||
|
<TYPE.body color={theme.text1}>{t('Add Liquidity')}</TYPE.body>
|
||||||
|
</ButtonGray>
|
||||||
) : null}
|
) : null}
|
||||||
{tokenId && (
|
{tokenId && (
|
||||||
<Link to={`/remove/${tokenId}`}>
|
<ResponsiveButtonPrimary
|
||||||
<ButtonPrimary width="175px" padding="8px" borderRadius="12px">
|
as={Link}
|
||||||
Remove liquidity
|
to={`/remove/${tokenId}`}
|
||||||
</ButtonPrimary>
|
width="fit-content"
|
||||||
</Link>
|
padding="6px 8px"
|
||||||
|
borderRadius="12px"
|
||||||
|
>
|
||||||
|
<MinusCircle size={16} style={{ marginRight: '8px' }} /> {t('Remove Liquidity')}
|
||||||
|
</ResponsiveButtonPrimary>
|
||||||
)}
|
)}
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
)}
|
)}
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
<RowBetween>
|
<RowBetween></RowBetween>
|
||||||
<BadgeWrapper>
|
|
||||||
{inRange ? (
|
|
||||||
<Badge variant={BadgeVariant.DEFAULT}>
|
|
||||||
<ActiveDot />
|
|
||||||
<BadgeText>{t('Active')}</BadgeText>
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge variant={BadgeVariant.WARNING}>
|
|
||||||
<AlertTriangle width={14} height={14} style={{ marginRight: '4px' }} />
|
|
||||||
|
|
||||||
<BadgeText>{t('Out of range')}</BadgeText>
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</BadgeWrapper>
|
|
||||||
</RowBetween>
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
<Row align="stretch">
|
<RowBetween align="flex-start">
|
||||||
{'result' in metadata ? (
|
{'result' in metadata ? (
|
||||||
<div style={{ marginRight: 12 }}>
|
<DarkCard
|
||||||
<img src={metadata.result.image} />
|
width="100%"
|
||||||
</div>
|
height="100%"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
marginRight: '12px',
|
||||||
|
maxWidth: '360px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ marginRight: 12 }}>
|
||||||
|
<img height="400px" src={metadata.result.image} />
|
||||||
|
</div>
|
||||||
|
</DarkCard>
|
||||||
) : null}
|
) : null}
|
||||||
<DarkCard>
|
<AutoColumn gap="sm" style={{ width: '100%' }}>
|
||||||
<AutoColumn gap="lg">
|
<DarkCard>
|
||||||
<ResponsiveGrid>
|
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
||||||
<Label>Tokens</Label>
|
<AutoColumn gap="md">
|
||||||
<Label end={true}>Liquidity</Label>
|
<Label>Position liquidity</Label>
|
||||||
<Label end={true}>Fees</Label>
|
<TYPE.largeHeader fontSize="36px" fontWeight={500}>
|
||||||
</ResponsiveGrid>
|
$1222.22
|
||||||
<ResponsiveGrid>
|
</TYPE.largeHeader>
|
||||||
<RowFixed>
|
</AutoColumn>
|
||||||
<CurrencyLogo currency={currencyQuote} />
|
|
||||||
<TYPE.label ml="10px">{currencyQuote?.symbol}</TYPE.label>
|
|
||||||
</RowFixed>
|
|
||||||
<Label end={true}>
|
|
||||||
{inverted ? position?.amount0.toSignificant(4) : position?.amount1.toSignificant(4)}
|
|
||||||
</Label>
|
|
||||||
<Label end={true}>
|
|
||||||
{inverted
|
|
||||||
? feeValue0
|
|
||||||
? formatTokenAmount(feeValue0, 4)
|
|
||||||
: '-'
|
|
||||||
: feeValue1
|
|
||||||
? formatTokenAmount(feeValue1, 4)
|
|
||||||
: '-'}
|
|
||||||
</Label>
|
|
||||||
</ResponsiveGrid>
|
|
||||||
<ResponsiveGrid>
|
|
||||||
<RowFixed>
|
|
||||||
<CurrencyLogo currency={currencyBase} />
|
|
||||||
<TYPE.label ml="10px">{currencyBase?.symbol}</TYPE.label>
|
|
||||||
</RowFixed>
|
|
||||||
<Label end={true}>
|
|
||||||
{inverted ? position?.amount1.toSignificant(4) : position?.amount0.toSignificant(4)}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<Label end={true}>
|
<LightCard padding="12px 16px">
|
||||||
{inverted
|
<AutoColumn gap="md">
|
||||||
? feeValue1
|
<RowBetween>
|
||||||
? formatTokenAmount(feeValue1, 4)
|
<RowFixed>
|
||||||
: '-'
|
<CurrencyLogo currency={currencyQuote} size={'20px'} style={{ marginRight: '0.5rem' }} />
|
||||||
: feeValue0
|
<TYPE.main>
|
||||||
? formatTokenAmount(feeValue0, 4)
|
{inverted ? position?.amount0.toSignificant(4) : position?.amount1.toSignificant(4)}
|
||||||
: '-'}
|
</TYPE.main>
|
||||||
</Label>
|
</RowFixed>
|
||||||
</ResponsiveGrid>
|
<TYPE.main>{currencyQuote?.symbol}</TYPE.main>
|
||||||
</AutoColumn>
|
</RowBetween>
|
||||||
</DarkCard>
|
<RowBetween>
|
||||||
</Row>
|
<RowFixed>
|
||||||
|
<CurrencyLogo currency={currencyBase} size={'20px'} style={{ marginRight: '0.5rem' }} />
|
||||||
|
<TYPE.main>
|
||||||
|
{inverted ? position?.amount1.toSignificant(4) : position?.amount0.toSignificant(4)}
|
||||||
|
</TYPE.main>
|
||||||
|
</RowFixed>
|
||||||
|
<TYPE.main>{currencyBase?.symbol}</TYPE.main>
|
||||||
|
</RowBetween>
|
||||||
|
</AutoColumn>
|
||||||
|
</LightCard>
|
||||||
|
</AutoColumn>
|
||||||
|
</DarkCard>
|
||||||
|
<span style={{ width: '24px' }}></span>
|
||||||
|
<DarkCard>
|
||||||
|
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
||||||
|
<AutoColumn gap="md">
|
||||||
|
<RowBetween style={{ alignItems: 'flex-start' }}>
|
||||||
|
<AutoColumn gap="md">
|
||||||
|
<Label>Fees Earned</Label>
|
||||||
|
<TYPE.largeHeader color={theme.green1} fontSize="36px" fontWeight={500}>
|
||||||
|
$2.22
|
||||||
|
</TYPE.largeHeader>
|
||||||
|
</AutoColumn>
|
||||||
|
{feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0) || !!collectMigrationHash ? (
|
||||||
|
<ButtonConfirmed
|
||||||
|
disabled={collecting || !!collectMigrationHash}
|
||||||
|
confirmed={!!collectMigrationHash && !isCollectPending}
|
||||||
|
width="fit-content"
|
||||||
|
style={{ borderRadius: '12px' }}
|
||||||
|
padding="4px 8px"
|
||||||
|
onClick={collect}
|
||||||
|
>
|
||||||
|
{!!collectMigrationHash && !isCollectPending ? (
|
||||||
|
<TYPE.main color={theme.text1}> Collected</TYPE.main>
|
||||||
|
) : isCollectPending || collecting ? (
|
||||||
|
<TYPE.main color={theme.text1}>
|
||||||
|
{' '}
|
||||||
|
<Dots>Collecting</Dots>
|
||||||
|
</TYPE.main>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TYPE.main color={theme.white}>Collect fees</TYPE.main>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ButtonConfirmed>
|
||||||
|
) : null}
|
||||||
|
</RowBetween>
|
||||||
|
</AutoColumn>
|
||||||
|
|
||||||
|
<LightCard padding="12px 16px">
|
||||||
|
<AutoColumn gap="md">
|
||||||
|
<RowBetween>
|
||||||
|
<RowFixed>
|
||||||
|
<CurrencyLogo currency={currencyQuote} size={'20px'} style={{ marginRight: '0.5rem' }} />
|
||||||
|
<TYPE.main>
|
||||||
|
{inverted
|
||||||
|
? feeValue0
|
||||||
|
? formatTokenAmount(feeValue0, 4)
|
||||||
|
: '-'
|
||||||
|
: feeValue1
|
||||||
|
? formatTokenAmount(feeValue1, 4)
|
||||||
|
: '-'}
|
||||||
|
</TYPE.main>
|
||||||
|
</RowFixed>
|
||||||
|
<TYPE.main>{currencyQuote?.symbol}</TYPE.main>
|
||||||
|
</RowBetween>
|
||||||
|
<RowBetween>
|
||||||
|
<RowFixed>
|
||||||
|
<CurrencyLogo currency={currencyBase} size={'20px'} style={{ marginRight: '0.5rem' }} />
|
||||||
|
<TYPE.main>
|
||||||
|
{inverted
|
||||||
|
? feeValue0
|
||||||
|
? formatTokenAmount(feeValue1, 4)
|
||||||
|
: '-'
|
||||||
|
: feeValue1
|
||||||
|
? formatTokenAmount(feeValue0, 4)
|
||||||
|
: '-'}
|
||||||
|
</TYPE.main>
|
||||||
|
</RowFixed>
|
||||||
|
<TYPE.main>{currencyBase?.symbol}</TYPE.main>
|
||||||
|
</RowBetween>
|
||||||
|
</AutoColumn>
|
||||||
|
</LightCard>
|
||||||
|
</AutoColumn>
|
||||||
|
</DarkCard>
|
||||||
|
</AutoColumn>
|
||||||
|
</RowBetween>
|
||||||
<DarkCard>
|
<DarkCard>
|
||||||
<AutoColumn gap="lg">
|
<AutoColumn gap="md">
|
||||||
<TYPE.label display="flex">
|
<RowBetween>
|
||||||
Position Limits
|
<Label display="flex" style={{ marginRight: '12px' }}>
|
||||||
<ButtonText style={{ marginLeft: '10px', color: 'inherit' }}>
|
Price range
|
||||||
{manuallyInverted ? (
|
</Label>
|
||||||
<ToggleLeft onClick={() => setManuallyInverted(false)} />
|
|
||||||
) : (
|
|
||||||
<ToggleRight onClick={() => setManuallyInverted(true)} />
|
|
||||||
)}
|
|
||||||
</ButtonText>
|
|
||||||
</TYPE.label>
|
|
||||||
|
|
||||||
{below && (
|
<RowFixed>
|
||||||
<CurrentPriceCard
|
<RangeBadge inRange={inRange} />
|
||||||
inverted={inverted}
|
<span style={{ width: '8px' }} />
|
||||||
pool={pool}
|
{currencyBase && currencyQuote && (
|
||||||
currencyQuote={currencyQuote}
|
<RateToggle
|
||||||
currencyBase={currencyBase}
|
currencyA={currencyBase}
|
||||||
/>
|
currencyB={currencyQuote}
|
||||||
)}
|
handleRateToggle={() => setManuallyInverted(!manuallyInverted)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</RowFixed>
|
||||||
|
</RowBetween>
|
||||||
|
|
||||||
<RowBetween>
|
<RowBetween>
|
||||||
<DarkGreyCard width="32%">
|
<LightCard padding="12px" width="100%">
|
||||||
<AutoColumn gap="sm" justify="flex-start">
|
<AutoColumn gap="12px" justify="center">
|
||||||
<TYPE.main>Lower</TYPE.main>
|
<ExtentsText>Min</ExtentsText>
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
<TYPE.label>
|
<TYPE.label textAlign="center">
|
||||||
{priceLower?.toSignificant(4)} {currencyQuote?.symbol} / 1 {currencyBase?.symbol}
|
{priceLower?.toSignificant(4)} {currencyQuote?.symbol}
|
||||||
</TYPE.label>
|
</TYPE.label>
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
<DarkBadge>
|
<TYPE.subHeader color={theme.text3} textAlign="center">
|
||||||
<RowFixed>
|
Your position will be <CurrencyLogo currency={inverted ? currency1 : currency0} size="12px" /> 100%{' '}
|
||||||
<TYPE.label mr="6px">100%</TYPE.label>
|
{inverted ? currency1?.symbol : currency0?.symbol} at this price
|
||||||
<CurrencyLogo currency={inverted ? currency1 : currency0} size="16px" />
|
</TYPE.subHeader>
|
||||||
<TYPE.label ml="4px">{inverted ? currency1?.symbol : currency0?.symbol}</TYPE.label>
|
|
||||||
</RowFixed>
|
|
||||||
</DarkBadge>
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</DarkGreyCard>
|
</LightCard>
|
||||||
|
|
||||||
{inRange && (
|
<DoubleArrow>⟷</DoubleArrow>
|
||||||
<CurrentPriceCard
|
<LightCard padding="12px" width="100%">
|
||||||
inverted={inverted}
|
<AutoColumn gap="12px" justify="center">
|
||||||
pool={pool}
|
<ExtentsText>Max</ExtentsText>
|
||||||
currencyQuote={currencyQuote}
|
|
||||||
currencyBase={currencyBase}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DarkGreyCard width="32%">
|
|
||||||
<AutoColumn gap="sm" justify="flex-start">
|
|
||||||
<TYPE.main>Upper</TYPE.main>
|
|
||||||
<RowFixed>
|
<RowFixed>
|
||||||
<TYPE.label>
|
<TYPE.label textAlign="center">
|
||||||
{priceUpper?.toSignificant(4)} {currencyQuote?.symbol} / 1 {currencyBase?.symbol}
|
{priceUpper?.toSignificant(4)} {currencyQuote?.symbol}
|
||||||
</TYPE.label>
|
</TYPE.label>
|
||||||
</RowFixed>
|
</RowFixed>
|
||||||
<DarkBadge>
|
<TYPE.subHeader color={theme.text3} textAlign="center">
|
||||||
<RowFixed>
|
Your position will be <CurrencyLogo currency={inverted ? currency0 : currency1} size="12px" /> 100%{' '}
|
||||||
<TYPE.label mr="6px">100%</TYPE.label>
|
{inverted ? currency0?.symbol : currency1?.symbol} at this price
|
||||||
<CurrencyLogo currency={inverted ? currency0 : currency1} size="16px" />
|
</TYPE.subHeader>
|
||||||
<TYPE.label ml="4px">{inverted ? currency0?.symbol : currency1?.symbol}</TYPE.label>
|
|
||||||
</RowFixed>
|
|
||||||
</DarkBadge>
|
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</DarkGreyCard>
|
</LightCard>
|
||||||
|
|
||||||
{above && (
|
|
||||||
<CurrentPriceCard
|
|
||||||
inverted={inverted}
|
|
||||||
pool={pool}
|
|
||||||
currencyQuote={currencyQuote}
|
|
||||||
currencyBase={currencyBase}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
|
<CurrentPriceCard
|
||||||
|
inverted={inverted}
|
||||||
|
pool={pool}
|
||||||
|
currencyQuote={currencyQuote}
|
||||||
|
currencyBase={currencyBase}
|
||||||
|
/>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</DarkCard>
|
</DarkCard>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
|
@ -15,6 +15,8 @@ import styled, { ThemeContext } from 'styled-components'
|
|||||||
import { HideSmall, TYPE } from 'theme'
|
import { HideSmall, TYPE } from 'theme'
|
||||||
import { LoadingRows } from './styleds'
|
import { LoadingRows } from './styleds'
|
||||||
|
|
||||||
|
import CTACards from './CTACards'
|
||||||
|
|
||||||
const PageWrapper = styled(AutoColumn)`
|
const PageWrapper = styled(AutoColumn)`
|
||||||
max-width: 870px;
|
max-width: 870px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -76,7 +78,7 @@ const ResponsiveButtonPrimary = styled(ButtonPrimary)`
|
|||||||
|
|
||||||
const MainContentWrapper = styled.main`
|
const MainContentWrapper = styled.main`
|
||||||
background-color: ${({ theme }) => theme.bg0};
|
background-color: ${({ theme }) => theme.bg0};
|
||||||
padding: 16px;
|
padding: 8px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -131,7 +133,7 @@ export default function Pool() {
|
|||||||
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
<AutoColumn gap="lg" style={{ width: '100%' }}>
|
||||||
<TitleRow style={{ marginTop: '1rem' }} padding={'0'}>
|
<TitleRow style={{ marginTop: '1rem' }} padding={'0'}>
|
||||||
<HideSmall>
|
<HideSmall>
|
||||||
<TYPE.mediumHeader>Your Positions</TYPE.mediumHeader>
|
<TYPE.mediumHeader>{t('Pools Overview')}</TYPE.mediumHeader>
|
||||||
</HideSmall>
|
</HideSmall>
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
<Menu
|
<Menu
|
||||||
@ -152,6 +154,8 @@ export default function Pool() {
|
|||||||
</ButtonRow>
|
</ButtonRow>
|
||||||
</TitleRow>
|
</TitleRow>
|
||||||
|
|
||||||
|
<CTACards />
|
||||||
|
|
||||||
<MainContentWrapper>
|
<MainContentWrapper>
|
||||||
{positionsLoading ? (
|
{positionsLoading ? (
|
||||||
<LoadingRows>
|
<LoadingRows>
|
||||||
@ -194,6 +198,16 @@ export default function Pool() {
|
|||||||
</NoLiquidity>
|
</NoLiquidity>
|
||||||
)}
|
)}
|
||||||
</MainContentWrapper>
|
</MainContentWrapper>
|
||||||
|
<RowFixed justify="center" style={{ width: '100%' }}>
|
||||||
|
<ButtonGray
|
||||||
|
as={Link}
|
||||||
|
to="/migrate/v2"
|
||||||
|
id="import-pool-link"
|
||||||
|
style={{ padding: '8px 16px', borderRadius: '12px', width: 'fit-content' }}
|
||||||
|
>
|
||||||
|
<TYPE.subHeader>{t('Looking for your V2 Liquidity')}?</TYPE.subHeader>
|
||||||
|
</ButtonGray>
|
||||||
|
</RowFixed>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
@ -68,6 +68,7 @@ const loadingAnimation = keyframes`
|
|||||||
export const LoadingRows = styled.div`
|
export const LoadingRows = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
min-width: 75%;
|
min-width: 75%;
|
||||||
|
max-width: 960px;
|
||||||
grid-column-gap: 0.5em;
|
grid-column-gap: 0.5em;
|
||||||
grid-row-gap: 0.8em;
|
grid-row-gap: 0.8em;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
@ -143,7 +143,7 @@ export const TYPE = {
|
|||||||
return <TextWrapper fontWeight={500} color={'primary1'} {...props} />
|
return <TextWrapper fontWeight={500} color={'primary1'} {...props} />
|
||||||
},
|
},
|
||||||
label(props: TextProps) {
|
label(props: TextProps) {
|
||||||
return <TextWrapper fontWeight={600} color={'text1'} {...props} />
|
return <TextWrapper fontWeight={500} color={'text1'} {...props} />
|
||||||
},
|
},
|
||||||
black(props: TextProps) {
|
black(props: TextProps) {
|
||||||
return <TextWrapper fontWeight={500} color={'text1'} {...props} />
|
return <TextWrapper fontWeight={500} color={'text1'} {...props} />
|
||||||
|
Loading…
Reference in New Issue
Block a user