Stats page alignment (#28)

* total deposit/borrow value charts, new chart styles

* responsive and bar chart

* reduce icon size to match trade page

* round large numbers

* use historic prices for total deposits/borrows

* fix lint errors

* use current price if no match from prices api

* fix data passed to borrow chart

* update prices endpoint url

Co-authored-by: Maximilian Schneider <mail@maximilianschneider.net>
This commit is contained in:
saml33 2021-06-22 21:49:52 +10:00 committed by GitHub
parent 1157d55307
commit 8a59b00347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1473 additions and 1134 deletions

115
components/Chart.tsx Normal file
View File

@ -0,0 +1,115 @@
import { useState } from 'react'
import { AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar } from 'recharts'
import useDimensions from 'react-cool-dimensions'
const Chart = ({ title, xAxis, yAxis, data, labelFormat, type }) => {
const [mouseData, setMouseData] = useState<string | null>(null)
// @ts-ignore
const { observe, width, height } = useDimensions()
const handleMouseMove = (coords) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
return (
<div className="h-full w-full" ref={observe}>
<div className="absolute h-full w-full pb-4">
<div className="pb-0.5 text-xs text-th-fgd-3">{title}</div>
{mouseData ? (
<>
<div className="pb-1 text-xl text-th-fgd-1">
{labelFormat(mouseData[yAxis])}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{new Date(mouseData[xAxis]).toDateString()}
</div>
</>
) : data.length > 0 && data[data.length - 1][yAxis] ? (
<>
<div className="pb-1 text-xl text-th-fgd-1">
{labelFormat(data[data.length - 1][yAxis])}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{new Date(data[data.length - 1][xAxis]).toDateString()}
</div>
</>
) : (
<>
<div className="animate-pulse bg-th-bkg-3 h-8 mt-1 rounded w-48" />
<div className="animate-pulse bg-th-bkg-3 h-4 mt-1 rounded w-24" />
</>
)}
</div>
{width > 0 && type === 'area' ? (
<AreaChart
width={width}
height={height}
data={data}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
strokeOpacity: 0,
}}
content={<></>}
/>
<defs>
<linearGradient id="gradientArea" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#FF9C24" stopOpacity={0.5} />
<stop offset="100%" stopColor="#FF9C24" stopOpacity={0} />
</linearGradient>
</defs>
<Area
isAnimationActive={false}
type="monotone"
dataKey={yAxis}
stroke="#FF9C24"
fill="url(#gradientArea)"
/>
<XAxis dataKey={xAxis} hide />
<YAxis dataKey={yAxis} hide />
</AreaChart>
) : null}
{width > 0 && type === 'bar' ? (
<BarChart
width={width}
height={height}
data={data}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
fill: '#fff',
opacity: 0.2,
}}
content={<></>}
/>
<defs>
<linearGradient id="gradientBar" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#FF9C24" stopOpacity={1} />
<stop offset="100%" stopColor="#FF9C24" stopOpacity={0.5} />
</linearGradient>
</defs>
<Bar
isAnimationActive={false}
type="monotone"
dataKey={yAxis}
fill="url(#gradientBar)"
/>
<XAxis dataKey={xAxis} hide />
<YAxis dataKey={yAxis} hide />
</BarChart>
) : null}
</div>
)
}
export default Chart

View File

