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
This commit is contained in:
parent
8734ee5986
commit
6798bf3cf1
@ -13,6 +13,13 @@ describe('PoolDetailsHeader', () => {
|
||||
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', () => {
|
||||
const { asFragment } = render(<PoolDetailsHeader {...mockProps} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg'
|
||||
import Column from 'components/Column'
|
||||
import { ChainLogo } from 'components/Logo/ChainLogo'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { BIPS_BASE } from 'constants/misc'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
import { ReversedArrowsIcon } from './icons'
|
||||
import { DetailBubble } from './shared'
|
||||
|
||||
const HeaderColumn = styled(Column)`
|
||||
gap: 36px;
|
||||
@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)`
|
||||
${ClickableStyle}
|
||||
`
|
||||
|
||||
const IconBubble = styled(LoadingBubble)`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
interface Token {
|
||||
id: string
|
||||
symbol: string
|
||||
@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps {
|
||||
token1?: Token
|
||||
feeTier?: number
|
||||
toggleReversed: React.DispatchWithoutAction
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function PoolDetailsHeader({
|
||||
@ -56,10 +65,25 @@ export function PoolDetailsHeader({
|
||||
token1,
|
||||
feeTier,
|
||||
toggleReversed,
|
||||
loading,
|
||||
}: PoolDetailsHeaderProps) {
|
||||
const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined]
|
||||
const chainName = chainIdToBackendName(chainId)
|
||||
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 (
|
||||
<HeaderColumn>
|
||||
<Row>
|
||||
|
@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
|
||||
import { PoolData } from 'graphql/thegraph/PoolData'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
@ -15,6 +16,8 @@ import { colors } from 'theme/colors'
|
||||
import { ThemedText } from 'theme/components'
|
||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||
|
||||
import { DetailBubble } from './shared'
|
||||
|
||||
const HeaderText = styled(Text)`
|
||||
font-weight: 485;
|
||||
font-size: 24px;
|
||||
@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b
|
||||
${({ 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 {
|
||||
poolData: PoolData
|
||||
isReversed: boolean
|
||||
poolData?: PoolData
|
||||
isReversed?: boolean
|
||||
chainId?: number
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsStatsProps) {
|
||||
export function PoolDetailsStats({ poolData, isReversed, chainId, loading }: PoolDetailsStatsProps) {
|
||||
const isScreenSize = useScreenSize()
|
||||
const screenIsNotLarge = isScreenSize['lg']
|
||||
const { formatNumber } = useFormatter()
|
||||
@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS
|
||||
}
|
||||
|
||||
const [token0, token1] = useMemo(() => {
|
||||
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
|
||||
const token0FullData = {
|
||||
...poolData?.token0,
|
||||
price: poolData?.token0Price,
|
||||
tvl: poolData?.tvlToken0,
|
||||
color: color0,
|
||||
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
|
||||
currency: currency0,
|
||||
if (poolData) {
|
||||
const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1
|
||||
const token0FullData = {
|
||||
...poolData?.token0,
|
||||
price: poolData?.token0Price,
|
||||
tvl: poolData?.tvlToken0,
|
||||
color: color0,
|
||||
percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth,
|
||||
currency: currency0,
|
||||
}
|
||||
const token1FullData = {
|
||||
...poolData?.token1,
|
||||
price: poolData?.token1Price,
|
||||
tvl: poolData?.tvlToken1,
|
||||
color: color1,
|
||||
percent: poolData?.tvlToken1 / fullWidth,
|
||||
currency: currency1,
|
||||
}
|
||||
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
|
||||
} else {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
const token1FullData = {
|
||||
...poolData?.token1,
|
||||
price: poolData?.token1Price,
|
||||
tvl: poolData?.tvlToken1,
|
||||
color: color1,
|
||||
percent: poolData?.tvlToken1 / fullWidth,
|
||||
currency: currency1,
|
||||
}
|
||||
return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData]
|
||||
}, [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 (
|
||||
<StatsWrapper>
|
||||
<HeaderText>
|
||||
|
@ -26,6 +26,13 @@ describe('PoolDetailsStatsButton', () => {
|
||||
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', () => {
|
||||
const { asFragment } = render(<PoolDetailsStatsButtons {...mockProps} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache
|
||||
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import { useSwitchChain } from 'hooks/useSwitchChain'
|
||||
@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)`
|
||||
width: 50%;
|
||||
`
|
||||
|
||||
const ButtonBubble = styled(LoadingBubble)`
|
||||
height: 44px;
|
||||
width: 175px;
|
||||
border-radius: 900px;
|
||||
`
|
||||
|
||||
interface PoolDetailsStatsButtonsProps {
|
||||
chainId?: number
|
||||
token0?: Token
|
||||
token1?: Token
|
||||
feeTier?: number
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) {
|
||||
@ -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 { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined)
|
||||
const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier)
|
||||
@ -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 (
|
||||
<PoolDetailsStatsButtonsRow>
|
||||
<PoolButton
|
||||
@ -75,7 +91,6 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po
|
||||
>
|
||||
<Trans>Add liquidity</Trans>
|
||||
</PoolButton>
|
||||
|
||||
<PoolButton
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.highSoft}
|
||||
|
98
src/pages/PoolDetails/PoolDetailsTableSkeleton.tsx
Normal file
98
src/pages/PoolDetails/PoolDetailsTableSkeleton.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -1,5 +1,120 @@
|
||||
// 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`] = `
|
||||
<DocumentFragment>
|
||||
.c2 {
|
||||
|
@ -1,5 +1,75 @@
|
||||
// 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`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -76,7 +76,6 @@ describe('PoolDetailsPage', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// TODO replace with loading skeleton when designed
|
||||
it('nothing displayed while data is loading', () => {
|
||||
mocked(usePoolData).mockReturnValue({
|
||||
data: undefined,
|
||||
@ -86,7 +85,7 @@ describe('PoolDetailsPage', () => {
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('pdp-links-loading-skeleton')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
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 { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||
import { usePoolData } from 'graphql/thegraph/PoolData'
|
||||
@ -15,14 +17,18 @@ import { isAddress } from 'utils'
|
||||
import { PoolDetailsHeader } from './PoolDetailsHeader'
|
||||
import { PoolDetailsStats } from './PoolDetailsStats'
|
||||
import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons'
|
||||
import { PoolDetailsTableSkeleton } from './PoolDetailsTableSkeleton'
|
||||
import { DetailBubble, SmallDetailBubble } from './shared'
|
||||
|
||||
const PageWrapper = styled(Row)`
|
||||
padding: 48px;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
gap: 60px;
|
||||
|
||||
@media (max-width: ${BREAKPOINTS.lg - 1}px) {
|
||||
flex-direction: column;
|
||||
gap: unset;
|
||||
}
|
||||
|
||||
@media (max-width: ${BREAKPOINTS.sm - 1}px) {
|
||||
@ -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)`
|
||||
gap: 24px;
|
||||
margin: 0 48px 0 auto;
|
||||
@ -79,31 +112,55 @@ export default function PoolDetailsPage() {
|
||||
const isInvalidPool = !chainName || !poolAddress || !getValidUrlChainName(chainName) || !isAddress(poolAddress)
|
||||
const poolNotFound = (!loading && !poolData) || isInvalidPool
|
||||
|
||||
// TODO(WEB-2814): Add skeleton once designed
|
||||
if (loading) return null
|
||||
if (poolNotFound) return <NotFound />
|
||||
return (
|
||||
<PageWrapper>
|
||||
<PoolDetailsHeader
|
||||
chainId={chainId}
|
||||
poolAddress={poolAddress}
|
||||
token0={token0}
|
||||
token1={token1}
|
||||
feeTier={poolData?.feeTier}
|
||||
toggleReversed={toggleReversed}
|
||||
/>
|
||||
<LeftColumn>
|
||||
<Column gap="sm">
|
||||
<PoolDetailsHeader
|
||||
chainId={chainId}
|
||||
poolAddress={poolAddress}
|
||||
token0={token0}
|
||||
token1={token1}
|
||||
feeTier={poolData?.feeTier}
|
||||
toggleReversed={toggleReversed}
|
||||
loading={loading}
|
||||
/>
|
||||
<LoadingChart />
|
||||
</Column>
|
||||
<HR />
|
||||
<ChartHeaderBubble />
|
||||
<PoolDetailsTableSkeleton />
|
||||
</LeftColumn>
|
||||
<RightColumn>
|
||||
<PoolDetailsStatsButtons chainId={chainId} token0={token0} token1={token1} feeTier={poolData?.feeTier} />
|
||||
{poolData && <PoolDetailsStats poolData={poolData} isReversed={isReversed} chainId={chainId} />}
|
||||
{(token0 || token1) && (
|
||||
<TokenDetailsWrapper>
|
||||
<TokenDetailsHeader>
|
||||
<Trans>Info</Trans>
|
||||
</TokenDetailsHeader>
|
||||
{token0 && <TokenDescription tokenAddress={token0.id} chainId={chainId} />}
|
||||
{token1 && <TokenDescription tokenAddress={token1.id} chainId={chainId} />}
|
||||
</TokenDetailsWrapper>
|
||||
)}
|
||||
<PoolDetailsStatsButtons
|
||||
chainId={chainId}
|
||||
token0={token0}
|
||||
token1={token1}
|
||||
feeTier={poolData?.feeTier}
|
||||
loading={loading}
|
||||
/>
|
||||
<PoolDetailsStats poolData={poolData} isReversed={isReversed} chainId={chainId} loading={loading} />
|
||||
{(token0 || token1 || loading) &&
|
||||
(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>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
13
src/pages/PoolDetails/shared.ts
Normal file
13
src/pages/PoolDetails/shared.ts
Normal file
@ -0,0 +1,13 @@
|
||||
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[] = [
|
||||
}),
|
||||
createRouteDefinition({
|
||||
path: 'explore/pools/:chainName/:poolAddress',
|
||||
getElement: () => <PoolDetails />,
|
||||
getElement: () => (
|
||||
<Suspense fallback={null}>
|
||||
<PoolDetails />
|
||||
</Suspense>
|
||||
),
|
||||
enabled: (args) => Boolean(args.infoExplorePageEnabled && args.infoPoolPageEnabled),
|
||||
}),
|
||||
createRouteDefinition({
|
||||
|
Loading…
Reference in New Issue
Block a user