From cb86996365ef00ea218602df09edf8d24099ecae Mon Sep 17 00:00:00 2001 From: zaidejjo Date: Fri, 16 Oct 2026 04:33:24 +0300 Subject: [PATCH] feat(notifications): header bell replaces sidebar entry - bell top-right with live unread badge + preview dropdown (open in browser + mark read, view-all footer) - sidebar entry removed; page/route/palette untouched - title fallback fixed for the pageless route --- desktop/frontend/src/AppLayout.tsx | 98 ++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/desktop/frontend/src/AppLayout.tsx b/desktop/frontend/src/AppLayout.tsx index 52be7db..38391d1 100644 --- a/desktop/frontend/src/AppLayout.tsx +++ b/desktop/frontend/src/AppLayout.tsx @@ -26,7 +26,6 @@ const navItems = [ { id: "pull-requests", label: "PRs", Icon: GitPullRequest }, { id: "issues", label: "Issues", Icon: CircleDot }, { id: "actions", label: "Actions", Icon: Play }, - { id: "notifications", label: "Notifications", Icon: Bell }, { id: "ai", label: "AI", Icon: Brain }, ] as const; @@ -46,6 +45,7 @@ export default function AppLayout() { theme, setTheme, loadUserPreferences, userPreferences, notifications, fetchNotifications, + markNotificationRead, } = useAppStore(); const navigate = useNavigate(); const location = useLocation(); @@ -64,6 +64,8 @@ export default function AppLayout() { const [themeMenuOpen, setThemeMenuOpen] = useState(false); const repoMenuRef = useRef(null); const themeMenuRef = useRef(null); + const bellRef = useRef(null); + const [bellOpen, setBellOpen] = useState(false); // Initialize useEffect(() => { @@ -182,6 +184,19 @@ export default function AppLayout() { } }, [themeMenuOpen]); + // Close notifications dropdown on outside click + useEffect(() => { + const handler = (e: MouseEvent) => { + if (bellRef.current && !bellRef.current.contains(e.target as Node)) { + setBellOpen(false); + } + }; + if (bellOpen) { + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + } + }, [bellOpen]); + // Auto-dismiss error useEffect(() => { if (error) { @@ -215,7 +230,7 @@ export default function AppLayout() { const p = location.pathname.slice(1); return navItems.find((n) => n.id === p)?.id || bottomNavItems.find((n) => n.id === p)?.id - || "status"; + || (p === "notifications" ? p : "status"); }; const paletteShortcut = userPreferences?.keybindings?.command_palette || "Ctrl+K"; @@ -352,8 +367,6 @@ export default function AppLayout() { const NavIcon = item.Icon; const isActive = currentNavId === item.id && !isBottomNav; const isStatusWithDirty = item.id === "status" && dirtyCount > 0; - const isNotificationsWithUnread = item.id === "notifications" && unreadCount > 0; - const badgeCount = isStatusWithDirty ? dirtyCount : isNotificationsWithUnread ? unreadCount : 0; return ( @@ -499,6 +512,79 @@ export default function AppLayout() { )} + {/* Notifications bell */} + {ghAuthenticated && ( +
+ + + {bellOpen && ( +
+
+ Notifications + {unreadCount > 0 && ( + + {unreadCount} unread + + )} +
+
+ {notifications.length === 0 ? ( +

Inbox zero. Enjoy the quiet.

+ ) : ( + [...notifications] + .sort((a, b) => Number(b.unread) - Number(a.unread)) + .slice(0, 6) + .map((n) => ( + + )) + )} +
+ +
+ )} +
+ )} + {/* AI Assistant trigger */}