add basic structure for advanced mode (#703)

* add basic structure for advanced mode

* auto dismiss popup

* Remove test code

* remove shadow

* Update advanced section, ui tweaks, balances back inline

* fix memory leak

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
This commit is contained in:
Ian Lapham 2020-04-30 21:14:29 -04:00 committed by GitHub
parent 60d7bc4532
commit 3f1d7ab310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 821 additions and 132 deletions

@ -34,7 +34,7 @@
"react": "^16.8.6",
"react-device-detect": "^1.6.2",
"react-dom": "^16.8.6",
"react-feather": "^1.1.6",
"react-feather": "^2.0.8",
"react-ga": "^2.5.7",
"react-i18next": "^10.7.0",
"react-router-dom": "^5.0.0",

@ -0,0 +1,95 @@
import React, { useState } from 'react'
import Copy from '../AccountDetails/Copy'
import TokenLogo from '../TokenLogo'
import { Link } from '../../theme/components'
import { TYPE } from '../../theme'
import { Hover } from '../../theme'
import { GreyCard } from '../Card'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import { ChevronDown, ChevronUp } from 'react-feather'
import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils'
export default function BalanceCard({ token0, balance0, import0, token1, balance1, import1 }) {
const [details0, setDetails0] = useState(false)
const [details1, setDetails1] = useState(false)
const { chainId } = useWeb3React()
return (
<AutoColumn gap="lg">
<GreyCard>
<AutoColumn gap="md">
<TYPE.black>Selected Tokens</TYPE.black>
{token0 && balance0 && (
<Hover onClick={() => setDetails0(!details0)}>
<RowBetween>
<RowFixed>
<TokenLogo address={token0?.address || ''} />
<TYPE.black marginLeft="10px">
{token0?.name} ({token0?.symbol})
</TYPE.black>
</RowFixed>
<RowFixed>
<TYPE.black>{balance0?.toSignificant(6)}</TYPE.black>
{details0 ? (
<ChevronUp size="20" style={{ marginLeft: '10px' }} color="black" />
) : (
<ChevronDown size="20" style={{ marginLeft: '10px' }} color="black" />
)}
</RowFixed>
</RowBetween>
{import0 && <TYPE.yellow style={{ paddingLeft: '32px' }}>Token imported by user</TYPE.yellow>}
</Hover>
)}
{details0 && (
<AutoColumn gap="sm" style={{ marginTop: '2px', marginBottom: '6px' }}>
<RowFixed>
<TYPE.blue style={{ paddingLeft: '32px' }}>Copy token address</TYPE.blue>
<Copy toCopy={token0?.address} />
</RowFixed>
<Link href={getEtherscanLink(chainId, token0?.address, 'address')} style={{ paddingLeft: '32px' }}>
View on etherscan
</Link>
</AutoColumn>
)}
{token1 && balance1 && (
<Hover onClick={() => setDetails1(!details1)}>
<RowBetween>
<RowFixed>
<TokenLogo address={token1?.address || ''} />
<TYPE.black marginLeft="10px">
{token1?.name} ({token1?.symbol})
</TYPE.black>
</RowFixed>
<RowFixed>
<TYPE.black>{balance1?.toSignificant(6)}</TYPE.black>
{details1 ? (
<ChevronUp size="20" style={{ marginLeft: '10px' }} color="black" />
) : (
<ChevronDown size="20" style={{ marginLeft: '10px' }} color="black" />
)}
</RowFixed>
</RowBetween>
{import0 && <TYPE.yellow style={{ paddingLeft: '32px' }}>Token imported by user</TYPE.yellow>}
</Hover>
)}
{details1 && (
<AutoColumn gap="sm" style={{ marginTop: '2px', marginBottom: '6px' }}>
<RowFixed>
<TYPE.blue style={{ paddingLeft: '32px' }}>Copy token address</TYPE.blue>
<Copy toCopy={token1?.address} />
</RowFixed>
<Link href={getEtherscanLink(chainId, token1?.address, 'address')} style={{ paddingLeft: '32px' }}>
View on etherscan
</Link>
</AutoColumn>
)}
</AutoColumn>
</GreyCard>
</AutoColumn>
)
}

@ -20,6 +20,10 @@ const Base = styled(RebassButton)`
&:disabled {
cursor: auto;
}
> * {
user-select: none;
}
`
export const ButtonPrimary = styled(Base)`

@ -22,7 +22,7 @@ export const GreyCard = styled(Card)`
`
export const YellowCard = styled(Card)`
background-color: rgba(243, 190, 30, 0.3);
background-color: rgba(243, 132, 30, 0.05);
color: ${({ theme }) => theme.yellow2};
font-weight: 500;
`

@ -6,10 +6,14 @@ import { darken } from 'polished'
import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo'
import SearchModal from '../SearchModal'
import { RowBetween } from '../Row'
import { TYPE, Hover } from '../../theme'
import { Input as NumericalInput } from '../NumericalInput'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next'
import { useAddressBalance } from '../../contexts/Balances'
const InputRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
@ -39,6 +43,27 @@ const CurrencySelect = styled.button`
}
`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme }) => theme.text1};
font-size: 0.75rem;
line-height: 1rem;
padding: 0 1.25rem 1rem 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.text2)};
}
`
const ErrorSpan = styled.span`
color: ${({ error, theme }) => error && theme.red1};
:hover {
cursor: pointer;
color: ${({ error, theme }) => error && darken(0.1, theme.red1)};
}
`
const Aligner = styled.span`
display: flex;
align-items: center;
@ -65,9 +90,7 @@ const InputPanel = styled.div`
const Container = styled.div`
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
/* border: 1px solid ${({ error, theme }) => (error ? theme.red1 : theme.bg2)}; */
border: 1px solid ${({ theme }) => theme.bg2};
background-color: ${({ theme }) => theme.bg1};
`
@ -118,6 +141,8 @@ export default function CurrencyInputPanel({
const { t } = useTranslation()
const [modalOpen, setModalOpen] = useState(false)
const { account } = useWeb3React()
const userTokenBalance = useAddressBalance(account, token)
return (
<InputPanel>
@ -164,6 +189,16 @@ export default function CurrencyInputPanel({
</Aligner>
</CurrencySelect>
</InputRow>
{!hideBalance && !!token && (
<LabelRow>
<RowBetween>
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
<Hover onClick={onMax}>
<TYPE.body fontWeight={500}>Balance: {userTokenBalance?.toSignificant(6)}</TYPE.body>
</Hover>
</RowBetween>
</LabelRow>
)}
</Container>
{!disableTokenSelect && (
<SearchModal

@ -5,24 +5,24 @@ import { withRouter } from 'react-router-dom'
import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TradeType, Pair, Trade, TokenAmount, JSBI, Percent } from '@uniswap/sdk'
import Copy from '../AccountDetails/Copy'
import TokenLogo from '../TokenLogo'
import SlippageTabs from '../SlippageTabs'
import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput'
import AdvancedSettings from '../AdvancedSettings'
import AddressInputPanel from '../AddressInputPanel'
import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import Copy from '../AccountDetails/Copy'
import { Link } from '../../theme/components'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { Hover } from '../../theme'
import { ArrowDown } from 'react-feather'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { RowBetween, RowFixed, AutoRow } from '../../components/Row'
import { GreyCard, BlueCard, YellowCard } from '../../components/Card'
import { ArrowDown, ChevronDown, ChevronUp } from 'react-feather'
import { ButtonPrimary, ButtonError, ButtonLight } from '../Button'
import { GreyCard, BlueCard, YellowCard, LightCard } from '../../components/Card'
import { usePair } from '../../contexts/Pairs'
import { useToken } from '../../contexts/Tokens'
@ -33,7 +33,7 @@ import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { ROUTER_ADDRESSES } from '../../constants'
import { INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
// import { INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { getRouterContract, calculateGasMargin, getProviderOrSigner, getEtherscanLink } from '../../utils'
const Wrapper = styled.div`
@ -60,6 +60,41 @@ const FixedBottom = styled.div`
margin-bottom: 40px;
`
const AdvancedDropwdown = styled.div`
position: absolute;
margin-top: 2px;
left: -8px;
width: 339px;
margin-bottom: 40px;
padding: 10px 0;
padding-top: 24px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
color: #565a69;
background-color: rgba(237, 238, 242, 0.8);
color: ${({ theme }) => theme.text2};
z-index: -1;
`
// const PseudoBottom = styled.div`
// height: 14px;
// width: 340px;
// background: white;
// position: absolute;
// border-bottom-left-radius: 41px;
// top: 0px;
// border-bottom-right-radius: 40px;
// left: -1px;
// /* 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); */
// `
const SectionBreak = styled.div`
height: 2px;
width: 100%;
background-color: ${({ theme }) => theme.bg1};
`
const BottomGrouping = styled.div`
margin-top: 20px;
position: relative;
@ -67,7 +102,7 @@ const BottomGrouping = styled.div`
const ErrorText = styled(Text)`
color: ${({ theme, warningLow, warningMedium, warningHigh }) =>
warningHigh ? theme.red1 : warningMedium ? theme.yellow2 : warningLow ? theme.green1 : theme.text3};
warningHigh ? theme.red1 : warningMedium ? theme.yellow2 : warningLow ? theme.green1 : theme.text1};
`
const InputGroup = styled(AutoColumn)`
@ -313,9 +348,9 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
const pendingApprovalInput = usePendingApproval(tokens[Field.INPUT]?.address)
// check for imported tokens to show warning
const importedTokenInput = tokens[Field.INPUT] && !!!INITIAL_TOKENS_CONTEXT?.[chainId]?.[tokens[Field.INPUT]?.address]
const importedTokenOutput =
tokens[Field.OUTPUT] && !!!INITIAL_TOKENS_CONTEXT?.[chainId]?.[tokens[Field.OUTPUT]?.address]
// const importedTokenInput = tokens[Field.INPUT] && !!!INITIAL_TOKENS_CONTEXT?.[chainId]?.[tokens[Field.INPUT]?.address]
// const importedTokenOutput =
// tokens[Field.OUTPUT] && !!!INITIAL_TOKENS_CONTEXT?.[chainId]?.[tokens[Field.OUTPUT]?.address]
// entities for swap
const pair: Pair = usePair(tokens[Field.INPUT], tokens[Field.OUTPUT])
@ -378,6 +413,16 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : ''
}
const priceSlippage =
slippageFromTrade &&
new Percent(
JSBI.subtract(
JSBI.multiply(slippageFromTrade.numerator, JSBI.BigInt('1000')),
JSBI.multiply(JSBI.BigInt('3'), slippageFromTrade.denominator)
),
JSBI.multiply(slippageFromTrade.denominator, JSBI.BigInt('1000'))
)
const onTokenSelection = useCallback((field: Field, address: string) => {
dispatch({
type: SwapAction.SELECT_TOKEN,
@ -915,16 +960,6 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
</AutoColumn>
)
}
if (showAdvanced) {
return (
<AdvancedSettings
setIsOpen={setShowAdvanced}
setDeadline={setDeadline}
setAllowedSlippage={setAllowedSlippage}
allowedSlippage={allowedSlippage}
/>
)
}
if (!sending || (sending && sendingWithSwap)) {
return (
@ -949,13 +984,6 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
} or the transaction will revert.`}
</TYPE.italic>
)}
<Link
onClick={() => {
setShowAdvanced(true)
}}
>
Advanced
</Link>
</AutoColumn>
</>
)
@ -989,10 +1017,14 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
warningMedium={warningMedium}
warningHigh={warningHigh}
>
{slippageFromTrade ? slippageFromTrade.toFixed(4) : '0'}%
{priceSlippage
? priceSlippage.toFixed(4) === '0.0000'
? '<0.0001%'
: priceSlippage.toFixed(4) + '%'
: '-'}
</ErrorText>
<Text fontWeight={500} fontSize={14} color="#888D9B" pt={1}>
Slippage
Price Slippage
</Text>
</AutoColumn>
</AutoRow>
@ -1167,6 +1199,11 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
/>
</AutoColumn>
)}
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<LightCard padding="1rem" borderRadius={'20px'}>
<PriceBar />
</LightCard>
)}
</AutoColumn>
<BottomGrouping>
{noRoute ? (
@ -1225,64 +1262,144 @@ function ExchangePage({ sendingInput = false, history, initialCurrency, params }
</ButtonError>
)}
</BottomGrouping>
<FixedBottom>
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<GreyCard pt={2} mb={2}>
<PriceBar />
</GreyCard>
)}
<AutoColumn gap="lg">
{importedTokenInput && (
<YellowCard style={{ paddingTop: '1rem' }}>
<AutoColumn gap="sm">
<TYPE.mediumHeader>Token imported via address</TYPE.mediumHeader>
<AutoRow gap="4px">
<TokenLogo address={tokens[Field.INPUT]?.address || ''} />
<TYPE.body>({tokens[Field.INPUT]?.symbol})</TYPE.body>
<Link href={getEtherscanLink(chainId, tokens[Field.INPUT]?.address, 'address')}>
(View on Etherscan)
</Link>
<Copy toCopy={tokens[Field.INPUT]?.address} />
</AutoRow>
<TYPE.subHeader>
Please verify the legitimacy of this token before making any transactions.
</TYPE.subHeader>
</AutoColumn>
</YellowCard>
)}
{importedTokenOutput && (
<YellowCard style={{ paddingTop: '1rem' }}>
<AutoColumn gap="sm">
<TYPE.mediumHeader>Token imported via address</TYPE.mediumHeader>
<AutoRow gap="4px">
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} />
<TYPE.body>({tokens[Field.OUTPUT]?.symbol})</TYPE.body>
<Link href={getEtherscanLink(chainId, tokens[Field.OUTPUT]?.address, 'address')}>
(View on Etherscan)
</Link>
</AutoRow>
<TYPE.subHeader>
Please verify the legitimacy of this token before making any transactions.
</TYPE.subHeader>
</AutoColumn>
</YellowCard>
)}
{warningHigh && (
<GreyCard style={{ paddingTop: '1rem' }}>
<AutoColumn gap="md" mt={2}>
<RowBetween>
<Text fontWeight={500}>Slippage Warning</Text>
<QuestionHelper text="" />
</RowBetween>
<Text color="#565A69" lineHeight="145.23%;">
This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesnt have
enough liquidity to support this trade. Are you sure you want to continue this trade?
{tokens[Field.INPUT] && tokens[Field.OUTPUT] && !noRoute && (
<AdvancedDropwdown>
{!showAdvanced && (
<Hover>
<RowBetween onClick={() => setShowAdvanced(true)} padding={'0 20px'}>
<Text fontSize={14} fontWeight={500} style={{ userSelect: 'none' }}>
Show Advanced
</Text>
</AutoColumn>
</GreyCard>
<ChevronDown color={'#565A69'} />
</RowBetween>
</Hover>
)}
</AutoColumn>
</FixedBottom>
{showAdvanced && (
<AutoColumn gap="md">
<Hover>
<RowBetween onClick={() => setShowAdvanced(false)} padding={'0 20px'}>
<Text fontSize={14} color="#565A69" fontWeight={500} style={{ userSelect: 'none' }}>
Hide Advanced
</Text>
<ChevronUp color="#565A69" />
</RowBetween>
</Hover>
<SectionBreak />
<AutoColumn style={{ padding: '0 20px' }}>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400}>
{independentField === Field.INPUT
? sending
? 'Maximum amount sent'
: 'Maximum amount received'
: 'Minimum amount sold'}
</TYPE.black>
<QuestionHelper text="" />
</RowFixed>
<RowFixed>
<TYPE.black fontSize={14}>
{independentField === Field.INPUT
? slippageAdjustedAmounts[Field.OUTPUT]
? slippageAdjustedAmounts[Field.OUTPUT]?.toFixed(5) === '0.00000'
? '<0.00001'
: slippageAdjustedAmounts[Field.OUTPUT]?.toFixed(5)
: '-'
: slippageAdjustedAmounts[Field.INPUT]
? slippageAdjustedAmounts[Field.INPUT]?.toFixed(5) === '0.00000'
? '<0.00001'
: slippageAdjustedAmounts[Field.INPUT]?.toFixed(5)
: '-'}
</TYPE.black>
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
<TYPE.black fontSize={14} marginLeft={'4px'}>
{independentField === Field.INPUT
? parsedAmounts[Field.OUTPUT] && tokens[Field.OUTPUT]?.symbol
: parsedAmounts[Field.INPUT] && tokens[Field.INPUT]?.symbol}
</TYPE.black>
)}
</RowFixed>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400}>
Price Slippage
</TYPE.black>
<QuestionHelper text="" />
</RowFixed>
<ErrorText
fontWeight={500}
fontSize={14}
warningLow={warningLow}
warningMedium={warningMedium}
warningHigh={warningHigh}
>
{priceSlippage
? priceSlippage.toFixed(4) === '0.0000'
? '<0.0001%'
: priceSlippage.toFixed(4) + '%'
: '-'}
</ErrorText>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400}>
Total Slippage
</TYPE.black>
<QuestionHelper text="" />
</RowFixed>
<TYPE.black fontSize={14}>
{slippageFromTrade ? slippageFromTrade.toSignificant(4) + '%' : '-'}
</TYPE.black>
</RowBetween>
</AutoColumn>
<SectionBreak />
<RowFixed padding={'0 20px'}>
<TYPE.black fontSize={14}>Set front running resistance</TYPE.black>
<QuestionHelper text="" />
</RowFixed>
<SlippageTabs
rawSlippage={allowedSlippage}
setRawSlippage={setAllowedSlippage}
deadline={deadline}
setDeadline={setDeadline}
/>
</AutoColumn>
)}
<FixedBottom>
<AutoColumn gap="lg">
{warningHigh && (
<YellowCard style={{ padding: '20px', paddingTop: '10px', marginBottom: '10px' }}>
<AutoColumn gap="md" mt={2}>
<RowBetween>
<RowFixed>
<span role="img" aria-label="warning">
</span>{' '}
<Text fontWeight={500} marginLeft="4px">
Slippage Warning
</Text>
</RowFixed>
</RowBetween>
<Text lineHeight="145.23%;" fontSize={14} fontWeight={400}>
This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesnt have
enough liquidity to support this trade.
</Text>
</AutoColumn>
</YellowCard>
)}
{/* <BalanceCard
token0={tokens[Field.INPUT]}
token1={tokens[Field.OUTPUT]}
import0={importedTokenInput}
balance0={userBalances[Field.INPUT]}
balance1={userBalances[Field.OUTPUT]}
import1={importedTokenOutput}
/> */}
</AutoColumn>
</FixedBottom>
</AdvancedDropwdown>
)}
</Wrapper>
)
}

