Commit cba76c43 authored by Moody Salem's avatar Moody Salem Committed by GitHub

Token warning a la #789 and #790 (#807)

* Token warning a la #789 and #790

* Clean up the duplicate name or symbol implementation

* Fix the spacing

* Change wording and add to send page

* Wording changes

* Refactor the common code to a component

* Fix lint error
parent 373b3180
......@@ -59,7 +59,15 @@ export default function QuestionHelper({ text }: { text: string }) {
const [popperElement, setPopperElement] = useState(null)
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'auto',
strategy: 'fixed'
strategy: 'fixed',
modifiers: [
{
name: 'offset',
options: {
offset: [6, 6]
}
}
]
})
const portal = createPortal(
......
import { Token } from '@uniswap/sdk'
import { transparentize } from 'polished'
import React, { useEffect, useMemo, useState } from 'react'
import styled from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import { ALL_TOKENS, useAllTokens } from '../../hooks/Tokens'
import { Field } from '../../state/swap/actions'
import { getEtherscanLink } from '../../utils'
import { Link } from '../../theme'
import QuestionHelper from '../Question'
import TokenLogo from '../TokenLogo'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { TYPE } from '../../theme'
const Wrapper = styled.div<{ error: boolean }>`
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
position: relative;
padding: 1rem;
border: 0.5px solid ${({ theme, error }) => transparentize(0.4, error ? theme.red1 : theme.yellow1)};
border-radius: 10px;
margin-bottom: 20px;
display: grid;
grid-template-rows: auto auto auto;
grid-row-gap: 14px;
`
const Row = styled.div`
display: flex;
align-items: center;
justify-items: flex-start;
& > * {
margin-right: 6px;
}
`
const CloseColor = styled(Close)`
color: #aeaeae;
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
& > * {
height: 14px;
width: 14px;
}
`
interface TokenWarningCardProps {
token?: Token
}
const HELP_TEXT = `
The Uniswap V2 smart contracts are designed to support any ERC20 token on Ethereum. Any token can be
loaded into the interface by entering its Ethereum address into the search field or passing it as a URL
parameter.
`
const DUPLICATE_NAME_HELP_TEXT = `${HELP_TEXT} This token has the same name or symbol as another token in your list.`
export default function TokenWarningCard({ token }: TokenWarningCardProps) {
const { chainId } = useActiveWeb3React()
const [dismissed, setDismissed] = useState<boolean>(false)
const isDefaultToken = Boolean(
token && token.address && chainId && ALL_TOKENS[chainId] && ALL_TOKENS[chainId][token.address]
)
useEffect(() => {
setDismissed(false)
}, [token, setDismissed])
const tokenSymbol = token?.symbol?.toLowerCase() ?? ''
const tokenName = token?.name?.toLowerCase() ?? ''
const allTokens = useAllTokens()
const duplicateNameOrSymbol = useMemo(() => {
if (isDefaultToken || !token || !chainId) return false
return Object.keys(allTokens).some(tokenAddress => {
const userToken = allTokens[tokenAddress]
if (userToken.equals(token)) {
return false
}
return userToken.symbol.toLowerCase() === tokenSymbol || userToken.name.toLowerCase() === tokenName
})
}, [isDefaultToken, token, chainId, allTokens, tokenSymbol, tokenName])
if (isDefaultToken || !token || dismissed) return null
return (
<Wrapper error={duplicateNameOrSymbol}>
{duplicateNameOrSymbol ? null : (
<CloseIcon onClick={() => setDismissed(true)}>
<CloseColor />
</CloseIcon>
)}
<Row>
<TYPE.subHeader>{duplicateNameOrSymbol ? 'Duplicate token name or symbol' : 'Imported token'}</TYPE.subHeader>
<QuestionHelper text={duplicateNameOrSymbol ? DUPLICATE_NAME_HELP_TEXT : HELP_TEXT} />
</Row>
<Row>
<TokenLogo address={token.address} />
<div style={{ fontWeight: 500 }}>
{token && token.name && token.symbol && token.name !== token.symbol
? `${token.name} (${token.symbol})`
: token.name || token.symbol}
</div>
<Link style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'address')}>
(View on Etherscan)
</Link>
</Row>
<Row>
<TYPE.italic>Verify this is the correct token before making any transactions.</TYPE.italic>
</Row>
</Wrapper>
)
}
export function TokenWarningCards({ tokens }: { tokens: { [field in Field]?: Token } }) {
return (
<div style={{ width: '100%', position: 'absolute', top: 'calc(100% + 30px)' }}>
{Object.keys(tokens).map(field => (
<div key={field} style={{ marginBottom: 10 }}>
<TokenWarningCard token={tokens[field]} />
</div>
))}
</div>
)
}
import { Token } from '@uniswap/sdk'
import React, { useState } from 'react'
import styled, { keyframes } from 'styled-components'
import { useActiveWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { getEtherscanLink } from '../../utils'
import { Link } from '../../theme'
import TokenLogo from '../TokenLogo'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import question from '../../assets/images/question.svg'
const Flex = styled.div`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
const Wrapper = styled.div`
background: rgba(243, 190, 30, 0.1);
position: relative;
padding: 1rem;
border: 0.5px solid #f3be1e;
border-radius: 10px;
margin-bottom: 20px;
display: grid;
grid-template-rows: 1fr 1fr 1fr;
grid-row-gap: 10px;
`
const Row = styled.div`
display: flex;
align-items: center;
justify-items: flex-start;
& > * {
margin-right: 6px;
}
`
const CloseColor = styled(Close)`
color: #aeaeae;
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
& > * {
height: 14px;
width: 14px;
}
`
const QuestionWrapper = styled.button`
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
margin-left: 0.4rem;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
:hover,
:focus {
opacity: 0.7;
}
`
const HelpCircleStyled = styled.img`
height: 18px;
width: 18px;
`
const fadeIn = keyframes`
from {
opacity : 0;
}
to {
opacity : 1;
}
`
const Popup = styled(Flex)`
position: absolute;
width: 228px;
right: 110px;
top: 4px;
z-index: 10;
flex-direction: column;
align-items: center;
padding: 0.6rem 1rem;
line-height: 150%;
background: ${({ theme }) => theme.bg2};
border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 8px;
animation: ${fadeIn} 0.15s linear;
color: ${({ theme }) => theme.text1};
font-style: italic;
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.04), 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.04);
${({ theme }) => theme.mediaWidth.upToSmall`
left: 2px;
top: 50px;
`}
`
const Text = styled.div`
color: ${({ theme }) => theme.text1};
`
interface WarningCardProps {
onDismiss: () => void
urlAddedTokens: Token[]
currency: string
}
export default function WarningCard({ onDismiss, urlAddedTokens, currency }: WarningCardProps) {
const [showPopup, setPopup] = useState<boolean>(false)
const { chainId } = useActiveWeb3React()
const { symbol: inputSymbol, name: inputName } = useToken(currency)
const fromURL = urlAddedTokens.hasOwnProperty(currency)
return (
<Wrapper>
<CloseIcon onClick={() => onDismiss()}>
<CloseColor />
</CloseIcon>
<Row style={{ fontSize: '12px' }}>
<Text>{fromURL ? 'Token imported by URL ' : 'Token imported by user'}</Text>
<QuestionWrapper
onClick={() => {
setPopup(!showPopup)
}}
onMouseEnter={() => {
setPopup(true)
}}
onMouseLeave={() => {
setPopup(false)
}}
>
<HelpCircleStyled src={question} alt="popup" />
</QuestionWrapper>
{showPopup ? (
<Popup>
<Text>
The Uniswap V2 smart contracts are designed to support any ERC20 token on Ethereum. Any token can be
loaded into the interface by entering its Ethereum address into the search field or passing it as a URL
parameter. Be careful when interacting with imported tokens as they have not been verified.
</Text>
</Popup>
) : (
''
)}
</Row>
<Row>
<TokenLogo address={currency} />
<div style={{ fontWeight: 500 }}>{inputName && inputSymbol ? inputName + ' (' + inputSymbol + ')' : ''}</div>
<Link style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, currency, 'address')}>
(View on Etherscan)
</Link>
</Row>
<Row style={{ fontSize: '12px', fontStyle: 'italic' }}>
<Text>Please verify the legitimacy of this token before making any transactions.</Text>
</Row>
</Wrapper>
)
}
......@@ -22,6 +22,7 @@ import TradePrice from '../../components/swap/TradePrice'
import { TransferModalHeader } from '../../components/swap/TransferModalHeader'
import V1TradeLink from '../../components/swap/V1TradeLink'
import TokenLogo from '../../components/TokenLogo'
import { TokenWarningCards } from '../../components/TokenWarningCard'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, MIN_ETH } from '../../constants'
import { useActiveWeb3React } from '../../hooks'
import { useApproveCallbackFromTrade, Approval } from '../../hooks/useApproveCallback'
......@@ -496,6 +497,8 @@ export default function Send({ location: { search } }: RouteComponentProps) {
setRawSlippage={setAllowedSlippage}
/>
)}
{sendingWithSwap ? <TokenWarningCards tokens={tokens} /> : null}
</Wrapper>
)
}
......@@ -20,6 +20,7 @@ import SwapModalFooter from '../../components/swap/SwapModalFooter'
import SwapModalHeader from '../../components/swap/SwapModalHeader'
import TradePrice from '../../components/swap/TradePrice'
import V1TradeLink from '../../components/swap/V1TradeLink'
import { TokenWarningCards } from '../../components/TokenWarningCard'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE, MIN_ETH } from '../../constants'
import { useActiveWeb3React } from '../../hooks'
import { useApproveCallbackFromTrade, Approval } from '../../hooks/useApproveCallback'
......@@ -298,6 +299,8 @@ export default function Swap({ location: { search } }: RouteComponentProps) {
setRawSlippage={setAllowedSlippage}
/>
)}
<TokenWarningCards tokens={tokens} />
</Wrapper>
)
}
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