From 2984c72d774a353bb9c53cac279b6ce123eb13ae Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Wed, 12 Aug 2026 12:38:48 -0400 Subject: [PATCH] fix(notifications): harden bell dropdown against failed mark-read --- .../notifications/NotificationsBell.tsx | 112 ++++++++++++------ .../notifications/NotificationsInbox.tsx | 4 +- 2 files changed, 82 insertions(+), 34 deletions(-) diff --git a/src/components/frontend/notifications/NotificationsBell.tsx b/src/components/frontend/notifications/NotificationsBell.tsx index 353bb62..f3330f4 100644 --- a/src/components/frontend/notifications/NotificationsBell.tsx +++ b/src/components/frontend/notifications/NotificationsBell.tsx @@ -49,6 +49,8 @@ export function NotificationsBell() { const [open, setOpen] = useState(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); + const [actionError, setActionError] = useState(null); + const [marking, setMarking] = useState(false); const intervalRef = useRef | null>(null); const unreadCount = notifications.filter((n) => !n.read).length; @@ -78,9 +80,24 @@ export function NotificationsBell() { }, [fetchNotifications]); async function handleItemClick(item: NotificationItem) { + if (marking) return; if (!item.read) { - await markNotificationsRead([item.id]); - setNotifications((prev) => prev.map((n) => (n.id === item.id ? { ...n, read: true } : n))); + setMarking(true); + setActionError(null); + try { + const result = await markNotificationsRead([item.id]); + if (result.success) { + setNotifications((prev) => + prev.map((n) => (n.id === item.id ? { ...n, read: true } : n)), + ); + } else { + setActionError(result.error ?? "Failed to update notifications."); + } + } catch (err) { + setActionError(err instanceof Error ? err.message : "Failed to update notifications."); + } finally { + setMarking(false); + } } if (item.link) { router.push(item.link); @@ -89,9 +106,21 @@ export function NotificationsBell() { } async function handleMarkAll() { - if (unreadCount === 0) return; - await markAllNotificationsRead(); - setNotifications((prev) => prev.map((n) => (n.read ? n : { ...n, read: true }))); + if (unreadCount === 0 || marking) return; + setMarking(true); + setActionError(null); + try { + const result = await markAllNotificationsRead(); + if (result.success) { + setNotifications((prev) => prev.map((n) => (n.read ? n : { ...n, read: true }))); + } else { + setActionError(result.error ?? "Failed to update notifications."); + } + } catch (err) { + setActionError(err instanceof Error ? err.message : "Failed to update notifications."); + } finally { + setMarking(false); + } } return ( @@ -104,6 +133,11 @@ export function NotificationsBell() { {unreadCount > 9 ? "9+" : unreadCount} )} + + {unreadCount > 0 + ? `${unreadCount} unread notification${unreadCount === 1 ? "" : "s"}` + : "No unread notifications"} + @@ -112,9 +146,11 @@ export function NotificationsBell() { {unreadCount > 0 && ( )} @@ -131,34 +167,44 @@ export function NotificationsBell() { No notifications yet.

) : ( - notifications.map((item) => ( - void handleItemClick(item)} - > - - - {!item.read && ( - - )} - {item.title} + <> + {actionError && ( +

+ {actionError} +

+ )} + {notifications.map((item) => ( + void handleItemClick(item)} + > + + + {!item.read && ( + + )} + {item.title} + + + {timeAgo(item.createdAt)} + - - {timeAgo(item.createdAt)} + {item.message && ( + + {item.message} + + )} + + {notificationLabel(item.type)} - - {item.message && ( - {item.message} - )} - - {notificationLabel(item.type)} - - - )) +
+ ))} + )} diff --git a/src/components/frontend/notifications/NotificationsInbox.tsx b/src/components/frontend/notifications/NotificationsInbox.tsx index 39a0aa4..23c21ff 100644 --- a/src/components/frontend/notifications/NotificationsInbox.tsx +++ b/src/components/frontend/notifications/NotificationsInbox.tsx @@ -185,7 +185,9 @@ export function NotificationsInbox({ {timeAgo(item.createdAt)} - {item.title} + + {item.title} + {item.message && {item.message}} {item.link && (