@ -30,6 +30,7 @@ const HeaderFrame = styled.div`
padding: 10px;
width: calc(100% - 20px);
`};
z-index: 2;
`
const HeaderElement = styled.div`

@ -45,9 +45,9 @@ const MobilePopupInner = styled.div`
const FixedPopupColumn = styled(AutoColumn)`
position: absolute;
top: 80px;
right: 20px
width: 380px;
top: 56px;
right: 24px;
width: 355px;
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
@ -57,7 +57,6 @@ const FixedPopupColumn = styled(AutoColumn)`
const Popup = styled.div`
display: inline-block;
width: 100%;
min-height: 120px;
padding: 1em;
box-sizing: border-box;
background-color: white;
@ -67,6 +66,7 @@ const Popup = styled.div`
padding: 20px;
padding-right: 35px;
z-index: 2;
overflow: hidden;
${({ theme }) => theme.mediaWidth.upToSmall`
min-width: 290px;
@ -96,7 +96,7 @@ export default function App() {
return (
<Popup key={item.key}>
<StyledClose color="#888D9B" onClick={() => removePopup(item.key)} />
{item.content}
{React.cloneElement(item.content, { popKey: item.key })}
</Popup>
)
})}
@ -127,7 +127,7 @@ export default function App() {
return (
<Popup key={item.key}>
<StyledClose color="#888D9B" onClick={() => removePopup(item.key)} />
{item.content}
{React.cloneElement(item.content, { popKey: item.key })}
</Popup>
)
})}

