1- import { PropsWithChildren , Suspense , useCallback , useEffect , useState } from "react"
1+ import { PropsWithChildren , Suspense } from "react"
2+ import { useQueries } from "@tanstack/react-query"
23import { Breadcrumbs } from "@/components/Breadcrumbs"
34import { PageTitle } from "@/components/PageTitle"
45import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card"
@@ -7,6 +8,15 @@ import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } fro
78import { Skeleton } from "@/components/ui/skeleton"
89import { debitBalance , creditBalance , onchainBalance } from "@/generated/client/sdk.gen"
910
11+ function withTimeout < T > ( promise : Promise < T > , timeoutMs : number ) : Promise < T > {
12+ return Promise . race ( [
13+ promise ,
14+ new Promise < T > ( ( _ , reject ) =>
15+ setTimeout ( ( ) => reject ( new Error ( `Request timeout after ${ timeoutMs } ms` ) ) , timeoutMs ) ,
16+ ) ,
17+ ] )
18+ }
19+
1020function Loader ( ) {
1121 return (
1222 < div className = "flex flex-col gap-4 my-2" >
@@ -134,110 +144,88 @@ export function BalanceText({ amount, unit, children }: PropsWithChildren<Balanc
134144 )
135145}
136146
137- function isErrorResponse < T > ( res : { data ?: T ; error ?: unknown } ) : res is { data : undefined ; error : unknown } {
138- return res . error !== undefined
139- }
140-
141147function useBalances ( ) {
142- const [ balances , setBalances ] = useState < Record < string , BalanceDisplay > > ( {
143- bitcoin : { amount : "0" , unit : "BTC" } ,
144- eiou : { amount : "0" , unit : "eIOU" } ,
145- credit : { amount : "0" , unit : "credit" } ,
146- debit : { amount : "0" , unit : "debit" } ,
147- } )
148- const [ isLoading , setIsLoading ] = useState ( false )
149- const [ error , setError ] = useState < string | null > ( null )
150-
151- const fetchAllBalances = useCallback ( async ( ) => {
152- setIsLoading ( true )
153- setError ( null )
154-
155- try {
156- // Fetch both credit and debit balances concurrently
157- const [ creditResponse , debitResponse , onchainResponse ] = await Promise . allSettled ( [
158- creditBalance ( { } ) ,
159- debitBalance ( { } ) ,
160- onchainBalance ( { } ) ,
161- ] )
162-
163- const newBalances : Record < string , BalanceDisplay > = {
164- bitcoin : { amount : "0" , unit : "BTC" } ,
165- eiou : { amount : "0" , unit : "eIOU" } ,
166- credit : { amount : "0" , unit : "credit" } ,
167- debit : { amount : "0" , unit : "debit" } ,
168- }
169-
170- // Handle credit balance response
171- if ( creditResponse . status === "fulfilled" && ! isErrorResponse ( creditResponse . value ) ) {
172- const creditData = creditResponse . value . data
173- if ( creditData && "amount" in creditData && "unit" in creditData ) {
174- newBalances . credit = {
175- amount : String ( creditData . amount ) ,
176- unit : String ( creditData . unit ) ,
148+ const queries = useQueries ( {
149+ queries : [
150+ {
151+ queryKey : [ "balance" , "credit" ] ,
152+ queryFn : async ( ) => {
153+ const response = await withTimeout ( creditBalance ( { } ) , 10_000 )
154+ if ( response . error ) {
155+ throw new Error ( "Failed to fetch credit balance" )
177156 }
178- }
179- } else {
180- console . warn (
181- "Failed to fetch credit balance:" ,
182- creditResponse . status === "rejected" ? creditResponse . reason : creditResponse . value . error ,
183- )
184- }
185-
186- // Handle debit balance response
187- if ( debitResponse . status === "fulfilled" && ! isErrorResponse ( debitResponse . value ) ) {
188- const debitData = debitResponse . value . data
189- if ( debitData && "amount" in debitData && "unit" in debitData ) {
190- newBalances . debit = {
191- amount : String ( debitData . amount ) ,
192- unit : String ( debitData . unit ) ,
157+ return response . data
158+ } ,
159+ refetchInterval : 30_000 ,
160+ staleTime : 25_000 ,
161+ retry : 2 ,
162+ gcTime : 10_000 ,
163+ } ,
164+ {
165+ queryKey : [ "balance" , "debit" ] ,
166+ queryFn : async ( ) => {
167+ const response = await withTimeout ( debitBalance ( { } ) , 10_000 )
168+ if ( response . error ) {
169+ throw new Error ( "Failed to fetch debit balance" )
193170 }
194- }
195- } else {
196- console . warn (
197- "Failed to fetch debit balance:" ,
198- debitResponse . status === "rejected" ? debitResponse . reason : debitResponse . value . error ,
199- )
200- }
201-
202- if ( onchainResponse . status === "fulfilled" && ! isErrorResponse ( onchainResponse . value ) ) {
203- const onchainData = onchainResponse . value . data
204- if ( onchainData && typeof onchainData === "object" && "confirmed" in onchainData ) {
205- console . log ( "Onchain balance:" , onchainData . confirmed )
206- newBalances . bitcoin = {
207- amount : String ( onchainData . confirmed ) ,
208- unit : "BTC" ,
171+ return response . data
172+ } ,
173+ refetchInterval : 30_000 ,
174+ staleTime : 25_000 ,
175+ retry : 2 ,
176+ gcTime : 10_000 ,
177+ } ,
178+ {
179+ queryKey : [ "balance" , "onchain" ] ,
180+ queryFn : async ( ) => {
181+ const response = await withTimeout ( onchainBalance ( { } ) , 10_000 )
182+ if ( response . error ) {
183+ throw new Error ( "Failed to fetch onchain balance" )
209184 }
210- }
211- } else {
212- console . warn (
213- "Failed to fetch onchain balance:" ,
214- onchainResponse . status === "rejected" ? onchainResponse . reason : onchainResponse . value . error ,
215- )
216- }
185+ return response . data
186+ } ,
187+ refetchInterval : 30_000 ,
188+ staleTime : 25_000 ,
189+ retry : 2 ,
190+ gcTime : 10_000 ,
191+ } ,
192+ ] ,
193+ } )
217194
218- setBalances ( newBalances )
219- } catch ( error ) {
220- const errorMessage = error instanceof Error ? error . message : "Unknown error occurred"
221- setError ( errorMessage )
222- console . error ( "Failed to fetch balances:" , error )
223- } finally {
224- setIsLoading ( false )
225- }
226- } , [ ] )
195+ const [ creditQuery , debitQuery , onchainQuery ] = queries
227196
228- useEffect ( ( ) => {
229- void fetchAllBalances ( )
197+ const isPending = queries . some ( ( query ) => query . isPending )
198+ const hasError = queries . some ( ( query ) => query . isError )
199+ const error = hasError ? "Failed to load one or more balances" : null
230200
231- const interval = setInterval ( ( ) => void fetchAllBalances ( ) , 30_000 )
201+ const balances : Record < string , BalanceDisplay > = {
202+ bitcoin : {
203+ amount :
204+ onchainQuery . data && typeof onchainQuery . data === "object" && "confirmed" in onchainQuery . data
205+ ? String ( onchainQuery . data . confirmed )
206+ : "0" ,
207+ unit : "BTC" ,
208+ } ,
209+ eiou : { amount : "0" , unit : "eIOU" } ,
210+ credit : {
211+ amount : creditQuery . data && "amount" in creditQuery . data ? String ( creditQuery . data . amount ) : "0" ,
212+ unit : creditQuery . data && "unit" in creditQuery . data ? String ( creditQuery . data . unit ) : "credit" ,
213+ } ,
214+ debit : {
215+ amount : debitQuery . data && "amount" in debitQuery . data ? String ( debitQuery . data . amount ) : "0" ,
216+ unit : debitQuery . data && "unit" in debitQuery . data ? String ( debitQuery . data . unit ) : "debit" ,
217+ } ,
218+ }
232219
233- return ( ) => clearInterval ( interval )
234- } , [ fetchAllBalances ] )
220+ const refetch = ( ) => {
221+ void Promise . all ( queries . map ( ( query ) => query . refetch ( ) ) )
222+ }
235223
236- return { balances, isLoading , error, refetch : fetchAllBalances }
224+ return { balances, isPending , error, refetch }
237225}
238226
239227function PageBodyWithDevSection ( ) {
240- const { balances, isLoading , error } = useBalances ( )
228+ const { balances, isPending , error } = useBalances ( )
241229
242230 if ( error ) {
243231 return (
@@ -253,14 +241,6 @@ function PageBodyWithDevSection() {
253241 )
254242 }
255243
256- if ( isLoading ) {
257- return (
258- < >
259- < Loader />
260- </ >
261- )
262- }
263-
264244 return (
265245 < >
266246 < div className = "flex flex-col gap-4 my-2" >
0 commit comments