Commit 6798bf3c authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: [info] add PDP loading skeleton (#7494)

* initial skeleton setup

* responsive table skeleton

* correct table widht

* right side column added

* add comments

* move loading components to their corresponding component

* remove extra bubble and adjust some styles

* move table skeleton to its own file

* add shared styles and skele file

* add loading skeleton tests

* design style nits

* update tests

* bips_base

* fix regression
parent 8734ee59
...@@ -13,6 +13,13 @@ describe('PoolDetailsHeader', () => { ...@@ -13,6 +13,13 @@ describe('PoolDetailsHeader', () => {
toggleReversed: jest.fn(), toggleReversed: jest.fn(),
} }
it('loading skeleton is shown', () => {
const { asFragment } = render(<PoolDetailsHeader {...mockProps} loading={true} />)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByTestId('pdp-header-loading-skeleton')).toBeInTheDocument()
})
it('renders header text correctly', () => { it('renders header text correctly', () => {
const { asFragment } = render(<PoolDetailsHeader {...mockProps} />) const { asFragment } = render(<PoolDetailsHeader {...mockProps} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
......
...@@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg' ...@@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg'
import Column from 'components/Column' import Column from 'components/Column'
import { ChainLogo } from 'components/Logo/ChainLogo' import { ChainLogo } from 'components/Logo/ChainLogo'
import Row from 'components/Row' import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { BIPS_BASE } from 'constants/misc' import { BIPS_BASE } from 'constants/misc'
import { chainIdToBackendName } from 'graphql/data/util' import { chainIdToBackendName } from 'graphql/data/util'
import { useCurrency } from 'hooks/Tokens' import { useCurrency } from 'hooks/Tokens'
...@@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components' ...@@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components'
import { shortenAddress } from 'utils' import { shortenAddress } from 'utils'
import { ReversedArrowsIcon } from './icons' import { ReversedArrowsIcon } from './icons'
import { DetailBubble } from './shared'
const HeaderColumn = styled(Column)` const HeaderColumn = styled(Column)`
gap: 36px; gap: 36px;
...@@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)` ...@@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)`
${ClickableStyle} ${ClickableStyle}
` `
const IconBubble = styled(LoadingBubble)`
width: 32px;
height: 32px;
border-radius: 50%;
`
interface Token { interface Token {
id: string id: string
symbol: string symbol: string
...@@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps { ...@@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps {
token1?: Token token1?: Token
feeTier?: number feeTier?: number
toggleReversed: React.DispatchWithoutAction toggleReversed: React.DispatchWithoutAction
loading?: boolean
} }
export function PoolDetailsHeader({ export function PoolDetailsHeader({
...@@ -56,10 +65,25 @@ export function PoolDetailsHeader({ ...@@ -56,10 +65,25 @@ export function PoolDetailsHeader({
token1, token1,
feeTier, feeTier,
toggleReversed, toggleReversed,
loading,
}: PoolDetailsHeaderProps) { }: PoolDetailsHeaderProps) {
const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined] const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined]
const chainName = chainIdToBackendName(chainId) const chainName = chainIdToBackendName(chainId)
const origin = `/tokens/${chainName}` const origin = `/tokens/${chainName}`
if (loading)
return (
<HeaderColumn data-testid="pdp-header-loading-skeleton">
<DetailBubble $width={300} />
<Column gap="sm">
<Row gap="8px">
<IconBubble />
<DetailBubble $width={137} />
</Row>
</Column>
</HeaderColumn>
)
return ( return (
<HeaderColumn> <HeaderColumn>
<Row> <Row>
......
...@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' ...@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import Column from 'components/Column' import Column from 'components/Column'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row' import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
import { PoolData } from 'graphql/thegraph/PoolData' import { PoolData } from 'graphql/thegraph/PoolData'
import { useCurrency } from 'hooks/Tokens' import { useCurrency } from 'hooks/Tokens'
...@@ -15,6 +16,8 @@ import { colors } from 'theme/colors' ...@@ -15,6 +16,8 @@ import { colors } from 'theme/colors'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { DetailBubble } from './shared'
const HeaderText = styled(Text)` const HeaderText = styled(Text)`
font-weight: 485; font-weight: 485;
font-size: 24px; font-size: 24px;
...@@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b ...@@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b
${({ isLeft }) => (isLeft ? leftBarChartStyles : rightBarChartStyles)} ${({ isLeft }) => (isLeft ? leftBarChartStyles : rightBarChartStyles)}
` `
const StatSectionBubble = styled(LoadingBubble)`
width: 180px;
height: 40px;
`
const StatHeaderBubble = styled(LoadingBubble)`
width: 116px;
height: 24px;
border-radius: 8px;
`
interface PoolDetailsStatsProps { interface PoolDetailsStatsProps {
poolData: PoolData poolData?: PoolData
isReversed: boolean isReversed?: boolean
chainId?: number chainId?: number
loading?: boolean
} }
export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsStatsProps) { export function PoolDetailsStats({ poolData, isReversed, chainId, loading }: PoolDetailsStatsProps) {
const isScreenSize = useScreenSize() const isScreenSize = useScreenSize()
const screenIsNotLarge = isScreenSize['lg'] const screenIsNotLarge = isScreenSize['lg']
const { formatNumber } = useFormatter() const { formatNumber } = useFormatter()
...@@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS ...@@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS
} }
const [token0, token1] = useMemo(() => { const [token0, token1] = useMemo(() => {
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1 if (poolData) {
const token0FullData = { const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
...poolData?.token0, const token0FullData = {
price: poolData?.token0Price, ...poolData?.token0,
tvl: poolData?.tvlToken0, price: poolData?.token0Price,
color: color0, tvl: poolData?.tvlToken0,
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth, color: color0,
currency: currency0, percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
} currency: currency0,
const token1FullData = { }
...poolData?.token1, const token1FullData = {
price: poolData?.token1Price, ...poolData?.token1,
tvl: poolData?.tvlToken1, price: poolData?.token1Price,
color: color1, tvl: poolData?.tvlToken1,
percent: poolData?.tvlToken1 / fullWidth, color: color1,
currency: currency1, percent: poolData?.tvlToken1 / fullWidth,
currency: currency1,
}
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
} else {
return [undefined, undefined]
} }
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
}, [color0, color1, currency0, currency1, isReversed, poolData]) }, [color0, color1, currency0, currency1, isReversed, poolData])
if (loading || !token0 || !token1 || !poolData) {
return (
<StatsWrapper>
<HeaderText>
<StatHeaderBubble />
</HeaderText>
{Array.from({ length: 4 }).map((_, i) => (
<Column gap="md" key={`loading-info-row-${i}`}>
<DetailBubble />
<StatSectionBubble />
</Column>
))}
</StatsWrapper>
)
}
return ( return (
<StatsWrapper> <StatsWrapper>
<HeaderText> <HeaderText>
......
...@@ -26,6 +26,13 @@ describe('PoolDetailsStatsButton', () => { ...@@ -26,6 +26,13 @@ describe('PoolDetailsStatsButton', () => {
mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue) mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
}) })
it('loading skeleton shown correctly', () => {
const { asFragment } = render(<PoolDetailsStatsButtons {...mockProps} loading={true} />)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByTestId('pdp-buttons-loading-skeleton')).toBeVisible()
})
it('renders both buttons correctly', () => { it('renders both buttons correctly', () => {
const { asFragment } = render(<PoolDetailsStatsButtons {...mockProps} />) const { asFragment } = render(<PoolDetailsStatsButtons {...mockProps} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
......
...@@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache ...@@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions' import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button' import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import Row from 'components/Row' import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks' import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
import { useCurrency } from 'hooks/Tokens' import { useCurrency } from 'hooks/Tokens'
import { useSwitchChain } from 'hooks/useSwitchChain' import { useSwitchChain } from 'hooks/useSwitchChain'
...@@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)` ...@@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)`
width: 50%; width: 50%;
` `
const ButtonBubble = styled(LoadingBubble)`
height: 44px;
width: 175px;
border-radius: 900px;
`
interface PoolDetailsStatsButtonsProps { interface PoolDetailsStatsButtonsProps {
chainId?: number chainId?: number
token0?: Token token0?: Token
token1?: Token token1?: Token
feeTier?: number feeTier?: number
loading?: boolean
} }
function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) { function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) {
...@@ -45,7 +53,7 @@ function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1? ...@@ -45,7 +53,7 @@ function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?
) )
} }
export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: PoolDetailsStatsButtonsProps) { export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier, loading }: PoolDetailsStatsButtonsProps) {
const { chainId: walletChainId, connector, account } = useWeb3React() const { chainId: walletChainId, connector, account } = useWeb3React()
const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined) const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined)
const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier) const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier)
...@@ -64,7 +72,15 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po ...@@ -64,7 +72,15 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
) )
} }
} }
if (!currency0 || !currency1) return null
if (loading || !currency0 || !currency1)
return (
<PoolDetailsStatsButtonsRow data-testid="pdp-buttons-loading-skeleton">
<ButtonBubble />
<ButtonBubble />
</PoolDetailsStatsButtonsRow>
)
return ( return (
<PoolDetailsStatsButtonsRow> <PoolDetailsStatsButtonsRow>
<PoolButton <PoolButton
...@@ -75,7 +91,6 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po ...@@ -75,7 +91,6 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
> >
<Trans>Add liquidity</Trans> <Trans>Add liquidity</Trans>
</PoolButton> </PoolButton>
<PoolButton <PoolButton
size={ButtonSize.medium} size={ButtonSize.medium}
emphasis={ButtonEmphasis.highSoft} emphasis={ButtonEmphasis.highSoft}
......
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common'
import Row from 'components/Row'
import { ArrowDown } from 'react-feather'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { DetailBubble, SmallDetailBubble } from './shared'
const Table = styled(Column)`
gap: 24px;
border-radius: 20px;
border: 1px solid ${({ theme }) => theme.surface3};
padding-bottom: 12px;
overflow-y: hidden;
${ScrollBarStyles}
`
const TableRow = styled(Row)<{ $borderBottom?: boolean }>`
justify-content: space-between;
border-bottom: ${({ $borderBottom, theme }) => ($borderBottom ? `1px solid ${theme.surface3}` : 'none')}};
padding: 12px;
min-width: max-content;
`
const TableElement = styled(ThemedText.BodySecondary)<{
alignRight?: boolean
small?: boolean
large?: boolean
}>`
display: flex;
padding: 0px 8px;
flex: ${({ small }) => (small ? 'unset' : '1')};
width: ${({ small }) => (small ? '44px' : 'auto')};
min-width: ${({ large, small }) => (large ? '136px' : small ? 'unset' : '121px')} !important;
justify-content: ${({ alignRight }) => (alignRight ? 'flex-end' : 'flex-start')};
`
{
/* TODO(WEB-2735): When making real datatable, merge in this code and deprecate this skeleton file */
}
export function PoolDetailsTableSkeleton() {
return (
<Table $isHorizontalScroll>
<TableRow $borderBottom>
<TableElement large>
<Row>
<ArrowDown size={16} />
<Trans>Time</Trans>
</Row>
</TableElement>
<TableElement>
<Trans>Type</Trans>
</TableElement>
<TableElement alignRight>
<Trans>USD</Trans>
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<Trans>Maker</Trans>
</TableElement>
<TableElement alignRight small>
<Trans>Txn</Trans>
</TableElement>
</TableRow>
{Array.from({ length: 10 }).map((_, i) => (
<TableRow key={`loading-table-row-${i}`}>
<TableElement large>
<DetailBubble />
</TableElement>
<TableElement>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight>
<DetailBubble />
</TableElement>
<TableElement alignRight small>
<SmallDetailBubble />
</TableElement>
</TableRow>
))}
</Table>
)
}
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PoolDetailsHeader loading skeleton is shown 1`] = `
<DocumentFragment>
.c5 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c6 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 8px;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 8px;
}
.c2 {
border-radius: 12px;
border-radius: 12px;
height: 24px;
width: 50%;
width: 50%;
-webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% );
will-change: background-position;
background-size: 400%;
}
.c3 {
height: 16px;
width: 300px;
}
.c8 {
height: 16px;
width: 137px;
}
.c1 {
gap: 36px;
}
.c7 {
width: 32px;
height: 32px;
border-radius: 50%;
}
<div
class="c0 c1"
data-testid="pdp-header-loading-skeleton"
>
<div
class="c2 c3"
/>
<div
class="c4"
>
<div
class="c5 c6"
>
<div
class="c2 c7"
/>
<div
class="c2 c8"
/>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`PoolDetailsHeader renders header text correctly 1`] = ` exports[`PoolDetailsHeader renders header text correctly 1`] = `
<DocumentFragment> <DocumentFragment>
.c2 { .c2 {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PoolDetailsStatsButton loading skeleton shown correctly 1`] = `
<DocumentFragment>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c3 {
border-radius: 12px;
border-radius: 12px;
height: 24px;
width: 50%;
width: 50%;
-webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% );
will-change: background-position;
background-size: 400%;
}
.c2 {
gap: 12px;
}
.c4 {
height: 44px;
width: 175px;
border-radius: 900px;
}
@media (max-width:1023px) {
.c2 {
display: none;
}
}
<div
class="c0 c1 c2"
data-testid="pdp-buttons-loading-skeleton"
>
<div
class="c3 c4"
/>
<div
class="c3 c4"
/>
</div>
</DocumentFragment>
`;
exports[`PoolDetailsStatsButton renders both buttons correctly 1`] = ` exports[`PoolDetailsStatsButton renders both buttons correctly 1`] = `
<DocumentFragment> <DocumentFragment>
.c0 { .c0 {
......
...@@ -76,7 +76,6 @@ describe('PoolDetailsPage', () => { ...@@ -76,7 +76,6 @@ describe('PoolDetailsPage', () => {
}) })
}) })
// TODO replace with loading skeleton when designed
it('nothing displayed while data is loading', () => { it('nothing displayed while data is loading', () => {
mocked(usePoolData).mockReturnValue({ mocked(usePoolData).mockReturnValue({
data: undefined, data: undefined,
...@@ -86,7 +85,7 @@ describe('PoolDetailsPage', () => { ...@@ -86,7 +85,7 @@ describe('PoolDetailsPage', () => {
render(<PoolDetails />) render(<PoolDetails />)
waitFor(() => { waitFor(() => {
expect(screen.getByText(/not found/i)).not.toBeInTheDocument() expect(screen.getByTestId('pdp-links-loading-skeleton')).toBeInTheDocument()
}) })
}) })
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import { LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
import { TokenDescription } from 'components/Tokens/TokenDetails/TokenDescription' import { TokenDescription } from 'components/Tokens/TokenDetails/TokenDescription'
import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util' import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { usePoolData } from 'graphql/thegraph/PoolData' import { usePoolData } from 'graphql/thegraph/PoolData'
...@@ -15,14 +17,18 @@ import { isAddress } from 'utils' ...@@ -15,14 +17,18 @@ import { isAddress } from 'utils'
import { PoolDetailsHeader } from './PoolDetailsHeader' import { PoolDetailsHeader } from './PoolDetailsHeader'
import { PoolDetailsStats } from './PoolDetailsStats' import { PoolDetailsStats } from './PoolDetailsStats'
import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons' import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons'
import { PoolDetailsTableSkeleton } from './PoolDetailsTableSkeleton'
import { DetailBubble, SmallDetailBubble } from './shared'
const PageWrapper = styled(Row)` const PageWrapper = styled(Row)`
padding: 48px; padding: 48px;
width: 100%; width: 100%;
align-items: flex-start; align-items: flex-start;
gap: 60px;
@media (max-width: ${BREAKPOINTS.lg - 1}px) { @media (max-width: ${BREAKPOINTS.lg - 1}px) {
flex-direction: column; flex-direction: column;
gap: unset;
} }
@media (max-width: ${BREAKPOINTS.sm - 1}px) { @media (max-width: ${BREAKPOINTS.sm - 1}px) {
...@@ -30,6 +36,33 @@ const PageWrapper = styled(Row)` ...@@ -30,6 +36,33 @@ const PageWrapper = styled(Row)`
} }
` `
const LeftColumn = styled(Column)`
gap: 24px;
width: 65vw;
overflow: hidden;
justify-content: flex-start;
@media (max-width: ${BREAKPOINTS.lg - 1}px) {
width: 100%;
}
`
const HR = styled.hr`
border: 0.5px solid ${({ theme }) => theme.surface3};
margin: 16px 0px;
width: 100%;
`
const ChartHeaderBubble = styled(LoadingBubble)`
width: 180px;
height: 32px;
`
const LinkColumn = styled(Column)`
gap: 16px;
padding: 20px;
`
const RightColumn = styled(Column)` const RightColumn = styled(Column)`
gap: 24px; gap: 24px;
margin: 0 48px 0 auto; margin: 0 48px 0 auto;
...@@ -79,31 +112,55 @@ export default function PoolDetailsPage() { ...@@ -79,31 +112,55 @@ export default function PoolDetailsPage() {
const isInvalidPool = !chainName || !poolAddress || !getValidUrlChainName(chainName) || !isAddress(poolAddress) const isInvalidPool = !chainName || !poolAddress || !getValidUrlChainName(chainName) || !isAddress(poolAddress)
const poolNotFound = (!loading && !poolData) || isInvalidPool const poolNotFound = (!loading && !poolData) || isInvalidPool
// TODO(WEB-2814): Add skeleton once designed
if (loading) return null
if (poolNotFound) return <NotFound /> if (poolNotFound) return <NotFound />
return ( return (
<PageWrapper> <PageWrapper>
<PoolDetailsHeader <LeftColumn>
chainId={chainId} <Column gap="sm">
poolAddress={poolAddress} <PoolDetailsHeader
token0={token0} chainId={chainId}
token1={token1} poolAddress={poolAddress}
feeTier={poolData?.feeTier} token0={token0}
toggleReversed={toggleReversed} token1={token1}
/> feeTier={poolData?.feeTier}
toggleReversed={toggleReversed}
loading={loading}
/>
<LoadingChart />
</Column>
<HR />
<ChartHeaderBubble />
<PoolDetailsTableSkeleton />
</LeftColumn>
<RightColumn> <RightColumn>
<PoolDetailsStatsButtons chainId={chainId} token0={token0} token1={token1} feeTier={poolData?.feeTier} /> <PoolDetailsStatsButtons
{poolData && <PoolDetailsStats poolData={poolData} isReversed={isReversed} chainId={chainId} />} chainId={chainId}
{(token0 || token1) && ( token0={token0}
<TokenDetailsWrapper> token1={token1}
<TokenDetailsHeader> feeTier={poolData?.feeTier}
<Trans>Info</Trans> loading={loading}
</TokenDetailsHeader> />
{token0 && <TokenDescription tokenAddress={token0.id} chainId={chainId} />} <PoolDetailsStats poolData={poolData} isReversed={isReversed} chainId={chainId} loading={loading} />
{token1 && <TokenDescription tokenAddress={token1.id} chainId={chainId} />} {(token0 || token1 || loading) &&
</TokenDetailsWrapper> (loading ? (
)} <LinkColumn data-testid="pdp-links-loading-skeleton">
<DetailBubble $height={24} $width={116} />
{Array.from({ length: 3 }).map((_, i) => (
<Row gap="8px" key={`loading-link-row-${i}`}>
<SmallDetailBubble />
<DetailBubble $width={117} />
</Row>
))}
</LinkColumn>
) : (
<TokenDetailsWrapper>
<TokenDetailsHeader>
<Trans>Info</Trans>
</TokenDetailsHeader>
{token0 && <TokenDescription tokenAddress={token0.id} chainId={chainId} />}
{token1 && <TokenDescription tokenAddress={token1.id} chainId={chainId} />}
</TokenDetailsWrapper>
))}
</RightColumn> </RightColumn>
</PageWrapper> </PageWrapper>
) )
......
import { LoadingBubble } from 'components/Tokens/loading'
import styled from 'styled-components'
export const DetailBubble = styled(LoadingBubble)<{ $height?: number; $width?: number }>`
height: ${({ $height }) => ($height ? `${$height}px` : '16px')};
width: ${({ $width }) => ($width ? `${$width}px` : '80px')};
`
export const SmallDetailBubble = styled(LoadingBubble)`
height: 20px;
width: 20px;
border-radius: 100px;
`
...@@ -139,7 +139,11 @@ export const routes: RouteDefinition[] = [ ...@@ -139,7 +139,11 @@ export const routes: RouteDefinition[] = [
}), }),
createRouteDefinition({ createRouteDefinition({
path: 'explore/pools/:chainName/:poolAddress', path: 'explore/pools/:chainName/:poolAddress',
getElement: () => <PoolDetails />, getElement: () => (
<Suspense fallback={null}>
<PoolDetails />
</Suspense>
),
enabled: (args) => Boolean(args.infoExplorePageEnabled && args.infoPoolPageEnabled), enabled: (args) => Boolean(args.infoExplorePageEnabled && args.infoPoolPageEnabled),
}), }),
createRouteDefinition({ createRouteDefinition({
......
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