@ -26,8 +26,8 @@ const QuestionWrapper = styled.div`
`
const HelpCircleStyled = styled.img`
height: 24px;
width: 23px;
height: 20px;
width: 20px;
`
const fadeIn = keyframes`

@ -72,8 +72,6 @@ export default function InputSlider({ value, onChange, override }) {
function handleChange(e, val) {
setInternalVal(val)
console.log(val)
console.log(debouncedInternalValue)
if (val === debouncedInternalValue) {
onChange(e, val)
}

@ -0,0 +1,379 @@
import React, { useState, useEffect, useRef, useCallback } from 'react'
import styled, { css } from 'styled-components'
import QuestionHelper from '../Question'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import { darken } from 'polished'
import { useDebounce } from '../../hooks'
const WARNING_TYPE = Object.freeze({
none: 'none',
emptyInput: 'emptyInput',
invalidEntryBound: 'invalidEntryBound',
riskyEntryHigh: 'riskyEntryHigh',
riskyEntryLow: 'riskyEntryLow'
})
const FancyButton = styled.button`
color: ${({ theme }) => theme.textColor};
align-items: center;
min-width: 55px;
height: 2rem;
border-radius: 36px;
font-size: 12px;
border: 1px solid ${({ theme }) => theme.bg3};
outline: none;
background: ${({ theme }) => theme.bg1};
:hover {
cursor: inherit;
border: 1px solid ${({ theme }) => theme.bg4};
}
:focus {
border: 1px solid ${({ theme }) => theme.blue1};
}
`
const Option = styled(FancyButton)`
margin-right: 8px;
:hover {
cursor: pointer;
}
background-color: ${({ active, theme }) => active && theme.blue1};
color: ${({ active, theme }) => (active ? theme.white : theme.text1)};
`
const Input = styled.input`
background: ${({ theme }) => theme.bg1};
flex-grow: 1;
font-size: 12px;
min-width: 20px;
outline: none;
box-sizing: border-box;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
cursor: inherit;
color: ${({ theme }) => theme.text1};
text-align: left;
${({ active }) =>
active &&
css`
color: initial;
cursor: initial;
text-align: right;
`}
${({ placeholder }) =>
placeholder !== 'Custom' &&
css`
text-align: right;
color: ${({ theme }) => theme.text1};
`}
${({ color }) =>
color === 'red' &&
css`
color: ${({ theme }) => theme.red1};
`}
`
const BottomError = styled(Text)`
font-size: 14px;
font-weight: 400;
${({ show }) =>
show &&
css`
padding-top: 12px;
`}
`
const OptionCustom = styled(FancyButton)`
height: 2rem;
position: relative;
padding: 0 0.75rem;
${({ active }) =>
active &&
css`
border: 1px solid ${({ theme, warning }) => (warning ? theme.red1 : theme.blue1)};
:hover {
border: 1px solid ${({ theme, warning }) => (warning ? darken(0.1, theme.red1) : darken(0.1, theme.blue1))};
}
`}
input {
width: 100%;
height: 100%;
border: 0px;
border-radius: 2rem;
}
`
const SlippageSelector = styled.div`
padding: 0 20px;
`
const Percent = styled.div`
color: inherit;
font-size: 0, 8rem;
flex-grow: 0;
${({ color, theme }) =>
(color === 'faded' &&
css`
color: ${theme.bg1};
`) ||
(color === 'red' &&
css`
color: ${theme.red1};
`)};
`
export default function TransactionDetails({ setRawSlippage, rawSlippage, deadline, setDeadline }) {
const [activeIndex, setActiveIndex] = useState(2)
const [warningType, setWarningType] = useState(WARNING_TYPE.none)
const inputRef = useRef()
const [userInput, setUserInput] = useState('')
const debouncedInput = useDebounce(userInput, 150)
const [initialSlippage] = useState(rawSlippage)
const [deadlineInput, setDeadlineInput] = useState(deadline / 60)
function parseCustomDeadline(e) {
let val = e.target.value
const acceptableValues = [/^$/, /^\d+$/]
if (acceptableValues.some(re => re.test(val))) {
setDeadlineInput(val)
setDeadline(val * 60)
}
}
const setFromCustom = () => {
setActiveIndex(4)
inputRef.current.focus()
// if there's a value, evaluate the bounds
checkBounds(debouncedInput)
}
const updateSlippage = useCallback(
newSlippage => {
// round to 2 decimals to prevent ethers error
let numParsed = parseInt(newSlippage * 100)
// set both slippage values in parents
setRawSlippage(numParsed)
},
[setRawSlippage]
)
// used for slippage presets
const setFromFixed = useCallback(
(index, slippage) => {
// update slippage in parent, reset errors and input state
updateSlippage(slippage)
setWarningType(WARNING_TYPE.none)
setActiveIndex(index)
},
[updateSlippage]
)
useEffect(() => {
switch (Number.parseInt(initialSlippage)) {
case 10:
setFromFixed(1, 0.1)
break
case 50:
setFromFixed(2, 0.5)
break
case 100:
setFromFixed(3, 1)
break
default:
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(val => val.test(initialSlippage / 100))) {
setUserInput(initialSlippage / 100)
setActiveIndex(4)
}
}
}, [initialSlippage, setFromFixed])
const checkBounds = useCallback(
slippageValue => {
setWarningType(WARNING_TYPE.none)
if (slippageValue === '' || slippageValue === '.') {
return setWarningType(WARNING_TYPE.emptyInput)
}
// check bounds and set errors
if (Number(slippageValue) < 0 || Number(slippageValue) > 50) {
return setWarningType(WARNING_TYPE.invalidEntryBound)
}
if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) {
setWarningType(WARNING_TYPE.riskyEntryLow)
}
if (Number(slippageValue) > 5) {
setWarningType(WARNING_TYPE.riskyEntryHigh)
}
//update the actual slippage value in parent
updateSlippage(Number(slippageValue))
},
[updateSlippage]
)
// check that the theyve entered number and correct decimal
const parseInput = e => {
let input = e.target.value
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(a => a.test(input))) {
setUserInput(input)
}
}
useEffect(() => {
if (activeIndex === 4) {
checkBounds(debouncedInput)
}
})
const dropDownContent = () => {
return (
<>
<SlippageSelector>
<RowBetween>
<Option
onClick={() => {
setFromFixed(1, 0.1)
}}
active={activeIndex === 1}
>
0.1%
</Option>
<Option
onClick={() => {
setFromFixed(2, 0.5)
}}
active={activeIndex === 2}
>
0.5%
</Option>
<Option
onClick={() => {
setFromFixed(3, 1)
}}
active={activeIndex === 3}
>
1%
</Option>
<OptionCustom
active={activeIndex === 4}
warning={
warningType !== WARNING_TYPE.none &&
warningType !== WARNING_TYPE.emptyInput &&
warningType !== WARNING_TYPE.riskyEntryLow
}
onClick={() => {
setFromCustom()
}}
>
<RowBetween>
{!(warningType === WARNING_TYPE.none || warningType === WARNING_TYPE.emptyInput) && (
<span
role="img"
aria-label="warning"
style={{
color:
warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: warningType === WARNING_TYPE.riskyEntryLow
? '#F3841E'
: ''
}}
>
</span>
)}
<Input
tabIndex={-1}
ref={inputRef}
active={activeIndex === 4}
placeholder={
activeIndex === 4
? !!userInput
? ''
: '0'
: activeIndex !== 4 && userInput !== ''
? userInput
: 'Custom'
}
value={activeIndex === 4 ? userInput : ''}
onChange={parseInput}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
/>
<Percent
color={
activeIndex !== 4
? 'faded'
: warningType === WARNING_TYPE.riskyEntryHigh || warningType === WARNING_TYPE.invalidEntryBound
? 'red'
: ''
}
>
%
</Percent>
</RowBetween>
</OptionCustom>
</RowBetween>
<RowBetween>
<BottomError
show={activeIndex === 4}
color={
warningType === WARNING_TYPE.emptyInput
? '#565A69'
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: warningType === WARNING_TYPE.riskyEntryLow
? '#F3841E'
: ''
}
>
{warningType === WARNING_TYPE.emptyInput && 'Enter a slippage percentage'}
{warningType === WARNING_TYPE.invalidEntryBound && 'Please select a value no greater than 50%'}
{warningType === WARNING_TYPE.riskyEntryHigh && 'Your transaction may be frontrun'}
{warningType === WARNING_TYPE.riskyEntryLow && 'Your transaction may fail'}
</BottomError>
</RowBetween>
</SlippageSelector>
<AutoColumn gap="md">
<RowFixed padding={'0 20px'}>
<TYPE.black fontSize={14}>Deadline</TYPE.black>
<QuestionHelper text="Deadline in minutes. If your transaction takes longer than this it will revert." />
</RowFixed>
<RowBetween padding={'0 20px'}>
<OptionCustom style={{ width: '80px' }}>
<Input tabIndex={-1} placeholder={deadlineInput} value={deadlineInput} onChange={parseCustomDeadline} />
</OptionCustom>
</RowBetween>
</AutoColumn>
</>
)
}
return dropDownContent()
}

@ -1,20 +1,81 @@
import React from 'react'
import React, { useState, useEffect, useRef } from 'react'
import { Link } from '../../theme/components'
import { TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { AutoRow } from '../Row'
import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils'
import { usePopups } from '../../contexts/Application'
export default function TxnPopup({ hash, success, summary }) {
import { CheckCircle, AlertCircle } from 'react-feather'
import styled from 'styled-components'
const Fader = styled.div`
position: absolute;
bottom: 0px;
left: 0px;
width: ${({ count }) => `calc(100% - (100% / ${150 / count}))`};
height: 2px;
background-color: ${({ theme }) => theme.bg3};
transition: width 100ms linear;
`
function useInterval(callback, delay) {
const savedCallback = useRef()
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback
return () => {}
}, [callback])
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current()
}
if (delay !== null) {
let id = setInterval(tick, delay)
return () => clearInterval(id)
}
return () => {}
}, [delay])
}
const delay = 100
export default function TxnPopup({ hash, success, summary, popKey }) {
const { chainId } = useWeb3React()
let [count, setCount] = useState(1)
const [isRunning, setIsRunning] = useState(true)
const [, , removePopup] = usePopups()
useInterval(
() => {
count > 150 && removePopup(popKey)
setCount(count + 1)
},
isRunning ? delay : null
)
return (
<AutoColumn gap="12px">
<TYPE.body>Transaction {success ? 'confirmed.' : 'failed.'}</TYPE.body>
<TYPE.green>{summary ? summary : 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.green>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</Link>
</AutoColumn>
<AutoRow onMouseEnter={() => setIsRunning(false)} onMouseLeave={() => setIsRunning(true)}>
{success ? (
<CheckCircle color={'#27AE60'} size={24} style={{ paddingRight: '24px' }} />
) : (
<AlertCircle color={'#FF6871'} size={24} style={{ paddingRight: '24px' }} />
)}
<AutoColumn gap="8px">
<TYPE.body fontWeight={500}>
{summary ? summary : 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}
</TYPE.body>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</Link>
</AutoColumn>
<Fader count={count} />
</AutoRow>
)
}

@ -414,7 +414,6 @@ export function useAllBalances(): Array<TokenAmount> {
let newBalances = {}
Object.keys(state[chainId]).map(address => {
return Object.keys(state[chainId][address]).map(tokenAddress => {
// console.log(allTokens[tokenAddress])
if (state[chainId][address][tokenAddress].value) {
// fix if ETH found in local storage from old storage
if (tokenAddress === 'ETH') {

@ -46,6 +46,8 @@ const BodyWrapper = styled.div`
max-width: calc(355px + 4rem);
width: 90%;
}
z-index: 1;
`
const Body = styled.div`
@ -72,7 +74,6 @@ export default function App() {
</HeaderWrapper>
<BodyWrapper>
<Popups />
<Body>
<Web3ReactManager>
<BrowserRouter>

@ -14,9 +14,9 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { Plus } from 'react-feather'
import { BlueCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonLight } from '../../components/Button'
import { BlueCard, LightCard, GreyCard } from '../../components/Card'
import Row, { AutoRow, RowBetween, RowFlat, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens'
@ -688,6 +688,11 @@ function AddLiquidity({ token0, token1, step = false }) {
error={outputError}
pair={pair}
/>
{tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<LightCard padding="1rem" borderRadius={'20px'}>
<PriceBar />
</LightCard>
)}
{showOutputApprove ? (
<ButtonLight
onClick={() => {
@ -721,11 +726,6 @@ function AddLiquidity({ token0, token1, step = false }) {
{!noLiquidity && (
<FixedBottom>
<AutoColumn>
{tokens[Field.OUTPUT] && (
<GreyCard pt={2} mb={2}>
<PriceBar />
</GreyCard>
)}
<PositionCard
pairAddress={pair?.liquidityToken?.address}
token0={tokens[Field.INPUT]}

@ -7,7 +7,6 @@ import { TokenAmount, JSBI, Route, WETH, Percent, Token, Pair } from '@uniswap/s
import TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard'
// import NumericalInput from '../../components/NumericalInput'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { TYPE } from '../../theme'
@ -266,9 +265,6 @@ export default function RemoveLiquidity({ token0, token1 }) {
parsedAmounts[Field.LIQUIDITY] &&
pair.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, parsedAmounts[Field.LIQUIDITY], false)
// controlled input for percetange input
// const [percentageInput, setPercentageInput] = useState(0)
// derived percent for advanced mode
const derivedPerecent =
userLiquidity &&
@ -285,15 +281,6 @@ export default function RemoveLiquidity({ token0, token1 }) {
)
}
// update controlled perctenage when derived is updated
// useEffect(() => {
// if (derivedPerecent) {
// setPercentageInput(parseFloat(derivedPerecent))
// } else {
// setPercentageInput(0)
// }
// }, [derivedPerecent])
// get formatted amounts
const formattedAmounts = {
[Field.LIQUIDITY]:
@ -608,7 +595,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
pendingText={pendingText}
title="You will recieve"
/>
<AutoColumn gap="20px">
<AutoColumn gap="md">
<LightCard>
<AutoColumn gap="20px">
<RowBetween>

@ -106,6 +106,11 @@ export const TYPE = {
{children}
</Text>
),
black: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().text1} {...rest}>
{children}
</Text>
),
largeHeader: ({ children, ...rest }) => (
<Text fontWeight={600} fontSize={24} {...rest}>
{children}
@ -122,7 +127,7 @@ export const TYPE = {
</Text>
),
body: ({ children, ...rest }) => (
<Text fontWeight={400} fontSize={16} color={'#888D9B'} {...rest}>
<Text fontWeight={400} fontSize={16} color={'#191B1F'} {...rest}>
{children}
</Text>
),
@ -131,6 +136,11 @@ export const TYPE = {
{children}
</Text>
),
yellow: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().yellow2} {...rest}>
{children}
</Text>
),
green: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().green1} {...rest}>
{children}

@ -12927,10 +12927,12 @@ react-error-overlay@^6.0.4:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.4.tgz#0d165d6d27488e660bc08e57bdabaad741366f7a"
integrity sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA==
react-feather@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.1.6.tgz#2a547e3d5cd5e383d3da0128d593cbdb3c1b32f7"
integrity sha512-iCofWhTjX+vQwvDmg7o6vg0XrUg1c41yBDZG+l83nz1FiCsleJoUgd3O+kHpOeWMXuPrRIFfCixvcqyOLGOgIg==
react-feather@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.8.tgz#455baf1470f756a57e2ad6c72545444ce5925781"
integrity sha512-J0dCEOvOxpovHeOVj3+8mAhN3/UERTfX6rSxnV6x4E+0s+STY536jhSjRfpSvTQA0SSFjYr4KrpPfdsLmK+zZg==
dependencies:
prop-types "^15.7.2"
react-focus-lock@^1.17.7:
version "1.19.1"