Commit eeb258eb authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

improvement(Add): skeleton UI with dummy state (#29)

* start add liquidity

* update add state, add input UI

* basic add skeleton with dummy state

* refactor with preview steps on add
parent ab1538b1
......@@ -87,5 +87,28 @@
"forAtLeast": "for at least ",
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds.",
"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",
"selectLiquidityRange": "Select liquidity range",
"selectPool": "Select Fee Pool",
"inputTokens": "Input Tokens",
"fee": "fee",
"setLimits": "Set Limits",
"percent": "Percent",
"rate": "Rate",
"currentRate": "Current {{label}} Rate:",
"inactiveRangeWarning": "Your position will not be active or earn fees until the selected rates come into range.",
"invalidRangeWarning": "Invalid Range",
"connectWallet": "Connect Wallet",
"unsupportedAsset": "Unsupported Asset",
"feePool": "Fee Pool",
"rebalanceMessage": "Your underlying tokens will be automatically rebalanced when the rate of the pool changes and may be different when you withdraw the position.",
"addEarnHelper": "You will earn fees from trades proportional to your share of the pool.",
"learnMoreAboutFess": " Learn more about earning fees.",
"selectAPool": "Select a pool to provide liquidity to.",
"poolType": "Select a pool type based on your preferred liquidity provider fee.",
"rangeWarning": "Your liquidity will only be active and earning fees when the rate of the pool is within this price range.",
"chooseLiquidityAmount": "Choose an amount of tokens to open this liquidity position. If you don’t have enough tokens you can trade for them with a Swap.",
"selectPriceLimits": "Select Price Limits",
"inputTokenDynamic": "Input {{label}}"
}
......@@ -3,7 +3,7 @@ import styled from 'styled-components'
import { darken, lighten } from 'polished'
import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather'
import { ChevronDown, Check } from 'react-feather'
import { Button as RebassButton, ButtonProps } from 'rebass/styled-components'
const Base = styled(RebassButton)<{
......@@ -221,6 +221,26 @@ export const ButtonEmpty = styled(Base)`
}
`
export const ButtonText = styled(Base)`
padding: 0;
width: fit-content;
background: none;
&:focus {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
text-decoration: underline;
}
&:hover {
text-decoration: underline;
}
&:active {
text-decoration: underline;
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
export const ButtonWhite = styled(Base)`
border: 1px solid #edeef2;
background-color: ${({ theme }) => theme.bg1};
......@@ -337,3 +357,48 @@ export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonPr
return <ButtonPrimary {...rest} />
}
}
const ActiveOutlined = styled(ButtonOutlined)`
border: 1px solid;
border-color: ${({ theme }) => theme.primary1};
`
const Circle = styled.div`
height: 20px;
width: 20px;
border-radius: 50%;
background-color: ${({ theme }) => theme.primary1};
display: flex;
align-items: center;
justify-content: center;
`
const CheckboxWrapper = styled.div`
width: 30px;
padding: 0 10px;
`
export function ButtonRadioChecked({ active = false, children, ...rest }: { active?: boolean } & ButtonProps) {
if (!active) {
return (
<ButtonOutlined borderRadius="12px" padding="12px 8px" {...rest}>
{<RowBetween>{children}</RowBetween>}
</ButtonOutlined>
)
} else {
return (
<ActiveOutlined {...rest} padding="12px 8px" borderRadius="12px">
{
<RowBetween>
{children}
<CheckboxWrapper>
<Circle>
<Check size={13} />
</Circle>
</CheckboxWrapper>
</RowBetween>
}
</ActiveOutlined>
)
}
}
import React from 'react'
import styled from 'styled-components'
import { CardProps, Text } from 'rebass'
import { Box } from 'rebass/styled-components'
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
......@@ -26,13 +24,17 @@ export const GreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg3};
`
export const DarkGreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg2};
`
export const OutlineCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg3};
`
export const YellowCard = styled(Card)`
background-color: rgba(243, 132, 30, 0.05);
color: ${({ theme }) => theme.yellow2};
color: ${({ theme }) => theme.yellow3};
font-weight: 500;
`
......@@ -42,19 +44,9 @@ export const PinkCard = styled(Card)`
font-weight: 500;
`
const BlueCardStyled = styled(Card)`
export const BlueCard = styled(Card)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primary1};
color: ${({ theme }) => theme.blue2};
border-radius: 12px;
width: fit-content;
`
export const BlueCard = ({ children }: CardProps) => {
return (
<BlueCardStyled>
<Text fontWeight={500} color="#2172E5">
{children}
</Text>
</BlueCardStyled>
)
}
......@@ -6,7 +6,7 @@ import { useCurrencyBalance } from '../../state/wallet/hooks'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo'
import { RowBetween } from '../Row'
import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme'
import { Input as NumericalInput } from '../NumericalInput'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
......@@ -15,15 +15,24 @@ import { useActiveWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next'
import useTheme from '../../hooks/useTheme'
const InputRow = styled.div<{ selected: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')};
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '12px' : '20px')};
background-color: ${({ theme }) => theme.bg2};
z-index: 1;
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
`
const Container = styled.div<{ hideInput: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '12px' : '20px')};
border: 1px solid ${({ theme }) => theme.bg2};
background-color: ${({ theme }) => theme.bg1};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
`
const CurrencySelect = styled.button<{ selected: boolean }>`
const CurrencySelect = styled.button<{ selected: boolean; hideInput?: boolean }>`
align-items: center;
height: 2.2rem;
font-size: 20px;
font-weight: 500;
background-color: ${({ selected, theme }) => (selected ? theme.bg1 : theme.primary1)};
......@@ -34,7 +43,9 @@ const CurrencySelect = styled.button<{ selected: boolean }>`
cursor: pointer;
user-select: none;
border: none;
padding: 0 0.5rem;
height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.2rem')};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
:focus,
:hover {
......@@ -42,13 +53,19 @@ const CurrencySelect = styled.button<{ selected: boolean }>`
}
`
const InputRow = styled.div<{ selected: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')};
`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme }) => theme.text1};
font-size: 0.75rem;
line-height: 1rem;
padding: 0.75rem 1rem 0 1rem;
padding: 0rem 1rem 0.75rem 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.text2)};
......@@ -71,20 +88,6 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
}
`
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
background-color: ${({ theme }) => theme.bg2};
z-index: 1;
`
const Container = styled.div<{ hideInput: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
border: 1px solid ${({ theme }) => theme.bg2};
background-color: ${({ theme }) => theme.bg1};
`
const StyledTokenName = styled.span<{ active?: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.75rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ active }) => (active ? '20px' : '16px')};
......@@ -148,6 +151,7 @@ export default function CurrencyInputPanel({
id,
showCommonBases,
customBalanceText,
...rest
}: CurrencyInputPanelProps) {
const { t } = useTranslation()
......@@ -161,30 +165,8 @@ export default function CurrencyInputPanel({
}, [setModalOpen])
return (
<InputPanel id={id}>
<InputPanel id={id} hideInput={hideInput} {...rest}>
<Container hideInput={hideInput}>
{!hideInput && (
<LabelRow>
<RowBetween>
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
{label}
</TYPE.body>
{account && (
<TYPE.body
onClick={onMax}
color={theme.text2}
fontWeight={500}
fontSize={14}
style={{ display: 'inline', cursor: 'pointer' }}
>
{!hideBalance && !!currency && selectedCurrencyBalance
? (customBalanceText ?? 'Balance: ') + selectedCurrencyBalance?.toSignificant(6)
: ' -'}
</TYPE.body>
)}
</RowBetween>
</LabelRow>
)}
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
{!hideInput && (
<>
......@@ -202,6 +184,7 @@ export default function CurrencyInputPanel({
)}
<CurrencySelect
selected={!!currency}
hideInput={hideInput}
className="open-currency-select-button"
onClick={() => {
if (!disableCurrencySelect) {
......@@ -210,6 +193,7 @@ export default function CurrencyInputPanel({
}}
>
<Aligner>
<RowFixed>
{pair ? (
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
) : currency ? (
......@@ -228,10 +212,33 @@ export default function CurrencyInputPanel({
: currency?.symbol) || t('selectToken')}
</StyledTokenName>
)}
</RowFixed>
{!disableCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner>
</CurrencySelect>
</InputRow>
{!hideInput && (
<LabelRow>
<RowBetween>
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
{label}
</TYPE.body>
{account && (
<TYPE.body
onClick={onMax}
color={theme.text2}
fontWeight={500}
fontSize={14}
style={{ display: 'inline', cursor: 'pointer' }}
>
{!hideBalance && !!currency && selectedCurrencyBalance
? (customBalanceText ?? 'Balance: ') + selectedCurrencyBalance?.toSignificant(6)
: ' -'}
</TYPE.body>
)}
</RowBetween>
</LabelRow>
)}
</Container>
{!disableCurrencySelect && onCurrencySelect && (
<CurrencySearchModal
......
import React, { useState, useCallback, useEffect } from 'react'
import { OutlineCard } from 'components/Card'
import { RowBetween } from 'components/Row'
import { ButtonGray } from 'components/Button'
import { TYPE } from 'theme'
import { Input as NumericalInput } from '../NumericalInput'
import styled, { keyframes, css } from 'styled-components'
const pulse = (color: string) => keyframes`
0% {
box-shadow: 0 0 0 0 ${color};
}
70% {
box-shadow: 0 0 0 2px ${color};
}
100% {
box-shadow: 0 0 0 0 ${color};
}
`
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
border-color: ${({ active, theme }) => active && theme.blue1};
padding: 8px 12px;
${({ pulsing, theme }) =>
pulsing &&
css`
animation: ${pulse(theme.blue1)} 0.8s linear;
`}
`
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
background-color: ${({ theme }) => theme.bg0};
text-align: ${({ usePercent }) => (usePercent ? 'right' : 'center')};
margin-right: 2px;
`
const ContentWrapper = styled(RowBetween)`
padding: 0 8px;
width: 70%;
`
interface StepCounterProps {
value: string
onUserInput: (value: string) => void
onIncrement?: () => void
onDecrement?: () => void
usePercent?: boolean
prependSymbol?: string | undefined
}
const StepCounter = ({ value, onUserInput, usePercent = false, prependSymbol }: StepCounterProps) => {
// for focus state, styled components doesnt let you select input parent container
const [active, setActive] = useState(false)
// let user type value and only update parent value on blur
const [localValue, setLocalValue] = useState('')
const [useLocalValue, setUseLocalValue] = useState(false)
// animation if parent value updates local value
const [pulsing, setPulsing] = useState<boolean>(false)
const handleOnFocus = () => {
setUseLocalValue(true)
setActive(true)
}
const handleOnBlur = useCallback(() => {
setUseLocalValue(false)
setActive(false)
onUserInput(localValue) // trigger update on parent value
}, [localValue, onUserInput])
useEffect(() => {
if (localValue !== value && !useLocalValue) {
setTimeout(() => {
setLocalValue(value) // reset local value to match parent
setPulsing(true) // trigger animation
setTimeout(function () {
setPulsing(false)
}, 1800)
}, 400)
}
}, [localValue, useLocalValue, value])
const handleDeccrement = useCallback(() => {
localValue && setLocalValue((parseFloat(localValue) * 0.997).toString())
}, [localValue])
const handleIncrement = useCallback(() => {
localValue && setLocalValue((parseFloat(localValue) * 1.003).toString())
}, [localValue])
return (
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur}>
<RowBetween>
<ButtonGray padding="2px 0px" borderRadius="8px" onClick={handleDeccrement} width="50px">
<TYPE.label>-</TYPE.label>
</ButtonGray>
<ContentWrapper>
<StyledInput
className="rate-input-0"
value={localValue}
fontSize="18px"
onUserInput={(val) => {
setLocalValue(val)
}}
prependSymbol={prependSymbol}
usePercent={usePercent}
/>
{usePercent && <TYPE.main>%</TYPE.main>}
</ContentWrapper>
<ButtonGray padding="2px 0px" borderRadius="8px" onClick={handleIncrement} width="50px">
<TYPE.label>+</TYPE.label>
</ButtonGray>
</RowBetween>
</FocusedOutlineCard>
)
}
export default StepCounter
......@@ -43,6 +43,7 @@ export const Input = React.memo(function InnerInput({
value,
onUserInput,
placeholder,
prependSymbol,
...rest
}: {
value: string | number
......@@ -50,6 +51,7 @@ export const Input = React.memo(function InnerInput({
error?: boolean
fontSize?: string
align?: 'right' | 'left'
prependSymbol?: string | undefined
} & Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'onChange' | 'as'>) {
const enforcer = (nextUserInput: string) => {
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
......@@ -60,10 +62,21 @@ export const Input = React.memo(function InnerInput({
return (
<StyledInput
{...rest}
value={value}
value={prependSymbol && value ? prependSymbol + value : value}
onChange={(event) => {
if (prependSymbol) {
const value = event.target.value
// cut off prepended symbol
const formattedValue = value.toString().includes(prependSymbol)
? value.toString().slice(1, value.toString().length + 1)
: value
// replace commas with periods, because uniswap exclusively uses period as the decimal separator
enforcer(formattedValue.replace(/,/g, '.'))
} else {
enforcer(event.target.value.replace(/,/g, '.'))
}
}}
// universal input options
inputMode="decimal"
......
......@@ -47,7 +47,7 @@ const QuestionMark = styled.span`
font-size: 1rem;
`
export default function QuestionHelper({ text }: { text: string }) {
export default function QuestionHelper({ text, size = 16 }: { text: string; size?: number }) {
const [show, setShow] = useState<boolean>(false)
const open = useCallback(() => setShow(true), [setShow])
......@@ -57,7 +57,7 @@ export default function QuestionHelper({ text }: { text: string }) {
<span style={{ marginLeft: 4 }}>
<Tooltip text={text} show={show}>
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}>
<Question size={16} />
<Question size={size} />
</QuestionWrapper>
</Tooltip>
</span>
......
import React from 'react'
import styled from 'styled-components'
const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
width: 100%;
padding: 0.25rem 0.5rem;
border-radius: 8px;
background: ${({ theme, isActive }) => (isActive ? theme.bg2 : 'none')};
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
font-size: 1rem;
font-weight: 400;
padding: 0.35rem 0.6rem;
:hover {
user-select: initial;
color: ${({ theme, isActive }) => (isActive ? theme.text2 : theme.text3)};
}
`
const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean; width?: string }>`
export const ToggleWrapper = styled.button<{ width?: string }>`
display: flex;
align-items: center;
width: ${({ width }) => width ?? '100%'}
padding: 2px;
padding: 1px;
background: ${({ theme }) => theme.bg0};
border-radius: 8px;
border: ${({ theme }) => '2px solid ' + theme.bg2};
......@@ -27,6 +13,25 @@ const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean
outline: none;
`
export const ToggleElement = styled.span<{ isActive?: boolean; fontSize?: string }>`
display: flex;
align-items: center;
width: 100%;
padding: 4px 0.5rem;
border-radius: 6px;
justify-content: center;
height: 100%;
background: ${({ theme, isActive }) => (isActive ? theme.bg2 : 'none')};
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
font-size: ${({ fontSize }) => fontSize ?? '1rem'};
font-weight: 500;
white-space: nowrap;
:hover {
user-select: initial;
color: ${({ theme, isActive }) => (isActive ? theme.text2 : theme.text3)};
}
`
export interface ToggleProps {
options: string[]
activeIndex: number
......@@ -37,17 +42,12 @@ export interface ToggleProps {
export default function MultiToggle({ id, options, activeIndex, toggle, width }: ToggleProps) {
return (
<StyledToggle id={id} isActive={activeIndex === 0} width={width}>
<ToggleWrapper id={id} width={width}>
{options.map((option, index) => (
<ToggleElement
key={id + '-' + index}
isActive={index === activeIndex}
isOnSwitch={true}
onClick={() => toggle(index)}
>
<ToggleElement key={id + '-' + index} isActive={index === activeIndex} onClick={() => toggle(index)}>
{option}
</ToggleElement>
))}
</StyledToggle>
</ToggleWrapper>
)
}
......@@ -23,7 +23,7 @@ const Section = styled(AutoColumn)`
`
const BottomSection = styled(Section)`
background-color: ${({ theme }) => theme.bg2};
// background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
`
......@@ -139,7 +139,7 @@ export function ConfirmationModalContent({
title: string
onDismiss: () => void
topContent: () => React.ReactNode
bottomContent: () => React.ReactNode
bottomContent?: () => React.ReactNode | undefined
}) {
return (
<Wrapper>
......@@ -152,7 +152,7 @@ export function ConfirmationModalContent({
</RowBetween>
{topContent()}
</Section>
<BottomSection gap="12px">{bottomContent()}</BottomSection>
{bottomContent && <BottomSection gap="12px">{bottomContent()}</BottomSection>}
</Wrapper>
)
}
......
import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk'
import React from 'react'
import { Text } from 'rebass'
import { ButtonPrimary } from '../../components/Button'
import { RowBetween, RowFixed } from '../../components/Row'
import CurrencyLogo from '../../components/CurrencyLogo'
import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme'
export function ConfirmAddModalBottom({
noLiquidity,
price,
currencies,
parsedAmounts,
poolTokenPercentage,
onAdd,
}: {
noLiquidity?: boolean
price?: Fraction
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
poolTokenPercentage?: Percent
onAdd: () => void
}) {
return (
<>
<RowBetween>
<TYPE.body>{currencies[Field.CURRENCY_A]?.symbol} Deposited</TYPE.body>
<RowFixed>
<CurrencyLogo currency={currencies[Field.CURRENCY_A]} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>{currencies[Field.CURRENCY_B]?.symbol} Deposited</TYPE.body>
<RowFixed>
<CurrencyLogo currency={currencies[Field.CURRENCY_B]} style={{ marginRight: '8px' }} />
<TYPE.body>{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<TYPE.body>Rates</TYPE.body>
<TYPE.body>
{`1 ${currencies[Field.CURRENCY_A]?.symbol} = ${price?.toSignificant(4)} ${
currencies[Field.CURRENCY_B]?.symbol
}`}
</TYPE.body>
</RowBetween>
<RowBetween style={{ justifyContent: 'flex-end' }}>
<TYPE.body>
{`1 ${currencies[Field.CURRENCY_B]?.symbol} = ${price?.invert().toSignificant(4)} ${
currencies[Field.CURRENCY_A]?.symbol
}`}
</TYPE.body>
</RowBetween>
<RowBetween>
<TYPE.body>Share of Pool:</TYPE.body>
<TYPE.body>{noLiquidity ? '100' : poolTokenPercentage?.toSignificant(4)}%</TYPE.body>
</RowBetween>
<ButtonPrimary style={{ margin: '20px 0 0 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}>
{noLiquidity ? 'Create Pool & Supply' : 'Confirm Supply'}
</Text>
</ButtonPrimary>
</>
)
}
import { Currency, CurrencyAmount, Fraction, Percent } from '@uniswap/sdk'
import React, { useState } from 'react'
import { ButtonPrimary } from '../../components/Button'
import { RowBetween, RowFixed } from '../../components/Row'
import CurrencyLogo from '../../components/CurrencyLogo'
import { Field } from '../../state/mint/actions'
import { TYPE, ExternalLink } from '../../theme'
import { AutoColumn } from 'components/Column'
import { OutlineCard, GreyCard, BlueCard } from 'components/Card'
import styled from 'styled-components'
import { Break } from 'components/earn/styled'
import useTheme from 'hooks/useTheme'
import { AlertOctagon } from 'react-feather'
import { ToggleWrapper, ToggleElement } from 'components/Toggle/MultiToggle'
import { useTranslation } from 'react-i18next'
const Wrapper = styled(AutoColumn)`
padding: 1rem 0;
`
export function ConfirmContent({
currencies,
parsedAmounts,
onAdd,
}: {
noLiquidity?: boolean
price?: Fraction
currencies: { [field in Field]?: Currency }
parsedAmounts: { [field in Field]?: CurrencyAmount }
poolTokenPercentage?: Percent
onAdd: () => void
}) {
const currencyA: Currency | undefined = currencies[Field.CURRENCY_A]
const currencyB: Currency | undefined = currencies[Field.CURRENCY_B]
const [rateCurrency, setRateCurrency] = useState<Currency | undefined>(currencyA)
const theme = useTheme()
const { t } = useTranslation()
return (
<Wrapper gap="lg">
<OutlineCard>
<AutoColumn gap="md">
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyA} />
<TYPE.label ml="8px">{currencyA?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</TYPE.label>
<GreyCard padding="4px 8px" width="fit-content" borderRadius="12px">
<TYPE.darkGray>50%</TYPE.darkGray>
</GreyCard>
</RowFixed>
</RowBetween>
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyB} />
<TYPE.label ml="8px">{currencyB?.symbol}</TYPE.label>
</RowFixed>
<RowFixed>
<TYPE.label mr="8px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</TYPE.label>
<GreyCard padding="4px 8px" borderRadius="12px" width="fit-content">
<TYPE.darkGray>50%</TYPE.darkGray>
</GreyCard>
</RowFixed>
</RowBetween>
<Break />
<RowBetween>
<TYPE.label>{t('feePool')}</TYPE.label>
<TYPE.label>0.6%</TYPE.label>
</RowBetween>
</AutoColumn>
</OutlineCard>
<BlueCard padding="12px">
<RowBetween>
<AlertOctagon stroke={theme.primary1} width={'90px'} />
<TYPE.blue ml="12px" fontSize="14px">
{t('rebalanceMessage')}
</TYPE.blue>
</RowBetween>
</BlueCard>
<OutlineCard padding="16px">
<AutoColumn gap="md">
<RowBetween>
<TYPE.label>Position Limits</TYPE.label>
{currencyA && currencyB && (
<ToggleWrapper width="80px">
<ToggleElement
fontSize="10px"
isActive={rateCurrency === currencyA}
onClick={() => setRateCurrency(currencyA)}
>
{currencyA?.symbol}
</ToggleElement>
<ToggleElement
fontSize="10px"
isActive={rateCurrency === currencyB}
onClick={() => setRateCurrency(currencyB)}
>
{currencyB?.symbol}
</ToggleElement>
</ToggleWrapper>
)}
</RowBetween>
<RowBetween>
<AutoColumn gap="2px">
<TYPE.darkGray>Lower Limit</TYPE.darkGray>
<TYPE.label fontSize="14px">1621.82</TYPE.label>
<GreyCard padding="4px" borderRadius="4px" mt="6px">
<RowFixed>
<CurrencyLogo currency={currencyA} size="12px" />
<TYPE.label fontSize="12px" ml="6px">
100%
</TYPE.label>
<TYPE.label fontSize="12px" ml="6px">
{currencyA?.symbol}
</TYPE.label>
</RowFixed>
</GreyCard>
</AutoColumn>
<AutoColumn gap="2px">
<TYPE.darkGray>Entry Price</TYPE.darkGray>
<TYPE.label fontSize="14px">1621.82</TYPE.label>
<GreyCard padding="4px" borderRadius="4px" mt="6px">
<TYPE.label fontSize="12px" ml="6px">
50%/50%
</TYPE.label>
</GreyCard>
</AutoColumn>
<AutoColumn gap="2px">
<TYPE.darkGray>Upper Limit</TYPE.darkGray>
<TYPE.label fontSize="14px">1621.82</TYPE.label>
<GreyCard padding="4px" borderRadius="4px" mt="6px">
<RowFixed>
<CurrencyLogo currency={currencyB} size="12px" />
<TYPE.label fontSize="12px" ml="6px">
100%
</TYPE.label>
<TYPE.label fontSize="12px" ml="6px">
{currencyB?.symbol}
</TYPE.label>
</RowFixed>
</GreyCard>
</AutoColumn>
</RowBetween>
</AutoColumn>
</OutlineCard>
<TYPE.main>
{t('addEarnHelper')}
<ExternalLink href="">{t('learnMoreAboutFess')}</ExternalLink>
</TYPE.main>
<ButtonPrimary onClick={onAdd} fontSize="20px">
{t('addLiquidity')}
</ButtonPrimary>
</Wrapper>
)
}
export default ConfirmContent
import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, currencyEquals, ETHER, TokenAmount, WETH } from '@uniswap/sdk'
import { Currency, ETHER, TokenAmount } from '@uniswap/sdk'
import React, { useCallback, useContext, useState } from 'react'
import { Plus } from 'react-feather'
import { Link2, AlertTriangle, LifeBuoy, Circle } from 'react-feather'
import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, LightCard } from '../../components/Card'
import { ButtonError, ButtonLight, ButtonPrimary, ButtonRadioChecked, ButtonText } from '../../components/Button'
import { YellowCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs'
import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFlat } from '../../components/Row'
import { RowBetween, RowFixed, AutoRow } from '../../components/Row'
import ConfirmContent from './ConfirmContent'
import { ROUTER_ADDRESS } from '../../constants'
import { PairState } from '../../data/Reserves'
import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/mint/actions'
import { Field, Bound, RangeType } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks'
......@@ -35,11 +31,22 @@ import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { wrappedCurrency } from '../../utils/wrappedCurrency'
import AppBody from '../AppBody'
import { Dots, Wrapper } from '../Pool/styleds'
import { ConfirmAddModalBottom } from './ConfirmAddModalBottom'
import { currencyId } from '../../utils/currencyId'
import { PoolPriceBar } from './PoolPriceBar'
import { useIsTransactionUnsupported } from 'hooks/Trades'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { ToggleWrapper, ToggleElement } from 'components/Toggle/MultiToggle'
import StepCounter from 'components/InputStepCounter/InputStepCounter'
import {
DynamicSection,
CurrencyDropdown,
ScrollablePage,
ScrollableContent,
FixedPreview,
PreviewCard,
} from './styled'
import { useTranslation } from 'react-i18next'
import CurrencyLogo from 'components/CurrencyLogo'
import QuestionHelper from 'components/QuestionHelper'
export default function AddLiquidity({
match: {
......@@ -47,39 +54,41 @@ export default function AddLiquidity({
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { t } = useTranslation()
const { account, chainId, library } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
// const oneCurrencyIsWETH = Boolean(
// chainId &&
// ((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
// (currencyB && currencyEquals(currencyB, WETH[chainId])))
// )
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const { independentField, typedValue, otherTypedValue, rangeType } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
// pairState,
currencyBalances,
parsedAmounts,
ticks,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const { onFieldAInput, onFieldBInput, onLowerRangeInput, onUpperRangeInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
......@@ -211,51 +220,9 @@ export default function AddLiquidity({
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol}
</Text>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<Text fontSize="48px" fontWeight={500} lineHeight="42px" marginRight={10}>
{liquidityMinted?.toSignificant(6)}
</Text>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<Text fontSize="24px">
{currencies[Field.CURRENCY_A]?.symbol + '/' + currencies[Field.CURRENCY_B]?.symbol + ' Pool Tokens'}
</Text>
</Row>
<TYPE.italic fontSize={12} textAlign="left" padding={'8px 0 0 0 '}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</TYPE.italic>
</AutoColumn>
)
}
const modalBottom = () => {
const modalContent = () => {
return (
<ConfirmAddModalBottom
<ConfirmContent
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
......@@ -306,15 +273,41 @@ export default function AddLiquidity({
setTxHash('')
}, [onFieldAInput, txHash])
const isCreate = history.location.pathname.includes('/create')
// const isCreate = history.location.pathname.includes('/create')
const addIsUnsupported = useIsTransactionUnsupported(currencies?.CURRENCY_A, currencies?.CURRENCY_B)
/**
*
* dummy values for v3 prototype
*
*/
const [feeLevel, setFeeLevel] = useState<number>(4)
const clearAll = useCallback(() => {
onFieldAInput('')
onFieldBInput('')
onLowerRangeInput('')
onUpperRangeInput('')
history.push(`/add/`)
}, [history, onFieldAInput, onFieldBInput, onLowerRangeInput, onUpperRangeInput])
const currentTick = ticks[Bound.CURRENT]
const lowerTick = ticks[Bound.LOWER]
const upperTick = ticks[Bound.UPPER]
const [rateCurrencyBase, setRateCurrencyBase] = useState<Currency | null | undefined>(currencyA)
const invalidRange = lowerTick && upperTick && lowerTick.rate > upperTick.rate
const outOfRange =
currentTick &&
lowerTick &&
upperTick &&
!invalidRange &&
(lowerTick.rate > currentTick.rate || upperTick.rate < currentTick.rate)
return (
<>
<AppBody>
<AddRemoveTabs creating={isCreate} adding={true} />
<Wrapper>
<ScrollablePage>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
......@@ -322,49 +315,162 @@ export default function AddLiquidity({
hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
title={'Review Position'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
topContent={modalContent}
/>
)}
pendingText={pendingText}
currencyToAdd={pair?.liquidityToken}
/>
<AutoColumn gap="20px">
{noLiquidity ||
(isCreate ? (
<ColumnCenter>
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={600} color={'primaryText1'}>
You are the first liquidity provider.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
The ratio of tokens you add will set the price of this pool.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
Once you are happy with the rate click supply to review.
</TYPE.link>
<ScrollableContent>
<AppBody>
<Wrapper>
<AutoColumn gap="40px">
<AutoColumn gap="md">
<RowBetween paddingBottom="20px">
<TYPE.label>Select a pair</TYPE.label>
<ButtonText onClick={clearAll}>
<TYPE.blue fontSize="12px">Clear All</TYPE.blue>
</ButtonText>
</RowBetween>
<TYPE.main fontWeight={400} fontSize="14px">
{t('selectAPool')}
</TYPE.main>
<RowBetween>
<CurrencyDropdown
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
hideInput={true}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases
/>
<CurrencyDropdown
value={formattedAmounts[Field.CURRENCY_B]}
hideInput={true}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases
/>
</RowBetween>
</AutoColumn>
</BlueCard>
</ColumnCenter>
) : (
<ColumnCenter>
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={400} color={'primaryText1'}>
<b>Tip:</b> When you add liquidity, you will receive pool tokens representing your position.
These tokens automatically earn fees proportional to your share of the pool, and can be redeemed
at any time.
</TYPE.link>
<AutoColumn gap="16px">
<DynamicSection gap="md" disabled={!currencyB || !currencyA}>
<TYPE.label>{t('selectPool')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="14px">
{t('poolType')}
</TYPE.main>
<RowBetween>
<ButtonRadioChecked width="32%" active={feeLevel === 0} onClick={() => setFeeLevel(0)}>
<TYPE.label>0.1% {t('fee')}</TYPE.label>
</ButtonRadioChecked>
<ButtonRadioChecked active={feeLevel === 1} width="32%" onClick={() => setFeeLevel(1)}>
<TYPE.label>0.3% {t('fee')}</TYPE.label>
</ButtonRadioChecked>
<ButtonRadioChecked width="32%" active={feeLevel === 2} onClick={() => setFeeLevel(2)}>
<TYPE.label>0.5% {t('fee')}</TYPE.label>
</ButtonRadioChecked>
</RowBetween>
</DynamicSection>
</AutoColumn>
</BlueCard>
</ColumnCenter>
))}
<DynamicSection gap="md" disabled={feeLevel >= 4}>
<RowBetween>
<TYPE.label>{t('selectLiquidityRange')}</TYPE.label>
{currencyA && currencyB && (
<ToggleWrapper width="fit-content">
<ToggleElement
isActive={rateCurrencyBase === currencyA}
fontSize="12px"
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyA.symbol} {t('rate')}
</ToggleElement>
<ToggleElement
fontSize="12px"
isActive={currencyB === rateCurrencyBase}
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyB.symbol} Rate
</ToggleElement>
</ToggleWrapper>
)}
</RowBetween>
<TYPE.main fontWeight={400} fontSize="14px">
{t('rangeWarning')}
</TYPE.main>
{price && rateCurrencyBase && (
<RowBetween style={{ backgroundColor: '#1A2028', padding: '8px' }}>
<TYPE.main>{t('currentRate', { label: rateCurrencyBase.symbol })}</TYPE.main>
<TYPE.main>
{rateCurrencyBase === currencyA ? price.toSignificant(3) : price.invert().toSignificant(3)}{' '}
{rateCurrencyBase === currencyB ? currencyA?.symbol : currencyB?.symbol}
</TYPE.main>
</RowBetween>
)}
<RowBetween>
<StepCounter
value={lowerTick?.rate?.toString() ?? ''}
onUserInput={onLowerRangeInput}
usePercent={rangeType === RangeType.PERCENT}
prependSymbol={rangeType === RangeType.PERCENT ? '-' : undefined}
/>
<Link2 style={{ margin: '0 10px' }} size={40} />
<StepCounter
value={upperTick?.rate?.toString() ?? ''}
onUserInput={onUpperRangeInput}
usePercent={rangeType === RangeType.PERCENT}
prependSymbol={rangeType === RangeType.PERCENT ? '+' : undefined}
/>
</RowBetween>
{outOfRange && (
<YellowCard>
<RowBetween>
<AlertTriangle stroke={theme.yellow3} size="24px" />
<TYPE.yellow ml="12px" fontSize="12px">
{t('inactiveRangeWarning')}
</TYPE.yellow>
</RowBetween>
</YellowCard>
)}
{invalidRange && (
<YellowCard>
<RowBetween>
<AlertTriangle stroke={theme.yellow3} size="24px" />
<TYPE.yellow ml="12px" fontSize="12px">
{t('invalidRangeWarning')}
</TYPE.yellow>
</RowBetween>
</YellowCard>
)}
</DynamicSection>
<DynamicSection disabled={!lowerTick || !upperTick}>
<AutoColumn gap="md">
<TYPE.label>{t('inputTokens')}</TYPE.label>
<TYPE.main fontWeight={400} fontSize="14px">
{t('chooseLiquidityAmount')}
</TYPE.main>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
disableCurrencySelect={true}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
......@@ -375,10 +481,12 @@ export default function AddLiquidity({
showCommonBases
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
<Link2 stroke={theme.text2} size={'24px'} />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
disableCurrencySelect={true}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
......@@ -389,32 +497,124 @@ export default function AddLiquidity({
id="add-liquidity-input-tokenb"
showCommonBases
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<>
<LightCard padding="0px" borderRadius={'20px'}>
<RowBetween padding="1rem">
<TYPE.subHeader fontWeight={500} fontSize={14}>
{noLiquidity ? 'Initial prices' : 'Prices'} and pool share
</TYPE.subHeader>
</RowBetween>{' '}
<LightCard padding="1rem" borderRadius={'20px'}>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
</AutoColumn>
</DynamicSection>
</AutoColumn>
</Wrapper>
</AppBody>
</ScrollableContent>
{addIsUnsupported && (
<UnsupportedCurrencyFooter
show={addIsUnsupported}
currencies={[currencies.CURRENCY_A, currencies.CURRENCY_B]}
/>
</LightCard>
</LightCard>
</>
)}
<FixedPreview>
<AutoColumn gap="md">
<TYPE.main fontSize="12px">Position Preview</TYPE.main>
<PreviewCard>
{!currencyA || !currencyB ? (
<RowBetween>
<TYPE.label>Select a pair to begin</TYPE.label>
<QuestionHelper text="Select a pair to begin" size={20} />
</RowBetween>
) : (
<AutoRow gap="4px">
<CurrencyLogo currency={currencyA} size={'20px'} />
<TYPE.label ml="4px">{currencyA.symbol}</TYPE.label>
<TYPE.main>/</TYPE.main>
<CurrencyLogo currency={currencyB} size={'20px'} />
<TYPE.label ml="4px">{currencyB.symbol}</TYPE.label>
</AutoRow>
)}
</PreviewCard>
<PreviewCard disabled={!currencyA || !currencyB}>
<RowBetween>
<TYPE.label>
{feeLevel >= 4
? t('selectPool')
: `${feeLevel === 0 ? '0.1' : feeLevel === 1 ? '0.3' : '0.5'}% fee pool`}
</TYPE.label>
<LifeBuoy size="20px" />
</RowBetween>
</PreviewCard>
<PreviewCard disabled={feeLevel >= 4}>
{!lowerTick || !upperTick ? (
<RowBetween>
<TYPE.label>{t('selectPriceLimits')}</TYPE.label>
<Circle size="20px" />
</RowBetween>
) : (
currencyA &&
currencyB && (
<AutoColumn gap="sm" style={{ width: '100%' }}>
<ToggleWrapper width="100%">
<ToggleElement
isActive={rateCurrencyBase === currencyA}
fontSize="12px"
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyA.symbol} {t('rate')}
</ToggleElement>
<ToggleElement
fontSize="12px"
isActive={currencyB === rateCurrencyBase}
onClick={() => setRateCurrencyBase(rateCurrencyBase === currencyB ? currencyA : currencyB)}
>
{currencyB.symbol} Rate
</ToggleElement>
</ToggleWrapper>
<RowBetween padding="0 32px">
<TYPE.label>{lowerTick.rate}</TYPE.label>
<TYPE.main></TYPE.main>
<TYPE.label>{upperTick.rate}</TYPE.label>
</RowBetween>
</AutoColumn>
)
)}
</PreviewCard>
<PreviewCard disabled={!lowerTick || !upperTick}>
{!formattedAmounts[Field.CURRENCY_A] || !currencyA || !currencyB ? (
<RowBetween>
<TYPE.label>{t('inputTokenDynamic', { label: currencyA ? currencyA.symbol : 'Token' })}</TYPE.label>
<Circle size="20px" />
</RowBetween>
) : (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyA} />
<TYPE.label ml="8px">{formattedAmounts[Field.CURRENCY_A]}</TYPE.label>
<TYPE.label ml="8px">{currencyA.symbol}</TYPE.label>
</RowFixed>
<TYPE.main>50%</TYPE.main>
</RowBetween>
)}
</PreviewCard>
<PreviewCard disabled={!lowerTick || !upperTick}>
{!formattedAmounts[Field.CURRENCY_B] || !currencyA || !currencyB ? (
<RowBetween>
<TYPE.label>{t('inputTokenDynamic', { label: currencyB ? currencyB.symbol : 'Token' })}</TYPE.label>
<Circle size="20px" />
</RowBetween>
) : (
<RowBetween>
<RowFixed>
<CurrencyLogo currency={currencyB} />
<TYPE.label ml="8px">{formattedAmounts[Field.CURRENCY_B]}</TYPE.label>
<TYPE.label ml="8px">{currencyB.symbol}</TYPE.label>
</RowFixed>
<TYPE.main>50%</TYPE.main>
</RowBetween>
)}
</PreviewCard>
{addIsUnsupported ? (
<ButtonPrimary disabled={true}>
<TYPE.main mb="4px">Unsupported Asset</TYPE.main>
<ButtonPrimary disabled={true} borderRadius="12px" padding={'12px'}>
<TYPE.main mb="4px">{t('unsupportedAsset')}</TYPE.main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
<ButtonLight onClick={toggleWalletModal} borderRadius="12px" padding={'12px'}>
{t('connectWallet')}
</ButtonLight>
) : (
<AutoColumn gap={'md'}>
{(approvalA === ApprovalState.NOT_APPROVED ||
......@@ -425,6 +625,8 @@ export default function AddLiquidity({
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<ButtonPrimary
borderRadius="12px"
padding={'12px'}
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
width={approvalB !== ApprovalState.APPROVED ? '48%' : '100%'}
......@@ -438,6 +640,8 @@ export default function AddLiquidity({
)}
{approvalB !== ApprovalState.APPROVED && (
<ButtonPrimary
borderRadius="12px"
padding={'12px'}
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
width={approvalA !== ApprovalState.APPROVED ? '48%' : '100%'}
......@@ -455,30 +659,17 @@ export default function AddLiquidity({
onClick={() => {
expertMode ? onAdd() : setShowConfirm(true)
}}
style={{ borderRadius: '12px' }}
padding={'12px'}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontSize={20} fontWeight={500}>
{error ?? 'Supply'}
</Text>
<Text fontWeight={500}>{error ?? 'Next'}</Text>
</ButtonError>
</AutoColumn>
)}
</AutoColumn>
</Wrapper>
</AppBody>
{!addIsUnsupported ? (
pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', width: '100%', maxWidth: '400px', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null
) : (
<UnsupportedCurrencyFooter
show={addIsUnsupported}
currencies={[currencies.CURRENCY_A, currencies.CURRENCY_B]}
/>
)}
</>
</FixedPreview>
</ScrollablePage>
)
}
import styled from 'styled-components'
import { AutoColumn } from 'components/Column'
import CurrencyInputPanel from 'components/CurrencyInputPanel'
import { DarkGreyCard } from 'components/Card'
export const ScrollablePage = styled.div`
position: relative;
display: flex;
flex-direction: row;
`
export const ScrollableContent = styled.div`
margin-right: 24px;
`
export const FixedPreview = styled.div`
position: relative;
padding: 8px;
width: 260px;
height: fit-content;
background: ${({ theme }) => theme.bg0};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px;
`
export const DynamicSection = styled(AutoColumn)<{ disabled?: boolean }>`
opacity: ${({ disabled }) => (disabled ? '0.3' : '1')}
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'initial')}
`
export const CurrencyDropdown = styled(CurrencyInputPanel)`
width: 49%;
`
export const PreviewCard = styled(DarkGreyCard)<{ disabled?: boolean }>`
padding: 8px;
border-radius: 12px;
min-height: 40px;
opacity: ${({ disabled }) => (disabled ? '0.2' : '1')};
display: flex;
align-items: center;
justify-content: center;
`
......@@ -40,22 +40,15 @@ const AppWrapper = styled.div`
overflow-x: hidden;
`
const HeaderWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap}
width: 100%;
justify-content: space-between;
`
const BodyWrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
padding-top: 100px;
padding-top: 160px;
align-items: center;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
z-index: 10;
${({ theme }) => theme.mediaWidth.upToSmall`
padding: 16px;
......@@ -65,6 +58,15 @@ const BodyWrapper = styled.div`
z-index: 1;
`
const HeaderWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap}
width: 100%;
justify-content: space-between;
position: fixed;
top: 0;
z-index: 2;
`
const Marginer = styled.div`
margin-top: 5rem;
`
......
......@@ -3,7 +3,7 @@ import styled from 'styled-components'
export const BodyWrapper = styled.div`
position: relative;
max-width: 420px;
max-width: 460px;
width: 100%;
background: ${({ theme }) => theme.bg0};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
......
......@@ -3,7 +3,7 @@ import styled from 'styled-components'
export const Wrapper = styled.div`
position: relative;
padding: 1rem;
padding: 20px;
`
export const ClickableText = styled(Text)`
......
......@@ -5,5 +5,19 @@ export enum Field {
CURRENCY_B = 'CURRENCY_B',
}
export enum Bound {
CURRENT = 'CURRENT',
LOWER = 'LOWER',
UPPER = 'UPPER',
}
export enum RangeType {
PERCENT = 'PERCENT',
RATE = 'RATE',
}
export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint')
export const typeLowerRangeInput = createAction<{ typedValue: string }>('mint/typeLowerRangeInput')
export const typeUpperRangeInput = createAction<{ typedValue: string }>('mint/typeUpperRangeInput')
export const updateRangeType = createAction<{ rangeType: RangeType }>('mint/updateRangeType')
export const resetMintState = createAction<void>('mint/resetMintState')
......@@ -9,7 +9,16 @@ import { wrappedCurrency, wrappedCurrencyAmount } from '../../utils/wrappedCurre
import { AppDispatch, AppState } from '../index'
import { tryParseAmount } from '../swap/hooks'
import { useCurrencyBalances } from '../wallet/hooks'
import { Field, typeInput } from './actions'
import {
Field,
Bound,
typeInput,
typeLowerRangeInput,
typeUpperRangeInput,
RangeType,
updateRangeType as updateRangeTypeAction,
} from './actions'
import { tryParseTick } from './utils'
const ZERO = JSBI.BigInt(0)
......@@ -22,6 +31,9 @@ export function useMintActionHandlers(
): {
onFieldAInput: (typedValue: string) => void
onFieldBInput: (typedValue: string) => void
onLowerRangeInput: (typedValue: string) => void
onUpperRangeInput: (typedValue: string) => void
updateRangeType: (rangetype: RangeType) => void
} {
const dispatch = useDispatch<AppDispatch>()
......@@ -31,6 +43,7 @@ export function useMintActionHandlers(
},
[dispatch, noLiquidity]
)
const onFieldBInput = useCallback(
(typedValue: string) => {
dispatch(typeInput({ field: Field.CURRENCY_B, typedValue, noLiquidity: noLiquidity === true }))
......@@ -38,12 +51,41 @@ export function useMintActionHandlers(
[dispatch, noLiquidity]
)
const onLowerRangeInput = useCallback(
(typedValue: string) => {
dispatch(typeLowerRangeInput({ typedValue }))
},
[dispatch]
)
const onUpperRangeInput = useCallback(
(typedValue: string) => {
dispatch(typeUpperRangeInput({ typedValue }))
},
[dispatch]
)
const updateRangeType = useCallback(
(rangeType: RangeType) => {
dispatch(updateRangeTypeAction({ rangeType }))
},
[dispatch]
)
return {
onFieldAInput,
onFieldBInput,
onLowerRangeInput,
onUpperRangeInput,
updateRangeType,
}
}
// dummy entity
export interface Tick {
rate: number
}
export function useDerivedMintInfo(
currencyA: Currency | undefined,
currencyB: Currency | undefined
......@@ -54,6 +96,7 @@ export function useDerivedMintInfo(
pairState: PairState
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmounts: { [field in Field]?: CurrencyAmount }
ticks: { [bound in Bound]?: Tick }
price?: Price
noLiquidity?: boolean
liquidityMinted?: TokenAmount
......@@ -62,7 +105,7 @@ export function useDerivedMintInfo(
} {
const { account, chainId } = useActiveWeb3React()
const { independentField, typedValue, otherTypedValue } = useMintState()
const { independentField, typedValue, otherTypedValue, lowerRangeTypedValue, upperRangeTypedValue } = useMintState()
const dependentField = independentField === Field.CURRENCY_A ? Field.CURRENCY_B : Field.CURRENCY_A
......@@ -117,10 +160,13 @@ export function useDerivedMintInfo(
return undefined
}
}, [noLiquidity, otherTypedValue, currencies, dependentField, independentAmount, currencyA, chainId, currencyB, pair])
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = {
const parsedAmounts: { [field in Field]: CurrencyAmount | undefined } = useMemo(() => {
return {
[Field.CURRENCY_A]: independentField === Field.CURRENCY_A ? independentAmount : dependentAmount,
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount,
}
}, [dependentAmount, independentAmount, independentField])
const price = useMemo(() => {
if (noLiquidity) {
......@@ -135,6 +181,17 @@ export function useDerivedMintInfo(
}
}, [chainId, currencyA, noLiquidity, pair, parsedAmounts])
// parse typed range values and determine closest ticks, dummy rn
const ticks = {
[Bound.CURRENT]: {
rate: parseFloat(
independentField === Field.CURRENCY_A ? price?.toFixed(6) ?? '0' : price?.invert().toFixed(6) ?? '0'
),
},
[Bound.LOWER]: tryParseTick(lowerRangeTypedValue),
[Bound.UPPER]: tryParseTick(upperRangeTypedValue),
}
// liquidity minted
const liquidityMinted = useMemo(() => {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
......@@ -187,6 +244,7 @@ export function useDerivedMintInfo(
pairState,
currencyBalances,
parsedAmounts,
ticks,
price,
noLiquidity,
liquidityMinted,
......
import { createReducer } from '@reduxjs/toolkit'
import { Field, resetMintState, typeInput } from './actions'
import {
Field,
resetMintState,
typeInput,
typeLowerRangeInput,
typeUpperRangeInput,
RangeType,
updateRangeType,
} from './actions'
export interface MintState {
readonly independentField: Field
readonly typedValue: string
readonly otherTypedValue: string // for the case when there's no liquidity
readonly lowerRangeTypedValue: string
readonly upperRangeTypedValue: string
readonly rangeType: RangeType
}
const initialState: MintState = {
independentField: Field.CURRENCY_A,
typedValue: '',
otherTypedValue: '',
lowerRangeTypedValue: '',
upperRangeTypedValue: '',
rangeType: RangeType.RATE,
}
export default createReducer<MintState>(initialState, (builder) =>
builder
.addCase(resetMintState, () => initialState)
.addCase(updateRangeType, (state, { payload: { rangeType } }) => {
return {
...state,
rangeType,
}
})
.addCase(typeLowerRangeInput, (state, { payload: { typedValue } }) => {
return {
...state,
lowerRangeTypedValue: typedValue,
}
})
.addCase(typeUpperRangeInput, (state, { payload: { typedValue } }) => {
return {
...state,
upperRangeTypedValue: typedValue,
}
})
.addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => {
if (noLiquidity) {
// they're typing into the field they've last typed in
......
import { Tick } from './hooks'
/**
* @todo
* udpate to actually parse input and calculate next tick
*/
export function tryParseTick(value?: string): Tick | undefined {
if (!value) {
return undefined
}
try {
return { rate: parseFloat(value) * 0.999 }
} catch (error) {
console.debug(`Failed to parse range amount: "${value}"`, error)
}
return undefined
}
......@@ -162,7 +162,7 @@ export function useUserAddedTokens(): Token[] {
return useMemo(() => {
if (!chainId) return []
return Object.values(serializedTokensMap[chainId as ChainId] ?? {}).map(deserializeToken)
return Object.values(serializedTokensMap?.[chainId as ChainId] ?? {}).map(deserializeToken)
}, [serializedTokensMap, chainId])
}
......
......@@ -80,7 +80,9 @@ export function colors(darkMode: boolean): Colors {
green1: '#27AE60',
yellow1: '#FFE270',
yellow2: '#F3841E',
yellow3: '#F3B71E',
blue1: '#2172E5',
blue2: '#5199FF',
error: '#FD4040',
success: '#27AE60',
......@@ -139,6 +141,9 @@ export const TYPE = {
link(props: TextProps) {
return <TextWrapper fontWeight={500} color={'primary1'} {...props} />
},
label(props: TextProps) {
return <TextWrapper fontWeight={600} color={'text1'} {...props} />
},
black(props: TextProps) {
return <TextWrapper fontWeight={500} color={'text1'} {...props} />
},
......@@ -164,7 +169,7 @@ export const TYPE = {
return <TextWrapper fontWeight={500} color={'blue1'} {...props} />
},
yellow(props: TextProps) {
return <TextWrapper fontWeight={500} color={'yellow1'} {...props} />
return <TextWrapper fontWeight={500} color={'yellow3'} {...props} />
},
darkGray(props: TextProps) {
return <TextWrapper fontWeight={500} color={'text3'} {...props} />
......
......@@ -45,7 +45,9 @@ export interface Colors {
green1: Color
yellow1: Color
yellow2: Color
yellow3: Color
blue1: Color
blue2: Color
error: Color
success: Color
......
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