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 {
getTokenAddress,
} from 'analytics/utils'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useIsDarkMode } from 'state/user/hooks'
import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
import { computeRealizedPriceImpact } from 'utils/prices'
......@@ -34,24 +34,34 @@ export const WIDGET_WIDTH = 360
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
function useWidgetTheme() {
return useIsDarkMode() ? DARK_THEME : LIGHT_THEME
}
export interface WidgetProps {
defaultToken?: Currency
onTokensChange?: (input: Currency | undefined, output: Currency | undefined) => void
onReviewSwapClick?: OnReviewSwapClick
}
export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) {
const locale = useActiveLocale()
const theme = useIsDarkMode() ? DARK_THEME : LIGHT_THEME
export default function Widget({ defaultToken, onTokensChange, onReviewSwapClick }: WidgetProps) {
const { connector, provider } = useWeb3React()
const locale = useActiveLocale()
const theme = useWidgetTheme()
const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken)
const { settings } = useSyncWidgetSettings()
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 [initialQuoteDate, setInitialQuoteDate] = useState<Date>()
const onInitialSwapQuote = useCallback(
(trade: Trade<Currency, Currency, TradeType>) => {
setInitialQuoteDate(new Date())
......@@ -68,7 +78,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
},
[trace]
)
const onApproveToken = useCallback(() => {
const input = inputs.value.INPUT
if (!input) return
......@@ -80,11 +89,9 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
}
sendAnalyticsEvent(EventName.APPROVE_TOKEN_TXN_SUBMITTED, eventProperties)
}, [inputs.value.INPUT, trace])
const onExpandSwapDetails = useCallback(() => {
sendAnalyticsEvent(EventName.SWAP_DETAILS_EXPANDED, { ...trace })
}, [trace])
const onSwapPriceUpdateAck = useCallback(
(stale: Trade<Currency, Currency, TradeType>, update: Trade<Currency, Currency, TradeType>) => {
const eventProperties = {
......@@ -99,7 +106,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
},
[trace]
)
const onSubmitSwapClick = useCallback(
(trade: Trade<Currency, Currency, TradeType>) => {
const eventProperties = {
......@@ -127,11 +133,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
},
[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) {
return <WidgetSkeleton />
......@@ -143,9 +144,9 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
disableBranding
hideConnectionUI
routerUrl={WIDGET_ROUTER_URL}
width={WIDGET_WIDTH}
locale={locale}
theme={theme}
width={WIDGET_WIDTH}
// defaultChainId is excluded - it is always inferred from the passed provider
provider={provider}
onSwitchChain={onSwitchChain}
......@@ -166,5 +167,6 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
}
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) {
useEffect(() => {
// Avoid overwriting tokens if none are specified, so that a loading token does not cause layout flashing.
if (!defaultToken) return
setTokens({
[Field.OUTPUT]: defaultToken,
})
setTokens((tokens) =>
// 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)
}, [defaultToken])
......
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
......@@ -70,14 +71,14 @@ export const RightPanel = styled.div`
export default function TokenDetails() {
const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>()
const { account } = useWeb3React()
const currentChainName = validateUrlChainParam(chainName)
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[currentChainName]
const chain = validateUrlChainParam(chainName)
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const nativeCurrency = nativeOnChain(pageChainId)
const timePeriod = useAtomValue(filterTimeAtom)
const isNative = tokenAddress === NATIVE_CHAIN_ID
const [tokenQueryData, prices] = useTokenQuery(
isNative ? nativeCurrency.wrapped.address : tokenAddress ?? '',
currentChainName,
chain,
timePeriod
)
const queryToken = useTokenFromQuery(isNative ? undefined : { ...tokenQueryData, chainId: pageChainId })
......@@ -91,28 +92,34 @@ export default function TokenDetails() {
const isBlockedToken = tokenWarning?.canProceed === false
const navigate = useNavigate()
const switchChains = useCallback(
(newChain: Chain) => {
const chainSegment = newChain.toLowerCase()
const navigateToTokenForChain = useCallback(
(chain: Chain) => {
const chainName = chain.toLowerCase()
const token = tokenQueryData?.project?.tokens.find((token) => token.chain === chain && token.address)
if (isNative) {
navigate(`/tokens/${chainSegment}/NATIVE`)
} else {
tokenQueryData?.project?.tokens?.forEach((token) => {
if (token.chain === newChain && token.address) {
navigate(`/tokens/${chainSegment}/${token.address}`)
}
})
navigate(`/tokens/${chainName}/${NATIVE_CHAIN_ID}`)
} else if (token) {
navigate(`/tokens/${chainName}/${token.address}`)
}
},
[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 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
const onReviewSwap = useCallback(
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(tokenAddress, pageChainId) && tokenWarning !== null
const onReviewSwapClick = useCallback(
() => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))),
[shouldShowSpeedbump]
)
......@@ -157,9 +164,9 @@ export default function TokenDetails() {
</LeftPanel>
<RightPanel>
<Widget
// A null token is still loading, and should not be overridden.
defaultToken={token === null ? undefined : token ?? nativeCurrency}
onReviewSwapClick={onReviewSwap}
defaultToken={token === null ? undefined : token ?? nativeCurrency} // a null token is still loading, and should not be overridden.
onTokensChange={navigateToWidgetSelectedToken}
onReviewSwapClick={onReviewSwapClick}
/>
{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