merge main
This commit is contained in:
commit
dbe5ca582a
|
@ -1,13 +1,16 @@
|
|||
import SheenLoader from '../shared/SheenLoader'
|
||||
import Tooltip from '../shared/Tooltip'
|
||||
|
||||
const HeroStat = ({
|
||||
title,
|
||||
tooltipContent,
|
||||
value,
|
||||
loading,
|
||||
}: {
|
||||
title: string
|
||||
tooltipContent?: string
|
||||
value: string
|
||||
loading: boolean
|
||||
}) => {
|
||||
return (
|
||||
<div className="col-span-4 sm:col-span-2 lg:col-span-1">
|
||||
|
@ -17,9 +20,15 @@ const HeroStat = ({
|
|||
>
|
||||
<p className="lg:text-lg mb-1">{title}</p>
|
||||
</Tooltip>
|
||||
<p className="text-4xl md:text-5xl font-display tracking-tight text-th-fgd-1">
|
||||
{value}
|
||||
</p>
|
||||
{!loading ? (
|
||||
<p className="text-4xl md:text-5xl font-display tracking-tight text-th-fgd-1">
|
||||
{value}
|
||||
</p>
|
||||
) : (
|
||||
<SheenLoader className="mt-1">
|
||||
<div className="h-12 w-32 bg-th-bkg-3" />
|
||||
</SheenLoader>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@ import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|||
import { MotionPathPlugin } from 'gsap/dist/MotionPathPlugin'
|
||||
import ColorBlur from '../shared/ColorBlur'
|
||||
import Ottersec from '../icons/Ottersec'
|
||||
import { AppStatsData, MarketData } from '../../types'
|
||||
import TabsText from '../shared/TabsText'
|
||||
import MarketCard from './MarketCard'
|
||||
import { formatNumericValue, numberCompacter } from '../../utils'
|
||||
import HeroStat from './HeroStat'
|
||||
import useMarketsData from '../../hooks/useMarketData'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { MANGO_DATA_API_URL } from '../../utils/constants'
|
||||
|
||||
gsap.registerPlugin(MotionPathPlugin)
|
||||
gsap.registerPlugin(ScrollTrigger)
|
||||
|
@ -43,17 +45,28 @@ const tokenIcons = [
|
|||
const MOBILE_IMAGE_CLASSES =
|
||||
'core-image h-[240px] w-[240px] sm:h-[300px] sm:w-[300px] md:h-[480px] md:w-[480px] mb-6 lg:mb-0'
|
||||
|
||||
const HomePage = ({
|
||||
perpData,
|
||||
spotData,
|
||||
appData,
|
||||
}: {
|
||||
perpData: MarketData
|
||||
spotData: MarketData
|
||||
appData: AppStatsData
|
||||
}) => {
|
||||
const fetchAppData = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${MANGO_DATA_API_URL}/stats/mango-protocol-summary`,
|
||||
)
|
||||
const data = await response.json()
|
||||
return data
|
||||
} catch (e) {
|
||||
console.error('failed to fetch account followers', e)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const HomePage = () => {
|
||||
const { t } = useTranslation(['common', 'home'])
|
||||
const [activeMarketTab, setActiveMarketTab] = useState('spot')
|
||||
const { data: marketData, isLoading: loadingMarketData } = useMarketsData()
|
||||
|
||||
const { data: appData, isLoading: loadingAppData } = useQuery({
|
||||
queryKey: ['app-data'],
|
||||
queryFn: fetchAppData,
|
||||
})
|
||||
|
||||
const topSection = useRef()
|
||||
const callouts = useRef()
|
||||
|
@ -62,39 +75,44 @@ const HomePage = ({
|
|||
const build = useRef()
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
const perpMarketsNumber = Object.keys(perpData)?.length || 0
|
||||
const spotMarketsNumber = Object.keys(spotData)?.length || 0
|
||||
const perpMarketsNumber =
|
||||
(marketData?.perpData && Object.keys(marketData?.perpData)?.length) || 0
|
||||
const spotMarketsNumber =
|
||||
(marketData?.spotData && Object.keys(marketData?.spotData)?.length) || 0
|
||||
const tabs: [string, number][] = [
|
||||
['spot', spotMarketsNumber],
|
||||
['perp', perpMarketsNumber],
|
||||
]
|
||||
return tabs
|
||||
}, [perpData, spotData])
|
||||
}, [marketData])
|
||||
|
||||
const formattedSpotData = useMemo(() => {
|
||||
if (!spotData || !Object.keys(spotData)?.length) return []
|
||||
const data = Object.entries(spotData)
|
||||
if (!marketData?.spotData || !Object.keys(marketData?.spotData)?.length)
|
||||
return []
|
||||
const data = Object.entries(marketData.spotData)
|
||||
.sort((a, b) => b[1][0].quote_volume_24h - a[1][0].quote_volume_24h)
|
||||
.map(([key, value]) => {
|
||||
const data = value[0]
|
||||
return { name: key, data }
|
||||
})
|
||||
return data
|
||||
}, [spotData])
|
||||
}, [marketData])
|
||||
|
||||
const formattedPerpData = useMemo(() => {
|
||||
if (!perpData || !Object.keys(perpData)?.length) return []
|
||||
const data = Object.entries(perpData)
|
||||
if (!marketData?.perpData || !Object.keys(marketData?.perpData)?.length)
|
||||
return []
|
||||
const data = Object.entries(marketData.perpData)
|
||||
.sort((a, b) => b[1][0].quote_volume_24h - a[1][0].quote_volume_24h)
|
||||
.map(([key, value]) => {
|
||||
const data = value[0]
|
||||
return { name: key, data }
|
||||
})
|
||||
return data
|
||||
}, [perpData])
|
||||
}, [marketData])
|
||||
|
||||
const formattedAppStatsData = useMemo(() => {
|
||||
if (!appData || !Object.keys(appData).length) return
|
||||
if (!appData || !Object.keys(appData).length)
|
||||
return { totalVol24h: 0, totalTrades24h: 0, weeklyActiveTraders: 0 }
|
||||
// volume
|
||||
const spotVol24h = appData?.openbook_volume_usd_24h || 0
|
||||
const perpVol24h = appData?.perp_volume_usd_24h || 0
|
||||
|
@ -269,25 +287,29 @@ const HomePage = ({
|
|||
value={(
|
||||
formattedSpotData.length + formattedPerpData.length
|
||||
).toString()}
|
||||
loading={loadingMarketData}
|
||||
/>
|
||||
<HeroStat
|
||||
title={t('home:active-traders')}
|
||||
tooltipContent={t('home:tooltip-active-traders')}
|
||||
value={formatNumericValue(
|
||||
formattedAppStatsData.weeklyActiveTraders
|
||||
formattedAppStatsData.weeklyActiveTraders,
|
||||
)}
|
||||
loading={loadingAppData}
|
||||
/>
|
||||
<HeroStat
|
||||
title={t('home:daily-volume')}
|
||||
tooltipContent={t('home:tooltip-daily-volume')}
|
||||
value={`$${numberCompacter.format(
|
||||
formattedAppStatsData.totalVol24h
|
||||
formattedAppStatsData.totalVol24h,
|
||||
)}`}
|
||||
loading={loadingAppData}
|
||||
/>
|
||||
<HeroStat
|
||||
title={t('home:daily-trades')}
|
||||
tooltipContent={t('home:tooltip-daily-trades')}
|
||||
value={formatNumericValue(formattedAppStatsData.totalTrades24h)}
|
||||
loading={loadingAppData}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
|
|
@ -21,6 +21,10 @@ const MarketCard = ({ marketData }: { marketData: MarketCardData }) => {
|
|||
data?.last_price && data?.price_24h
|
||||
? ((data.last_price - data.price_24h) / data.last_price) * 100
|
||||
: 0
|
||||
const chartData =
|
||||
data?.price_history && data?.price_history?.length
|
||||
? data.price_history.sort((a, b) => a.time.localeCompare(b.time))
|
||||
: []
|
||||
return (
|
||||
<div className="p-4 rounded-lg border border-th-bkg-3">
|
||||
<div className="flex items-start justify-between">
|
||||
|
@ -74,18 +78,15 @@ const MarketCard = ({ marketData }: { marketData: MarketCardData }) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{data?.price_history && data?.price_history.length ? (
|
||||
{chartData.length ? (
|
||||
<div className="h-12 w-20">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
data.price_history[0].price <=
|
||||
data.price_history[data.price_history.length - 1]?.price
|
||||
chartData[0].price <= chartData[chartData.length - 1].price
|
||||
? 'var(--up)'
|
||||
: 'var(--down)'
|
||||
}
|
||||
data={data.price_history.sort((a, b) =>
|
||||
a.time.localeCompare(b.time)
|
||||
)}
|
||||
data={chartData}
|
||||
name={name}
|
||||
xKey="time"
|
||||
yKey="price"
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
import { MANGO_DATA_API_URL } from '../utils/constants'
|
||||
|
||||
const fetchMarketData = async () => {
|
||||
const promises = [
|
||||
fetch(`${MANGO_DATA_API_URL}/stats/perp-market-summary`),
|
||||
fetch(`${MANGO_DATA_API_URL}/stats/spot-market-summary`),
|
||||
]
|
||||
try {
|
||||
const data = await Promise.all(promises)
|
||||
const perpData = await data[0].json()
|
||||
const spotData = await data[1].json()
|
||||
return { perpData, spotData }
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch market data', e)
|
||||
return { perpData: undefined, spotData: undefined }
|
||||
}
|
||||
}
|
||||
|
||||
export default function useMarketsData() {
|
||||
return useQuery({
|
||||
queryKey: ['market-data'],
|
||||
queryFn: fetchMarketData,
|
||||
})
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@tanstack/react-query": "^5.8.4",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"decimal.js": "^10.4.3",
|
||||
"gsap": "^3.12.2",
|
||||
|
@ -30,6 +31,7 @@
|
|||
"immer": "^10.0.3",
|
||||
"next": "^14.0.3",
|
||||
"next-i18next": "^15.0.0",
|
||||
"next-plausible": "^3.11.3",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
@ -3,6 +3,8 @@ import { ThemeProvider } from 'next-themes'
|
|||
import '../styles/index.css'
|
||||
import LayoutWrapper from '../components/layout/LayoutWrapper'
|
||||
import { appWithTranslation } from 'next-i18next'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import PlausibleProvider from 'next-plausible'
|
||||
|
||||
const metaTitle = 'Mango Markets – Safer. Smarter. Faster.'
|
||||
const metaDescription =
|
||||
|
@ -10,16 +12,24 @@ const metaDescription =
|
|||
const keywords =
|
||||
'Mango Markets, DEFI, Decentralized Finance, Decentralized Finance, Crypto, ERC20, Ethereum, Solana, SOL, SPL, Cross-Chain, Trading, Fastest, Fast, SPL Tokens'
|
||||
|
||||
// init react-query
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function App({ Component, pageProps }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Mango Markets</title>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="keywords" content={keywords} />
|
||||
|
@ -36,11 +46,20 @@ function App({ Component, pageProps }) {
|
|||
content="https://mango.markets/twitter-card.png?123456789"
|
||||
/>
|
||||
</Head>
|
||||
<ThemeProvider defaultTheme="Mango">
|
||||
<LayoutWrapper>
|
||||
<Component {...pageProps} />
|
||||
</LayoutWrapper>
|
||||
</ThemeProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider defaultTheme="Mango">
|
||||
<PlausibleProvider
|
||||
domain="mango.markets"
|
||||
customDomain="https://pl.mngo.cloud"
|
||||
selfHosted={true}
|
||||
trackOutboundLinks={true}
|
||||
>
|
||||
<LayoutWrapper>
|
||||
<Component {...pageProps} />
|
||||
</LayoutWrapper>
|
||||
</PlausibleProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,58 +1,23 @@
|
|||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { NextPage } from 'next'
|
||||
import HomePage from '../components/home/HomePage'
|
||||
import type { InferGetStaticPropsType } from 'next'
|
||||
import { MANGO_DATA_API_URL } from '../utils/constants'
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
const promises = [
|
||||
fetch(`${MANGO_DATA_API_URL}/stats/perp-market-summary`),
|
||||
fetch(`${MANGO_DATA_API_URL}/stats/spot-market-summary`),
|
||||
fetch(`${MANGO_DATA_API_URL}/stats/mango-protocol-summary`),
|
||||
]
|
||||
try {
|
||||
const data = await Promise.all(promises)
|
||||
const perpData = await data[0].json()
|
||||
const spotData = await data[1].json()
|
||||
const appData = await data[2].json()
|
||||
|
||||
return {
|
||||
props: {
|
||||
perpData,
|
||||
spotData,
|
||||
appData,
|
||||
...(await serverSideTranslations(locale, [
|
||||
'common',
|
||||
'home',
|
||||
'footer',
|
||||
'navigation',
|
||||
])),
|
||||
},
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch market data', e)
|
||||
return {
|
||||
props: {
|
||||
perpData: null,
|
||||
spotData: null,
|
||||
appData: null,
|
||||
...(await serverSideTranslations(locale, [
|
||||
'common',
|
||||
'home',
|
||||
'footer',
|
||||
'navigation',
|
||||
])),
|
||||
},
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, [
|
||||
'common',
|
||||
'home',
|
||||
'footer',
|
||||
'navigation',
|
||||
])),
|
||||
// Will be passed to the page component as props
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const Index: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
|
||||
perpData,
|
||||
spotData,
|
||||
appData,
|
||||
}) => {
|
||||
return <HomePage perpData={perpData} spotData={spotData} appData={appData} />
|
||||
const Index: NextPage = () => {
|
||||
return <HomePage />
|
||||
}
|
||||
|
||||
export default Index
|
||||
|
|
22023
tsconfig.tsbuildinfo
22023
tsconfig.tsbuildinfo
File diff suppressed because one or more lines are too long
17
yarn.lock
17
yarn.lock
|
@ -982,6 +982,18 @@
|
|||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@tanstack/query-core@5.8.3":
|
||||
version "5.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.8.3.tgz#7c6d62721518223e8b27f1a0b5e9bd4e3bb7ecd8"
|
||||
integrity sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==
|
||||
|
||||
"@tanstack/react-query@^5.8.4":
|
||||
version "5.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.8.4.tgz#7d5ef4dc4e2985fdc607d0f30ff79a8453abe652"
|
||||
integrity sha512-CD+AkXzg8J72JrE6ocmuBEJfGzEzu/bzkD6sFXFDDB5yji9N20JofXZlN6n0+CaPJuIi+e4YLCbGsyPFKkfNQA==
|
||||
dependencies:
|
||||
"@tanstack/query-core" "5.8.3"
|
||||
|
||||
"@testing-library/dom@^9.0.0":
|
||||
version "9.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.3.tgz#108c23a5b0ef51121c26ae92eb3179416b0434f5"
|
||||
|
@ -3785,6 +3797,11 @@ next-i18next@^15.0.0:
|
|||
hoist-non-react-statics "^3.3.2"
|
||||
i18next-fs-backend "^2.2.0"
|
||||
|
||||
next-plausible@^3.11.3:
|
||||
version "3.11.3"
|
||||
resolved "https://registry.yarnpkg.com/next-plausible/-/next-plausible-3.11.3.tgz#387d2039b66f17f8f704027b2572a0e84e58eee7"
|
||||
integrity sha512-2dpG58ryxdsr4ZI8whWQpGv0T6foRDPGiehcICpDhYfmMJmluewswQgfDA8Z37RFMXAY+6SHOPS7Xi+9ewNi2Q==
|
||||
|
||||
next-themes@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.2.1.tgz#0c9f128e847979daf6c67f70b38e6b6567856e45"
|
||||
|
|
Loading…
Reference in New Issue