@ -61,17 +61,16 @@ export default function MarginInfo() {
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const tradeHistory = useTradeHistory()
const tradeHistoryLength = useMemo(() => tradeHistory.length, [tradeHistory])
const [mAccountInfo, setMAccountInfo] =
useState<
| {
label: string
value: string
unit: string
desc: string
currency: string
}[]
| null
>(null)
const [mAccountInfo, setMAccountInfo] = useState<
| {
label: string
value: string
unit: string
desc: string
currency: string
}[]
| null
>(null)
const [openAlertModal, setOpenAlertModal] = useState(false)
useEffect(() => {

View File

@ -0,0 +1,188 @@
import { useState } from 'react'
import useMangoStats from '../../hooks/useMangoStats'
import Chart from '../Chart'
const icons = {
BTC: '/assets/icons/btc.svg',
ETH: '/assets/icons/eth.svg',
SOL: '/assets/icons/sol.svg',
SRM: '/assets/icons/srm.svg',
USDT: '/assets/icons/usdt.svg',
USDC: '/assets/icons/usdc.svg',
WUSDT: '/assets/icons/usdt.svg',
}
export default function StatsAssets() {
const [selectedAsset, setSelectedAsset] = useState<string>('BTC')
const { latestStats, stats } = useMangoStats()
const selectedStatsData = stats.filter(
(stat) => stat.symbol === selectedAsset
)
return (
<>
<div className="flex flex-col-reverse items-center sm:flex-row sm:justify-between sm:h-12 mb-4 w-full">
<AssetHeader asset={selectedAsset} />
<div className="flex pb-4 sm:pb-0">
{latestStats.map((stat) => (
<div
className={`px-2 py-1 ml-2 rounded-md cursor-pointer default-transition bg-th-bkg-3
${
selectedAsset === stat.symbol
? `ring-1 ring-inset ring-th-primary text-th-primary`
: `text-th-fgd-1 opacity-50 hover:opacity-100`
}
`}
onClick={() => setSelectedAsset(stat.symbol)}
key={stat.symbol as string}
>
{stat.symbol}
</div>
))}
</div>
</div>
<div className="grid grid-flow-col grid-cols-1 grid-rows-4 md:grid-cols-2 md:grid-rows-2 gap-4 pb-8">
<div
className="border border-th-bkg-3 relative md:mb-0 p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Total Deposits"
xAxis="time"
yAxis="totalDeposits"
data={selectedStatsData}
labelFormat={(x) =>
x && x.toLocaleString(undefined, { maximumFractionDigits: 2 })
}
type="area"
/>
</div>
<div
className="border border-th-bkg-3 relative p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Deposit Interest"
xAxis="time"
yAxis="depositInterest"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
type="bar"
/>
</div>
<div
className="border border-th-bkg-3 relative md:mb-0 p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Total Borrows"
xAxis="time"
yAxis="totalBorrows"
data={selectedStatsData}
labelFormat={(x) =>
x && x.toLocaleString(undefined, { maximumFractionDigits: 2 })
}
type="area"
/>
</div>
<div
className="border border-th-bkg-3 relative p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Borrow Interest"
xAxis="time"
yAxis="borrowInterest"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
type="bar"
/>
</div>
</div>
</>
)
}
const AssetHeader = ({ asset }) => {
switch (asset) {
case 'BTC':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
Bitcoin
</div>
)
case 'ETH':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
Ethereum
</div>
)
case 'SOL':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
Solana
</div>
)
case 'SRM':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
Serum
</div>
)
case 'USDC':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
USD Coin
</div>
)
default:
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
Bitcoin
</div>
)
}
}

View File

