Commit 3a2566b4 authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

Changes to useReserves, escapreRegex moved to utils, code cleanup (#762)

* bug fixes on non-existent vs no reserves, fix scanning on pair search, small ui tweaks

* remove unused vars

* remove mainnet in injected connector

* code cleanup for pr

* update pair value check
parent c9db5fb2
import React, { useState, useEffect, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
// import QR from '../../assets/svg/QR.svg'
import { isAddress } from '../../utils'
import { useWeb3React, useDebounce } from '../../hooks'
import { Link, TYPE } from '../../theme'
......@@ -64,16 +63,6 @@ const Input = styled.input<{ error?: boolean }>`
}
`
// const QRWrapper = styled.div`
// display: flex;
// align-items: center;
// justify-content: center;
// border: 1px solid ${({ theme }) => theme.bg3};
// background: #fbfbfb;
// padding: 4px;
// border-radius: 8px;
// `
export default function AddressInputPanel({
initialInput = '',
onChange,
......@@ -190,7 +179,7 @@ export default function AddressInputPanel({
<TYPE.black color={theme.text2} fontWeight={500} fontSize={14}>
Recipient
</TYPE.black>
{(data.name || data.address) && (
{data.address && (
<Link
href={getEtherscanLink(chainId, data.name || data.address, 'address')}
style={{ fontSize: '14px' }}
......@@ -210,9 +199,6 @@ export default function AddressInputPanel({
onChange={onInput}
value={input}
/>
{/* <QRWrapper>
<img src={QR} alt="" />
</QRWrapper> */}
</AutoColumn>
</InputContainer>
</ContainerRow>
......
import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { Token, JSBI, WETH } from '@uniswap/sdk'
import { withRouter, RouteComponentProps } from 'react-router-dom'
import { Token, WETH } from '@uniswap/sdk'
import Row, { AutoRow } from '../Row'
import TokenLogo from '../TokenLogo'
......@@ -21,7 +21,13 @@ const Fields = {
TOKEN1: 1
}
function CreatePool({ history }) {
const STEP = {
SELECT_TOKENS: 'SELECT_TOKENS', // choose input and output tokens
READY_TO_CREATE: 'READY_TO_CREATE', // enable 'create' button
SHOW_CREATE_PAGE: 'SHOW_CREATE_PAGE' // show create page
}
function CreatePool({ history }: RouteComponentProps<{}>) {
const { chainId } = useWeb3React()
const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
......@@ -32,108 +38,109 @@ function CreatePool({ history }) {
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const [step, setStep] = useState<number>(1)
const [step, setStep] = useState<string>(STEP.SELECT_TOKENS)
const pair = usePair(token0, token1)
const pairExists = // used to detect new exchange
pair && JSBI.notEqual(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.notEqual(pair.reserve1.raw, JSBI.BigInt(0))
// if both tokens selected but pair doesnt exist, enable button to create pair
useEffect(() => {
if (token0Address && token1Address && pair && !pairExists) {
setStep(2)
if (token0Address && token1Address && pair === null) {
setStep(STEP.READY_TO_CREATE)
}
}, [pair, pairExists, token0Address, token1Address])
}, [pair, token0Address, token1Address])
if (step === 2 && !pairExists) {
// if theyve clicked create, show add liquidity page
if (step === STEP.SHOW_CREATE_PAGE) {
return <AddLiquidity token0={token0Address} token1={token1Address} />
} else
return (
<AutoColumn gap="20px">
<AutoColumn gap="24px">
{!token0Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Text fontSize={20}>Select first token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Row align="flex-end">
<TokenLogo address={token0Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token0?.symbol}{' '}
</Text>
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
{token0?.symbol === 'ETH' && '(default)'}
</TYPE.darkGray>
</Row>
</ButtonDropwdownLight>
)}
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
{!token1Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
disabled={step !== 1}
>
<Text fontSize={20}>Select second token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
>
<Row>
<TokenLogo address={token1Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token1?.symbol}
</Text>
</Row>
</ButtonDropwdownLight>
)}
{pairExists ? (
<AutoRow padding="10px" justify="center">
<TYPE.body textAlign="center">
Pool already exists!
<Link onClick={() => history.push('/add/' + token0Address + '-' + token1Address)}> Join the pool.</Link>
</TYPE.body>
</AutoRow>
) : (
<ButtonPrimary disabled={step !== 2}>
<Text fontWeight={500} fontSize={20}>
Create Pool
}
return (
<AutoColumn gap="20px">
<AutoColumn gap="24px">
{!token0Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Text fontSize={20}>Select first token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Row align="flex-end">
<TokenLogo address={token0Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token0?.symbol}{' '}
</Text>
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
{token0?.symbol === 'ETH' && '(default)'}
</TYPE.darkGray>
</Row>
</ButtonDropwdownLight>
)}
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
{!token1Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
disabled={step !== STEP.SELECT_TOKENS}
>
<Text fontSize={20}>Select second token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
>
<Row>
<TokenLogo address={token1Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token1?.symbol}
</Text>
</ButtonPrimary>
)}
</AutoColumn>
<SearchModal
isOpen={showSearch}
filterType="tokens"
onTokenSelect={address => {
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
}}
onDismiss={() => {
setShowSearch(false)
}}
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
showCommonBases={activeField === Fields.TOKEN0}
/>
</Row>
</ButtonDropwdownLight>
)}
{pair ? ( // pair already exists - prompt to add liquidity to existing pool
<AutoRow padding="10px" justify="center">
<TYPE.body textAlign="center">
Pool already exists!
<Link onClick={() => history.push('/add/' + token0Address + '-' + token1Address)}> Join the pool.</Link>
</TYPE.body>
</AutoRow>
) : (
<ButtonPrimary disabled={step !== STEP.READY_TO_CREATE} onClick={() => setStep(STEP.SHOW_CREATE_PAGE)}>
<Text fontWeight={500} fontSize={20}>
Create Pool
</Text>
</ButtonPrimary>
)}
</AutoColumn>
)
<SearchModal
isOpen={showSearch}
filterType="tokens"
onTokenSelect={address => {
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
}}
onDismiss={() => {
setShowSearch(false)
}}
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
showCommonBases={activeField === Fields.TOKEN0}
/>
</AutoColumn>
)
}
export default withRouter(CreatePool)
......@@ -23,7 +23,7 @@ export default function Footer() {
return (
<FooterFrame>
<form action="https://forms.gle/DaLuqvJsVhVaAM3J9">
<form action="https://forms.gle/DaLuqvJsVhVaAM3J9" target="_blank">
<ButtonSecondary
style={{
padding: ' 8px 12px',
......
import React from 'react'
import styled from 'styled-components'
import { escapeRegExp } from '../../utils'
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string }>`
color: ${({ error, theme }) => error && theme.red1};
......@@ -41,10 +42,6 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
const inputRegex = RegExp(`^\\d*(?:\\\\.)?\\d*$`) // match escaped "." characters via in a non-capturing group
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
export const Input = React.memo(function InnerInput({
value,
onUserInput,
......
......@@ -46,7 +46,8 @@ function PoolFinder({ history }: RouteComponentProps) {
const position: TokenAmount = useAddressBalance(account, pair?.liquidityToken)
const newPair: boolean =
!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))
pair === null ||
(!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)))
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
return (
......
import React, { useState, useRef, useMemo, useEffect, useContext } from 'react'
import '@reach/tooltip/styles.css'
import styled, { ThemeContext } from 'styled-components'
import escapeStringRegex from 'escape-string-regexp'
import { JSBI, Token, WETH } from '@uniswap/sdk'
import { isMobile } from 'react-device-detect'
import { RouteComponentProps, withRouter } from 'react-router-dom'
......@@ -22,7 +21,7 @@ import { ButtonPrimary, ButtonSecondary } from '../../components/Button'
import { Spinner, TYPE } from '../../theme'
import { RowBetween, RowFixed, AutoRow } from '../Row'
import { isAddress } from '../../utils'
import { isAddress, escapeRegExp } from '../../utils'
import { useWeb3React } from '../../hooks'
import {
useAllDummyPairs,
......@@ -191,6 +190,16 @@ function SearchModal({
// if the current input is an address, and we don't have the token in context, try to fetch it
const token = useToken(searchQuery)
const [temporaryToken, setTemporaryToken] = useState<Token | null>()
// filters for ordering
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
// toggle specific token import view
const [showTokenImport, setShowTokenImport] = useState(false)
// used to help scanning on results, put token found from input on left
const [identifiedToken, setIdentifiedToken] = useState<Token | null>()
useEffect(() => {
const address = isAddress(searchQuery)
if (address && !token) {
......@@ -207,10 +216,6 @@ function SearchModal({
}
}, [searchQuery, token, fetchTokenByAddress])
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
const [showTokenImport, setShowTokenImport] = useState(false)
// reset view on close
useEffect(() => {
if (!isOpen) {
......@@ -272,13 +277,13 @@ function SearchModal({
include &&
inputIsAddress &&
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeRegExp(searchQuery), 'i'))
)
}
return (
include &&
typeof tokenEntry[tokenEntryKey] === 'string' &&
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeStringRegex(searchQuery), 'i'))
!!tokenEntry[tokenEntryKey].match(new RegExp(escapeRegExp(searchQuery), 'i'))
)
})
return regexMatches.some(m => m)
......@@ -293,7 +298,6 @@ function SearchModal({
// manage focus on modal show
const inputRef = useRef()
function onInput(event) {
const input = event.target.value
const checksummedInput = isAddress(input)
......@@ -305,9 +309,6 @@ function SearchModal({
onDismiss()
}
// sort tokens
const escapeStringRegexp = string => string
const sortedPairList = useMemo(() => {
return allPairs.sort((a, b): number => {
// sort by balance
......@@ -342,9 +343,15 @@ function SearchModal({
(field === 'name' && !isAddress) ||
(field === 'symbol' && !isAddress)
) {
if (token0[field].match(new RegExp(escapeRegExp(searchQuery), 'i'))) {
setIdentifiedToken(token0)
}
if (token1[field].match(new RegExp(escapeRegExp(searchQuery), 'i'))) {
setIdentifiedToken(token1)
}
return (
token0[field].match(new RegExp(escapeStringRegexp(searchQuery), 'i')) ||
token1[field].match(new RegExp(escapeStringRegexp(searchQuery), 'i'))
token0[field].match(new RegExp(escapeRegExp(searchQuery), 'i')) ||
token1[field].match(new RegExp(escapeRegExp(searchQuery), 'i'))
)
}
return false
......@@ -366,8 +373,9 @@ function SearchModal({
return (
filteredPairList &&
filteredPairList.map((pair, i) => {
const token0 = pair.token0
const token1 = pair.token1
// reset ordering to help scan search results
const token0 = identifiedToken ? (identifiedToken.equals(pair.token0) ? pair.token0 : pair.token1) : pair.token0
const token1 = identifiedToken ? (identifiedToken.equals(pair.token0) ? pair.token1 : pair.token0) : pair.token1
const pairAddress = pair.liquidityToken.address
const balance = allBalances?.[account]?.[pairAddress]?.toSignificant(6)
const zeroBalance =
......@@ -434,20 +442,6 @@ function SearchModal({
return <TokenModalInfo>{t('noToken')}</TokenModalInfo>
}
} else {
/**
* @TODO
// TODO is this the right place to link to create exchange?
// else if (isAddress(searchQuery) && tokenAddress === ethers.constants.AddressZero) {
// return (
// <>
// <TokenModalInfo>{t('noToken')}</TokenModalInfo>
// <TokenModalInfo>
// <Link to={`/create-exchange/${searchQuery}`}>{t('createExchange')}</Link>
// </TokenModalInfo>
// </>
// )
// }
*/
return filteredTokenList
.sort((a, b) => {
if (a.address === WETH[chainId].address) {
......
import React from 'react'
import styled from 'styled-components'
import QRCode from 'qrcode.react'
import { useDarkModeManager } from '../../state/user/hooks'
const QRCodeWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
......@@ -17,12 +16,5 @@ interface WalletConnectDataProps {
}
export default function WalletConnectData({ uri = '', size }: WalletConnectDataProps) {
const [isDark] = useDarkModeManager()
return (
<QRCodeWrapper>
{uri && (
<QRCode size={size} value={uri} bgColor={isDark ? '#333639' : 'white'} fgColor={isDark ? 'white' : 'black'} />
)}
</QRCodeWrapper>
)
return <QRCodeWrapper>{uri && <QRCode size={size} value={uri} />}</QRCodeWrapper>
}
......@@ -15,9 +15,16 @@ function getReserves(contract: Contract, token0: Token, token1: Token): () => Pr
return new Pair(new TokenAmount(token0, reserve0.toString()), new TokenAmount(token1, reserve1.toString()))
}
)
.catch(() => {
return null
})
}
// undefined while loading, null if no liquidity, pair otherwise
/*
* if loading, return undefined
* if no pair created yet, return null
* if pair already created (even if 0 reserves), return pair
*/
export function usePair(tokenA?: Token, tokenB?: Token): undefined | Pair | null {
const bothDefined = !!tokenA && !!tokenB
const invalid = bothDefined && tokenA.equals(tokenB)
......
......@@ -221,7 +221,8 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
const route: Route = pair ? new Route([pair], tokens[independentField]) : undefined
const totalSupply: TokenAmount = useTotalSupply(pair?.liquidityToken)
const noLiquidity = // used to detect new exchange
!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0))
pair === null ||
(!!pair && JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) && JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)))
// get user-pecific and token-specific lookup data
const userBalances: { [field: number]: TokenAmount } = {
......
......@@ -218,3 +218,7 @@ export async function getTokenBalance(tokenAddress, address, library) {
return getContract(tokenAddress, ERC20_ABI, library).balanceOf(address)
}
export function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
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