Skip to content

Commit

Permalink
load coingecko data on the client
Browse files Browse the repository at this point in the history
  • Loading branch information
saml33 committed Dec 19, 2023
1 parent 5f18add commit 2af6c14
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 93 deletions.
72 changes: 7 additions & 65 deletions app/(pages)/token/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,12 @@ import {
fetchTokenPage,
fetchTokenPages,
} from '../../../../contentful/tokenPage'
import { CUSTOM_TOKEN_ICONS } from '../../../utils/constants'
import RichTextDisplay from '../../../components/shared/RichTextDisplay'
import { fetchMangoMarketData, fetchMangoTokenData } from '../../../utils/mango'
import { MangoMarketsData, MangoTokenData } from '../../../types/mango'
import Image from 'next/image'
import TokenMangoStats from '../../../components/explore/token-page/TokenMangoStats'
import { fetchCoingeckoData } from '../../../utils/coingecko'
import { CoingeckoData } from '../../../types/coingecko'
import DailyRange from '../../../components/explore/token-page/DailyRange'
import Links from '../../../components/explore/token-page/Links'
import TokenInfo from '../../../components/explore/token-page/TokenInfo'
import dynamic from 'next/dynamic'
import DataDisclaimer from '../../../components/explore/DataDisclaimer'
const TokenPriceChart = dynamic(
() => import('../../../components/explore/token-page/TokenPriceChart'),
{ ssr: false },
)
import InfoAndStats from '../../../components/explore/token-page/InfoAndStats'

const SECTION_WRAPPER_CLASSES = 'border-t border-th-bkg-3 pt-6 mt-12'

Expand Down Expand Up @@ -71,8 +60,7 @@ async function TokenPage({ params }: TokenPageProps) {
return notFound()
}

const { birdeyeData, coingeckoId, description, mint, symbol, tokenName } =
tokenPageData
const { description, mint, tokenName } = tokenPageData

