Skip to content

Commit 7e49bab

Browse files
authored
improvement(token search): No automatic add (Uniswap#888)
* no automatic add, major refactors * fix nit with version link * add/remove links in the token search modal * close tooltip when user types * remove skip
1 parent b35653a commit 7e49bab

19 files changed

Lines changed: 421 additions & 335 deletions

File tree

src/components/CurrencyInputPanel/index.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Pair, Token } from '@uniswap/sdk'
2-
import React, { useState, useContext } from 'react'
2+
import React, { useState, useContext, useCallback } from 'react'
33
import styled, { ThemeContext } from 'styled-components'
44
import { darken } from 'polished'
55
import { Field } from '../../state/swap/actions'
66
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
7+
import TokenSearchModal from '../SearchModal/TokenSearchModal'
78
import TokenLogo from '../TokenLogo'
89
import DoubleLogo from '../DoubleLogo'
9-
import SearchModal from '../SearchModal'
1010
import { RowBetween } from '../Row'
1111
import { TYPE, CursorPointer } from '../../theme'
1212
import { Input as NumericalInput } from '../NumericalInput'
@@ -159,6 +159,10 @@ export default function CurrencyInputPanel({
159159
const userTokenBalance = useTokenBalanceTreatingWETHasETH(account, token)
160160
const theme = useContext(ThemeContext)
161161

162+
const handleDismissSearch = useCallback(() => {
163+
setModalOpen(false)
164+
}, [setModalOpen])
165+
162166
return (
163167
<InputPanel id={id}>
164168
<Container hideInput={hideInput}>
@@ -235,12 +239,9 @@ export default function CurrencyInputPanel({
235239
</InputRow>
236240
</Container>
237241
{!disableTokenSelect && (
238-
<SearchModal
242+
<TokenSearchModal
239243
isOpen={modalOpen}
240-
onDismiss={() => {
241-
setModalOpen(false)
242-
}}
243-
filterType="tokens"
244+
onDismiss={handleDismissSearch}
244245
onTokenSelect={onTokenSelection}
245246
showSendWithSwap={showSendWithSwap}
246247
hiddenToken={token?.address}

src/components/SearchModal/PairList.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,7 @@ export default function PairList({
2525
}
2626

2727
return (
28-
<FixedSizeList
29-
itemSize={54}
30-
height={500}
31-
itemCount={pairs.length}
32-
width="100%"
33-
style={{ flex: '1', minHeight: 200 }}
34-
>
28+
<FixedSizeList itemSize={56} height={500} itemCount={pairs.length} width="100%" style={{ flex: '1' }}>
3529
{({ index, style }) => {
3630
const pair = pairs[index]
3731

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { Pair } from '@uniswap/sdk'
2+
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
3+
import { isMobile } from 'react-device-detect'
4+
import { useTranslation } from 'react-i18next'
5+
import { RouteComponentProps, withRouter } from 'react-router-dom'
6+
import { Text } from 'rebass'
7+
import { ThemeContext } from 'styled-components'
8+
import Card from '../../components/Card'
9+
import { useActiveWeb3React } from '../../hooks'
10+
import { useAllTokens } from '../../hooks/Tokens'
11+
import { useAllDummyPairs } from '../../state/user/hooks'
12+
import { useTokenBalances } from '../../state/wallet/hooks'
13+
import { CloseIcon, StyledInternalLink } from '../../theme/components'
14+
import { isAddress } from '../../utils'
15+
import Column from '../Column'
16+
import Modal from '../Modal'
17+
import QuestionHelper from '../QuestionHelper'
18+
import { AutoRow, RowBetween } from '../Row'
19+
import { filterPairs } from './filtering'
20+
import PairList from './PairList'
21+
import { pairComparator } from './sorting'
22+
import { PaddedColumn, SearchInput } from './styleds'
23+
24+
interface PairSearchModalProps extends RouteComponentProps {
25+
isOpen?: boolean
26+
onDismiss?: () => void
27+
}
28+
29+
function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
30+
const { t } = useTranslation()
31+
const { account } = useActiveWeb3React()
32+
const theme = useContext(ThemeContext)
33+
34+
const [searchQuery, setSearchQuery] = useState<string>('')
35+
36+
const allTokens = useAllTokens()
37+
const allPairs = useAllDummyPairs()
38+
39+
const allPairBalances = useTokenBalances(
40+
account,
41+
allPairs.map(p => p.liquidityToken)
42+
)
43+
44+
// clear the input on open
45+
useEffect(() => {
46+
if (isOpen) setSearchQuery('')
47+
}, [isOpen, setSearchQuery])
48+
49+
// manage focus on modal show
50+
const inputRef = useRef<HTMLInputElement>()
51+
function onInput(event) {
52+
const input = event.target.value
53+
const checksummedInput = isAddress(input)
54+
setSearchQuery(checksummedInput || input)
55+
}
56+
57+
const sortedPairList = useMemo(() => {
58+
return allPairs.sort((a, b): number => {
59+
const balanceA = allPairBalances[a.liquidityToken.address]
60+
const balanceB = allPairBalances[b.liquidityToken.address]
61+
return pairComparator(a, b, balanceA, balanceB)
62+
})
63+
}, [allPairs, allPairBalances])
64+
65+
const filteredPairs = useMemo(() => {
66+
return filterPairs(sortedPairList, searchQuery)
67+
}, [searchQuery, sortedPairList])
68+
69+
const selectPair = useCallback(
70+
(pair: Pair) => {
71+
history.push(`/add/${pair.token0.address}-${pair.token1.address}`)
72+
},
73+
[history]
74+
)
75+
76+
const focusedToken = Object.values(allTokens ?? {}).filter(token => {
77+
return token.symbol.toLowerCase() === searchQuery || searchQuery === token.address
78+
})[0]
79+
80+
return (
81+
<Modal
82+
isOpen={isOpen}
83+
onDismiss={onDismiss}
84+
maxHeight={70}
85+
initialFocusRef={isMobile ? undefined : inputRef}
86+
minHeight={70}
87+
>
88+
<Column style={{ width: '100%' }}>
89+
<PaddedColumn gap="20px">
90+
<RowBetween>
91+
<Text fontWeight={500} fontSize={16}>
92+
Select a pool
93+
<QuestionHelper text="Find a pair by searching for its name below." />
94+
</Text>
95+
<CloseIcon onClick={onDismiss} />
96+
</RowBetween>
97+
<SearchInput
98+
type="text"
99+
id="token-search-input"
100+
placeholder={t('tokenSearchPlaceholder')}
101+
value={searchQuery}
102+
ref={inputRef}
103+
onChange={onInput}
104+
/>
105+
<RowBetween>
106+
<Text fontSize={14} fontWeight={500}>
107+
Pool Name
108+
</Text>
109+
</RowBetween>
110+
</PaddedColumn>
111+
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
112+
<PairList
113+
pairs={filteredPairs}
114+
focusTokenAddress={focusedToken?.address}
115+
onAddLiquidity={selectPair}
116+
onSelectPair={selectPair}
117+
pairBalances={allPairBalances}
118+
/>
119+
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
120+
<Card>
121+
<AutoRow justify={'center'}>
122+
<div>
123+
<Text fontWeight={500}>
124+
{!isMobile && "Don't see a pool? "}
125+
<StyledInternalLink to="/find">{!isMobile ? 'Import it.' : 'Import pool.'}</StyledInternalLink>
126+
</Text>
127+
</div>
128+
</AutoRow>
129+
</Card>
130+
</Column>
131+
</Modal>
132+
)
133+
}
134+
135+
export default withRouter(PairSearchModal)

src/components/SearchModal/TokenList.tsx

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { Text } from 'rebass'
66
import { ThemeContext } from 'styled-components'
77
import { ALL_TOKENS } from '../../constants/tokens'
88
import { useActiveWeb3React } from '../../hooks'
9+
import { useAllTokens } from '../../hooks/Tokens'
10+
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
911
import { LinkStyledButton, TYPE } from '../../theme'
10-
import { isAddress } from '../../utils'
1112
import { ButtonSecondary } from '../Button'
1213
import Column, { AutoColumn } from '../Column'
1314
import { RowFixed } from '../Row'
@@ -16,8 +17,7 @@ import { FadedSpan, GreySpan, MenuItem, ModalInfo } from './styleds'
1617
import Loader from '../Loader'
1718

1819
function isDefaultToken(tokenAddress: string, chainId?: number): boolean {
19-
const address = isAddress(tokenAddress)
20-
return Boolean(chainId && address && ALL_TOKENS[chainId as ChainId]?.[tokenAddress])
20+
return Boolean(chainId && ALL_TOKENS[chainId as ChainId]?.[tokenAddress])
2121
}
2222

2323
export default function TokenList({
@@ -27,39 +27,42 @@ export default function TokenList({
2727
onTokenSelect,
2828
otherToken,
2929
showSendWithSwap,
30-
onRemoveAddedToken,
31-
otherSelectedText,
32-
hideRemove
30+
otherSelectedText
3331
}: {
3432
tokens: Token[]
3533
selectedToken: string
3634
allTokenBalances: { [tokenAddress: string]: TokenAmount }
3735
onTokenSelect: (tokenAddress: string) => void
38-
onRemoveAddedToken: (chainId: number, tokenAddress: string) => void
3936
otherToken: string
4037
showSendWithSwap?: boolean
4138
otherSelectedText: string
42-
hideRemove?: boolean
4339
}) {
4440
const { t } = useTranslation()
4541
const { account, chainId } = useActiveWeb3React()
4642
const theme = useContext(ThemeContext)
43+
const allTokens = useAllTokens()
44+
const addToken = useAddUserToken()
45+
const removeToken = useRemoveUserAddedToken()
4746

4847
if (tokens.length === 0) {
4948
return <ModalInfo>{t('noToken')}</ModalInfo>
5049
}
50+
5151
return (
5252
<FixedSizeList
5353
width="100%"
5454
height={500}
5555
itemCount={tokens.length}
56-
itemSize={50}
57-
style={{ flex: '1', minHeight: 200 }}
56+
itemSize={56}
57+
style={{ flex: '1' }}
58+
itemKey={index => tokens[index].address}
5859
>
5960
{({ index, style }) => {
60-
const { address, symbol } = tokens[index]
61+
const token = tokens[index]
62+
const { address, symbol } = token
6163

62-
const customAdded = !isDefaultToken(address, chainId)
64+
const isDefault = isDefaultToken(address, chainId)
65+
const customAdded = Boolean(!isDefault && allTokens[address])
6366
const balance = allTokenBalances[address]
6467

6568
const zeroBalance = balance && JSBI.equal(JSBI.BigInt(0), balance.raw)
@@ -81,22 +84,36 @@ export default function TokenList({
8184
{otherToken === address && <GreySpan> ({otherSelectedText})</GreySpan>}
8285
</Text>
8386
<FadedSpan>
84-
<TYPE.main fontWeight={500}>{customAdded && 'Added by user'}</TYPE.main>
85-
{customAdded && !hideRemove && (
86-
<LinkStyledButton
87-
onClick={event => {
88-
event.stopPropagation()
89-
onRemoveAddedToken(chainId, address)
90-
}}
91-
style={{ marginLeft: '4px', fontWeight: 400 }}
92-
>
93-
(Remove)
94-
</LinkStyledButton>
95-
)}
87+
{customAdded ? (
88+
<TYPE.main fontWeight={500}>
89+
Added by user
90+
<LinkStyledButton
91+
onClick={event => {
92+
event.stopPropagation()
93+
removeToken(chainId, address)
94+
}}
95+
>
96+
(Remove)
97+
</LinkStyledButton>
98+
</TYPE.main>
99+
) : null}
100+
{!isDefault && !customAdded ? (
101+
<TYPE.main fontWeight={500}>
102+
Found by address
103+
<LinkStyledButton
104+
onClick={event => {
105+
event.stopPropagation()
106+
addToken(token)
107+
}}
108+
>
109+
(Add)
110+
</LinkStyledButton>
111+
</TYPE.main>
112+
) : null}
96113
</FadedSpan>
97114
</Column>
98115
</RowFixed>
99-
<AutoColumn gap="4px" justify="end">
116+
<AutoColumn>
100117
{balance ? (
101118
<Text>
102119
{zeroBalance && showSendWithSwap ? (

0 commit comments

Comments
 (0)