Files
paperclip/ui/src/components/Sidebar.tsx
Forgotten 33d549db13 feat(ui): mobile UX improvements, comment attachments, and cost breakdown
Add PWA meta tags for iOS home screen. Fix mobile properties drawer with safe
area insets. Add image attachment button to comment thread. Improve sidebar
with collapsible sections, project grouping, and mobile bottom nav. Show
token and billing type breakdown on costs page. Fix inbox loading state to
show content progressively. Various mobile overflow and layout fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:36:06 -06:00

103 lines
3.7 KiB
TypeScript

import {
Inbox,
CircleDot,
Target,
LayoutDashboard,
DollarSign,
History,
Search,
SquarePen,
Network,
} from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { SidebarSection } from "./SidebarSection";
import { SidebarNavItem } from "./SidebarNavItem";
import { SidebarProjects } from "./SidebarProjects";
import { SidebarAgents } from "./SidebarAgents";
import { useDialog } from "../context/DialogContext";
import { useCompany } from "../context/CompanyContext";
import { sidebarBadgesApi } from "../api/sidebarBadges";
import { heartbeatsApi } from "../api/heartbeats";
import { queryKeys } from "../lib/queryKeys";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
export function Sidebar() {
const { openNewIssue } = useDialog();
const { selectedCompanyId, selectedCompany } = useCompany();
const { data: sidebarBadges } = useQuery({
queryKey: queryKeys.sidebarBadges(selectedCompanyId!),
queryFn: () => sidebarBadgesApi.get(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const { data: liveRuns } = useQuery({
queryKey: queryKeys.liveRuns(selectedCompanyId!),
queryFn: () => heartbeatsApi.liveRunsForCompany(selectedCompanyId!),
enabled: !!selectedCompanyId,
refetchInterval: 10_000,
});
const liveRunCount = liveRuns?.length ?? 0;
function openSearch() {
document.dispatchEvent(new KeyboardEvent("keydown", { key: "k", metaKey: true }));
}
return (
<aside className="w-60 h-full min-h-0 border-r border-border bg-background flex flex-col">
{/* Top bar: Company name (bold) + Search — aligned with top sections (no visible border) */}
<div className="flex items-center gap-1 px-3 h-12 shrink-0">
<span className="flex-1 text-sm font-bold text-foreground truncate pl-1">
{selectedCompany?.name ?? "Select company"}
</span>
<Button
variant="ghost"
size="icon-sm"
className="text-muted-foreground shrink-0"
onClick={openSearch}
>
<Search className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="flex-1">
<nav className="flex flex-col gap-4 px-3 py-2">
<div className="flex flex-col gap-0.5">
{/* New Issue button aligned with nav items */}
<button
onClick={() => openNewIssue()}
className="flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground transition-colors"
>
<SquarePen className="h-4 w-4 shrink-0" />
<span className="truncate">New Issue</span>
</button>
<SidebarNavItem to="/dashboard" label="Dashboard" icon={LayoutDashboard} liveCount={liveRunCount} />
<SidebarNavItem
to="/inbox"
label="Inbox"
icon={Inbox}
badge={sidebarBadges?.inbox}
badgeTone={sidebarBadges?.failedRuns ? "danger" : "default"}
alert={(sidebarBadges?.failedRuns ?? 0) > 0}
/>
</div>
<SidebarProjects />
<SidebarSection label="Work">
<SidebarNavItem to="/issues" label="Issues" icon={CircleDot} />
<SidebarNavItem to="/goals" label="Goals" icon={Target} />
</SidebarSection>
<SidebarAgents />
<SidebarSection label="Company">
<SidebarNavItem to="/org" label="Org" icon={Network} />
<SidebarNavItem to="/costs" label="Costs" icon={DollarSign} />
<SidebarNavItem to="/activity" label="Activity" icon={History} />
</SidebarSection>
</nav>
</ScrollArea>
</aside>
);
}