feat(layout): unified Inbox + UserMenu extraction

Replaces the topbar's separate AlertBell + NotificationBell with a
single Inbox popover that tabs between alerts and notifications.
NotificationBell keeps a popover-gate so it doesn't fire its list
fetch when Inbox is mounted alongside it.

Extracts the user dropdown into <UserMenu> and moves the port
switcher + role label + theme toggle into the sidebar footer so
the topbar can reclaim space for breadcrumbs and command search.

Adds dedicated Insights / Receipts nav sections in the sidebar
(scaffolds the website-analytics + upload-receipts entry points).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-04 22:54:06 +02:00
parent f5772ce318
commit e598cc0708
5 changed files with 609 additions and 146 deletions

View File

@@ -1,5 +1,6 @@
'use client';
import { useState } from 'react';
import { Bell } from 'lucide-react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
@@ -27,11 +28,18 @@ interface NotificationListResponse {
export function NotificationBell() {
const { unreadCount } = useNotifications();
const queryClient = useQueryClient();
// Track popover open state so we only fire the list fetch when the user
// actually opens the bell. Without this, an instance of NotificationBell
// mounted alongside <Inbox /> would populate the same ['notifications',
// 'list'] cache key without the gating Inbox carefully applies, defeating
// Inbox's enabled-on-open optimization.
const [open, setOpen] = useState(false);
const { data, isLoading } = useQuery<NotificationListResponse>({
queryKey: ['notifications', 'list'],
queryFn: () => apiFetch('/api/v1/notifications?limit=20'),
staleTime: 30_000,
enabled: open,
});
const markReadMutation = useMutation({
@@ -52,7 +60,7 @@ export function NotificationBell() {
const notifications = data?.data ?? [];
return (
<Popover>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="relative">
<Bell className="h-5 w-5" />