Commit a955b373 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

feat: navigate to widget-selected token (#4975)

* fix: navigate to widget-selected token

* fix: leave tokens if default is already set

* refactor: clean up widget skeleton

* fix: clean widget skeleton

* fix: nits
parent 2f7c5b1d
...@@ -20,7 +20,7 @@ import { ...@@ -20,7 +20,7 @@ import {
getTokenAddress, getTokenAddress,
} from 'analytics/utils' } from 'analytics/utils'
import { useActiveLocale } from 'hooks/useActiveLocale' import { useActiveLocale } from 'hooks/useActiveLocale'
import { useCallback, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useIsDarkMode } from 'state/user/hooks' import { useIsDarkMode } from 'state/user/hooks'
import { DARK_THEME, LIGHT_THEME } from 'theme/widget' import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
import { computeRealizedPriceImpact } from 'utils/prices' import { computeRealizedPriceImpact } from 'utils/prices'
...@@ -34,24 +34,34 @@ export const WIDGET_WIDTH = 360 ...@@ -34,24 +34,34 @@ export const WIDGET_WIDTH = 360
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/' const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
function useWidgetTheme() {
return useIsDarkMode() ? DARK_THEME : LIGHT_THEME
}
export interface WidgetProps { export interface WidgetProps {
defaultToken?: Currency defaultToken?: Currency
onTokensChange?: (input: Currency | undefined, output: Currency | undefined) => void
onReviewSwapClick?: OnReviewSwapClick onReviewSwapClick?: OnReviewSwapClick
} }
export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) { export default function Widget({ defaultToken, onTokensChange, onReviewSwapClick }: WidgetProps) {
const locale = useActiveLocale()
const theme = useIsDarkMode() ? DARK_THEME : LIGHT_THEME
const { connector, provider } = useWeb3React() const { connector, provider } = useWeb3React()
const locale = useActiveLocale()
const theme = useWidgetTheme()
const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken) const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken)
const { settings } = useSyncWidgetSettings() const { settings } = useSyncWidgetSettings()
const { transactions } = useSyncWidgetTransactions() const { transactions } = useSyncWidgetTransactions()
const onSwitchChain = useCallback(
// TODO(WEB-1757): Widget should not break if this rejects - upstream the catch to ignore it.
({ chainId }: AddEthereumChainParameter) => switchChain(connector, Number(chainId)).catch(() => undefined),
[connector]
)
useEffect(() => {
onTokensChange?.(inputs.value.INPUT, inputs.value.OUTPUT)
}, [inputs.value.INPUT, inputs.value.OUTPUT, onTokensChange])
const trace = useTrace({ section: SectionName.WIDGET }) const trace = useTrace({ section: SectionName.WIDGET })
const [initialQuoteDate, setInitialQuoteDate] = useState<Date>() const [initialQuoteDate, setInitialQuoteDate] = useState<Date>()
const onInitialSwapQuote = useCallback( const onInitialSwapQuote = useCallback(
(trade: Trade<Currency, Currency, TradeType>) => { (trade: Trade<Currency, Currency, TradeType>) => {
setInitialQuoteDate(new Date()) setInitialQuoteDate(new Date())
...@@ -68,7 +78,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) ...@@ -68,7 +78,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
}, },
[trace] [trace]
) )
const onApproveToken = useCallback(() => { const onApproveToken = useCallback(() => {
const input = inputs.value.INPUT const input = inputs.value.INPUT
if (!input) return if (!input) return
...@@ -80,11 +89,9 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) ...@@ -80,11 +89,9 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
} }
sendAnalyticsEvent(EventName.APPROVE_TOKEN_TXN_SUBMITTED, eventProperties) sendAnalyticsEvent(EventName.APPROVE_TOKEN_TXN_SUBMITTED, eventProperties)
}, [inputs.value.INPUT, trace]) }, [inputs.value.INPUT, trace])
const onExpandSwapDetails = useCallback(() => { const onExpandSwapDetails = useCallback(() => {
sendAnalyticsEvent(EventName.SWAP_DETAILS_EXPANDED, { ...trace }) sendAnalyticsEvent(EventName.SWAP_DETAILS_EXPANDED, { ...trace })
}, [trace]) }, [trace])
const onSwapPriceUpdateAck = useCallback( const onSwapPriceUpdateAck = useCallback(
(stale: Trade<Currency, Currency, TradeType>, update: Trade<Currency, Currency, TradeType>) => { (stale: Trade<Currency, Currency, TradeType>, update: Trade<Currency, Currency, TradeType>) => {
const eventProperties = { const eventProperties = {
...@@ -99,7 +106,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) ...@@ -99,7 +106,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
}, },
[trace] [trace]
) )
const onSubmitSwapClick = useCallback( const onSubmitSwapClick = useCallback(
(trade: Trade<Currency, Currency, TradeType>) => { (trade: Trade<Currency, Currency, TradeType>) => {
const eventProperties = { const eventProperties = {
...@@ -127,11 +133,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) ...@@ -127,11 +133,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
}, },
[initialQuoteDate, trace] [initialQuoteDate, trace]
) )
const onSwitchChain = useCallback(
// TODO: Widget should not break if this rejects - upstream the catch to ignore it.
({ chainId }: AddEthereumChainParameter) => switchChain(connector, Number(chainId)).catch(() => undefined),
[connector]
)
if (!inputs.value.INPUT && !inputs.value.OUTPUT) { if (!inputs.value.INPUT && !inputs.value.OUTPUT) {
return <WidgetSkeleton /> return <WidgetSkeleton />
...@@ -143,9 +144,9 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) ...@@ -143,9 +144,9 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
disableBranding disableBranding
hideConnectionUI hideConnectionUI
routerUrl={WIDGET_ROUTER_URL} routerUrl={WIDGET_ROUTER_URL}
width={WIDGET_WIDTH}
locale={locale} locale={locale}
theme={theme} theme={theme}
width={WIDGET_WIDTH}
// 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}
onSwitchChain={onSwitchChain} onSwitchChain={onSwitchChain}
...@@ -166,5 +167,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) ...@@ -166,5 +167,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
} }
export function WidgetSkeleton() { export function WidgetSkeleton() {
return <SwapWidgetSkeleton theme={useIsDarkMode() ? DARK_THEME : LIGHT_THEME} width={WIDGET_WIDTH} /> const theme = useWidgetTheme()
return <SwapWidgetSkeleton theme={theme} width={WIDGET_WIDTH} />
} }
...@@ -34,9 +34,10 @@ export function useSyncWidgetInputs(defaultToken?: Currency) { ...@@ -34,9 +34,10 @@ export function useSyncWidgetInputs(defaultToken?: Currency) {
useEffect(() => { useEffect(() => {
// Avoid overwriting tokens if none are specified, so that a loading token does not cause layout flashing. // Avoid overwriting tokens if none are specified, so that a loading token does not cause layout flashing.
if (!defaultToken) return if (!defaultToken) return
setTokens({ setTokens((tokens) =>
[Field.OUTPUT]: defaultToken, // Avoid overwriting tokens if the default is already included, so that the widget does not spuriously reset.
}) Object.values(tokens).some((token) => token?.equals(defaultToken)) ? tokens : { [Field.OUTPUT]: defaultToken }
)
setAmount(EMPTY_AMOUNT) setAmount(EMPTY_AMOUNT)
}, [defaultToken]) }, [defaultToken])
......
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { PageName } from 'analytics/constants' import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace' import { Trace } from 'analytics/Trace'
...@@ -70,14 +71,14 @@ export const RightPanel = styled.div` ...@@ -70,14 +71,14 @@ export const RightPanel = styled.div`
export default function TokenDetails() { export default function TokenDetails() {
const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>() const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>()
const { account } = useWeb3React() const { account } = useWeb3React()
const currentChainName = validateUrlChainParam(chainName) const chain = validateUrlChainParam(chainName)
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[currentChainName] const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const nativeCurrency = nativeOnChain(pageChainId) const nativeCurrency = nativeOnChain(pageChainId)
const timePeriod = useAtomValue(filterTimeAtom) const timePeriod = useAtomValue(filterTimeAtom)
const isNative = tokenAddress === NATIVE_CHAIN_ID const isNative = tokenAddress === NATIVE_CHAIN_ID
const [tokenQueryData, prices] = useTokenQuery( const [tokenQueryData, prices] = useTokenQuery(
isNative ? nativeCurrency.wrapped.address : tokenAddress ?? '', isNative ? nativeCurrency.wrapped.address : tokenAddress ?? '',
currentChainName, chain,
timePeriod timePeriod
) )
const queryToken = useTokenFromQuery(isNative ? undefined : { ...tokenQueryData, chainId: pageChainId }) const queryToken = useTokenFromQuery(isNative ? undefined : { ...tokenQueryData, chainId: pageChainId })
...@@ -91,28 +92,34 @@ export default function TokenDetails() { ...@@ -91,28 +92,34 @@ export default function TokenDetails() {
const isBlockedToken = tokenWarning?.canProceed === false const isBlockedToken = tokenWarning?.canProceed === false
const navigate = useNavigate() const navigate = useNavigate()
const switchChains = useCallback( const navigateToTokenForChain = useCallback(
(newChain: Chain) => { (chain: Chain) => {
const chainSegment = newChain.toLowerCase() const chainName = chain.toLowerCase()
const token = tokenQueryData?.project?.tokens.find((token) => token.chain === chain && token.address)
if (isNative) { if (isNative) {
navigate(`/tokens/${chainSegment}/NATIVE`) navigate(`/tokens/${chainName}/${NATIVE_CHAIN_ID}`)
} else { } else if (token) {
tokenQueryData?.project?.tokens?.forEach((token) => { navigate(`/tokens/${chainName}/${token.address}`)
if (token.chain === newChain && token.address) {
navigate(`/tokens/${chainSegment}/${token.address}`)
}
})
} }
}, },
[isNative, navigate, tokenQueryData?.project?.tokens] [isNative, navigate, tokenQueryData?.project?.tokens]
) )
useOnGlobalChainSwitch(switchChains) useOnGlobalChainSwitch(navigateToTokenForChain)
const navigateToWidgetSelectedToken = useCallback(
(input: Currency | undefined, output: Currency | undefined) => {
const update = output || input
if (!token || !update || input?.equals(token) || output?.equals(token)) return
const address = update.isNative ? NATIVE_CHAIN_ID : update.address
navigate(`/tokens/${chainName}/${address}`)
},
[chainName, navigate, token]
)
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>() const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(tokenAddress, pageChainId) && tokenWarning !== null
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked // Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
const onReviewSwap = useCallback( const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(tokenAddress, pageChainId) && tokenWarning !== null
const onReviewSwapClick = useCallback(
() => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))), () => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))),
[shouldShowSpeedbump] [shouldShowSpeedbump]
) )
...@@ -157,9 +164,9 @@ export default function TokenDetails() { ...@@ -157,9 +164,9 @@ export default function TokenDetails() {
</LeftPanel> </LeftPanel>
<RightPanel> <RightPanel>
<Widget <Widget
// A null token is still loading, and should not be overridden. defaultToken={token === null ? undefined : token ?? nativeCurrency} // a null token is still loading, and should not be overridden.
defaultToken={token === null ? undefined : token ?? nativeCurrency} onTokensChange={navigateToWidgetSelectedToken}
onReviewSwapClick={onReviewSwap} onReviewSwapClick={onReviewSwapClick}
/> />
{tokenWarning && ( {tokenWarning && (
<TokenSafetyMessage tokenAddress={tokenQueryData.address ?? ''} warning={tokenWarning} /> <TokenSafetyMessage tokenAddress={tokenQueryData.address ?? ''} warning={tokenWarning} />
......
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