From 60281caa64bf35d495917c4fa040951bdc80d1e9 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Wed, 4 Mar 2026 10:30:35 +0800 Subject: [PATCH] Unify toast accept path with notification center via useConnections Toast now uses the same respond() from useConnections hook instead of raw api.put, making both accept surfaces share identical code. Also made respondMutation.onSuccess fire-and-forget to prevent invalidation errors from surfacing as mutation failures. Co-Authored-By: Claude Opus 4.6 --- .../notifications/NotificationToaster.tsx | 17 +++++++---------- frontend/src/hooks/useConnections.ts | 11 +++++------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/notifications/NotificationToaster.tsx b/frontend/src/components/notifications/NotificationToaster.tsx index 9ac1f36..0dff878 100644 --- a/frontend/src/components/notifications/NotificationToaster.tsx +++ b/frontend/src/components/notifications/NotificationToaster.tsx @@ -3,11 +3,13 @@ import { toast } from 'sonner'; import { Check, X, Bell, UserPlus } from 'lucide-react'; import { useQueryClient } from '@tanstack/react-query'; import { useNotifications } from '@/hooks/useNotifications'; -import api, { getErrorMessage } from '@/lib/api'; +import { useConnections } from '@/hooks/useConnections'; +import { getErrorMessage } from '@/lib/api'; import type { AppNotification } from '@/types'; export default function NotificationToaster() { const { notifications, unreadCount } = useNotifications(); + const { respond } = useConnections(); const queryClient = useQueryClient(); const maxSeenIdRef = useRef(0); const initializedRef = useRef(false); @@ -16,20 +18,15 @@ export default function NotificationToaster() { const handleConnectionRespond = useCallback( async (requestId: number, action: 'accept' | 'reject', toastId: string | number) => { try { - await api.put(`/connections/requests/${requestId}/respond`, { action }); + await respond({ requestId, action }); + // onSuccess in useConnections already dismisses the custom toast and invalidates caches + toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined'); } catch (err) { toast.dismiss(toastId); toast.error(getErrorMessage(err, 'Failed to respond to request')); - return; } - toast.dismiss(toastId); - toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined'); - // Fire-and-forget — invalidation errors should not surface as "Failed to respond" - queryClient.invalidateQueries({ queryKey: ['connections'] }); - queryClient.invalidateQueries({ queryKey: ['people'] }); - queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, - [queryClient], + [respond], ); // Track unread count changes to force-refetch the list diff --git a/frontend/src/hooks/useConnections.ts b/frontend/src/hooks/useConnections.ts index 65e31b5..919abed 100644 --- a/frontend/src/hooks/useConnections.ts +++ b/frontend/src/hooks/useConnections.ts @@ -57,14 +57,13 @@ export function useConnections() { const { data } = await api.put(`/connections/requests/${requestId}/respond`, { action }); return data; }, - onSuccess: async (_, variables) => { + onSuccess: (_, variables) => { // Dismiss any lingering Sonner toast for this request toast.dismiss(`connection-request-${variables.requestId}`); - await Promise.all([ - queryClient.invalidateQueries({ queryKey: ['connections'] }), - queryClient.invalidateQueries({ queryKey: ['people'] }), - queryClient.invalidateQueries({ queryKey: ['notifications'] }), - ]); + // Fire-and-forget — errors here must not surface as mutation failures + queryClient.invalidateQueries({ queryKey: ['connections'] }); + queryClient.invalidateQueries({ queryKey: ['people'] }); + queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, });