Commit 94dc3898 authored by cartcrom's avatar cartcrom Committed by GitHub

feat: widget speedbumps on swap review (#4544)

* initial commit
* finished feature
* addressed PR comments
parent 4a8c621f
import Modal from '../Modal' import Modal from '../Modal'
import TokenSafety from '.' import TokenSafety, { TokenSafetyProps } from '.'
interface TokenSafetyModalProps { interface TokenSafetyModalProps extends TokenSafetyProps {
isOpen: boolean isOpen: boolean
tokenAddress: string | null
secondTokenAddress?: string
onContinue: () => void
onCancel: () => void
showCancel?: boolean
} }
export default function TokenSafetyModal({ export default function TokenSafetyModal({
...@@ -16,6 +11,7 @@ export default function TokenSafetyModal({ ...@@ -16,6 +11,7 @@ export default function TokenSafetyModal({
secondTokenAddress, secondTokenAddress,
onContinue, onContinue,
onCancel, onCancel,
onBlocked,
showCancel, showCancel,
}: TokenSafetyModalProps) { }: TokenSafetyModalProps) {
return ( return (
...@@ -23,8 +19,9 @@ export default function TokenSafetyModal({ ...@@ -23,8 +19,9 @@ export default function TokenSafetyModal({
<TokenSafety <TokenSafety
tokenAddress={tokenAddress} tokenAddress={tokenAddress}
secondTokenAddress={secondTokenAddress} secondTokenAddress={secondTokenAddress}
onCancel={onCancel}
onContinue={onContinue} onContinue={onContinue}
onBlocked={onBlocked}
onCancel={onCancel}
showCancel={showCancel} showCancel={showCancel}
/> />
</Modal> </Modal>
......
...@@ -73,11 +73,13 @@ const Buttons = ({ ...@@ -73,11 +73,13 @@ const Buttons = ({
warning, warning,
onContinue, onContinue,
onCancel, onCancel,
onBlocked,
showCancel, showCancel,
}: { }: {
warning: Warning warning: Warning
onContinue: () => void onContinue: () => void
onCancel: () => void onCancel: () => void
onBlocked?: () => void
showCancel?: boolean showCancel?: boolean
}) => { }) => {
return warning.canProceed ? ( return warning.canProceed ? (
...@@ -88,7 +90,7 @@ const Buttons = ({ ...@@ -88,7 +90,7 @@ const Buttons = ({
{showCancel && <StyledCancelButton onClick={onCancel}>Cancel</StyledCancelButton>} {showCancel && <StyledCancelButton onClick={onCancel}>Cancel</StyledCancelButton>}
</> </>
) : ( ) : (
<StyledCloseButton onClick={onCancel}> <StyledCloseButton onClick={onBlocked ?? onCancel}>
<Trans>Close</Trans> <Trans>Close</Trans>
</StyledCloseButton> </StyledCloseButton>
) )
...@@ -184,11 +186,12 @@ const StyledExternalLink = styled(ExternalLink)` ...@@ -184,11 +186,12 @@ const StyledExternalLink = styled(ExternalLink)`
font-weight: 600; font-weight: 600;
` `
interface TokenSafetyProps { export interface TokenSafetyProps {
tokenAddress: string | null tokenAddress: string | null
secondTokenAddress?: string secondTokenAddress?: string
onContinue: () => void onContinue: () => void
onCancel: () => void onCancel: () => void
onBlocked?: () => void
showCancel?: boolean showCancel?: boolean
} }
...@@ -197,6 +200,7 @@ export default function TokenSafety({ ...@@ -197,6 +200,7 @@ export default function TokenSafety({
secondTokenAddress, secondTokenAddress,
onContinue, onContinue,
onCancel, onCancel,
onBlocked,
showCancel, showCancel,
}: TokenSafetyProps) { }: TokenSafetyProps) {
const logos = [] const logos = []
......
...@@ -4,18 +4,16 @@ import { useWeb3React } from '@web3-react/core' ...@@ -4,18 +4,16 @@ import { useWeb3React } from '@web3-react/core'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import PriceChart from 'components/Tokens/TokenDetails/PriceChart' import PriceChart from 'components/Tokens/TokenDetails/PriceChart'
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon' import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { getChainInfo } from 'constants/chainInfo' import { getChainInfo } from 'constants/chainInfo'
import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens' import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { checkWarning, WARNING_LEVEL } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { chainIdToChainName, useTokenDetailQuery } from 'graphql/data/TokenDetailQuery' import { chainIdToChainName, useTokenDetailQuery } from 'graphql/data/TokenDetailQuery'
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens' import { useCurrency, useToken } from 'hooks/Tokens'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { darken } from 'polished' import { darken } from 'polished'
import { Suspense, useCallback } from 'react' import { Suspense } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { CopyContractAddress } from 'theme' import { CopyContractAddress } from 'theme'
import { formatDollarAmount } from 'utils/formatDollarAmt' import { formatDollarAmount } from 'utils/formatDollarAmt'
...@@ -178,17 +176,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -178,17 +176,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
const isFavorited = useIsFavorited(address) const isFavorited = useIsFavorited(address)
const toggleFavorite = useToggleFavorite(address) const toggleFavorite = useToggleFavorite(address)
const warning = checkWarning(address) const warning = checkWarning(address)
const navigate = useNavigate()
const isUserAddedToken = useIsUserAddedToken(token)
const [warningModalOpen, setWarningModalOpen] = useState(!!warning && !isUserAddedToken)
const handleDismissWarning = useCallback(() => {
setWarningModalOpen(false)
}, [setWarningModalOpen])
const handleCancel = useCallback(() => {
setWarningModalOpen(false)
warning && warning.level === WARNING_LEVEL.BLOCKED && navigate(-1)
}, [setWarningModalOpen, navigate, warning])
const chainInfo = getChainInfo(token?.chainId) const chainInfo = getChainInfo(token?.chainId)
const networkLabel = chainInfo?.label const networkLabel = chainInfo?.label
const networkBadgebackgroundColor = chainInfo?.backgroundColor const networkBadgebackgroundColor = chainInfo?.backgroundColor
...@@ -286,12 +273,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -286,12 +273,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
</ContractAddress> </ContractAddress>
</Contract> </Contract>
</ContractAddressSection> </ContractAddressSection>
<TokenSafetyModal
isOpen={warningModalOpen}
tokenAddress={address}
onCancel={handleCancel}
onContinue={handleDismissWarning}
/>
</TopArea> </TopArea>
</Suspense> </Suspense>
) )
......
import { Currency, SwapWidget } from '@uniswap/widgets' import { Currency, OnReviewSwapClick, SwapWidget } from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { RPC_URLS } from 'constants/networks' import { RPC_URLS } from 'constants/networks'
import { useActiveLocale } from 'hooks/useActiveLocale' import { useActiveLocale } from 'hooks/useActiveLocale'
...@@ -16,9 +16,10 @@ const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/' ...@@ -16,9 +16,10 @@ const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
export interface WidgetProps { export interface WidgetProps {
defaultToken?: Currency defaultToken?: Currency
onReviewSwapClick?: OnReviewSwapClick
} }
export default function Widget({ defaultToken }: WidgetProps) { export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) {
const locale = useActiveLocale() const locale = useActiveLocale()
const darkMode = useIsDarkMode() const darkMode = useIsDarkMode()
const theme = useMemo(() => (darkMode ? DARK_THEME : LIGHT_THEME), [darkMode]) const theme = useMemo(() => (darkMode ? DARK_THEME : LIGHT_THEME), [darkMode])
...@@ -38,6 +39,7 @@ export default function Widget({ defaultToken }: WidgetProps) { ...@@ -38,6 +39,7 @@ export default function Widget({ defaultToken }: WidgetProps) {
width={WIDGET_WIDTH} width={WIDGET_WIDTH}
locale={locale} locale={locale}
theme={theme} theme={theme}
onReviewSwapClick={onReviewSwapClick}
// defaultChainId is excluded - it is always inferred from the passed provider // defaultChainId is excluded - it is always inferred from the passed provider
provider={provider} provider={provider}
{...inputs} {...inputs}
......
...@@ -11,14 +11,15 @@ import LoadingTokenDetail from 'components/Tokens/TokenDetails/LoadingTokenDetai ...@@ -11,14 +11,15 @@ import LoadingTokenDetail from 'components/Tokens/TokenDetails/LoadingTokenDetai
import NetworkBalance from 'components/Tokens/TokenDetails/NetworkBalance' import NetworkBalance from 'components/Tokens/TokenDetails/NetworkBalance'
import TokenDetail from 'components/Tokens/TokenDetails/TokenDetail' import TokenDetail from 'components/Tokens/TokenDetails/TokenDetail'
import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage' import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import Widget, { WIDGET_WIDTH } from 'components/Widget' import Widget, { WIDGET_WIDTH } from 'components/Widget'
import { getChainInfo } from 'constants/chainInfo' import { getChainInfo } from 'constants/chainInfo'
import { L1_CHAIN_IDS, L2_CHAIN_IDS, SupportedChainId, TESTNET_CHAIN_IDS } from 'constants/chains' import { L1_CHAIN_IDS, L2_CHAIN_IDS, SupportedChainId, TESTNET_CHAIN_IDS } from 'constants/chains'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { useToken } from 'hooks/Tokens' import { useIsUserAddedToken, useToken } from 'hooks/Tokens'
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances' import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
import { useMemo } from 'react' import { useCallback, useMemo, useState } from 'react'
import { Navigate, useLocation, useParams } from 'react-router-dom' import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
const Footer = styled.div` const Footer = styled.div`
...@@ -79,10 +80,28 @@ export default function TokenDetails() { ...@@ -79,10 +80,28 @@ export default function TokenDetails() {
const location = useLocation() const location = useLocation()
const { tokenAddress } = useParams<{ tokenAddress?: string }>() const { tokenAddress } = useParams<{ tokenAddress?: string }>()
const token = useToken(tokenAddress) const token = useToken(tokenAddress)
const tokenWarning = tokenAddress ? checkWarning(tokenAddress) : null
const isBlockedToken = tokenWarning?.canProceed === false
const navigate = useNavigate()
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
const shouldShowSpeedbump = !useIsUserAddedToken(token) && tokenWarning !== null
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
const onReviewSwap = useCallback(() => {
return new Promise<boolean>((resolve) => {
shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true)
})
}, [shouldShowSpeedbump])
const onResolveSwap = useCallback(
(value: boolean) => {
continueSwap?.resolve(value)
setContinueSwap(undefined)
},
[continueSwap, setContinueSwap]
)
const tokenWarning = token ? checkWarning(token.address) : null
/* network balance handling */ /* network balance handling */
const { data: networkData } = NetworkBalances(token?.address) const { data: networkData } = NetworkBalances(token?.address)
const { chainId: connectedChainId } = useWeb3React() const { chainId: connectedChainId } = useWeb3React()
const totalBalance = 4.3 // dummy data const totalBalance = 4.3 // dummy data
...@@ -128,7 +147,7 @@ export default function TokenDetails() { ...@@ -128,7 +147,7 @@ export default function TokenDetails() {
<> <>
<TokenDetail address={token.address} /> <TokenDetail address={token.address} />
<RightPanel> <RightPanel>
<Widget defaultToken={token ?? undefined} /> <Widget defaultToken={token ?? undefined} onReviewSwapClick={onReviewSwap} />
{tokenWarning && <TokenSafetyMessage tokenAddress={token.address} warning={tokenWarning} />} {tokenWarning && <TokenSafetyMessage tokenAddress={token.address} warning={tokenWarning} />}
<BalanceSummary address={token.address} /> <BalanceSummary address={token.address} />
</RightPanel> </RightPanel>
...@@ -139,6 +158,14 @@ export default function TokenDetails() { ...@@ -139,6 +158,14 @@ export default function TokenDetails() {
networkBalances={balancesByNetwork} networkBalances={balancesByNetwork}
/> />
</Footer> </Footer>
<TokenSafetyModal
isOpen={isBlockedToken || !!continueSwap}
tokenAddress={token.address}
onContinue={() => onResolveSwap(true)}
onBlocked={() => navigate(-1)}
onCancel={() => onResolveSwap(false)}
showCancel={true}
/>
</> </>
)} )}
</TokenDetailsLayout> </TokenDetailsLayout>
......
...@@ -5103,9 +5103,9 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: ...@@ -5103,9 +5103,9 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
uri-js "^4.2.2" uri-js "^4.2.2"
ajv@^8.0.1: ajv@^8.0.1:
version "8.6.2" version "8.11.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
dependencies: dependencies:
fast-deep-equal "^3.1.1" fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0" json-schema-traverse "^1.0.0"
......
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