feat: [info] Initial Pool Details Page (#7250)
* feat: setup initial pool details page and route * add pool data query and call on enw page * make query dynamic to url chainId * Get and display Header info * add token symbols * split header into its own file * add helper function to not default to eth chain * add helper function tests * add header component tests * add mocked test for PDP * use valid values * allow unsupported BE chains supported by thegraph * typecheck * remove useless row * no longer needed child * use first and last child * move mock consts to their own file * skele linear task * return null * descriptiive pool not found bool * modify correct logo container * update snapshots * instantiate all chain apollo clients * added snapshot test * merge main and update snapshots * Update src/pages/PoolDetails/PoolDetailsHeader.tsx Co-authored-by: Nate Wienert <natewienert@gmail.com> * type feeTier --------- Co-authored-by: Nate Wienert <natewienert@gmail.com>
This commit is contained in:
parent
184d515e16
commit
dcf7d29357
@ -3,3 +3,7 @@ import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
export function useInfoPoolPageFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.infoPoolPage)
|
||||
}
|
||||
|
||||
export function useInfoPoolPageEnabled(): boolean {
|
||||
return useInfoPoolPageFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { Chain } from './__generated__/types-and-hooks'
|
||||
import { isSupportedGQLChain, supportedChainIdFromGQLChain } from './util'
|
||||
import { getValidUrlChainName, isSupportedGQLChain, supportedChainIdFromGQLChain } from './util'
|
||||
|
||||
describe('fromGraphQLChain', () => {
|
||||
it('should return the corresponding chain ID for supported chains', () => {
|
||||
@ -34,3 +34,25 @@ describe('fromGraphQLChain', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('isValidUrlChainParam', () => {
|
||||
it('should return true for valid chain name', () => {
|
||||
const validChainName = 'ethereum'
|
||||
expect(getValidUrlChainName(validChainName)).toBe(Chain.Ethereum)
|
||||
})
|
||||
|
||||
it('should return false for undefined chain name', () => {
|
||||
const undefinedChainName = undefined
|
||||
expect(getValidUrlChainName(undefinedChainName)).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should return false for invalid chain name', () => {
|
||||
const invalidChainName = 'invalidchain'
|
||||
expect(getValidUrlChainName(invalidChainName)).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should return false for a misconfigured chain name', () => {
|
||||
const invalidChainName = 'eThErEuM'
|
||||
expect(getValidUrlChainName(invalidChainName)).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
@ -132,6 +132,15 @@ const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: InterfaceGqlChain } = {
|
||||
base: Chain.Base,
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chainName parsed in chain name from url query parameter
|
||||
* @returns if chainName is a valid chain name, returns the backend chain name, otherwise returns undefined
|
||||
*/
|
||||
export function getValidUrlChainName(chainName: string | undefined): Chain | undefined {
|
||||
const validChainName = chainName && URL_CHAIN_PARAM_TO_BACKEND[chainName]
|
||||
return validChainName ? validChainName : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chainName parsed in chain name from url query parameter
|
||||
* @returns if chainName is a valid chain name supported by the backend, returns the backend chain name, otherwise returns Chain.Ethereum
|
||||
|
56
src/graphql/thegraph/PoolData.ts
Normal file
56
src/graphql/thegraph/PoolData.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import gql from 'graphql-tag'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { usePoolDataQuery } from './__generated__/types-and-hooks'
|
||||
import { chainToApolloClient } from './apollo'
|
||||
|
||||
gql`
|
||||
query PoolData($poolId: [ID!]) {
|
||||
data: pools(where: { id_in: $poolId }, orderBy: totalValueLockedUSD, orderDirection: desc, subgraphError: allow) {
|
||||
id
|
||||
feeTier
|
||||
liquidity
|
||||
sqrtPrice
|
||||
tick
|
||||
token0 {
|
||||
id
|
||||
symbol
|
||||
name
|
||||
decimals
|
||||
derivedETH
|
||||
}
|
||||
token1 {
|
||||
id
|
||||
symbol
|
||||
name
|
||||
decimals
|
||||
derivedETH
|
||||
}
|
||||
token0Price
|
||||
token1Price
|
||||
volumeUSD
|
||||
volumeToken0
|
||||
volumeToken1
|
||||
txCount
|
||||
totalValueLockedToken0
|
||||
totalValueLockedToken1
|
||||
totalValueLockedUSD
|
||||
}
|
||||
bundles(where: { id: "1" }) {
|
||||
ethPriceUSD
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function usePoolData(poolAddress: string, chainId?: ChainId) {
|
||||
const poolId = [poolAddress]
|
||||
const apolloClient = chainToApolloClient[chainId || ChainId.MAINNET]
|
||||
const { data, loading } = usePoolDataQuery({ variables: { poolId }, client: apolloClient })
|
||||
return useMemo(() => {
|
||||
return {
|
||||
data: data?.data[0],
|
||||
loading,
|
||||
}
|
||||
}, [data, loading])
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache } from '@apollo/client'
|
||||
import { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache, NormalizedCacheObject } from '@apollo/client'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import store from '../../state/index'
|
||||
@ -32,3 +32,34 @@ export const apolloClient = new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
link: concat(authMiddleware, httpLink),
|
||||
})
|
||||
|
||||
export const chainToApolloClient: Record<number, ApolloClient<NormalizedCacheObject>> = {
|
||||
[ChainId.MAINNET]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.MAINNET],
|
||||
}),
|
||||
[ChainId.ARBITRUM_ONE]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.ARBITRUM_ONE],
|
||||
}),
|
||||
[ChainId.OPTIMISM]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.OPTIMISM],
|
||||
}),
|
||||
[ChainId.POLYGON]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.POLYGON],
|
||||
}),
|
||||
[ChainId.CELO]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.CELO],
|
||||
}),
|
||||
[ChainId.BNB]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.BNB],
|
||||
}),
|
||||
[ChainId.AVALANCHE]: new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
uri: CHAIN_SUBGRAPH_URL[ChainId.AVALANCHE],
|
||||
}),
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { getDeviceId, sendAnalyticsEvent, Trace, user } from 'analytics'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import TopLevelModals from 'components/TopLevelModals'
|
||||
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||
import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useBag } from 'nft/hooks/useBag'
|
||||
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
||||
@ -46,6 +47,7 @@ import { RedirectPathToSwapOnly } from './Swap/redirects'
|
||||
import Tokens from './Tokens'
|
||||
|
||||
const TokenDetails = lazy(() => import('./TokenDetails'))
|
||||
const PoolDetails = lazy(() => import('./PoolDetails'))
|
||||
const Vote = lazy(() => import('./Vote'))
|
||||
const NftExplore = lazy(() => import('nft/pages/explore'))
|
||||
const Collection = lazy(() => import('nft/pages/collection'))
|
||||
@ -118,6 +120,7 @@ export default function App() {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const [routerPreference] = useRouterPreference()
|
||||
const [scrolledState, setScrolledState] = useState(false)
|
||||
const infoPoolPageEnabled = useInfoPoolPageEnabled()
|
||||
|
||||
useAnalyticsReporter()
|
||||
|
||||
@ -236,6 +239,7 @@ export default function App() {
|
||||
<Route path=":chainName" />
|
||||
</Route>
|
||||
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
|
||||
{infoPoolPageEnabled && <Route path="pools/:chainName/:poolAddress" element={<PoolDetails />} />}
|
||||
<Route
|
||||
path="vote/*"
|
||||
element={
|
||||
|
33
src/pages/PoolDetails/PoolDetailsHeader.test.tsx
Normal file
33
src/pages/PoolDetails/PoolDetailsHeader.test.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { act, render, screen } from 'test-utils/render'
|
||||
|
||||
import { PoolDetailsHeader } from './PoolDetailsHeader'
|
||||
|
||||
describe('PoolDetailsHeader', () => {
|
||||
const mockProps = {
|
||||
chainId: 1,
|
||||
poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
|
||||
token0: { id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', symbol: 'USDC' },
|
||||
token1: { id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', symbol: 'WETH' },
|
||||
feeTier: 500,
|
||||
toggleReversed: jest.fn(),
|
||||
}
|
||||
|
||||
it('renders header text correctly', () => {
|
||||
const { asFragment } = render(<PoolDetailsHeader {...mockProps} />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
expect(screen.getByText(/Explore/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Pool/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/USDC \/ WETH \(0x88e6...5640\)/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('0.05%')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls toggleReversed when arrows are clicked', async () => {
|
||||
render(<PoolDetailsHeader {...mockProps} />)
|
||||
|
||||
await act(() => userEvent.click(screen.getByTestId('toggle-tokens-reverse-arrows')))
|
||||
|
||||
expect(mockProps.toggleReversed).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
208
src/pages/PoolDetails/PoolDetailsHeader.tsx
Normal file
208
src/pages/PoolDetails/PoolDetailsHeader.tsx
Normal file
@ -0,0 +1,208 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId, Currency } from '@uniswap/sdk-core'
|
||||
import blankTokenUrl from 'assets/svg/blank_token.svg'
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import { ClickableStyle, ThemedText } from 'theme'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
import { ReversedArrowsIcon } from './icons'
|
||||
|
||||
const HeaderColumn = styled(Column)`
|
||||
gap: 36px;
|
||||
`
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
${ClickableStyle}
|
||||
`
|
||||
|
||||
const FeeTier = styled(ThemedText.LabelMicro)`
|
||||
background: ${({ theme }) => theme.surface2};
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
`
|
||||
|
||||
const ToggleReverseArrows = styled(ReversedArrowsIcon)`
|
||||
${ClickableStyle}
|
||||
`
|
||||
|
||||
interface Token {
|
||||
id: string
|
||||
symbol: string
|
||||
}
|
||||
|
||||
interface PoolDetailsHeaderProps {
|
||||
chainId?: number
|
||||
poolAddress?: string
|
||||
token0?: Token
|
||||
token1?: Token
|
||||
feeTier?: number
|
||||
toggleReversed: React.DispatchWithoutAction
|
||||
}
|
||||
|
||||
export function PoolDetailsHeader({
|
||||
chainId,
|
||||
poolAddress,
|
||||
token0,
|
||||
token1,
|
||||
feeTier,
|
||||
toggleReversed,
|
||||
}: PoolDetailsHeaderProps) {
|
||||
const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined]
|
||||
const chainName = chainIdToBackendName(chainId)
|
||||
const origin = `/tokens/${chainName}`
|
||||
return (
|
||||
<HeaderColumn>
|
||||
<Row>
|
||||
<StyledLink to={origin}>
|
||||
<ThemedText.BodySecondary>
|
||||
<Trans>Explore</Trans>
|
||||
</ThemedText.BodySecondary>
|
||||
</StyledLink>
|
||||
<ThemedText.BodySecondary> {'>'} </ThemedText.BodySecondary>
|
||||
{/* TODO: When Explore Pool table is added, link directly back to it */}
|
||||
<StyledLink to={origin}>
|
||||
<ThemedText.BodySecondary>
|
||||
<Trans>Pool</Trans>
|
||||
</ThemedText.BodySecondary>
|
||||
</StyledLink>
|
||||
<ThemedText.BodySecondary> {'>'} </ThemedText.BodySecondary>
|
||||
<ThemedText.BodyPrimary>
|
||||
{token0?.symbol} / {token1?.symbol} ({shortenAddress(poolAddress)})
|
||||
</ThemedText.BodyPrimary>
|
||||
</Row>
|
||||
<Row gap="18px">
|
||||
<Row gap="8px" width="max-content">
|
||||
{chainId && (
|
||||
<DoubleCurrencyAndChainLogo data-testid="double-token-logo" chainId={chainId} currencies={currencies} />
|
||||
)}
|
||||
<ThemedText.HeadlineSmall>
|
||||
{token0?.symbol} / {token1?.symbol}
|
||||
</ThemedText.HeadlineSmall>
|
||||
</Row>
|
||||
{!!feeTier && <FeeTier>{feeTier / 10000}%</FeeTier>}
|
||||
<ToggleReverseArrows data-testid="toggle-tokens-reverse-arrows" onClick={toggleReversed} />
|
||||
</Row>
|
||||
</HeaderColumn>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledLogoParentContainer = styled.div`
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
`
|
||||
|
||||
function DoubleCurrencyAndChainLogo({
|
||||
chainId,
|
||||
currencies,
|
||||
}: {
|
||||
chainId: number
|
||||
currencies: Array<Currency | undefined>
|
||||
}) {
|
||||
return (
|
||||
<StyledLogoParentContainer>
|
||||
<DoubleCurrencyLogo chainId={chainId} currencies={currencies} />
|
||||
<SquareL2Logo chainId={chainId} />
|
||||
</StyledLogoParentContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const L2LogoContainer = styled.div<{ hasSquareLogo?: boolean }>`
|
||||
background-color: ${({ theme, hasSquareLogo }) => (hasSquareLogo ? theme.surface2 : theme.neutral1)};
|
||||
border-radius: 2px;
|
||||
height: 12px;
|
||||
left: 60%;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
outline: 2px solid ${({ theme }) => theme.surface1};
|
||||
width: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const StyledChainLogo = styled.img`
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
`
|
||||
|
||||
const SquareChainLogo = styled.img`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
function SquareL2Logo({ chainId }: { chainId: ChainId }) {
|
||||
if (chainId === ChainId.MAINNET) return null
|
||||
const { squareLogoUrl, logoUrl } = getChainInfo(chainId)
|
||||
|
||||
const chainLogo = squareLogoUrl ?? logoUrl
|
||||
|
||||
return (
|
||||
<L2LogoContainer hasSquareLogo={!!squareLogoUrl}>
|
||||
{squareLogoUrl ? (
|
||||
<SquareChainLogo src={chainLogo} alt="chainLogo" />
|
||||
) : (
|
||||
<StyledChainLogo src={chainLogo} alt="chainLogo" />
|
||||
)}
|
||||
</L2LogoContainer>
|
||||
)
|
||||
}
|
||||
|
||||
function DoubleCurrencyLogo({ chainId, currencies }: { chainId: number; currencies: Array<Currency | undefined> }) {
|
||||
const [src, nextSrc] = useTokenLogoSource(currencies?.[0]?.wrapped.address, chainId, currencies?.[0]?.isNative)
|
||||
const [src2, nextSrc2] = useTokenLogoSource(currencies?.[1]?.wrapped.address, chainId, currencies?.[1]?.isNative)
|
||||
|
||||
return <DoubleLogo logo1={src} onError1={nextSrc} logo2={src2} onError2={nextSrc2} />
|
||||
}
|
||||
|
||||
const DoubleLogoContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
img {
|
||||
width: 16px;
|
||||
height: 32px;
|
||||
object-fit: cover;
|
||||
}
|
||||
img:first-child {
|
||||
border-radius: 16px 0 0 16px;
|
||||
object-position: 0 0;
|
||||
}
|
||||
img:last-child {
|
||||
border-radius: 0 16px 16px 0;
|
||||
object-position: 100% 0;
|
||||
}
|
||||
`
|
||||
|
||||
const CircleLogoImage = styled.img`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
interface DoubleLogoProps {
|
||||
logo1?: string
|
||||
logo2?: string
|
||||
onError1?: () => void
|
||||
onError2?: () => void
|
||||
}
|
||||
|
||||
function DoubleLogo({ logo1, onError1, logo2, onError2 }: DoubleLogoProps) {
|
||||
return (
|
||||
<DoubleLogoContainer>
|
||||
<CircleLogoImage src={logo1 ?? blankTokenUrl} onError={onError1} />
|
||||
<CircleLogoImage src={logo2 ?? blankTokenUrl} onError={onError2} />
|
||||
</DoubleLogoContainer>
|
||||
)
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PoolDetailsHeader renders header text correctly 1`] = `
|
||||
<DocumentFragment>
|
||||
.c2 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
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;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
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: 18px;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
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;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
color: #7D7D7D;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
gap: 36px;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-webkit-transition-duration: 125ms;
|
||||
transition-duration: 125ms;
|
||||
}
|
||||
|
||||
.c4:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.c4:active {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
background: #F9F9F9;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-webkit-transition-duration: 125ms;
|
||||
transition-duration: 125ms;
|
||||
}
|
||||
|
||||
.c14:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.c14:active {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c11 img {
|
||||
width: 16px;
|
||||
height: 32px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.c11 img:first-child {
|
||||
border-radius: 16px 0 0 16px;
|
||||
object-position: 0 0;
|
||||
}
|
||||
|
||||
.c11 img:last-child {
|
||||
border-radius: 0 16px 16px 0;
|
||||
object-position: 100% 0;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c2 c3"
|
||||
>
|
||||
<a
|
||||
class="c4"
|
||||
href="/tokens/ETHEREUM"
|
||||
>
|
||||
<div
|
||||
class="c5 css-1urox24"
|
||||
>
|
||||
Explore
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
class="c5 css-1urox24"
|
||||
>
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
class="c4"
|
||||
href="/tokens/ETHEREUM"
|
||||
>
|
||||
<div
|
||||
class="c5 css-1urox24"
|
||||
>
|
||||
Pool
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
class="c5 css-1urox24"
|
||||
>
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="c6 css-1urox24"
|
||||
>
|
||||
USDC / WETH (0x88e6...5640)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c7"
|
||||
>
|
||||
<div
|
||||
class="c8 c9"
|
||||
width="max-content"
|
||||
>
|
||||
<div
|
||||
class="c10"
|
||||
>
|
||||
<div
|
||||
class="c11"
|
||||
>
|
||||
<img
|
||||
class="c12"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
||||
/>
|
||||
<img
|
||||
class="c12"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c6 css-1jyz67g"
|
||||
>
|
||||
USDC / WETH
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c13 css-1m65e73"
|
||||
>
|
||||
0.05%
|
||||
</div>
|
||||
<svg
|
||||
class="c14"
|
||||
data-testid="toggle-tokens-reverse-arrows"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M18.125 10V12.5C18.125 14.2233 16.7225 15.625 15 15.625H4.0092L5.4425 17.0583C5.68667 17.3025 5.68667 17.6983 5.4425 17.9425C5.32084 18.0642 5.16081 18.1258 5.00081 18.1258C4.84081 18.1258 4.68079 18.065 4.55912 17.9425L2.05912 15.4425C2.00162 15.385 1.9559 15.3159 1.92424 15.2393C1.8609 15.0868 1.8609 14.9143 1.92424 14.7618C1.9559 14.6851 2.00162 14.6158 2.05912 14.5583L4.55912 12.0583C4.80329 11.8141 5.19915 11.8141 5.44332 12.0583C5.68749 12.3025 5.68749 12.6983 5.44332 12.9425L4.01001 14.3758H15C16.0333 14.3758 16.875 13.535 16.875 12.5008V10.0008C16.875 9.65581 17.155 9.37581 17.5 9.37581C17.845 9.37581 18.125 9.655 18.125 10ZM2.5 10.625C2.845 10.625 3.125 10.345 3.125 10V7.5C3.125 6.46583 3.96667 5.625 5 5.625H15.9908L14.5575 7.05831C14.3133 7.30247 14.3133 7.69834 14.5575 7.9425C14.6792 8.06417 14.8392 8.12581 14.9992 8.12581C15.1592 8.12581 15.3192 8.065 15.4409 7.9425L17.9409 5.4425C17.9984 5.385 18.0441 5.31592 18.0758 5.23926C18.1391 5.08676 18.1391 4.91426 18.0758 4.76176C18.0441 4.68509 17.9984 4.61581 17.9409 4.55831L15.4409 2.05831C15.1967 1.81414 14.8008 1.81414 14.5567 2.05831C14.3125 2.30247 14.3125 2.69834 14.5567 2.9425L15.99 4.37581H5C3.2775 4.37581 1.875 5.77748 1.875 7.50081V10.0008C1.875 10.345 2.155 10.625 2.5 10.625Z"
|
||||
fill="#5E5E5E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
282
src/pages/PoolDetails/__snapshots__/index.test.tsx.snap
Normal file
282
src/pages/PoolDetails/__snapshots__/index.test.tsx.snap
Normal file
@ -0,0 +1,282 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PoolDetailsPage pool header is displayed when data is received from thegraph 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
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: 18px;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
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;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
color: #7D7D7D;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
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 {
|
||||
gap: 36px;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-webkit-transition-duration: 125ms;
|
||||
transition-duration: 125ms;
|
||||
}
|
||||
|
||||
.c5:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.c5:active {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
background: #F9F9F9;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-webkit-transition-duration: 125ms;
|
||||
transition-duration: 125ms;
|
||||
}
|
||||
|
||||
.c15:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.c15:active {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c12 img {
|
||||
width: 16px;
|
||||
height: 32px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.c12 img:first-child {
|
||||
border-radius: 16px 0 0 16px;
|
||||
object-position: 0 0;
|
||||
}
|
||||
|
||||
.c12 img:last-child {
|
||||
border-radius: 0 16px 16px 0;
|
||||
object-position: 100% 0;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
padding: 40px 56px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1 c2"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<a
|
||||
class="c5"
|
||||
href="/tokens/ETHEREUM"
|
||||
>
|
||||
<div
|
||||
class="c6 css-1urox24"
|
||||
>
|
||||
Explore
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
class="c6 css-1urox24"
|
||||
>
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
class="c5"
|
||||
href="/tokens/ETHEREUM"
|
||||
>
|
||||
<div
|
||||
class="c6 css-1urox24"
|
||||
>
|
||||
Pool
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
class="c6 css-1urox24"
|
||||
>
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-1urox24"
|
||||
>
|
||||
USDC / WETH (0x88e6...5640)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c8"
|
||||
>
|
||||
<div
|
||||
class="c9 c10"
|
||||
width="max-content"
|
||||
>
|
||||
<div
|
||||
class="c11"
|
||||
>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<img
|
||||
class="c13"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
||||
/>
|
||||
<img
|
||||
class="c13"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-1jyz67g"
|
||||
>
|
||||
USDC / WETH
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c6 c14 css-1m65e73"
|
||||
>
|
||||
0.05%
|
||||
</div>
|
||||
<svg
|
||||
class="c15"
|
||||
data-testid="toggle-tokens-reverse-arrows"
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M18.125 10V12.5C18.125 14.2233 16.7225 15.625 15 15.625H4.0092L5.4425 17.0583C5.68667 17.3025 5.68667 17.6983 5.4425 17.9425C5.32084 18.0642 5.16081 18.1258 5.00081 18.1258C4.84081 18.1258 4.68079 18.065 4.55912 17.9425L2.05912 15.4425C2.00162 15.385 1.9559 15.3159 1.92424 15.2393C1.8609 15.0868 1.8609 14.9143 1.92424 14.7618C1.9559 14.6851 2.00162 14.6158 2.05912 14.5583L4.55912 12.0583C4.80329 11.8141 5.19915 11.8141 5.44332 12.0583C5.68749 12.3025 5.68749 12.6983 5.44332 12.9425L4.01001 14.3758H15C16.0333 14.3758 16.875 13.535 16.875 12.5008V10.0008C16.875 9.65581 17.155 9.37581 17.5 9.37581C17.845 9.37581 18.125 9.655 18.125 10ZM2.5 10.625C2.845 10.625 3.125 10.345 3.125 10V7.5C3.125 6.46583 3.96667 5.625 5 5.625H15.9908L14.5575 7.05831C14.3133 7.30247 14.3133 7.69834 14.5575 7.9425C14.6792 8.06417 14.8392 8.12581 14.9992 8.12581C15.1592 8.12581 15.3192 8.065 15.4409 7.9425L17.9409 5.4425C17.9984 5.385 18.0441 5.31592 18.0758 5.23926C18.1391 5.08676 18.1391 4.91426 18.0758 4.76176C18.0441 4.68509 17.9984 4.61581 17.9409 4.55831L15.4409 2.05831C15.1967 1.81414 14.8008 1.81414 14.5567 2.05831C14.3125 2.30247 14.3125 2.69834 14.5567 2.9425L15.99 4.37581H5C3.2775 4.37581 1.875 5.77748 1.875 7.50081V10.0008C1.875 10.345 2.155 10.625 2.5 10.625Z"
|
||||
fill="#5E5E5E"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
15
src/pages/PoolDetails/icons.tsx
Normal file
15
src/pages/PoolDetails/icons.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
type SVGProps = React.SVGProps<SVGSVGElement> & {
|
||||
fill?: string
|
||||
height?: string | number
|
||||
width?: string | number
|
||||
gradientId?: string
|
||||
}
|
||||
|
||||
export const ReversedArrowsIcon = (props: SVGProps) => (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M18.125 10V12.5C18.125 14.2233 16.7225 15.625 15 15.625H4.0092L5.4425 17.0583C5.68667 17.3025 5.68667 17.6983 5.4425 17.9425C5.32084 18.0642 5.16081 18.1258 5.00081 18.1258C4.84081 18.1258 4.68079 18.065 4.55912 17.9425L2.05912 15.4425C2.00162 15.385 1.9559 15.3159 1.92424 15.2393C1.8609 15.0868 1.8609 14.9143 1.92424 14.7618C1.9559 14.6851 2.00162 14.6158 2.05912 14.5583L4.55912 12.0583C4.80329 11.8141 5.19915 11.8141 5.44332 12.0583C5.68749 12.3025 5.68749 12.6983 5.44332 12.9425L4.01001 14.3758H15C16.0333 14.3758 16.875 13.535 16.875 12.5008V10.0008C16.875 9.65581 17.155 9.37581 17.5 9.37581C17.845 9.37581 18.125 9.655 18.125 10ZM2.5 10.625C2.845 10.625 3.125 10.345 3.125 10V7.5C3.125 6.46583 3.96667 5.625 5 5.625H15.9908L14.5575 7.05831C14.3133 7.30247 14.3133 7.69834 14.5575 7.9425C14.6792 8.06417 14.8392 8.12581 14.9992 8.12581C15.1592 8.12581 15.3192 8.065 15.4409 7.9425L17.9409 5.4425C17.9984 5.385 18.0441 5.31592 18.0758 5.23926C18.1391 5.08676 18.1391 4.91426 18.0758 4.76176C18.0441 4.68509 17.9984 4.61581 17.9409 4.55831L15.4409 2.05831C15.1967 1.81414 14.8008 1.81414 14.5567 2.05831C14.3125 2.30247 14.3125 2.69834 14.5567 2.9425L15.99 4.37581H5C3.2775 4.37581 1.875 5.77748 1.875 7.50081V10.0008C1.875 10.345 2.155 10.625 2.5 10.625Z"
|
||||
fill="#5E5E5E"
|
||||
/>
|
||||
</svg>
|
||||
)
|
98
src/pages/PoolDetails/index.test.tsx
Normal file
98
src/pages/PoolDetails/index.test.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { usePoolData } from 'graphql/thegraph/PoolData'
|
||||
import Router from 'react-router-dom'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { validParams, validPoolDataResponse } from 'test-utils/pools/fixtures'
|
||||
import { render, screen, waitFor } from 'test-utils/render'
|
||||
|
||||
import PoolDetails from '.'
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock('graphql/thegraph/PoolData', () => {
|
||||
const originalModule = jest.requireActual('graphql/thegraph/PoolData')
|
||||
return {
|
||||
...originalModule,
|
||||
usePoolData: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('PoolDetailsPage', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Router, 'useParams').mockReturnValue(validParams)
|
||||
mocked(usePoolData).mockReturnValue(validPoolDataResponse)
|
||||
})
|
||||
|
||||
it('not found page displayed when given no poolAddress', () => {
|
||||
jest.spyOn(Router, 'useParams').mockReturnValue({ chainName: validParams.chainName })
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('not found page displayed when given no chainName', () => {
|
||||
jest.spyOn(Router, 'useParams').mockReturnValue({ poolAddress: validParams.poolAddress })
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('not found page displayed when given invalid chainName', () => {
|
||||
jest
|
||||
.spyOn(Router, 'useParams')
|
||||
.mockReturnValue({ poolAddress: validParams.poolAddress, chainName: 'invalid-chain' })
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('not found page displayed when given invalid pool address', () => {
|
||||
jest.spyOn(Router, 'useParams').mockReturnValue({ poolAddress: '0xFakeAddress', chainName: validParams.chainName })
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('not found page displayed when no data is received from thegraph', () => {
|
||||
mocked(usePoolData).mockReturnValue({
|
||||
data: undefined,
|
||||
loading: false,
|
||||
})
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// TODO replace with loading skeleton when designed
|
||||
it('nothing displayed while data is loading', () => {
|
||||
mocked(usePoolData).mockReturnValue({ data: undefined, loading: true })
|
||||
render(<PoolDetails />)
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/not found/i)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('pool header is displayed when data is received from thegraph', () => {
|
||||
const { asFragment } = render(<PoolDetails />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText(/Explore/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Pool/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/USDC \/ WETH \(0x88e6...5640\)/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
46
src/pages/PoolDetails/index.tsx
Normal file
46
src/pages/PoolDetails/index.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import Row from 'components/Row'
|
||||
import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||
import { usePoolData } from 'graphql/thegraph/PoolData'
|
||||
import NotFound from 'pages/NotFound'
|
||||
import { useReducer } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
import { PoolDetailsHeader } from './PoolDetailsHeader'
|
||||
|
||||
const PageWrapper = styled(Row)`
|
||||
padding: 40px 56px;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
export default function PoolDetailsPage() {
|
||||
const { poolAddress, chainName } = useParams<{
|
||||
poolAddress: string
|
||||
chainName: string
|
||||
}>()
|
||||
const chain = getValidUrlChainName(chainName)
|
||||
const chainId = chain && supportedChainIdFromGQLChain(chain)
|
||||
const { data: poolData, loading } = usePoolData(poolAddress ?? '', chainId)
|
||||
const [isReversed, toggleReversed] = useReducer((x) => !x, false)
|
||||
const token0 = isReversed ? poolData?.token1 : poolData?.token0
|
||||
const token1 = isReversed ? poolData?.token0 : poolData?.token1
|
||||
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}
|
||||
/>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
37
src/test-utils/pools/fixtures.ts
Normal file
37
src/test-utils/pools/fixtures.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export const validParams = { poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', chainName: 'ethereum' }
|
||||
export const validPoolDataResponse = {
|
||||
data: {
|
||||
__typename: 'Pool' as const,
|
||||
id: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
|
||||
feeTier: '500',
|
||||
liquidity: '32118065613640312417',
|
||||
sqrtPrice: '1943494374075311739809880994923792',
|
||||
tick: '202163',
|
||||
token0: {
|
||||
__typename: 'Token' as const,
|
||||
id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
symbol: 'USDC',
|
||||
name: 'USD Coin',
|
||||
decimals: '6',
|
||||
derivedETH: '0.000602062055419695968472438533210813',
|
||||
},
|
||||
token1: {
|
||||
__typename: 'Token' as const,
|
||||
id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
symbol: 'WETH',
|
||||
name: 'Wrapped Ether',
|
||||
decimals: '18',
|
||||
derivedETH: '1',
|
||||
},
|
||||
token0Price: '1661.85294822715829371652214854595',
|
||||
token1Price: '0.0006017379582632664031212782038199158',
|
||||
volumeUSD: '394920157156.0515346899898790592366',
|
||||
volumeToken0: '394894081779.781168',
|
||||
volumeToken1: '190965971.266407832255075308',
|
||||
txCount: '5406827',
|
||||
totalValueLockedToken0: '180078648.881221',
|
||||
totalValueLockedToken1: '142782.017882048454494774',
|
||||
totalValueLockedUSD: '417233634.1468435997761171520463339',
|
||||
},
|
||||
loading: false,
|
||||
}
|
Loading…
Reference in New Issue
Block a user