@ -0,0 +1,215 @@
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
import { formatBalanceDisplay, tokenPrecision } from '../../utils/index'
import useMangoStats from '../../hooks/useMangoStats'
import useHistoricPrices from '../../hooks/useHistoricPrices'
import useMarketList from '../../hooks/useMarketList'
import useMangoStore from '../../stores/useMangoStore'
import Chart from '../Chart'
const icons = {
BTC: '/assets/icons/btc.svg',
ETH: '/assets/icons/eth.svg',
SOL: '/assets/icons/sol.svg',
SRM: '/assets/icons/srm.svg',
USDT: '/assets/icons/usdt.svg',
USDC: '/assets/icons/usdc.svg',
WUSDT: '/assets/icons/usdt.svg',
}
export default function StatsTotals() {
const { latestStats, stats } = useMangoStats()
const { prices } = useHistoricPrices()
const backupPrices = useMangoStore((s) => s.selectedMangoGroup.prices)
const { getTokenIndex, symbols } = useMarketList()
const startTimestamp = 1622905200000
const trimmedStats = stats.filter(
(stat) => new Date(stat.hourly).getTime() >= startTimestamp
)
// get deposit and borrow values from stats
const depositValues = []
const borrowValues = []
if (prices) {
for (let i = 0; i < trimmedStats.length; i++) {
// use the current price if no match for hour from the prices api
const price =
trimmedStats[i].symbol !== 'USDC' &&
prices[trimmedStats[i].symbol][trimmedStats[i].hourly]
? prices[trimmedStats[i].symbol][trimmedStats[i].hourly]
: backupPrices[getTokenIndex(symbols[trimmedStats[i].symbol])]
const depositValue =
trimmedStats[i].symbol === 'USDC'
? trimmedStats[i].totalDeposits
: trimmedStats[i].totalDeposits * price
const borrowValue =
trimmedStats[i].symbol === 'USDC'
? trimmedStats[i].totalBorrows
: trimmedStats[i].totalBorrows * price
depositValues.push({
symbol: trimmedStats[i].symbol,
value: depositValue,
time: trimmedStats[i].hourly,
})
borrowValues.push({
symbol: trimmedStats[i].symbol,
value: borrowValue,
time: trimmedStats[i].hourly,
})
}
}
const formatValues = (values) => {
// get value for each symbol every hour
const hours = values.reduce((acc, d) => {
const found = acc.find((a) => a.time === d.time && a.symbol === d.symbol)
const value = {
value: d.value,
symbol: d.symbol,
time: d.time,
}
if (!found) {
acc.push(value)
} else {
found.value = d.value
}
return acc
}, [])
// sum the values for each hour
const holder = {}
hours.forEach(function (d) {
if (d.time in holder) {
holder[d.time] = holder[d.time] + d.value
} else {
holder[d.time] = d.value
}
})
const points = []
for (const prop in holder) {
points.push({ time: prop, value: holder[prop] })
}
return points
}
return (
<>
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 gap-4 pb-8">
<div
className="border border-th-bkg-3 relative md:mb-0 p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Total Deposit Value"
xAxis="time"
yAxis="value"
data={formatValues(depositValues)}
labelFormat={(x) =>
x &&
'$' + x.toLocaleString(undefined, { maximumFractionDigits: 0 })
}
type="area"
/>
</div>
<div
className="border border-th-bkg-3 relative p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Total Borrow Value"
xAxis="time"
yAxis="value"
data={formatValues(borrowValues)}
labelFormat={(x) =>
x &&
'$' + x.toLocaleString(undefined, { maximumFractionDigits: 0 })
}
type="area"
/>
</div>
</div>
<div className="md:flex md:flex-col min-w-full">
<Table className="min-w-full divide-y divide-th-bkg-2">
<Thead>
<Tr className="text-th-fgd-3 text-xs">
<Th scope="col" className="px-6 py-3 text-left font-normal">
Asset
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Total Deposits
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Total Borrows
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Deposit Interest
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Borrow Interest
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Utilization
</Th>
</Tr>
</Thead>
<Tbody>
{latestStats.map((stat, index) => (
<Tr
key={stat.symbol}
className={`border-b border-th-bkg-2
${index % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
`}
>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
<div className="flex items-center">
<img
src={icons[stat.symbol]}
alt={icons[stat.symbol]}
width="20"
height="20"
className="mr-2.5"
/>
{stat.symbol}
</div>
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{formatBalanceDisplay(
stat.totalDeposits,
tokenPrecision[stat.symbol]
).toLocaleString(undefined, {
maximumFractionDigits: tokenPrecision[stat.symbol],
})}
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{formatBalanceDisplay(
stat.totalBorrows,
tokenPrecision[stat.symbol]
).toLocaleString(undefined, {
maximumFractionDigits: tokenPrecision[stat.symbol],
})}
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{stat.depositInterest.toFixed(2)}%
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{stat.borrowInterest.toFixed(2)}%
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{(parseFloat(stat.utilization) * 100).toFixed(2)}%
</Td>
</Tr>
))}
</Tbody>
</Table>
</div>
</>
)
}

View File

@ -0,0 +1,20 @@
import { useEffect, useState } from 'react'
const useHistoricPrices = () => {
const [prices, setPrices] = useState<any>(null)
useEffect(() => {
const fetchPrices = async () => {
const response = await fetch(
`https://mango-transaction-log.herokuapp.com/stats/prices/2oogpTYm1sp6LPZAWD3bp2wsFpnV2kXL1s52yyFhW5vp`
)
const prices = await response.json()
setPrices(prices)
}
fetchPrices()
}, [])
return { prices }
}
export default useHistoricPrices

69
hooks/useMangoStats.tsx Normal file
View File

