diff --git a/components/Chart.tsx b/components/Chart.tsx index 6c826773..77a5a0bc 100644 --- a/components/Chart.tsx +++ b/components/Chart.tsx @@ -30,6 +30,7 @@ interface ChartProps { titleValue?: number useMulticoloredBars?: boolean zeroLine?: boolean + loading?: boolean } const Chart: FunctionComponent = ({ @@ -47,6 +48,7 @@ const Chart: FunctionComponent = ({ titleValue, useMulticoloredBars, zeroLine, + loading, }) => { const [mouseData, setMouseData] = useState(null) const [daysToShow, setDaysToShow] = useState(daysRange || 30) @@ -83,268 +85,292 @@ const Chart: FunctionComponent = ({ return (
-
-
-
{title}
- {mouseData ? ( - <> -
- {labelFormat(mouseData[yAxis])} + {data.length > 0 ? ( + <> +
+
+
{title}
+ {mouseData ? ( + <> +
+ {labelFormat(mouseData[yAxis])} +
+
+ {dayjs(mouseData[xAxis]).format('ddd MMM D YYYY, h:mma')} +
+ + ) : ( + <> +
+ {titleValue + ? labelFormat(titleValue) + : labelFormat(data[data.length - 1][yAxis])} +
+
+ {titleValue + ? '' + : dayjs(data[data.length - 1][xAxis]).format( + 'ddd MMM D YYYY, h:mma' + )} +
+ + )} +
+ {!hideRangeFilters ? ( +
+ + + + {showAll ? ( + + ) : null}
-
- {dayjs(mouseData[xAxis]).format('ddd MMM D YYYY, h:mma')} -
- - ) : data.length > 0 ? ( - <> -
- {titleValue - ? labelFormat(titleValue) - : labelFormat(data[data.length - 1][yAxis])} -
-
- {titleValue - ? '' - : dayjs(data[data.length - 1][xAxis]).format( - 'ddd MMM D YYYY, h:mma' - )} -
- - ) : ( - <> -
-
- - )} -
- {!hideRangeFilters ? ( -
- - - - {showAll ? ( - ) : null}
- ) : null} -
- {width > 0 && type === 'area' ? ( - - } - /> - - - - - - - - 0 ? false : true} - dy={10} - minTickGap={20} - tick={{ - fill: - theme === 'Light' - ? 'rgba(0,0,0,0.4)' - : 'rgba(255,255,255,0.35)', - fontSize: 10, - }} - tickLine={false} - tickFormatter={(v) => formatDateAxis(v)} - /> - 0 ? false : true} - dx={-10} - domain={['dataMin', 'dataMax']} - tick={{ - fill: - theme === 'Light' - ? 'rgba(0,0,0,0.4)' - : 'rgba(255,255,255,0.35)', - fontSize: 10, - }} - tickLine={false} - tickFormatter={ - tickFormat - ? (v) => tickFormat(v) - : (v) => numberCompactFormatter.format(v) - } - type="number" - width={yAxisWidth || 50} - /> - {zeroLine ? ( - - ) : null} - - ) : null} - {width > 0 && type === 'bar' ? ( - - } - /> - - - - - - - 0 && type === 'area' ? ( + + } /> - + + + + + + - - - 0 ? false : true} + dy={10} + minTickGap={20} + tick={{ + fill: + theme === 'Light' + ? 'rgba(0,0,0,0.4)' + : 'rgba(255,255,255,0.35)', + fontSize: 10, + }} + tickLine={false} + tickFormatter={(v) => formatDateAxis(v)} /> - - - - - {data.map((entry, index) => ( - 0 - ? 'url(#greenGradientBar)' - : 'url(#redGradientBar)' - : 'url(#defaultGradientBar)' + 0 ? false : true} + dx={-10} + domain={['dataMin', 'dataMax']} + tick={{ + fill: + theme === 'Light' + ? 'rgba(0,0,0,0.4)' + : 'rgba(255,255,255,0.35)', + fontSize: 10, + }} + tickLine={false} + tickFormatter={ + tickFormat + ? (v) => tickFormat(v) + : (v) => numberCompactFormatter.format(v) } + type="number" + width={yAxisWidth || 50} /> - ))} - - 0 ? false : true} - dy={10} - minTickGap={20} - tick={{ - fill: - theme === 'Light' - ? 'rgba(0,0,0,0.4)' - : 'rgba(255,255,255,0.35)', - fontSize: 10, - }} - tickLine={false} - tickFormatter={(v) => formatDateAxis(v)} - /> - 0 ? false : true} - dx={-10} - tick={{ - fill: - theme === 'Light' - ? 'rgba(0,0,0,0.4)' - : 'rgba(255,255,255,0.35)', - fontSize: 10, - }} - tickLine={false} - tickFormatter={ - tickFormat - ? (v) => tickFormat(v) - : (v) => numberCompactFormatter.format(v) - } - type="number" - width={yAxisWidth || 50} - /> - {zeroLine ? ( - + {zeroLine ? ( + + ) : null} + ) : null} - - ) : null} + {width > 0 && type === 'bar' ? ( + + } + /> + + + + + + + + + + + + + + + + {data.map((entry, index) => ( + 0 + ? 'url(#greenGradientBar)' + : 'url(#redGradientBar)' + : 'url(#defaultGradientBar)' + } + /> + ))} + + 0 ? false : true} + dy={10} + minTickGap={20} + tick={{ + fill: + theme === 'Light' + ? 'rgba(0,0,0,0.4)' + : 'rgba(255,255,255,0.35)', + fontSize: 10, + }} + tickLine={false} + tickFormatter={(v) => formatDateAxis(v)} + /> + 0 ? false : true} + dx={-10} + tick={{ + fill: + theme === 'Light' + ? 'rgba(0,0,0,0.4)' + : 'rgba(255,255,255,0.35)', + fontSize: 10, + }} + tickLine={false} + tickFormatter={ + tickFormat + ? (v) => tickFormat(v) + : (v) => numberCompactFormatter.format(v) + } + type="number" + width={yAxisWidth || 50} + /> + {zeroLine ? ( + + ) : null} + + ) : null} + + ) : loading ? ( + <> +
+
+ + ) : ( +
+

Chart not available

+
+ )}
) } diff --git a/components/TradeHistoryFilterModal.tsx b/components/TradeHistoryFilterModal.tsx index dc95a906..6f003426 100644 --- a/components/TradeHistoryFilterModal.tsx +++ b/components/TradeHistoryFilterModal.tsx @@ -89,8 +89,7 @@ const TradeHistoryFilterModal: FunctionComponent< return { ...prevSelected, size: { - condition: (size) => - parseFloat(size) >= from && parseFloat(size) <= to, + condition: (size) => size >= from && size <= to, values: { from: from, to: to }, }, } diff --git a/components/stats_page/StatsAssets.tsx b/components/stats_page/StatsAssets.tsx index 674640a8..7bd48f29 100644 --- a/components/stats_page/StatsAssets.tsx +++ b/components/stats_page/StatsAssets.tsx @@ -1,13 +1,22 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import Chart from '../Chart' import { useTranslation } from 'next-i18next' import TabButtons from 'components/TabButtons' -export default function StatsAssets({ latestStats, stats }) { +export default function StatsAssets({ + latestStats, + stats, + loadHistoricalStats, +}) { const { t } = useTranslation('common') const [selectedAsset, setSelectedAsset] = useState('BTC') - const selectedStatsData = stats.filter((stat) => stat.name === selectedAsset) + const selectedStatsData = useMemo(() => { + if (stats.length) { + return stats.filter((stat) => stat.name === selectedAsset) + } + return [] + }, [stats, selectedAsset]) return ( <> @@ -46,6 +55,7 @@ export default function StatsAssets({ latestStats, stats }) { x.toLocaleString(undefined, { maximumFractionDigits: 2 }) } type="area" + loading={loadHistoricalStats} />
diff --git a/components/stats_page/StatsPerps.tsx b/components/stats_page/StatsPerps.tsx index 9b1b0d06..b04018f2 100644 --- a/components/stats_page/StatsPerps.tsx +++ b/components/stats_page/StatsPerps.tsx @@ -36,7 +36,7 @@ function calculateFundingRate( return (fundingInQuoteDecimals / basePriceInBaseLots) * 100 } -export default function StatsPerps({ perpStats }) { +export default function StatsPerps({ perpStats, loadPerpStats }) { const { t } = useTranslation('common') const [selectedAsset, setSelectedAsset] = useState('BTC-PERP') const marketConfigs = useMangoStore( @@ -61,7 +61,7 @@ export default function StatsPerps({ perpStats }) { }, [selectedMarketConfig, perpMarkets]) const perpsData = useMemo(() => { - if (perpStats.length === 0 || !selectedMarket) return [] + if (!perpStats.length || !selectedMarket) return [] let selectedStatsData = perpStats.filter( (stat) => stat.name === selectedAsset @@ -160,6 +160,7 @@ export default function StatsPerps({ perpStats }) { } type="area" yAxisWidth={70} + loading={loadPerpStats} />
) : null}
diff --git a/components/stats_page/StatsTotals.tsx b/components/stats_page/StatsTotals.tsx index 7c92f239..87f904cb 100644 --- a/components/stats_page/StatsTotals.tsx +++ b/components/stats_page/StatsTotals.tsx @@ -32,7 +32,7 @@ const getAverageStats = ( symbol: string, type: string ): string => { - if (stats?.length) { + if (stats?.length > 0) { const priorDate = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000) const selectedStatsData = stats.filter((s) => s.name === symbol) const timeFilteredStats = selectedStatsData.filter( @@ -55,7 +55,11 @@ const getAverageStats = ( return '-' } -export default function StatsTotals({ latestStats, stats }) { +export default function StatsTotals({ + latestStats, + stats, + loadHistoricalStats, +}) { const { t } = useTranslation('common') const { width } = useViewport() const isMobile = width ? width < breakpoints.sm : false @@ -64,7 +68,7 @@ export default function StatsTotals({ latestStats, stats }) { const [depositValues, borrowValues]: [Values[], Values[]] = useMemo(() => { const depositValues: Values[] = [] const borrowValues: Values[] = [] - for (let i = 0; i < stats.length; i++) { + for (let i = 0; i < stats?.length; i++) { const time = stats[i].hourly const name = stats[i].name const depositValue = @@ -149,6 +153,7 @@ export default function StatsTotals({ latestStats, stats }) { '$' + x.toLocaleString(undefined, { maximumFractionDigits: 0 }) } type="area" + loading={loadHistoricalStats} />
@@ -250,9 +256,9 @@ export default function StatsTotals({ latestStats, stats }) { )}
-
-

{t('average-deposit')}

- {stats.length > 1 ? ( + {stats?.length > 0 ? ( +
+

{t('average-deposit')}

@@ -290,60 +296,62 @@ export default function StatsTotals({ latestStats, stats }) { ))}
- ) : ( - <> -
-
-
- - )} -
-

{t('average-borrow')}

- {stats.length > 1 ? ( - - - - - - - - - - - {latestStats.map((stat) => ( - - - - - - - ))} - -
{t('asset')}24h7d30d
-
- - {stat.name} -
-
- {getAverageStats(stats, 1, stat.name, 'borrowIndex')} - - {getAverageStats(stats, 7, stat.name, 'borrowIndex')} - - {getAverageStats(stats, 30, stat.name, 'borrowIndex')} -
- ) : ( +
+ ) : loadHistoricalStats ? ( <>
- )} + ) : null} + {stats?.length > 0 ? ( + <> +

{t('average-borrow')}

+ + + + + + + + + + + {latestStats.map((stat) => ( + + + + + + + ))} + +
{t('asset')}24h7d30d
+
+ + {stat.name} +
+
+ {getAverageStats(stats, 1, stat.name, 'borrowIndex')} + + {getAverageStats(stats, 7, stat.name, 'borrowIndex')} + + {getAverageStats(stats, 30, stat.name, 'borrowIndex')} +
+ + ) : loadHistoricalStats ? ( + <> +
+
+
+ + ) : null} ) : ( <> @@ -416,80 +424,92 @@ export default function StatsTotals({ latestStats, stats }) { /> ))}
-
-

{t('average-deposit')}

- {stats.length > 1 - ? latestStats.map((stat) => ( - -
-
-
- - {stat.name} -
-
-
-
24h
- {getAverageStats(stats, 1, stat.name, 'depositIndex')} -
-
-
7d
- {getAverageStats(stats, 7, stat.name, 'depositIndex')} -
-
-
30d
- {getAverageStats(stats, 30, stat.name, 'depositIndex')} + {stats?.length > 0 ? ( +
+

{t('average-deposit')}

+ {latestStats.map((stat) => ( + +
+
+
+ + {stat.name}
- - )) - : null} -
-
-

{t('average-borrow')}

- {stats.length > 1 - ? latestStats.map((stat) => ( - -
-
-
- - {stat.name} -
-
-
-
24h
- {getAverageStats(stats, 1, stat.name, 'borrowIndex')} -
-
-
7d
- {getAverageStats(stats, 7, stat.name, 'borrowIndex')} -
-
-
30d
- {getAverageStats(stats, 30, stat.name, 'borrowIndex')} +
+
24h
+ {getAverageStats(stats, 1, stat.name, 'depositIndex')} +
+
+
7d
+ {getAverageStats(stats, 7, stat.name, 'depositIndex')} +
+
+
30d
+ {getAverageStats(stats, 30, stat.name, 'depositIndex')} +
+
+ + ))} +
+ ) : loadHistoricalStats ? ( + <> +
+
+
+ + ) : null} + {stats?.length > 0 ? ( +
+

{t('average-borrow')}

+ {latestStats.map((stat) => ( + +
+
+
+ + {stat.name}
- - )) - : null} -
+
+
24h
+ {getAverageStats(stats, 1, stat.name, 'borrowIndex')} +
+
+
7d
+ {getAverageStats(stats, 7, stat.name, 'borrowIndex')} +
+
+
30d
+ {getAverageStats(stats, 30, stat.name, 'borrowIndex')} +
+
+ + ))} +
+ ) : loadHistoricalStats ? ( + <> +
+
+
+ + ) : null} )} diff --git a/hooks/useMangoStats.tsx b/hooks/useMangoStats.tsx index 372b6826..ff1c8bdc 100644 --- a/hooks/useMangoStats.tsx +++ b/hooks/useMangoStats.tsx @@ -29,6 +29,8 @@ const useMangoStats = () => { }, ]) const [latestStats, setLatestStats] = useState([]) + const [loadHistoricalStats, setLoadHistoricalStats] = useState(false) + const [loadPerpStats, setLoadPerpStats] = useState(false) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoGroupName = useMangoStore((s) => s.selectedMangoGroup.name) const connection = useMangoStore((s) => s.connection.current) @@ -36,22 +38,34 @@ const useMangoStats = () => { useEffect(() => { const fetchHistoricalStats = async () => { - const response = await fetch( - `https://mango-transaction-log.herokuapp.com/v3/stats/spot_stats_hourly?mango-group=${mangoGroupName}` - ) - const stats = await response.json() - setStats(stats) + try { + setLoadHistoricalStats(true) + const response = await fetch( + `https://mango-transaction-log.herokuapp.com/v3/stats/spot_stats_hourly?mango-group=${mangoGroupName}` + ) + const stats = await response.json() + setStats(stats) + setLoadHistoricalStats(false) + } catch { + setLoadHistoricalStats(false) + } } fetchHistoricalStats() }, [mangoGroupName]) useEffect(() => { const fetchHistoricalPerpStats = async () => { - const response = await fetch( - `https://mango-transaction-log.herokuapp.com/v3/stats/perp_stats_hourly?mango-group=${mangoGroupName}` - ) - const stats = await response.json() - setPerpStats(stats) + try { + setLoadPerpStats(true) + const response = await fetch( + `https://mango-transaction-log.herokuapp.com/v3/stats/perp_stats_hourly?mango-group=${mangoGroupName}` + ) + const stats = await response.json() + setPerpStats(stats) + setLoadPerpStats(false) + } catch { + setLoadPerpStats(false) + } } fetchHistoricalPerpStats() }, [mangoGroupName]) @@ -101,7 +115,7 @@ const useMangoStats = () => { getLatestStats() }, [mangoGroup]) - return { latestStats, stats, perpStats } + return { latestStats, stats, perpStats, loadHistoricalStats, loadPerpStats } } export default useMangoStats diff --git a/pages/stats.tsx b/pages/stats.tsx index bb1e0380..a24c8a7c 100644 --- a/pages/stats.tsx +++ b/pages/stats.tsx @@ -24,14 +24,9 @@ export async function getStaticProps({ locale }) { export default function StatsPage() { const { t } = useTranslation('common') - const TABS = [ - 'Totals', - 'Assets', - 'Perps', - // 'Markets', - // 'Liquidations', - ] - const { latestStats, stats, perpStats } = useMangoStats() + const TABS = ['Totals', 'Assets', 'Perps'] + const { latestStats, stats, perpStats, loadHistoricalStats, loadPerpStats } = + useMangoStats() const [viewIndex, setViewIndex] = useState(0) const [activeTab, setActiveTab] = useState(TABS[0]) const { width } = useViewport() @@ -51,49 +46,82 @@ export default function StatsPage() { } return ( -
-
+
+

{t('stats')}

-
- {!isMobile ? ( - - ) : ( - - )} - {!isMobile ? ( - + ) : ( + + )} + {!isMobile ? ( + + ) : ( + + - ) : ( - - - - - - )} -
+ + + + )}
) } -const TabContent = ({ activeTab, latestStats, perpStats, stats }) => { +const TabContent = ({ + activeTab, + latestStats, + perpStats, + stats, + loadHistoricalStats, + loadPerpStats, +}) => { switch (activeTab) { case 'Totals': - return + return ( + + ) case 'Assets': - return + return ( + + ) case 'Perps': - return + return default: - return + return ( + + ) } }