Skip to content

[EGN-742] Token page #1835

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ import { type ChainId, ChainKey } from 'sushi/chain'
export function NavigationItems({ chainId }: { chainId: ChainId }) {
return (
<>
<LinkInternal
shallow={true}
scroll={false}
href={`/${ChainKey[chainId]}/explore/tokens`}
>
<PathnameButton
id="tokens"
pathname={`/${ChainKey[chainId]}/explore/tokens`}
asChild
size="sm"
>
Tokens
</PathnameButton>
</LinkInternal>
<LinkInternal
shallow={true}
scroll={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Container } from '@sushiswap/ui'
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import type React from 'react'
import { GlobalStatsCharts } from 'src/ui/explore/global-stats-charts'
import { TokensFiltersProvider } from 'src/ui/token/TokensFiltersProvider'
import type { EvmChainId } from 'sushi/chain'
import { SUSHISWAP_SUPPORTED_CHAIN_IDS, isSushiSwapChainId } from 'sushi/config'
import { SidebarContainer } from '~evm/_common/ui/sidebar'
import { NavigationItems } from '../navigation-items'

export const metadata: Metadata = {
title: 'Tokens',
description: 'Explore SushiSwap tokens.',
}

export default async function ExploreLayout(props: {
children: React.ReactNode
params: Promise<{ chainId: string }>
}) {
const params = await props.params

const { children } = props

const chainId = +params.chainId as EvmChainId

if (!isSushiSwapChainId(chainId)) {
return notFound()
}

return (
<SidebarContainer
selectedNetwork={chainId}
supportedNetworks={SUSHISWAP_SUPPORTED_CHAIN_IDS}
unsupportedNetworkHref={'/ethereum/explore/tokens'}
shiftContent
>
<main className="flex flex-col h-full flex-1">
<Container maxWidth="7xl" className="px-4 py-4">
<GlobalStatsCharts chainId={chainId} />
</Container>
<Container maxWidth="7xl" className="px-4 flex gap-2 pb-4">
<NavigationItems chainId={chainId} />
</Container>
<section className="flex flex-col flex-1">
<div className="bg-gray-50 dark:bg-white/[0.02] border-t border-accent pt-4 pb-10 min-h-screen">
<TokensFiltersProvider>{children}</TokensFiltersProvider>
</div>
</section>
</main>
</SidebarContainer>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Container } from '@sushiswap/ui'
import { notFound } from 'next/navigation'
import React from 'react'
import { TableFiltersSearchToken } from 'src/ui/token/TableFiltersSearchToken'
import { TokensTable } from 'src/ui/token/TokensTable'
import { type SushiSwapChainId, isSushiSwapChainId } from 'sushi/config'

export default async function TokensPage(props: {
params: Promise<{ chainId: string }>
}) {
const params = await props.params
const chainId = +params.chainId as SushiSwapChainId

if (!isSushiSwapChainId(chainId)) {
return notFound()
}

return (
<Container maxWidth="7xl" className="px-4">
<div className="flex flex-wrap gap-3 mb-4">
<TableFiltersSearchToken />
</div>
<TokensTable chainId={chainId} />
</Container>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { notFound } from 'next/navigation'
import type { EvmChainId } from 'sushi/chain'
import { SUSHISWAP_SUPPORTED_CHAIN_IDS, isSushiSwapChainId } from 'sushi/config'
import { SidebarContainer, SidebarProvider } from '~evm/_common/ui/sidebar'
import { Header } from '../../header'
import { Providers } from './providers'

export default async function PoolLayout(props: {
children: React.ReactNode
params: Promise<{ chainId: string }>
}) {
const params = await props.params

const { children } = props

const chainId = +params.chainId as EvmChainId
if (!isSushiSwapChainId(chainId)) {
return notFound()
}

return (
<Providers>
<SidebarProvider>
<Header chainId={chainId} />
<SidebarContainer
selectedNetwork={chainId}
supportedNetworks={SUSHISWAP_SUPPORTED_CHAIN_IDS}
unsupportedNetworkHref={'/ethereum/explore/tokens'}
shiftContent
>
<main className="flex flex-col h-full flex-1">{children}</main>
</SidebarContainer>
</SidebarProvider>
</Providers>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
getTokenInfo,
getTokenList,
} from '@sushiswap/graph-client/data-api/queries'
import { unstable_cache } from 'next/cache'
import { notFound } from 'next/navigation'
import { TokenPage } from 'src/ui/token/TokenPage'
import type { EvmChainId } from 'sushi'
import { isSushiSwapChainId } from 'sushi/config'
import { Token } from 'sushi/currency'
import { isAddress } from 'viem'

export default async function _TokenPage(props: {
params: Promise<{ chainId: string; address: string }>
}) {
const params = await props.params
const { chainId: _chainId, address } = params
const chainId = +_chainId as EvmChainId

if (!isSushiSwapChainId(chainId) || !isAddress(address, { strict: false })) {
return notFound()
}

const { token, tokenInfo } = await unstable_cache(
async () => {
const [tokenInfoResult, tokenListResult] = await Promise.allSettled([
getTokenInfo({ chainId, address: address }),
getTokenList({ chainId, search: address }),
])

if (tokenListResult.status !== 'fulfilled' || !tokenListResult.value[0]) {
return notFound()
}

return {
token: new Token(tokenListResult.value[0]),
tokenInfo:
tokenInfoResult.status === 'fulfilled' ? tokenInfoResult.value : null,
}
},
['token', `${chainId}:${address}`],
{
revalidate: 60 * 15,
},
)()

return (
<TokenPage
token={
token && typeof token.serialize === 'function'
? token.serialize()
: token
}
tokenInfo={tokenInfo}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { EdgeProvider } from 'src/providers/edge-config-provider'
import { getSwapEdgeConfig } from '~evm/[chainId]/(trade)/swap/get-swap-edge-config'

export async function Providers({ children }: { children: React.ReactNode }) {
const config = await getSwapEdgeConfig()

return <EdgeProvider config={config}>{children}</EdgeProvider>
}
3 changes: 2 additions & 1 deletion apps/web/src/lib/hooks/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export * from './useApprovedCommunityTokens'
export * from './usePoolGraphData'
export * from './usePoolsInfinite'
export * from './userSmartPools'
export * from './useSmartPools'
export * from './useSkaleEuropaFaucet'
export * from './useSushiV2UserPositions'
export * from './useV2Pool'
export * from './useV3Pool'
export * from './useVault'
export * from './useVaults'
export * from './useTokenAnalysis'
export * from './useTokens'
export * from './usePendingTokenListings'
1 change: 1 addition & 0 deletions apps/web/src/lib/hooks/api/useTokenAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { useQuery } from '@tanstack/react-query'
import { isAddressFast } from 'sushi/validate'

// NOTE: This is not intended to be used anywhere else other than the token listing page, do not replace this with goplusapi requests.
export function useTokenAnalysis(
args: Partial<GetTokenAnalysis>,
shouldFetch = true,
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/lib/hooks/api/useTokenPriceChart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
type GetTokenPriceChart,
type TokenPriceChart,
getTokenPriceChart,
} from '@sushiswap/graph-client/data-api/queries'
import { useQuery } from '@tanstack/react-query'

export function useTokenPriceChart(
args: GetTokenPriceChart,
shouldFetch = true,
) {
return useQuery<TokenPriceChart>({
queryKey: ['token-chart', args],
queryFn: async () => await getTokenPriceChart(args),
enabled: Boolean(shouldFetch),
})
}
14 changes: 14 additions & 0 deletions apps/web/src/lib/hooks/api/useTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
type GetTokens,
type TokensResponse,
getTokens,
} from '@sushiswap/graph-client/data-api/queries'
import { useQuery } from '@tanstack/react-query'

export function useTokens(args: GetTokens, shouldFetch = true) {
return useQuery<TokensResponse>({
queryKey: ['explore-tokens', args],
queryFn: async () => await getTokens(args),
enabled: Boolean(shouldFetch),
})
}
3 changes: 3 additions & 0 deletions apps/web/src/lib/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const replaceNetworkSlug = (
if (pathname.includes('/pool/')) {
return `/${getNetworkKey(network)}/explore/pools`
}
if (pathname.includes('/token/')) {
return `/${getNetworkKey(network)}/explore/tokens`
}
const pathSegments = pathname.split('/')
pathSegments[1] = getNetworkKey(network)
return pathSegments.join('/')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SmartPoolChainId } from '@sushiswap/graph-client/data-api'
import { useMemo } from 'react'
import { useSmartPools } from 'src/lib/hooks/api/userSmartPools'
import { useSmartPools } from 'src/lib/hooks/api/useSmartPools'
import { useAllPrices } from 'src/lib/hooks/react-query'
import type { ID } from 'sushi'
import { Amount, Token } from 'sushi/currency'
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const config = {
'/:chainId/cross-chain-swap/:path*',
'/:chainId/explore/:path*',
'/:chainId/pool/:path*',
'/:chainId/token/:path*',
'/:chainId/positions/:path*',
'/:chainId/migrate',
'/:chainId/rewards',
Expand All @@ -30,6 +31,7 @@ export async function middleware(req: NextRequest) {
pathname === '/explore' ||
pathname === '/pools' ||
pathname === '/pool' ||
pathname === '/token' ||
pathname === '/swap' ||
pathname === '/limit' ||
pathname === '/dca' ||
Expand All @@ -54,7 +56,7 @@ export async function middleware(req: NextRequest) {
}

const networkNameMatch = pathname.match(
/([\w-]+)(?=\/swap|\/limit|\/dca|\/cross-chain-swap|\/explore|\/pool|\/positions|\/rewards|\/migrate)/,
/([\w-]+)(?=\/swap|\/limit|\/dca|\/cross-chain-swap|\/explore|\/pool|\/token|\/positions|\/rewards|\/migrate)/,
)
if (networkNameMatch?.length) {
const { chainId, networkName } = getEvmChainInfo(networkNameMatch[0])
Expand Down
22 changes: 12 additions & 10 deletions apps/web/src/ui/explore/global-stats-charts.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import {
type PoolChainId,
getAnalyticsDayBuckets,
isPoolChainId,
} from '@sushiswap/graph-client/data-api'
import { unstable_cache } from 'next/cache'
import { type FC, Suspense } from 'react'
import type { EvmChainId } from 'sushi/chain'
import { GlobalStatsLoading } from './global-stats-loading'
import { TVLChart } from './tvl-chart'
import { VolumeChart } from './volume-chart'

export const GlobalStatsCharts: FC<{ chainId: PoolChainId }> = ({
chainId,
}) => {
export const GlobalStatsCharts: FC<{ chainId: EvmChainId }> = ({ chainId }) => {
return (
<Suspense fallback={<GlobalStatsLoading chainId={chainId} />}>
<_GlobalStatsCharts chainId={chainId} />
</Suspense>
)
}

const _GlobalStatsCharts: FC<{ chainId: PoolChainId }> = async ({
chainId,
}) => {
const _GlobalStatsCharts: FC<{ chainId: EvmChainId }> = async ({ chainId }) => {
const dayBuckets = await unstable_cache(
async () =>
getAnalyticsDayBuckets({
chainId,
}),
isPoolChainId(chainId)
? getAnalyticsDayBuckets({
chainId,
})
: {
v2: [],
v3: [],
},
['dayBuckets', `${chainId}`],
{
revalidate: 60 * 15,
Expand Down
Loading
Loading