merge main

This commit is contained in:
saml33 2023-11-27 15:32:36 +11:00
commit dbe5ca582a
9 changed files with 22169 additions and 88 deletions

View File

@ -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>
)
}

View File

@ -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>

View File

@ -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"

25
hooks/useMarketData.ts Normal file
View File

@ -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,
})
}

View File

@ -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",

View File

@ -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>
</>
)
}

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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"