From 9a66ffb2fb1cae00d0ed0c87563f11bead3d6002 Mon Sep 17 00:00:00 2001 From: Tomiife Date: Sat, 26 Sep 2026 12:46:21 +0100 Subject: [PATCH] fix: consistent number formatting, per-route error boundaries, lazy soroban loading, and refetch indicator --- src/app/airdrops/error.tsx | 10 ++++ src/app/alerts/error.tsx | 10 ++++ src/app/contributors/error.tsx | 10 ++++ src/app/farm/error.tsx | 10 ++++ src/app/history/error.tsx | 10 ++++ src/app/leaderboard/error.tsx | 10 ++++ src/app/prices/error.tsx | 10 ++++ src/app/webhooks/error.tsx | 10 ++++ src/components/AppShell/AppShell.tsx | 2 + .../RefetchIndicator/RefetchIndicator.tsx | 46 ++++++++++++++++++ src/components/RouteError/RouteError.tsx | 48 +++++++++++++++++++ src/components/TvlChart/TvlChart.tsx | 5 +- src/hooks/useLeaderboard.ts | 6 ++- src/hooks/useSorobanQuery.ts | 41 ++++++++-------- src/lib/soroban.ts | 11 +++-- 15 files changed, 212 insertions(+), 27 deletions(-) create mode 100644 src/app/airdrops/error.tsx create mode 100644 src/app/alerts/error.tsx create mode 100644 src/app/contributors/error.tsx create mode 100644 src/app/farm/error.tsx create mode 100644 src/app/history/error.tsx create mode 100644 src/app/leaderboard/error.tsx create mode 100644 src/app/prices/error.tsx create mode 100644 src/app/webhooks/error.tsx create mode 100644 src/components/RefetchIndicator/RefetchIndicator.tsx create mode 100644 src/components/RouteError/RouteError.tsx diff --git a/src/app/airdrops/error.tsx b/src/app/airdrops/error.tsx new file mode 100644 index 0000000..c72dcf3 --- /dev/null +++ b/src/app/airdrops/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function AirdropsError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/alerts/error.tsx b/src/app/alerts/error.tsx new file mode 100644 index 0000000..de4f6ae --- /dev/null +++ b/src/app/alerts/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function AlertsError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/contributors/error.tsx b/src/app/contributors/error.tsx new file mode 100644 index 0000000..6a6dea0 --- /dev/null +++ b/src/app/contributors/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function ContributorsError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/farm/error.tsx b/src/app/farm/error.tsx new file mode 100644 index 0000000..440cf6d --- /dev/null +++ b/src/app/farm/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function FarmError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/history/error.tsx b/src/app/history/error.tsx new file mode 100644 index 0000000..d0db251 --- /dev/null +++ b/src/app/history/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function HistoryError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/leaderboard/error.tsx b/src/app/leaderboard/error.tsx new file mode 100644 index 0000000..09815c1 --- /dev/null +++ b/src/app/leaderboard/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function LeaderboardError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/prices/error.tsx b/src/app/prices/error.tsx new file mode 100644 index 0000000..eb0e3c1 --- /dev/null +++ b/src/app/prices/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function PricesError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/app/webhooks/error.tsx b/src/app/webhooks/error.tsx new file mode 100644 index 0000000..1b1077d --- /dev/null +++ b/src/app/webhooks/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import RouteError from "@/components/RouteError/RouteError"; + +export default function WebhooksError(props: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ; +} diff --git a/src/components/AppShell/AppShell.tsx b/src/components/AppShell/AppShell.tsx index d7bdbdc..9d6ba1a 100644 --- a/src/components/AppShell/AppShell.tsx +++ b/src/components/AppShell/AppShell.tsx @@ -4,6 +4,7 @@ import ConnectWalletButton from "@/components/ConnectWalletButton/ConnectWalletB import Footer from "@/components/Footer/Footer"; import Navbar from "@/components/Navbar/Navbar"; import NetworkMismatchBanner from "@/components/NetworkMismatchBanner/NetworkMismatchBanner"; +import RefetchIndicator from "@/components/RefetchIndicator/RefetchIndicator"; import RpcUnreachableBanner from "@/components/RpcUnreachableBanner/RpcUnreachableBanner"; import ContextProvider from "@/context"; import { @@ -46,6 +47,7 @@ function LayoutWrapper({ children }: { children: React.ReactNode }) { md: 0, }} > + diff --git a/src/components/RefetchIndicator/RefetchIndicator.tsx b/src/components/RefetchIndicator/RefetchIndicator.tsx new file mode 100644 index 0000000..2454fdd --- /dev/null +++ b/src/components/RefetchIndicator/RefetchIndicator.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { keyframes } from "@emotion/react"; +import { Box } from "@chakra-ui/react"; +import { useIsFetching } from "@tanstack/react-query"; + +const slide = keyframes` + 0% { transform: translateX(-100%); } + 100% { transform: translateX(250%); } +`; + +/** + * A thin bar at the top of the viewport while cached data is being refreshed + * (#499). Only queries that already hold data count, so first loads (which + * have their own spinners) don't trigger it; the old data stays on screen and + * this shows it is being updated. The animation is dropped for users who + * prefer reduced motion, leaving a static bar. + */ +export default function RefetchIndicator() { + const refetching = + useIsFetching({ predicate: (query) => query.state.data !== undefined }) > 0; + + return ( +