From dff36f30c8f02c6ecbc4322ae2b0a418d9645689 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Wed, 4 Mar 2026 19:38:29 +0800 Subject: [PATCH] Fix toast accept button: instant feedback + double-click guard Toast buttons are static Sonner elements that can't bind React state, so clicks had no visual feedback and allowed duplicate mutations. Now: dismiss custom toast immediately, show loading toast, and block concurrent clicks via a ref-based Set guard. Co-Authored-By: Claude Opus 4.6 --- .../notifications/NotificationToaster.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/notifications/NotificationToaster.tsx b/frontend/src/components/notifications/NotificationToaster.tsx index 0dff878..4ebd234 100644 --- a/frontend/src/components/notifications/NotificationToaster.tsx +++ b/frontend/src/components/notifications/NotificationToaster.tsx @@ -14,16 +14,30 @@ export default function NotificationToaster() { const maxSeenIdRef = useRef(0); const initializedRef = useRef(false); const prevUnreadRef = useRef(0); + // Track in-flight request IDs so repeated clicks are blocked + const respondingRef = useRef>(new Set()); const handleConnectionRespond = useCallback( async (requestId: number, action: 'accept' | 'reject', toastId: string | number) => { + // Guard against double-clicks (Sonner toasts are static, no disabled prop) + if (respondingRef.current.has(requestId)) return; + respondingRef.current.add(requestId); + + // Immediately dismiss the custom toast and show a loading indicator + toast.dismiss(toastId); + const loadingId = toast.loading( + action === 'accept' ? 'Accepting connection…' : 'Declining request…', + ); + try { await respond({ requestId, action }); - // onSuccess in useConnections already dismisses the custom toast and invalidates caches + toast.dismiss(loadingId); toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined'); } catch (err) { - toast.dismiss(toastId); + toast.dismiss(loadingId); toast.error(getErrorMessage(err, 'Failed to respond to request')); + } finally { + respondingRef.current.delete(requestId); } }, [respond],