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

feat: transition between tokens details (#4981)

* fix: navigate to widget-selected token

* fix: leave tokens if default is already set

* refactor: clean up widget skeleton

* fix: clean widget skeleton

* feat: transition between tokens

* fix: flicker on chart draw

* fix: nits

* fix: pixel-match loader

* fix: rm debug clause

* fix: hr color
parent 2d87e692
...@@ -124,7 +124,7 @@ export default function ChartSection({ ...@@ -124,7 +124,7 @@ export default function ChartSection({
</TokenInfoContainer> </TokenInfoContainer>
<ChartContainer> <ChartContainer>
<ParentSize> <ParentSize>
{({ width, height }) => prices && <PriceChart prices={prices[timePeriod]} width={width} height={height} />} {({ width }) => prices && <PriceChart prices={prices[timePeriod]} width={width} height={436} />}
</ParentSize> </ParentSize>
</ChartContainer> </ChartContainer>
</ChartHeader> </ChartHeader>
......
...@@ -284,7 +284,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) { ...@@ -284,7 +284,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
message={prices && prices.length === 0 ? <NoV3DataMessage /> : <MissingDataMessage />} message={prices && prices.length === 0 ? <NoV3DataMessage /> : <MissingDataMessage />}
/> />
) : ( ) : (
<svg width={width} height={graphHeight}> <svg width={width} height={graphHeight} style={{ minWidth: '100%' }}>
<AnimatedInLineChart <AnimatedInLineChart
data={prices} data={prices}
getX={getX} getX={getX}
...@@ -341,6 +341,19 @@ export function PriceChart({ width, height, prices }: PriceChartProps) { ...@@ -341,6 +341,19 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
) : ( ) : (
<AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks /> <AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks />
)} )}
{!width && (
// Ensures an axis is drawn even if the width is not yet initialized.
<line
x1={0}
y1={graphHeight - 1}
x2="100%"
y2={graphHeight - 1}
fill="transparent"
shapeRendering="crispEdges"
stroke={theme.backgroundOutline}
strokeWidth={1}
/>
)}
<rect <rect
x={0} x={0}
y={0} y={0}
...@@ -391,7 +404,7 @@ function MissingPriceChart({ width, height, message }: { width: number; height: ...@@ -391,7 +404,7 @@ function MissingPriceChart({ width, height, message }: { width: number; height:
const theme = useTheme() const theme = useTheme()
const midPoint = height / 2 + 45 const midPoint = height / 2 + 45
return ( return (
<StyledMissingChart width={width} height={height}> <StyledMissingChart width={width} height={height} style={{ minWidth: '100%' }}>
<path <path
d={`M 0 ${midPoint} Q 104 ${midPoint - 70}, 208 ${midPoint} T 416 ${midPoint} d={`M 0 ${midPoint} Q 104 ${midPoint - 70}, 208 ${midPoint} T 416 ${midPoint}
M 416 ${midPoint} Q 520 ${midPoint - 70}, 624 ${midPoint} T 832 ${midPoint}`} M 416 ${midPoint} Q 520 ${midPoint - 70}, 624 ${midPoint} T 832 ${midPoint}`}
......
import { WidgetSkeleton } from 'components/Widget' import { WidgetSkeleton } from 'components/Widget'
import { LeftPanel, RightPanel, TokenDetailsLayout } from 'pages/TokenDetails' import { WIDGET_WIDTH } from 'components/Widget'
import { ArrowLeft } from 'react-feather'
import { useParams } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { LoadingBubble } from '../loading' import { LoadingBubble } from '../loading'
import { AboutContainer, AboutHeader, ResourcesContainer } from './About' import { AboutContainer, AboutHeader } from './About'
import { ContractAddressSection } from './AddressSection'
import { BreadcrumbNavLink } from './BreadcrumbNavLink' import { BreadcrumbNavLink } from './BreadcrumbNavLink'
import { ChartContainer, ChartHeader, TokenInfoContainer, TokenNameCell } from './ChartSection' import { ChartContainer, ChartHeader, TokenInfoContainer, TokenNameCell } from './ChartSection'
import { DeltaContainer, TokenPrice } from './PriceChart' import { DeltaContainer, TokenPrice } from './PriceChart'
import { StatPair, StatWrapper, TokenStatsSection } from './StatsSection' import { StatPair, StatsWrapper, StatWrapper } from './StatsSection'
export const Hr = styled.hr`
background-color: ${({ theme }) => theme.backgroundOutline};
border: none;
height: 0.5px;
`
export const TokenDetailsLayout = styled.div`
display: flex;
padding: 0 8px 52px;
justify-content: center;
width: 100%;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
gap: 16px;
padding: 0 16px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
gap: 20px;
padding: 48px 20px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.xl}px) {
gap: 40px;
}
`
export const LeftPanel = styled.div`
flex: 1;
max-width: 780px;
overflow: hidden;
`
export const RightPanel = styled.div`
display: none;
flex-direction: column;
gap: 20px;
width: ${WIDGET_WIDTH}px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
display: flex;
}
`
const LoadingChartContainer = styled(ChartContainer)` const LoadingChartContainer = styled(ChartContainer)`
height: 336px; border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
height: 313px; // save 1px for the border-bottom (ie y-axis)
overflow: hidden; overflow: hidden;
` `
/* Loading state bubbles */ /* Loading state bubbles */
const LoadingDetailBubble = styled(LoadingBubble)` const DetailBubble = styled(LoadingBubble)`
height: 16px; height: 16px;
width: 180px; width: 180px;
` `
const TitleLoadingBubble = styled(LoadingDetailBubble)` const SquaredBubble = styled(DetailBubble)`
width: 140px;
`
const SquareLoadingBubble = styled(LoadingDetailBubble)`
height: 32px; height: 32px;
border-radius: 8px; border-radius: 8px;
margin-bottom: 10px;
` `
const PriceLoadingBubble = styled(SquareLoadingBubble)` const TokenLogoBubble = styled(DetailBubble)`
width: 32px;
height: 32px;
border-radius: 50%;
`
const TitleBubble = styled(DetailBubble)`
width: 140px;
`
const PriceBubble = styled(SquaredBubble)`
height: 40px; height: 40px;
` `
const LongLoadingBubble = styled(LoadingDetailBubble)` const DeltaBubble = styled(DetailBubble)`
margin-top: 6px; width: 96px;
width: 100%; `
const SectionBubble = styled(SquaredBubble)`
width: 96px;
`
const StatTitleBubble = styled(DetailBubble)`
width: 25%;
margin-bottom: 4px;
` `
const HalfLoadingBubble = styled(LoadingDetailBubble)` const StatBubble = styled(SquaredBubble)`
margin-top: 6px;
width: 50%; width: 50%;
` `
const IconLoadingBubble = styled(LoadingDetailBubble)` const WideBubble = styled(DetailBubble)`
width: 32px; margin-bottom: 6px;
height: 32px; width: 100%;
border-radius: 50%;
` `
const StatLoadingBubble = styled(SquareLoadingBubble)` const HalfWideBubble = styled(WideBubble)`
width: 116px; width: 50%;
` `
const StatsLoadingContainer = styled.div` const StatsLoadingContainer = styled.div`
width: 100%;
display: flex; display: flex;
gap: 24px;
flex-wrap: wrap; flex-wrap: wrap;
` `
const ChartAnimation = styled.div` const ChartAnimation = styled.div`
display: flex;
animation: wave 8s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite; animation: wave 8s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite;
display: flex;
overflow: hidden; overflow: hidden;
@keyframes wave { @keyframes wave {
...@@ -70,7 +119,7 @@ const Space = styled.div<{ heightSize: number }>` ...@@ -70,7 +119,7 @@ const Space = styled.div<{ heightSize: number }>`
height: ${({ heightSize }) => `${heightSize}px`}; height: ${({ heightSize }) => `${heightSize}px`};
` `
export function Wave() { function Wave() {
const theme = useTheme() const theme = useTheme()
return ( return (
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg"> <svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
...@@ -79,82 +128,95 @@ export function Wave() { ...@@ -79,82 +128,95 @@ export function Wave() {
) )
} }
function LoadingChart() {
return (
<ChartHeader>
<TokenInfoContainer>
<TokenNameCell>
<TokenLogoBubble />
<TitleBubble />
</TokenNameCell>
</TokenInfoContainer>
<TokenPrice>
<PriceBubble />
</TokenPrice>
<DeltaContainer>
<DeltaBubble />
</DeltaContainer>
<Space heightSize={6} />
<LoadingChartContainer>
<div>
<ChartAnimation>
<Wave />
<Wave />
<Wave />
<Wave />
<Wave />
</ChartAnimation>
</div>
</LoadingChartContainer>
</ChartHeader>
)
}
function LoadingStats() {
return (
<StatsWrapper>
<SectionBubble />
<StatsLoadingContainer>
<StatPair>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
</StatPair>
<StatPair>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
</StatPair>
</StatsLoadingContainer>
</StatsWrapper>
)
}
/* Loading State: row component with loading bubbles */ /* Loading State: row component with loading bubbles */
export default function LoadingTokenDetail() { export default function TokenDetailsSkeleton() {
const { chainName } = useParams<{ chainName?: string }>()
return ( return (
<LeftPanel> <LeftPanel>
<BreadcrumbNavLink to="/explore"> <BreadcrumbNavLink to={{ chainName } ? `/tokens/${chainName}` : `/explore`}>
<Space heightSize={20} /> <ArrowLeft size={14} /> Tokens
</BreadcrumbNavLink> </BreadcrumbNavLink>
<ChartHeader> <LoadingChart />
<TokenInfoContainer> <Space heightSize={45} />
<TokenNameCell> <LoadingStats />
<IconLoadingBubble /> <Hr />
<TitleLoadingBubble />
</TokenNameCell>
</TokenInfoContainer>
<TokenPrice>
<PriceLoadingBubble />
</TokenPrice>
<DeltaContainer>
<Space heightSize={20} />
</DeltaContainer>
<LoadingChartContainer>
<div>
<ChartAnimation>
<Wave />
<Wave />
<Wave />
<Wave />
<Wave />
</ChartAnimation>
</div>
</LoadingChartContainer>
<Space heightSize={32} />
</ChartHeader>
<TokenStatsSection>
<StatsLoadingContainer>
<StatPair>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
</StatPair>
<StatPair>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
</StatPair>
</StatsLoadingContainer>
</TokenStatsSection>
<AboutContainer> <AboutContainer>
<AboutHeader> <AboutHeader>
<SquareLoadingBubble /> <SectionBubble />
</AboutHeader> </AboutHeader>
<LongLoadingBubble />
<LongLoadingBubble />
<HalfLoadingBubble />
<ResourcesContainer>{null}</ResourcesContainer>
</AboutContainer> </AboutContainer>
<ContractAddressSection>{null}</ContractAddressSection> <WideBubble />
<WideBubble />
<HalfWideBubble />
</LeftPanel> </LeftPanel>
) )
} }
export function LoadingTokenDetails() { export function TokenDetailsPageSkeleton() {
return ( return (
<TokenDetailsLayout> <TokenDetailsLayout>
<LoadingTokenDetail /> <TokenDetailsSkeleton />
<RightPanel> <RightPanel>
<WidgetSkeleton /> <WidgetSkeleton />
</RightPanel> </RightPanel>
......
...@@ -44,7 +44,7 @@ const StatPrice = styled.span` ...@@ -44,7 +44,7 @@ const StatPrice = styled.span`
const NoData = styled.div` const NoData = styled.div`
color: ${({ theme }) => theme.textTertiary}; color: ${({ theme }) => theme.textTertiary};
` `
const Wrapper = styled.div` export const StatsWrapper = styled.div`
gap: 16px; gap: 16px;
${textFadeIn} ${textFadeIn}
` `
...@@ -84,7 +84,7 @@ export default function StatsSection(props: StatsSectionProps) { ...@@ -84,7 +84,7 @@ export default function StatsSection(props: StatsSectionProps) {
const { priceLow52W, priceHigh52W, TVL, volume24H } = props const { priceLow52W, priceHigh52W, TVL, volume24H } = props
if (TVL || volume24H || priceLow52W || priceHigh52W) { if (TVL || volume24H || priceLow52W || priceHigh52W) {
return ( return (
<Wrapper> <StatsWrapper>
<Header> <Header>
<Trans>Stats</Trans> <Trans>Stats</Trans>
</Header> </Header>
...@@ -110,7 +110,7 @@ export default function StatsSection(props: StatsSectionProps) { ...@@ -110,7 +110,7 @@ export default function StatsSection(props: StatsSectionProps) {
<Stat value={priceHigh52W} title={<Trans>52W high</Trans>} isPrice={true} /> <Stat value={priceHigh52W} title={<Trans>52W high</Trans>} isPrice={true} />
</StatPair> </StatPair>
</TokenStatsSection> </TokenStatsSection>
</Wrapper> </StatsWrapper>
) )
} else { } else {
return <NoData>No stats available</NoData> return <NoData>No stats available</NoData>
......
...@@ -20,7 +20,7 @@ import ErrorBoundary from '../components/ErrorBoundary' ...@@ -20,7 +20,7 @@ import ErrorBoundary from '../components/ErrorBoundary'
import NavBar from '../components/NavBar' import NavBar from '../components/NavBar'
import Polling from '../components/Polling' import Polling from '../components/Polling'
import Popups from '../components/Popups' import Popups from '../components/Popups'
import { LoadingTokenDetails } from '../components/Tokens/TokenDetails/LoadingTokenDetails' import { TokenDetailsPageSkeleton } from '../components/Tokens/TokenDetails/Skeleton'
import { useIsExpertMode } from '../state/user/hooks' import { useIsExpertMode } from '../state/user/hooks'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import AddLiquidity from './AddLiquidity'
...@@ -165,7 +165,7 @@ export default function App() { ...@@ -165,7 +165,7 @@ export default function App() {
<Route <Route
path="tokens/:chainName/:tokenAddress" path="tokens/:chainName/:tokenAddress"
element={ element={
<Suspense fallback={<LoadingTokenDetails />}> <Suspense fallback={<TokenDetailsPageSkeleton />}>
<TokenDetails /> <TokenDetails />
</Suspense> </Suspense>
} }
......
...@@ -9,10 +9,16 @@ import BalanceSummary from 'components/Tokens/TokenDetails/BalanceSummary' ...@@ -9,10 +9,16 @@ import BalanceSummary from 'components/Tokens/TokenDetails/BalanceSummary'
import { BreadcrumbNavLink } from 'components/Tokens/TokenDetails/BreadcrumbNavLink' import { BreadcrumbNavLink } from 'components/Tokens/TokenDetails/BreadcrumbNavLink'
import ChartSection from 'components/Tokens/TokenDetails/ChartSection' import ChartSection from 'components/Tokens/TokenDetails/ChartSection'
import MobileBalanceSummaryFooter from 'components/Tokens/TokenDetails/MobileBalanceSummaryFooter' import MobileBalanceSummaryFooter from 'components/Tokens/TokenDetails/MobileBalanceSummaryFooter'
import TokenDetailsSkeleton, {
Hr,
LeftPanel,
RightPanel,
TokenDetailsLayout,
} from 'components/Tokens/TokenDetails/Skeleton'
import StatsSection from 'components/Tokens/TokenDetails/StatsSection' import StatsSection from 'components/Tokens/TokenDetails/StatsSection'
import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage' import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import Widget, { WIDGET_WIDTH } from 'components/Widget' import Widget from 'components/Widget'
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { Chain } from 'graphql/data/__generated__/TokenQuery.graphql' import { Chain } from 'graphql/data/__generated__/TokenQuery.graphql'
...@@ -23,50 +29,9 @@ import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch' ...@@ -23,50 +29,9 @@ import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { useTokenFromQuery } from 'lib/hooks/useCurrency' import { useTokenFromQuery } from 'lib/hooks/useCurrency'
import useCurrencyBalance, { useTokenBalance } from 'lib/hooks/useCurrencyBalance' import useCurrencyBalance, { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
import { useCallback, useState } from 'react' import { useCallback, useState, useTransition } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import styled from 'styled-components/macro'
const Hr = styled.hr`
background-color: ${({ theme }) => theme.textSecondary};
opacity: 24%;
border: none;
height: 0.5px;
`
export const TokenDetailsLayout = styled.div`
display: flex;
padding: 0 8px 52px;
justify-content: center;
width: 100%;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
gap: 16px;
padding: 0 16px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
gap: 20px;
padding: 48px 20px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.xl}px) {
gap: 40px;
}
`
export const LeftPanel = styled.div`
flex: 1;
max-width: 780px;
overflow: hidden;
`
export const RightPanel = styled.div`
display: none;
flex-direction: column;
gap: 20px;
width: ${WIDGET_WIDTH}px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
display: flex;
}
`
export default function TokenDetails() { export default function TokenDetails() {
const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>() const { tokenAddress, chainName } = useParams<{ tokenAddress?: string; chainName?: string }>()
...@@ -92,15 +57,15 @@ export default function TokenDetails() { ...@@ -92,15 +57,15 @@ export default function TokenDetails() {
const isBlockedToken = tokenWarning?.canProceed === false const isBlockedToken = tokenWarning?.canProceed === false
const navigate = useNavigate() const navigate = useNavigate()
// Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again.
const [isPending, startTransition] = useTransition()
const navigateToTokenForChain = useCallback( const navigateToTokenForChain = useCallback(
(chain: Chain) => { (chain: Chain) => {
const chainName = chain.toLowerCase() const chainName = chain.toLowerCase()
const token = tokenQueryData?.project?.tokens.find((token) => token.chain === chain && token.address) const token = tokenQueryData?.project?.tokens.find((token) => token.chain === chain && token.address)
if (isNative) { const address = isNative ? NATIVE_CHAIN_ID : token?.address
navigate(`/tokens/${chainName}/${NATIVE_CHAIN_ID}`) if (!address) return
} else if (token) { startTransition(() => navigate(`/tokens/${chainName}/${address}`))
navigate(`/tokens/${chainName}/${token.address}`)
}
}, },
[isNative, navigate, tokenQueryData?.project?.tokens] [isNative, navigate, tokenQueryData?.project?.tokens]
) )
...@@ -110,7 +75,7 @@ export default function TokenDetails() { ...@@ -110,7 +75,7 @@ export default function TokenDetails() {
const update = output || input const update = output || input
if (!token || !update || input?.equals(token) || output?.equals(token)) return if (!token || !update || input?.equals(token) || output?.equals(token)) return
const address = update.isNative ? NATIVE_CHAIN_ID : update.address const address = update.isNative ? NATIVE_CHAIN_ID : update.address
navigate(`/tokens/${chainName}/${address}`) startTransition(() => navigate(`/tokens/${chainName}/${address}`))
}, },
[chainName, navigate, token] [chainName, navigate, token]
) )
...@@ -135,63 +100,62 @@ export default function TokenDetails() { ...@@ -135,63 +100,62 @@ export default function TokenDetails() {
return ( return (
<Trace page={PageName.TOKEN_DETAILS_PAGE} properties={{ tokenAddress, tokenName: chainName }} shouldLogImpression> <Trace page={PageName.TOKEN_DETAILS_PAGE} properties={{ tokenAddress, tokenName: chainName }} shouldLogImpression>
<TokenDetailsLayout> <TokenDetailsLayout>
{tokenQueryData && ( {tokenQueryData && !isPending ? (
<> <LeftPanel>
<LeftPanel> <BreadcrumbNavLink to={`/tokens/${chainName}`}>
<BreadcrumbNavLink to={`/tokens/${chainName}`}> <ArrowLeft size={14} /> Tokens
<ArrowLeft size={14} /> Tokens </BreadcrumbNavLink>
</BreadcrumbNavLink> <ChartSection
<ChartSection token={tokenQueryData}
token={tokenQueryData} currency={token}
currency={token} nativeCurrency={isNative ? nativeCurrency : undefined}
nativeCurrency={isNative ? nativeCurrency : undefined} prices={prices}
prices={prices} />
/> <StatsSection
<StatsSection TVL={tokenQueryData.market?.totalValueLocked?.value}
TVL={tokenQueryData.market?.totalValueLocked?.value} volume24H={tokenQueryData.market?.volume24H?.value}
volume24H={tokenQueryData.market?.volume24H?.value} priceHigh52W={tokenQueryData.market?.priceHigh52W?.value}
priceHigh52W={tokenQueryData.market?.priceHigh52W?.value} priceLow52W={tokenQueryData.market?.priceLow52W?.value}
priceLow52W={tokenQueryData.market?.priceLow52W?.value} />
/> {!isNative && (
<Hr /> <>
<AboutSection <Hr />
address={tokenQueryData.address ?? ''} <AboutSection
description={tokenQueryData.project?.description} address={tokenQueryData.address ?? ''}
homepageUrl={tokenQueryData.project?.homepageUrl} description={tokenQueryData.project?.description}
twitterName={tokenQueryData.project?.twitterName} homepageUrl={tokenQueryData.project?.homepageUrl}
/> twitterName={tokenQueryData.project?.twitterName}
<AddressSection address={tokenQueryData.address ?? ''} /> />
</LeftPanel> <AddressSection address={tokenQueryData.address ?? ''} />
<RightPanel> </>
<Widget
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} />
)}
<BalanceSummary
tokenAmount={tokenBalance}
nativeCurrencyAmount={nativeCurrencyBalance}
isNative={isNative}
/>
</RightPanel>
{tokenQueryAddress && (
<MobileBalanceSummaryFooter
tokenAmount={tokenBalance}
tokenAddress={tokenQueryAddress}
nativeCurrencyAmount={nativeCurrencyBalance}
isNative={isNative}
/>
)} )}
</> </LeftPanel>
) : (
<TokenDetailsSkeleton />
)}
<RightPanel>
<Widget
defaultToken={token === null ? undefined : token ?? nativeCurrency} // a null token is still loading, and should not be overridden.
onTokensChange={navigateToWidgetSelectedToken}
onReviewSwapClick={onReviewSwapClick}
/>
{tokenWarning && <TokenSafetyMessage tokenAddress={tokenAddress ?? ''} warning={tokenWarning} />}
<BalanceSummary tokenAmount={tokenBalance} nativeCurrencyAmount={nativeCurrencyBalance} isNative={isNative} />
</RightPanel>
{tokenQueryAddress && (
<MobileBalanceSummaryFooter
tokenAmount={tokenBalance}
tokenAddress={tokenQueryAddress}
nativeCurrencyAmount={nativeCurrencyBalance}
isNative={isNative}
/>
)} )}
{tokenAddress && ( {tokenQueryAddress && (
<TokenSafetyModal <TokenSafetyModal
isOpen={isBlockedToken || !!continueSwap} isOpen={isBlockedToken || !!continueSwap}
tokenAddress={tokenAddress} tokenAddress={tokenQueryAddress}
onContinue={() => onResolveSwap(true)} onContinue={() => onResolveSwap(true)}
onBlocked={() => navigate(-1)} onBlocked={() => navigate(-1)}
onCancel={() => onResolveSwap(false)} onCancel={() => onResolveSwap(false)}
......
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