@ -0,0 +1,69 @@
import { useEffect, useState } from 'react'
import { IDS, MangoClient } from '@blockworks-foundation/mango-client'
import { PublicKey, Connection } from '@solana/web3.js'
import useConnection from './useConnection'
import { DEFAULT_MANGO_GROUP } from '../utils/mango'
const useMangoStats = () => {
const [stats, setStats] = useState([
{
symbol: '',
hourly: '',
depositInterest: 0,
borrowInterest: 0,
totalDeposits: 0,
totalBorrows: 0,
utilization: '0',
},
])
const [latestStats, setLatestStats] = useState<any[]>([])
const { cluster } = useConnection()
useEffect(() => {
const fetchStats = async () => {
const response = await fetch(
`https://mango-stats.herokuapp.com?mangoGroup=BTC_ETH_SOL_SRM_USDC`
)
const stats = await response.json()
setStats(stats)
}
fetchStats()
}, [])
useEffect(() => {
const getLatestStats = async () => {
const client = new MangoClient()
const connection = new Connection(
IDS.cluster_urls[cluster],
'singleGossip'
)
const assets = IDS[cluster].mango_groups?.[DEFAULT_MANGO_GROUP]?.symbols
const mangoGroupId =
IDS[cluster].mango_groups?.[DEFAULT_MANGO_GROUP]?.mango_group_pk
if (!mangoGroupId) return
const mangoGroupPk = new PublicKey(mangoGroupId)
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
const latestStats = Object.keys(assets).map((symbol, index) => {
const totalDeposits = mangoGroup.getUiTotalDeposit(index)
const totalBorrows = mangoGroup.getUiTotalBorrow(index)
return {
time: new Date(),
symbol,
totalDeposits,
totalBorrows,
depositInterest: mangoGroup.getDepositRate(index) * 100,
borrowInterest: mangoGroup.getBorrowRate(index) * 100,
utilization: totalDeposits > 0.0 ? totalBorrows / totalDeposits : 0.0,
}
})
setLatestStats(latestStats)
}
getLatestStats()
}, [cluster])
return { latestStats, stats }
}
export default useMangoStats

View File

