From 053c2ae85e0a8f2b59c923cd7723fc4525c894bf Mon Sep 17 00:00:00 2001
From: Kyle Pope
Date: Thu, 5 Mar 2026 18:32:54 +0800
Subject: [PATCH] Mark notification as read when accepting via toast
Toast accept/reject now calls markRead on the corresponding notification
so it clears from unread in the notifications tab. Uses markReadRef to
avoid stale closure in Sonner toast callbacks. Covers both success and
409 (already resolved) paths.
Co-Authored-By: Claude Opus 4.6
---
.../components/notifications/NotificationToaster.tsx | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/frontend/src/components/notifications/NotificationToaster.tsx b/frontend/src/components/notifications/NotificationToaster.tsx
index 7792634..91a1e88 100644
--- a/frontend/src/components/notifications/NotificationToaster.tsx
+++ b/frontend/src/components/notifications/NotificationToaster.tsx
@@ -9,7 +9,7 @@ import { getErrorMessage } from '@/lib/api';
import type { AppNotification } from '@/types';
export default function NotificationToaster() {
- const { notifications, unreadCount } = useNotifications();
+ const { notifications, unreadCount, markRead } = useNotifications();
const { respond } = useConnections();
const queryClient = useQueryClient();
const maxSeenIdRef = useRef(0);
@@ -20,9 +20,11 @@ export default function NotificationToaster() {
// Always call the latest respond — Sonner toasts capture closures at creation time
const respondRef = useRef(respond);
respondRef.current = respond;
+ const markReadRef = useRef(markRead);
+ markReadRef.current = markRead;
const handleConnectionRespond = useCallback(
- async (requestId: number, action: 'accept' | 'reject', toastId: string | number) => {
+ async (requestId: number, action: 'accept' | 'reject', toastId: string | number, notificationId: number) => {
// Guard against double-clicks (Sonner toasts are static, no disabled prop)
if (respondingRef.current.has(requestId)) return;
respondingRef.current.add(requestId);
@@ -37,11 +39,13 @@ export default function NotificationToaster() {
await respondRef.current({ requestId, action });
toast.dismiss(loadingId);
toast.success(action === 'accept' ? 'Connection accepted' : 'Request declined');
+ markReadRef.current([notificationId]).catch(() => {});
} catch (err) {
toast.dismiss(loadingId);
// 409 means the request was already resolved (e.g. accepted via notification center)
if (axios.isAxiosError(err) && err.response?.status === 409) {
toast.success(action === 'accept' ? 'Connection already accepted' : 'Request already resolved');
+ markReadRef.current([notificationId]).catch(() => {});
} else {
toast.error(getErrorMessage(err, 'Failed to respond to request'));
}
@@ -119,14 +123,14 @@ export default function NotificationToaster() {