From 066d0849adae44e165a8bd3f3f8a7c99e1ab530b Mon Sep 17 00:00:00 2001 From: shaiksohelll Date: Mon, 18 May 2026 00:46:47 +0530 Subject: [PATCH] fix(wallet): bind top-up pending state to mutation lifecycle --- src/components/features/topup-dialog.tsx | 116 +++++++++++------------ 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/src/components/features/topup-dialog.tsx b/src/components/features/topup-dialog.tsx index 71d6d7d..896e81b 100644 --- a/src/components/features/topup-dialog.tsx +++ b/src/components/features/topup-dialog.tsx @@ -1,11 +1,10 @@ "use client"; -import { formatInr } from "@/lib/format"; -import { useRef, useState, useTransition, useEffect } from "react"; -import { useQueryClient } from "@tanstack/react-query"; -import { toast } from "sonner"; +import { useEffect, useRef, useState } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Plus } from "lucide-react"; - +import { toast } from "sonner"; +import { topupWalletAction } from "@/app/_actions/wallet"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -16,17 +15,18 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; - -import { topupWalletAction } from "@/app/_actions/wallet"; +import { formatInr } from "@/lib/format"; const QUICK_AMOUNTS = [1000, 5000, 10000, 25000]; +const blurOnWheel = (e: React.WheelEvent) => { + (e.target as HTMLInputElement).blur(); +}; + export function TopUpDialog() { const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const [amountStr, setAmountStr] = useState(""); - const [isPending, startTransition] = useTransition(); const inFlightRef = useRef(false); const mountedRef = useRef(true); @@ -37,46 +37,48 @@ export function TopUpDialog() { }; }, []); - function handleTopUp() { - const amount = Number(amountStr); - if (!Number.isFinite(amount) || amount < 100) { - toast.error("Enter at least ₹100."); - return; - } - if (amount > 100000) { - toast.error("Maximum top-up is ₹1,00,000."); - return; - } - if (inFlightRef.current) return; - inFlightRef.current = true; + const mutation = useMutation({ + mutationFn: async (amount: number) => { + const result = await topupWalletAction({ + amount, + idempotency_key: crypto.randomUUID(), + }); + if (!result.success) { + throw new Error(result.error); + } + return { amount, newBalance: result.data?.availableBalance ?? 0 }; + }, + onSuccess: ({ amount, newBalance }) => { + // Always invalidate so the wallet view re-fetches even if we unmounted. + queryClient.invalidateQueries({ queryKey: ["wallet"] }); + if (!mountedRef.current) return; + toast.success(`Added ${formatInr(amount)}. New balance: ${formatInr(newBalance)}`); + setAmountStr(""); + setOpen(false); + }, + onError: (err: Error) => { + if (!mountedRef.current) return; + toast.error(err.message || "Could not top up wallet. Please try again."); + }, + onSettled: () => { + inFlightRef.current = false; + }, + }); - startTransition(async () => { - try { - const result = await topupWalletAction({ - amount, - idempotency_key: crypto.randomUUID(), - }); + const isPending = mutation.isPending; - if (!result.success) { - if (mountedRef.current) toast.error(result.error); - return; - } + const handleSubmit = () => { + if (inFlightRef.current || isPending) return; - // Cache invalidation — safe to run regardless of mount state. - // Adjust query keys below if yours differ. - queryClient.invalidateQueries({ queryKey: ["wallet"] }); + const amount = Number(amountStr); + if (!Number.isFinite(amount) || amount < 100 || amount > 100000) { + toast.error("Amount must be between ₹100 and ₹1,00,000."); + return; + } - if (mountedRef.current) { - const newBalance = result.data?.availableBalance ?? 0; - toast.success(`Added ${formatInr(amount)}. New balance: ${formatInr(newBalance)}`); - setAmountStr(""); - setOpen(false); - } - } finally { - inFlightRef.current = false; - } - }); - } + inFlightRef.current = true; + mutation.mutate(amount); + }; return ( <> @@ -88,23 +90,24 @@ export function TopUpDialog() { Add money to wallet - Test-mode top-up. ₹100 – ₹1,00,000. + + Test-mode top-up. {formatInr(100)} – {formatInr(100000)}. +
- + Amount setAmountStr(e.target.value)} - onWheel={(e) => (e.target as HTMLInputElement).blur()} + onWheel={blurOnWheel} disabled={isPending} />
@@ -126,16 +129,11 @@ export function TopUpDialog() {
- -