diff --git a/frontend/src/app/hooks/useApi.ts b/frontend/src/app/hooks/useApi.ts index 82673826..1278d903 100644 --- a/frontend/src/app/hooks/useApi.ts +++ b/frontend/src/app/hooks/useApi.ts @@ -73,6 +73,26 @@ export const queryKeys = { borrowerLoans: { byAddress: (address: string) => ["borrowerLoans", address] as const, }, + creditScore: { + byUser: (userId: string) => ["creditScore", userId] as const, + }, + creditScoreHistory: { + byUser: (userId: string) => ["creditScoreHistory", userId] as const, + }, + yieldHistory: { + byUser: (userId: string, days: number) => ["yieldHistory", userId, days] as const, + }, + remittanceNft: { + byAddress: (walletAddress: string) => ["remittanceNft", walletAddress] as const, + }, + notificationPreferences: { + all: () => ["notificationPreferences"] as const, + }, + amortization: { + schedule: (loanId: string) => ["loans", loanId, "amortization"] as const, + preview: (amount: number, termDays: number) => + ["loans", "amortization-preview", amount, termDays] as const, + }, pool: { stats: () => ["pool", "stats"] as const, depositor: (address: string) => ["pool", "depositor", address] as const, @@ -710,7 +730,7 @@ export function useLoanAmortizationSchedule( options?: Omit, "queryKey" | "queryFn">, ) { return useQuery({ - queryKey: [...queryKeys.loans.detail(id ?? ""), "amortization"], + queryKey: queryKeys.amortization.schedule(id ?? ""), queryFn: async () => { const response = await apiFetch< LoanAmortization | { success: boolean; amortization: LoanAmortization } @@ -737,7 +757,7 @@ export function useLoanAmortizationPreview( options?: Omit, "queryKey" | "queryFn">, ) { return useQuery({ - queryKey: ["loans", "amortization-preview", params?.amount ?? 0, params?.termDays ?? 0], + queryKey: queryKeys.amortization.preview(params?.amount ?? 0, params?.termDays ?? 0), queryFn: async () => { const response = await apiFetch< LoanAmortization | { success: boolean; amortization: LoanAmortization } @@ -994,7 +1014,7 @@ export function useCreditScoreHistory( options?: Omit, "queryKey" | "queryFn">, ) { return useQuery({ - queryKey: ["creditScoreHistory", userId], + queryKey: queryKeys.creditScoreHistory.byUser(userId ?? ""), queryFn: () => apiFetch(`/score/${userId}/history`), enabled: !!userId, ...options, @@ -1018,7 +1038,7 @@ export function useRemittanceNft( options?: Omit, "queryKey" | "queryFn">, ) { return useQuery({ - queryKey: ["remittanceNft", walletAddress], + queryKey: queryKeys.remittanceNft.byAddress(walletAddress ?? ""), queryFn: async () => { const response = await apiFetch(`/score/${walletAddress}/nft`); return response.nft; @@ -1049,7 +1069,7 @@ export function useCreditScore( }); const query = useQuery({ - queryKey: ["creditScore", userId], + queryKey: queryKeys.creditScore.byUser(userId ?? ""), queryFn: async () => { const response = await apiFetch(`/score/${userId}`); return response.score; @@ -1136,7 +1156,7 @@ export function useCreditScore( payload.eventType === "LoanRepaid" || payload.eventType === "LoanDefaulted"; if (payload.borrower === walletAddress && scoreChangingEvent) { - const currentScore = queryClient.getQueryData(["creditScore", userId]); + const currentScore = queryClient.getQueryData(queryKeys.creditScore.byUser(userId ?? "")); setPreviousScoreState({ walletAddress, @@ -1144,7 +1164,7 @@ export function useCreditScore( }); queryClient.invalidateQueries({ - queryKey: ["creditScore", userId], + queryKey: queryKeys.creditScore.byUser(userId ?? ""), }); } } catch { @@ -1202,7 +1222,7 @@ export function useYieldHistory( const { days = 30, ...queryOptions } = options ?? {}; return useQuery({ - queryKey: ["yieldHistory", userId, days], + queryKey: queryKeys.yieldHistory.byUser(userId ?? "", days), queryFn: async () => { const response = await apiFetch< PoolApiResponse< @@ -1388,7 +1408,7 @@ export function useNotificationPreferences( options?: Omit, "queryKey" | "queryFn">, ) { return useQuery({ - queryKey: ["notificationPreferences"], + queryKey: queryKeys.notificationPreferences.all(), queryFn: async () => apiFetch("/notifications/preferences"), ...options, }); @@ -1403,7 +1423,7 @@ export function useUpdateNotificationPreferences() { body: JSON.stringify(payload), }), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["notificationPreferences"] }); + queryClient.invalidateQueries({ queryKey: queryKeys.notificationPreferences.all() }); }, }); } @@ -1523,9 +1543,13 @@ export function useResolveAdminDispute() { // ─── Optimistic mutations ───────────────────────────────────────────────────── /** - * Repays a loan with optimistic UI update. - * Instantly updates the cached loan detail and borrower loans, then rolls back - * on failure and refetches on settle to confirm server state. + * Submits a loan repayment directly to the server and updates the cache + * with optimistic rollback on failure. + * + * This hook calls `/loans/{loanId}/repay` (a full server-side build+sign+submit + * flow). It is NOT a build-only hook — the mutation succeeds only when the + * server has submitted the transaction on-chain and returned a `txHash`. + * The optimistic loan-status update is rolled back automatically on error. */ export function useRepayLoan() { const queryClient = useQueryClient(); @@ -1614,9 +1638,13 @@ export function useRepayLoan() { } /** - * Deposits to the lending pool with optimistic UI update. - * Instantly reflects the deposit in pool stats and depositor portfolio, - * then rolls back on failure. + * Builds an unsigned deposit transaction XDR. Does NOT deposit — it only calls + * `/pool/build-deposit` and returns `{ unsignedTxXdr, networkPassphrase }`. + * + * Callers must pair this with `signTransaction` + `submitPoolTransaction` to + * complete the deposit (see `useDepositOperation` in useRepaymentOperation.ts). + * The optimistic UI update fires on build success, not on-chain confirmation — + * treat it as a UX hint only and always confirm via `onSettled` refetch. */ export function useDepositToPool() { const queryClient = useQueryClient(); @@ -1684,9 +1712,13 @@ export function useDepositToPool() { } /** - * Withdraws from the lending pool with optimistic UI update. - * Instantly reflects the withdrawal in pool stats and depositor portfolio, - * then rolls back on failure. + * Builds an unsigned withdrawal transaction XDR. Does NOT withdraw — it only calls + * `/pool/build-withdraw` and returns `{ unsignedTxXdr, networkPassphrase }`. + * + * Callers must pair this with `signTransaction` + `submitPoolTransaction` to + * complete the withdrawal (see `useWithdrawalOperation` in useRepaymentOperation.ts). + * The optimistic UI update fires on build success, not on-chain confirmation — + * treat it as a UX hint only and always confirm via `onSettled` refetch. */ export function useWithdrawFromPool() { const queryClient = useQueryClient();