// get mango specific token data
const mangoTokenDataPromise: Promise<MangoTokenData> =
Expand All @@ -82,64 +70,18 @@ async function TokenPage({ params }: TokenPageProps) {
const mangoMarketsDataPromise: Promise<MangoMarketsData> =
fetchMangoMarketData()

// get coingecko token data
const coingeckoDataPromise: Promise<CoingeckoData> =
fetchCoingeckoData(coingeckoId)

// Wait for the promises to resolve
const [mangoTokenData, mangoMarketsData, coingeckoData] = await Promise.all([
const [mangoTokenData, mangoMarketsData] = await Promise.all([
mangoTokenDataPromise,
mangoMarketsDataPromise,
coingeckoDataPromise,
])

const hasCustomIcon = Object.keys(CUSTOM_TOKEN_ICONS).find(
(icon) => icon === mangoTokenData?.symbol?.toLowerCase(),
)
const logoPath = hasCustomIcon
? `/icons/tokens/${mangoTokenData?.symbol.toLowerCase()}.svg`
: birdeyeData?.logoURI

const high24h = coingeckoData?.market_data?.high_24h?.usd
const low24h = coingeckoData?.market_data?.low_24h?.usd
const currentPrice = coingeckoData?.market_data?.current_price?.usd
const lastUpdated = coingeckoData?.last_updated
const latestChartData =
currentPrice && lastUpdated
? [{ unixTime: new Date(lastUpdated).getTime(), value: currentPrice }]
: []

return (
<div>
<div className="pb-6 border-b border-th-bkg-3 flex flex-col-reverse md:flex-row md:items-end md:justify-between">
<div className="flex items-center space-x-3.5 w-full">
<Image src={logoPath} alt="Logo" height={56} width={56} />
<div className="w-full">
<h1 className="mb-1 text-4xl">
{tokenName}{' '}
<span className="text-xl font-body font-normal text-th-fgd-4">
{symbol}
</span>
</h1>
<DailyRange high={high24h} low={low24h} price={currentPrice} />
</div>
</div>
<div className="flex justify-end">
<Links birdeyeData={birdeyeData} />
</div>
</div>
<div className="grid grid-cols-12 gap-6 border-b border-th-bkg-3">
<div className="col-span-12 lg:col-span-8 py-6">
<TokenPriceChart latestChartData={latestChartData} mint={mint} />
</div>
<div className="col-span-12 lg:col-span-4 bg-th-bkg-2 p-6 rounded-xl lg:rounded-none">
<TokenInfo
coingeckoData={coingeckoData}
tokenPageData={tokenPageData}
birdeyeData={birdeyeData}
/>
</div>
</div>
<InfoAndStats
tokenPageData={tokenPageData}
mangoTokenData={mangoTokenData}
/>
<div className="mt-6">
<h2 className="mb-4 text-2xl">{`${tokenName} on Mango`}</h2>
<TokenMangoStats
Expand Down
120 changes: 120 additions & 0 deletions app/components/explore/token-page/InfoAndStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use client'

import { useQuery } from '@tanstack/react-query'
import { TokenPageWithData } from '../../../../contentful/tokenPage'
import { MangoTokenData } from '../../../types/mango'
import { CUSTOM_TOKEN_ICONS } from '../../../utils/constants'
import Image from 'next/image'
import DailyRange from './DailyRange'
import Links from './Links'
import TokenPriceChart from './TokenPriceChart'
import TokenInfo from './TokenInfo'
import { useEffect, useState } from 'react'
import SheenLoader from '../../shared/SheenLoader'

const fetchCoingeckoData = async (id: string) => {
try {
const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${id}?localization=false&tickers=false&developer_data=false&sparkline=false&community_data=false
`,
)
const data = await response.json()
return data
} catch (e) {
console.error('failed to fetch coingecko data', e)
}
}

const InfoAndStats = ({
tokenPageData,
mangoTokenData,
}: {
tokenPageData: TokenPageWithData
mangoTokenData: MangoTokenData
}) => {
const { birdeyeData, coingeckoId, mint, symbol, tokenName } = tokenPageData

const { data: coingeckoData, isLoading: loadingCoingeckoData } = useQuery({
queryKey: ['coingecko-data', coingeckoId],
queryFn: () => fetchCoingeckoData(coingeckoId),
enabled: !!coingeckoId,
})

const hasCustomIcon = Object.keys(CUSTOM_TOKEN_ICONS).find(
(icon) => icon === mangoTokenData?.symbol?.toLowerCase(),
)
const logoPath = hasCustomIcon
? `/icons/tokens/${mangoTokenData?.symbol.toLowerCase()}.svg`
: birdeyeData?.logoURI

const high24h = coingeckoData?.market_data?.high_24h?.usd
const low24h = coingeckoData?.market_data?.low_24h?.usd
const currentPrice = coingeckoData?.market_data?.current_price?.usd
const lastUpdated = coingeckoData?.last_updated
const latestChartData =
currentPrice && lastUpdated
? [{ unixTime: new Date(lastUpdated).getTime(), value: currentPrice }]
: []

const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted)
return (
<div>
<div className="flex items-end justify-between mb-6">
<SheenLoader>
<div className={`h-16 w-80 rounded-lg bg-th-bkg-2`} />
</SheenLoader>
<SheenLoader>
<div className={`h-8 w-40 rounded-lg bg-th-bkg-2`} />
</SheenLoader>
</div>
<SheenLoader className="flex flex-1">
<div
className={`h-[304px] lg:h-[393px] w-full rounded-lg bg-th-bkg-2`}
/>
</SheenLoader>
</div>
)

return (
<>
<div className="pb-6 border-b border-th-bkg-3 flex flex-col-reverse md:flex-row md:items-end md:justify-between">
<div className="flex items-center space-x-3.5 w-full">
<Image src={logoPath} alt="Logo" height={56} width={56} />
<div className="w-full">
<h1 className="mb-1 text-4xl">
{tokenName}{' '}
<span className="text-xl font-body font-normal text-th-fgd-4">
{symbol}
</span>
</h1>
<DailyRange high={high24h} low={low24h} price={currentPrice} />
</div>
</div>
<div className="flex justify-end">
<Links birdeyeData={birdeyeData} />
</div>
</div>
<div className="grid grid-cols-12 gap-6 border-b border-th-bkg-3">
<div className="col-span-12 lg:col-span-8 py-6">
<TokenPriceChart
latestChartData={latestChartData}
loadingLatestChartData={loadingCoingeckoData}
mint={mint}
/>
</div>
<div className="col-span-12 lg:col-span-4 bg-th-bkg-2 p-6 rounded-xl lg:rounded-none">
<TokenInfo
coingeckoData={coingeckoData}
loadingCoingeckoData={loadingCoingeckoData}
tokenPageData={tokenPageData}
birdeyeData={birdeyeData}
/>
</div>
</div>
</>
)
}

export default InfoAndStats
65 changes: 40 additions & 25 deletions app/components/explore/token-page/TokenInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ dayjs.extend(relativeTime)

const TokenInfo = ({
coingeckoData,
loadingCoingeckoData,
tokenPageData,
birdeyeData,
}: {
coingeckoData: CoingeckoData | undefined
loadingCoingeckoData: boolean
tokenPageData: TokenPageWithData
birdeyeData: BirdeyeOverviewData | undefined
}) => {
Expand All @@ -37,17 +39,6 @@ const TokenInfo = ({
setTimeout(() => setCopied(false), 2000)
}, [copied])

const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted)
return (
<SheenLoader className="flex flex-1">
<div
className={`h-[264px] lg:h-[344px] w-full rounded-lg bg-th-bkg-2`}
/>
</SheenLoader>
)

return (
<>
<h2 className="text-base mb-2">Info and stats</h2>
Expand Down Expand Up @@ -82,17 +73,23 @@ const TokenInfo = ({
/>
<KeyValuePair
label="Market cap"
value={`${
coingeckoData?.market_data?.market_cap?.usd
? `$${numberCompacter.format(
coingeckoData.market_data.market_cap.usd,
)}`
: '–'
} ${
coingeckoData?.market_data?.market_cap_rank
? `| #${coingeckoData.market_data.market_cap_rank}`
: ''
}`}
value={
loadingCoingeckoData ? (
<DataLoader />
) : (
`${
coingeckoData?.market_data?.market_cap?.usd
? `$${numberCompacter.format(
coingeckoData.market_data.market_cap.usd,
)}`
: '–'
} ${
coingeckoData?.market_data?.market_cap_rank
? `| #${coingeckoData.market_data.market_cap_rank}`
: ''
}`
)
}
/>
<KeyValuePair
label="Fully diluted value"
Expand All @@ -109,7 +106,12 @@ const TokenInfo = ({
<KeyValuePair
label="All-time high"
value={
coingeckoData?.market_data?.ath?.usd ? (
loadingCoingeckoData ? (
<div className="space-y-0.5 flex flex-col items-end">
<DataLoader />
<DataLoader small />
</div>
) : coingeckoData?.market_data?.ath?.usd ? (
<span className="flex flex-col items-end">
<span className="text-th-fgd-1 text-sm text-right">
{`$${formatNumericValue(coingeckoData.market_data.ath.usd)}`}{' '}
Expand Down Expand Up @@ -146,7 +148,12 @@ const TokenInfo = ({
<KeyValuePair
label="All-time low"
value={
coingeckoData?.market_data?.atl?.usd ? (
loadingCoingeckoData ? (
<div className="space-y-0.5 flex flex-col items-end">
<DataLoader />
<DataLoader small />
</div>
) : coingeckoData?.market_data?.atl?.usd ? (
<span className="flex flex-col items-end">
<span className="text-th-fgd-1 text-sm text-right">
{`$${formatNumericValue(coingeckoData.market_data.atl.usd)}`}{' '}
Expand Down Expand Up @@ -219,7 +226,15 @@ const KeyValuePair = ({
return (
<div className="flex justify-between">
<p className="text-sm">{label}</p>
<p className="text-th-fgd-1 text-sm">{value}</p>
<span className="text-th-fgd-1 text-sm">{value}</span>
</div>
)
}

const DataLoader = ({ small }: { small?: boolean }) => {
return (
<SheenLoader>
<div className={`${small ? 'h-3 w-32' : 'h-5 w-24'} bg-th-bkg-4`} />
</SheenLoader>
)
}
6 changes: 4 additions & 2 deletions app/components/explore/token-page/TokenPriceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ const fetchBirdeyePrices = async (daysToShow: string, mint: string) => {

const TokenPriceChart = ({
latestChartData,
loadingLatestChartData,
mint,
}: {
latestChartData: ChartData[]
loadingLatestChartData: boolean
mint: string
}) => {
const [daysToShow, setDaysToShow] = useState('30')

const { data: chartData, isLoading: loadingAppData } = useQuery({
const { data: chartData, isLoading: loadingChartData } = useQuery({
queryKey: ['token-chart-data', daysToShow, mint],
queryFn: () => fetchBirdeyePrices(daysToShow, mint),
})
Expand All @@ -52,7 +54,7 @@ const TokenPriceChart = ({
changeAsPercent
data={chartData?.length ? chartData.concat(latestChartData) : chartData}
daysToShow={daysToShow}
loading={loadingAppData}
loading={loadingChartData || loadingLatestChartData}
setDaysToShow={setDaysToShow}
heightClass="h-[180px] lg:h-[260px]"
loaderHeightClass="h-[264px] lg:h-[344px]"
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.tsbuildinfo

Large diffs are not rendered by default.

1 comment on commit 2af6c14

@vercel
Copy link

@vercel vercel bot commented on 2af6c14 Dec 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.