From 0ceb34f4aab76229e38f354471dd719035586776 Mon Sep 17 00:00:00 2001 From: wheval Date: Sun, 28 Jun 2026 15:45:53 +0100 Subject: [PATCH] fix(hooks): unify borrower-loan cache namespaces and implement real repayment flow - queryKeys.loans: add borrowerPagePrefix(address) helper for prefix-based invalidation that matches all paginated borrower-loan entries regardless of cursor/limit params (#1219) - useCreateLoan.onSuccess: also invalidate queryKeys.borrowerLoans.byAddress for the authenticated wallet so both borrower-loan lists refresh after a new loan is created (#1219) - useRepayLoan.onSettled: also invalidate queryKeys.loans.borrowerPagePrefix so the paginated borrower list refreshes alongside the byAddress list (#1219) - useLoanStream.invalidateLoanQueries: also invalidate borrowerPagePrefix on real-time loan events so SSE-driven refreshes hit both namespaces (#1219) - useRepaymentOperation.executeRepayment: replace setTimeout-based simulation (fake txHash, no on-chain effect) with a real call to useRepayLoan.mutateAsync, which submits the repayment and returns an actual txHash. XP/streak rewards and UI completion now only fire after the server confirms success (#1220) Closes LabsCrypt/remitlend#1219 Closes LabsCrypt/remitlend#1220 --- frontend/src/app/hooks/useApi.ts | 14 +++++++- frontend/src/app/hooks/useLoanStream.ts | 4 +++ .../src/app/hooks/useRepaymentOperation.ts | 34 ++++--------------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/hooks/useApi.ts b/frontend/src/app/hooks/useApi.ts index aae72a18..e3197e8d 100644 --- a/frontend/src/app/hooks/useApi.ts +++ b/frontend/src/app/hooks/useApi.ts @@ -44,6 +44,8 @@ export const queryKeys = { liquidatable: () => ["loans", "liquidatable"] as const, borrowerPage: (address: string, params: Record) => ["loans", "borrower", address, params] as const, + // Prefix key that matches all borrowerPage entries for an address regardless of pagination params + borrowerPagePrefix: (address: string) => ["loans", "borrower", address] as const, }, remittances: { all: () => ["remittances"] as const, @@ -858,6 +860,7 @@ export function useCreateLoan( >, ) { const queryClient = useQueryClient(); + const walletAddress = useUserStore((s) => s.user?.walletAddress); return useMutation>({ mutationFn: (data) => @@ -866,8 +869,13 @@ export function useCreateLoan( body: JSON.stringify(data), }), onSuccess: () => { - // Invalidate the loans list so it refetches with the new entry queryClient.invalidateQueries({ queryKey: queryKeys.loans.all() }); + // Also invalidate borrowerLoans.byAddress so both borrower-loan lists stay consistent (#1219) + if (walletAddress) { + queryClient.invalidateQueries({ + queryKey: queryKeys.borrowerLoans.byAddress(walletAddress), + }); + } }, ...options, }); @@ -1556,6 +1564,10 @@ export function useRepayLoan() { queryClient.invalidateQueries({ queryKey: queryKeys.borrowerLoans.byAddress(borrowerAddress), }); + // Also invalidate paginated borrower loans (borrowerPage) so both namespaces stay in sync (#1219) + queryClient.invalidateQueries({ + queryKey: queryKeys.loans.borrowerPagePrefix(borrowerAddress), + }); queryClient.invalidateQueries({ queryKey: queryKeys.pool.stats() }); }, }); diff --git a/frontend/src/app/hooks/useLoanStream.ts b/frontend/src/app/hooks/useLoanStream.ts index e4076302..dafd31f3 100644 --- a/frontend/src/app/hooks/useLoanStream.ts +++ b/frontend/src/app/hooks/useLoanStream.ts @@ -43,6 +43,10 @@ export function useLoanStream(loanId: string | undefined): RealtimeStatus { queryClient.invalidateQueries({ queryKey: queryKeys.borrowerLoans.byAddress(borrowerAddress), }); + // Also invalidate paginated borrower loans so both namespaces stay in sync (#1219) + queryClient.invalidateQueries({ + queryKey: queryKeys.loans.borrowerPagePrefix(borrowerAddress), + }); } }, [borrowerAddress, loanId, queryClient]); diff --git a/frontend/src/app/hooks/useRepaymentOperation.ts b/frontend/src/app/hooks/useRepaymentOperation.ts index 06a40060..007cadc0 100644 --- a/frontend/src/app/hooks/useRepaymentOperation.ts +++ b/frontend/src/app/hooks/useRepaymentOperation.ts @@ -27,6 +27,7 @@ import { useWallet } from "../components/providers/WalletProvider"; import { useDepositToPool, usePoolStats, + useRepayLoan, useWithdrawFromPool, submitPoolTransaction, } from "./useApi"; @@ -46,11 +47,11 @@ export function useRepaymentOperation(options?: { onSuccess?: (result: RepaymentOperationResult) => void; onError?: (error: Error) => void; }) { - const queryClient = useQueryClient(); const uid = useId(); const transactionId = `repayment-${uid}`; const transaction = useTransaction(transactionId); const [error, setError] = useState(null); + const repayLoan = useRepayLoan(); const executeRepayment = useCallback( async ({ @@ -62,37 +63,16 @@ export function useRepaymentOperation(options?: { setError(null); try { - // Step 1: Build unsigned transaction - transaction.updateProgress(20, "Building transaction..."); - await new Promise((resolve) => setTimeout(resolve, 300)); + transaction.updateProgress(20, "Submitting repayment..."); - // Step 2: Sign transaction (new signing state) - transaction.sign("Waiting for wallet signature..."); - await new Promise((resolve) => setTimeout(resolve, 500)); + // useRepayLoan handles the full submit flow with optimistic cache updates + const response = await repayLoan.mutateAsync({ loanId, amount, borrowerAddress }); + const txHash = response.txHash ?? String(loanId); - // Step 3: Submit to network (new submitted state) - const txHash = `tx_${Date.now()}`; transaction.submit(txHash, "Transaction submitted, waiting for confirmation..."); - await new Promise((resolve) => setTimeout(resolve, 300)); - - // Step 4: Poll for confirmation (new confirming state) transaction.confirm("Confirming transaction..."); - await new Promise((resolve) => setTimeout(resolve, 500)); - - // Mark complete transaction.complete(txHash); - // Invalidate related queries - queryClient.invalidateQueries({ - queryKey: ["loans"], - }); - queryClient.invalidateQueries({ - queryKey: ["borrowerLoans", borrowerAddress], - }); - queryClient.invalidateQueries({ - queryKey: ["pool", "stats"], - }); - const result = { txHash, status: "success" as const }; options?.onSuccess?.(result); return result; @@ -104,7 +84,7 @@ export function useRepaymentOperation(options?: { throw err; } }, - [transaction, queryClient, options], + [transaction, repayLoan, options], ); return {