diff --git a/web/src/hooks/useCoinPrice.tsx b/web/src/hooks/useCoinPrice.tsx index 1fc6aff61..8f95ce652 100644 --- a/web/src/hooks/useCoinPrice.tsx +++ b/web/src/hooks/useCoinPrice.tsx @@ -1,9 +1,12 @@ import { useQuery } from "@tanstack/react-query"; -const fetchCoinPrices = async (...coinIds) => { - const response = await fetch(`https://coins.llama.fi/prices/current/${coinIds.join(",")}?searchWidth=1h`); +const fetchCoinPrices = async (coinIds: readonly string[]): Promise => { + const ids = Array.from(new Set(coinIds)).filter(Boolean).sort().map(encodeURIComponent).join(","); + if (!ids) return {}; + const response = await fetch(`https://coins.llama.fi/prices/current/${ids}?searchWidth=1h`); + if (!response.ok) throw new Error(`Failed to fetch coin prices (${response.status})`); const data = await response.json(); - return data.coins; + return (data?.coins ?? {}) as Prices; }; export type Prices = { @@ -11,15 +14,20 @@ export type Prices = { }; export const useCoinPrice = (coinIds: string[]) => { - const isEnabled = coinIds !== undefined; - - const { data: prices, isError } = useQuery({ - queryKey: [`coinPrice${coinIds}`], - enabled: isEnabled, - queryFn: async () => fetchCoinPrices(coinIds), + const { + data: prices, + isError, + isLoading, + error, + } = useQuery({ + queryKey: ["coinPrice", coinIds], + enabled: Array.isArray(coinIds) && coinIds.length > 0, + queryFn: () => fetchCoinPrices(coinIds), }); return { prices, isError, + isLoading, + error, }; }; diff --git a/web/src/pages/Courts/CourtDetails/Stats/index.tsx b/web/src/pages/Courts/CourtDetails/Stats/index.tsx index 0ae92d7f2..aea9057ce 100644 --- a/web/src/pages/Courts/CourtDetails/Stats/index.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats/index.tsx @@ -14,6 +14,7 @@ import { useCourtDetails } from "queries/useCourtDetails"; import { landscapeStyle } from "styles/landscapeStyle"; import StatsContent from "./StatsContent"; +import Spinner from "components/Spinner"; const Container = styled.div` padding: 0 24px 12px 24px; @@ -50,13 +51,31 @@ const StyledAccordion = styled(Accordion)` )} `; +const ErrorMessage = styled.div` + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + color: ${({ theme }) => theme.error}; + font-size: 16px; + font-weight: 500; +`; + const Stats = () => { const { id } = useParams(); - const { data } = useCourtDetails(id); + const { data, isLoading: isLoadingCourt, error: courtError } = useCourtDetails(id); const coinIds = [CoinIds.PNK, CoinIds.ETH]; - const { prices: pricesData } = useCoinPrice(coinIds); + const { prices: pricesData, isLoading: isLoadingPrices, error: pricesError } = useCoinPrice(coinIds); const isDesktop = useIsDesktop(); + if (isLoadingCourt || isLoadingPrices) { + return ; + } + + if (courtError || pricesError) { + return Failed to load statistics; + } + return isDesktop ? (
Statistics
@@ -75,4 +94,4 @@ const Stats = () => { ); }; -export default Stats; +export default Stats; \ No newline at end of file