@ -62,18 +62,17 @@ const useMarginInfo = () => {
)
const tradeHistory = useTradeHistory()
const tradeHistoryLength = useMemo(() => tradeHistory.length, [tradeHistory])
const [mAccountInfo, setMAccountInfo] =
useState<
| {
label: string
value: string
unit: string
desc: string
currency: string
icon: ReactNode
}[]
| null
>(null)
const [mAccountInfo, setMAccountInfo] = useState<
| {
label: string
value: string
unit: string
desc: string
currency: string
icon: ReactNode
}[]
| null
>(null)
useEffect(() => {
if (selectedMangoGroup) {

View File

@ -1,342 +1,68 @@
import { useEffect, useState } from 'react'
import { LineChart, Line, ReferenceLine, XAxis, YAxis, Tooltip } from 'recharts'
import useDimensions from 'react-cool-dimensions'
import { IDS, MangoClient } from '@blockworks-foundation/mango-client'
import { PublicKey, Connection } from '@solana/web3.js'
import { DEFAULT_MANGO_GROUP } from '../utils/mango'
import useConnection from '../hooks/useConnection'
import { useState } from 'react'
import TopBar from '../components/TopBar'
import { formatBalanceDisplay } from '../utils/index'
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
import PageBodyContainer from '../components/PageBodyContainer'
import StatsTotals from '../components/stats-page/StatsTotals'
import StatsAssets from '../components/stats-page/StatsAssets'
const DECIMALS = {
BTC: 4,
ETH: 3,
SOL: 2,
SRM: 2,
USDT: 2,
USDC: 2,
}
const icons = {
BTC: '/assets/icons/btc.svg',
ETH: '/assets/icons/eth.svg',
SOL: '/assets/icons/sol.svg',
SRM: '/assets/icons/srm.svg',
USDT: '/assets/icons/usdt.svg',
USDC: '/assets/icons/usdc.svg',
WUSDT: '/assets/icons/usdt.svg',
}
const useMangoStats = () => {
const [stats, setStats] = useState([
{
symbol: '',
depositInterest: 0,
borrowInterest: 0,
totalDeposits: 0,
totalBorrows: 0,
utilization: '0',
},
])
const [latestStats, setLatestStats] = useState<any[]>([])
const { cluster } = useConnection()
useEffect(() => {
const fetchStats = async () => {
const response = await fetch(
`https://mango-stats.herokuapp.com?mangoGroup=BTC_ETH_SOL_SRM_USDC`
)
const stats = await response.json()
setStats(stats)
}
fetchStats()
}, [])
useEffect(() => {
const getLatestStats = async () => {
const client = new MangoClient()
const connection = new Connection(
IDS.cluster_urls[cluster],
'singleGossip'
)
const assets = IDS[cluster].mango_groups?.[DEFAULT_MANGO_GROUP]?.symbols
const mangoGroupId =
IDS[cluster].mango_groups?.[DEFAULT_MANGO_GROUP]?.mango_group_pk
if (!mangoGroupId) return
const mangoGroupPk = new PublicKey(mangoGroupId)
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
const latestStats = Object.keys(assets).map((symbol, index) => {
const totalDeposits = mangoGroup.getUiTotalDeposit(index)
const totalBorrows = mangoGroup.getUiTotalBorrow(index)
return {
time: new Date(),
symbol,
totalDeposits,
totalBorrows,
depositInterest: mangoGroup.getDepositRate(index) * 100,
borrowInterest: mangoGroup.getBorrowRate(index) * 100,
utilization: totalDeposits > 0.0 ? totalBorrows / totalDeposits : 0.0,
}
})
setLatestStats(latestStats)
}
getLatestStats()
}, [cluster])
return { latestStats, stats }
}
const StatsChart = ({ title, xAxis, yAxis, data, labelFormat }) => {
const [mouseData, setMouseData] = useState<string | null>(null)
// @ts-ignore
const { observe, width, height } = useDimensions()
const handleMouseMove = (coords) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
return (
<div className="h-full w-full" ref={observe}>
<div className="absolute -top-4 left-0 h-full w-full pb-4">
<div className="text-center text-th-fgd-1 text-base font-semibold">
{title}
</div>
{mouseData ? (
<div className="text-center pt-1">
<div className="text-sm font-normal text-th-fgd-3">
{labelFormat(mouseData[yAxis])}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{new Date(mouseData[xAxis]).toDateString()}
</div>
</div>
) : null}
</div>
{width > 0 ? (
<LineChart
width={width}
height={height}
margin={{ top: 50, right: 50 }}
data={data}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
stroke: '#f7f7f7',
strokeWidth: 1.5,
strokeOpacity: 0.3,
strokeDasharray: '6 5',
}}
content={<></>}
/>
<Line
isAnimationActive={false}
type="linear"
dot={false}
dataKey={yAxis}
stroke="#f2c94c"
strokeWidth={2}
/>
{mouseData ? (
<ReferenceLine
y={mouseData[yAxis]}
strokeDasharray="6 5"
strokeWidth={1.5}
strokeOpacity={0.3}
/>
) : null}
<XAxis dataKey={xAxis} hide />
<YAxis dataKey={yAxis} hide />
</LineChart>
) : null}
</div>
)
}
const TABS = [
'Totals',
'Assets',
// 'Markets',
// 'Liquidations',
]
export default function StatsPage() {
const [selectedAsset, setSelectedAsset] = useState<string>('BTC')
const { latestStats, stats } = useMangoStats()
const [activeTab, setActiveTab] = useState(TABS[0])
const selectedStatsData = stats.filter(
(stat) => stat.symbol === selectedAsset
)
const handleTabChange = (tabName) => {
setActiveTab(tabName)
}
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all `}>
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<div className="min-h-screen w-full xl:w-3/4 mx-auto px-4 sm:px-6 sm:py-1 md:px-8 md:py-1 lg:px-12">
<div className="text-center pt-8 pb-6 md:pt-10">
<h1 className={`text-th-fgd-1 text-2xl font-semibold`}>
Mango Stats
</h1>
<PageBodyContainer>
<div className="flex flex-col sm:flex-row pt-8 pb-3 sm:pb-6 md:pt-10">
<h1 className={`text-th-fgd-1 text-2xl font-semibold`}>Stats</h1>
</div>
<div className="md:flex md:flex-col min-w-full">
<Table className="min-w-full divide-y divide-th-bkg-2">
<Thead>
<Tr className="text-th-fgd-3">
<Th scope="col" className="px-6 py-3 text-left font-normal">
Asset
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Total Deposits
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Total Borrows
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Deposit Interest
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Borrow Interest
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Utilization
</Th>
</Tr>
</Thead>
<Tbody>
{latestStats.map((stat, index) => (
<Tr
key={stat.symbol}
className={`border-b border-th-bkg-2
${index % 2 === 0 ? `bg-th-bkg-2` : `bg-th-bkg-1`}
<div className="bg-th-bkg-2 overflow-none p-6 rounded-lg">
<div className="border-b border-th-fgd-4 mb-4">
<nav className={`-mb-px flex space-x-6`} aria-label="Tabs">
{TABS.map((tabName) => (
<a
key={tabName}
onClick={() => handleTabChange(tabName)}
className={`whitespace-nowrap pb-4 px-1 border-b-2 font-semibold cursor-pointer default-transition hover:opacity-100
${
activeTab === tabName
? `border-th-primary text-th-primary`
: `border-transparent text-th-fgd-4 hover:text-th-primary`
}
`}
>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
<div className="flex items-center">
<img
src={icons[stat.symbol]}
alt={icons[stat.symbol]}
className="w-5 h-5 md:w-6 md:h-6"
/>
<button
onClick={() => setSelectedAsset(stat.symbol)}
className="underline cursor-pointer ml-3 hover:text-th-primary hover:no-underline"
>
{stat.symbol}
</button>
</div>
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{formatBalanceDisplay(
stat.totalDeposits,
DECIMALS[stat.symbol]
).toLocaleString(undefined, {
maximumFractionDigits: DECIMALS[stat.symbol],
})}
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{formatBalanceDisplay(
stat.totalBorrows,
DECIMALS[stat.symbol]
).toLocaleString(undefined, {
maximumFractionDigits: DECIMALS[stat.symbol],
})}
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{stat.depositInterest.toFixed(2)}%
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{stat.borrowInterest.toFixed(2)}%
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{(parseFloat(stat.utilization) * 100).toFixed(2)}%
</Td>
</Tr>
{tabName}
</a>
))}
</Tbody>
</Table>
</div>
{selectedAsset ? (
<div className="py-10 md:py-14">
<div className="flex flex-col items-center pb-12">
<h2 className="text-th-fgd-1 text-center text-2xl font-semibold mb-4">
Historical Stats
</h2>
<div className="flex self-center">
{latestStats.map((stat) => (
<div
className={`px-2 py-1 mr-2 rounded-md cursor-pointer default-transition bg-th-bkg-3
${
selectedAsset === stat.symbol
? `text-th-primary`
: `text-th-fgd-1 opacity-50 hover:opacity-100`
}
`}
onClick={() => setSelectedAsset(stat.symbol)}
key={stat.symbol as string}
>
{stat.symbol}
</div>
))}
</div>
</div>
<div className="flex flex-col md:flex-row pb-14">
<div
className="relative my-2 pb-14 md:pb-0 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Total Deposits"
xAxis="time"
yAxis="totalDeposits"
data={selectedStatsData}
labelFormat={(x) => x.toFixed(DECIMALS[selectedAsset])}
/>
</div>
<div
className="relative my-2 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Total Borrows"
xAxis="time"
yAxis="totalBorrows"
data={selectedStatsData}
labelFormat={(x) => x.toFixed(DECIMALS[selectedAsset])}
/>
</div>
</div>
<div className="flex flex-col md:flex-row">
<div
className="relative my-2 pb-14 md:pb-0 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Deposit Interest"
xAxis="time"
yAxis="depositInterest"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
/>
</div>
<div
className="relative my-2 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Borrow Interest"
xAxis="time"
yAxis="borrowInterest"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
/>
</div>
</div>
</nav>
</div>
) : null}
</div>
<TabContent activeTab={activeTab} />
</div>
</PageBodyContainer>
</div>
)
}
const TabContent = ({ activeTab }) => {
switch (activeTab) {
case 'Totals':
return <StatsTotals />
case 'Assets':
return <StatsAssets />
case 'Markets':
return <div>Markets</div>
case 'Liquidations':
return <div>Liquidations</div>
default:
return <StatsTotals />
}
}

1578
yarn.lock

File diff suppressed because it is too large Load Diff