Files
vibn-frontend/components/layout/project-sidebar.tsx

656 lines
29 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useCallback, useEffect, useRef } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import {
LayoutDashboard,
Target,
ListChecks,
Palette,
Code2,
Server,
Zap,
ChevronDown,
ChevronRight,
Github,
MessageSquare,
Image,
Globe,
FolderOpen,
Inbox,
Users,
Eye,
Plus,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { MCPConnectModal } from "./mcp-connect-modal";
import { ConnectSourcesModal } from "./connect-sources-modal";
import { OpenAIIcon, V0Icon, CursorIcon } from "@/components/icons/custom-icons";
interface ProjectSidebarProps {
projectId: string;
activeSection?: string; // From left rail: 'projects', 'inbox', 'clients', etc.
workspace?: string;
}
// Map section IDs to display names
const SECTION_NAMES: Record<string, string> = {
home: 'Home',
product: 'Product',
site: 'Site',
pricing: 'Pricing',
content: 'Content',
social: 'Social',
inbox: 'Inbox',
people: 'People',
settings: 'Settings',
};
// Section-specific navigation items
const SECTION_ITEMS: Record<string, Array<{title: string; icon: any; href: string}>> = {
home: [
// { title: "Vision", icon: Eye, href: "/vision" }, // Hidden per user request
{ title: "Context", icon: FolderOpen, href: "/context" },
],
product: [
{ title: "Product Vision", icon: Target, href: "/plan" },
{ title: "Progress", icon: ListChecks, href: "/progress" },
{ title: "UI UX", icon: Palette, href: "/design" },
{ title: "Code", icon: Code2, href: "/code" },
{ title: "Deployment", icon: Server, href: "/deployment" },
{ title: "Automation", icon: Zap, href: "/automation" },
],
site: [
{ title: "Pages", icon: Globe, href: "/site/pages" },
{ title: "Templates", icon: Palette, href: "/site/templates" },
{ title: "Settings", icon: Target, href: "/site/settings" },
],
pricing: [
{ title: "Plans", icon: Target, href: "/pricing/plans" },
{ title: "Billing", icon: Code2, href: "/pricing/billing" },
{ title: "Invoices", icon: ListChecks, href: "/pricing/invoices" },
],
content: [
{ title: "Blog Posts", icon: Target, href: "/content/blog" },
{ title: "Case Studies", icon: Code2, href: "/content/cases" },
{ title: "Documentation", icon: ListChecks, href: "/content/docs" },
],
social: [
{ title: "Posts", icon: MessageSquare, href: "/social/posts" },
{ title: "Analytics", icon: Target, href: "/social/analytics" },
{ title: "Schedule", icon: ListChecks, href: "/social/schedule" },
],
inbox: [
{ title: "All", icon: Inbox, href: "/inbox/all" },
{ title: "Unread", icon: Target, href: "/inbox/unread" },
{ title: "Archived", icon: ListChecks, href: "/inbox/archived" },
],
people: [
{ title: "Team", icon: Users, href: "/people/team" },
{ title: "Clients", icon: Users, href: "/people/clients" },
{ title: "Contacts", icon: Users, href: "/people/contacts" },
],
};
type ConnectionStatus = 'inactive' | 'connected' | 'live';
export function ProjectSidebar({ projectId, activeSection = 'projects', workspace = 'marks-account' }: ProjectSidebarProps) {
const minWidth = 200;
const maxWidth = 500;
const [width, setWidth] = useState(minWidth);
const [isResizing, setIsResizing] = useState(false);
const [mcpModalOpen, setMcpModalOpen] = useState(false);
const [connectModalOpen, setConnectModalOpen] = useState(false);
const [isUserFlowsExpanded, setIsUserFlowsExpanded] = useState(true);
const [isProductScreensExpanded, setIsProductScreensExpanded] = useState(true);
const [getStartedCompleted, setGetStartedCompleted] = useState(false);
const pathname = usePathname();
const sidebarRef = useRef<HTMLDivElement>(null);
// Connection states - mock data, would come from API/database in production
const [connectionStates, setConnectionStates] = useState<{
github: ConnectionStatus;
openai: ConnectionStatus;
v0: ConnectionStatus;
cursor: ConnectionStatus;
}>({
github: 'connected',
openai: 'live',
v0: 'inactive',
cursor: 'connected',
});
// Helper function to get icon classes based on connection status
const getIconClasses = (status: ConnectionStatus) => {
switch (status) {
case 'inactive':
return 'text-muted-foreground/40';
case 'connected':
return 'text-muted-foreground';
case 'live':
return 'text-foreground';
default:
return 'text-muted-foreground/40';
}
};
const startResizing = useCallback((e: React.MouseEvent) => {
setIsResizing(true);
e.preventDefault();
}, []);
const stopResizing = useCallback(() => {
setIsResizing(false);
}, []);
const resize = useCallback(
(e: MouseEvent) => {
if (isResizing) {
const newWidth = e.clientX - 64; // Subtract left rail width (64px)
if (newWidth >= minWidth && newWidth <= maxWidth) {
setWidth(newWidth);
}
}
},
[isResizing]
);
useEffect(() => {
window.addEventListener("mousemove", resize);
window.addEventListener("mouseup", stopResizing);
return () => {
window.removeEventListener("mousemove", resize);
window.removeEventListener("mouseup", stopResizing);
};
}, [resize, stopResizing]);
// Determine header title based on active section
const isVAIPage = pathname?.includes('/v_ai_chat');
const headerTitle = isVAIPage ? 'v_ai' : (SECTION_NAMES[activeSection] || 'Home');
// Get section-specific items
const currentSectionItems = SECTION_ITEMS[activeSection] || SECTION_ITEMS['home'];
return (
<>
<aside
ref={sidebarRef}
style={{ width: `${width}px` }}
className="relative flex flex-col border-r bg-card/50"
>
{/* Header */}
<div className="flex h-14 items-center justify-between px-4 border-b">
<h2 className="font-semibold text-sm">{headerTitle}</h2>
{/* Connection icons - only show for Product section */}
{activeSection === 'product' && (
<div className="flex items-center gap-1">
{/* GitHub */}
<Button
size="icon"
variant="ghost"
className="h-7 w-7 relative"
onClick={() => setConnectModalOpen(true)}
>
<Github className={cn("h-4 w-4", getIconClasses(connectionStates.github))} />
{connectionStates.github === 'live' && (
<span className="absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" />
)}
</Button>
{/* OpenAI/ChatGPT */}
<Button
size="icon"
variant="ghost"
className="h-7 w-7 relative"
onClick={() => setConnectModalOpen(true)}
>
<OpenAIIcon className={cn("h-4 w-4", getIconClasses(connectionStates.openai))} />
{connectionStates.openai === 'live' && (
<span className="absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" />
)}
</Button>
{/* v0 */}
<Button
size="icon"
variant="ghost"
className="h-7 w-7 relative"
onClick={() => setConnectModalOpen(true)}
>
<V0Icon className={cn("h-4 w-4", getIconClasses(connectionStates.v0))} />
{connectionStates.v0 === 'live' && (
<span className="absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" />
)}
</Button>
{/* Cursor */}
<Button
size="icon"
variant="ghost"
className="h-7 w-7 relative"
onClick={() => setConnectModalOpen(true)}
>
<CursorIcon className={cn("h-4 w-4", getIconClasses(connectionStates.cursor))} />
{connectionStates.cursor === 'live' && (
<span className="absolute top-1 right-1 h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" />
)}
</Button>
</div>
)}
</div>
{/* Section-Specific Navigation */}
<div className="px-2 py-1.5 space-y-0.5">
{/* v_ai - Persistent AI Chat - Always show for Home section */}
{activeSection === 'home' && (
<div className="mb-2">
<Link
href={`/${workspace}/project/${projectId}/v_ai_chat`}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1 text-sm transition-colors",
pathname === `/${workspace}/project/${projectId}/v_ai_chat`
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<img src="/vibn-logo-circle.png" alt="v_ai" className="h-4 w-4 shrink-0" />
<span className="truncate font-medium">v_ai</span>
</Link>
</div>
)}
{currentSectionItems.map((item) => {
const href = `/${workspace}/project/${projectId}${item.href}`;
const isActive = pathname === href;
return (
<Link
key={item.href}
href={href}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1 text-sm transition-colors",
isActive
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<item.icon className="h-4 w-4 shrink-0" />
<span className="truncate">{item.title}</span>
</Link>
);
})}
</div>
{/* Divider */}
<Separator className="my-1.5" />
{/* Context Section - Only show in Home section */}
{activeSection === 'home' && !isVAIPage && (
<div className="flex-1 overflow-hidden flex flex-col">
<div className="px-2 py-2">
<div className="flex items-center justify-between px-2 mb-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Context</h3>
<Button
size="sm"
variant="ghost"
className="h-6 px-2 text-xs text-primary hover:text-primary hover:bg-primary/10"
onClick={() => {
// Navigate to context page
window.location.href = `/${workspace}/project/${projectId}/context`;
}}
>
<Plus className="h-3 w-3 mr-1" />
Add
</Button>
</div>
</div>
</div>
)}
{/* Context Section - Shows items based on selected project section */}
{!isVAIPage && activeSection !== 'home' && (
<ScrollArea className="flex-1 px-2">
<div className="space-y-1 py-1.5">
{/* Show context based on current page */}
{pathname.includes('/plan') && (
<div className="space-y-2">
<div className="flex items-center justify-between px-2">
<h3 className="text-xs font-semibold text-muted-foreground">VISION SOURCES</h3>
<button
onClick={() => setMcpModalOpen(true)}
className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs hover:bg-accent transition-colors text-muted-foreground hover:text-foreground"
>
{/* OpenAI Icon (SVG) */}
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
className="opacity-60"
>
<path
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
fill="currentColor"
/>
</svg>
<span>Connect</span>
</button>
</div>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span className="text-xs">📄</span>
<span className="truncate">chatgpt-brainstorm.json</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span className="text-xs">📄</span>
<span className="truncate">product-notes.md</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm border border-dashed border-primary/30 text-primary hover:bg-primary/5 transition-colors">
<span className="text-xs">+</span>
<span className="truncate">Upload new file</span>
</button>
</div>
</div>
)}
{pathname.includes('/progress') && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">FILTERS</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>All Tasks</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>In Progress</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>Completed</span>
</button>
</div>
</div>
)}
{pathname.includes('/design') && (
<div className="space-y-1.5">
{/* Sandbox Indicator */}
<div className="px-2 py-1">
<h3 className="text-xs font-semibold text-muted-foreground">YOUR SANDBOX</h3>
</div>
{/* User Flows */}
<button
onClick={() => setIsUserFlowsExpanded(!isUserFlowsExpanded)}
className="flex w-full items-center justify-between px-2 py-1 rounded hover:bg-accent transition-colors"
>
<h3 className="text-xs font-semibold text-muted-foreground">USER FLOWS</h3>
{isUserFlowsExpanded ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
</button>
{isUserFlowsExpanded && (
<div className="space-y-0.5">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>🚀 Onboarding</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span> Signup</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>👋 New User</span>
</button>
</div>
)}
{/* Product Screens */}
<button
onClick={() => setIsProductScreensExpanded(!isProductScreensExpanded)}
className="flex w-full items-center justify-between px-2 py-1 rounded hover:bg-accent transition-colors mt-2.5"
>
<h3 className="text-xs font-semibold text-muted-foreground">PRODUCT SCREENS</h3>
{isProductScreensExpanded ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
</button>
{isProductScreensExpanded && (
<div className="space-y-0.5">
<Link
href={`/${projectId}/design/landing-hero`}
className={cn(
"group flex w-full items-center justify-between rounded-md px-2 py-1 text-sm transition-colors",
pathname === `/${projectId}/design/landing-hero`
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<div className="flex items-center gap-2">
<span></span>
<span>Landing Hero</span>
</div>
<span className="text-xs opacity-0 group-hover:opacity-100 transition-opacity">3</span>
</Link>
<Link
href={`/${projectId}/design/dashboard`}
className={cn(
"group flex w-full items-center justify-between rounded-md px-2 py-1 text-sm transition-colors",
pathname === `/${projectId}/design/dashboard`
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<div className="flex items-center gap-2">
<span>📊</span>
<span>Dashboard</span>
</div>
<span className="text-xs opacity-0 group-hover:opacity-100 transition-opacity">1</span>
</Link>
<Link
href={`/${projectId}/design/pricing`}
className={cn(
"group flex w-full items-center justify-between rounded-md px-2 py-1 text-sm transition-colors",
pathname === `/${projectId}/design/pricing`
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<div className="flex items-center gap-2">
<span>💳</span>
<span>Pricing</span>
</div>
<span className="text-xs opacity-0 group-hover:opacity-100 transition-opacity">2</span>
</Link>
<Link
href={`/${projectId}/design/user-profile`}
className={cn(
"group flex w-full items-center justify-between rounded-md px-2 py-1 text-sm transition-colors",
pathname === `/${projectId}/design/user-profile`
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<div className="flex items-center gap-2">
<span>👤</span>
<span>User Profile</span>
</div>
<span className="text-xs opacity-0 group-hover:opacity-100 transition-opacity">1</span>
</Link>
{/* Actions */}
<div className="px-2 pt-1.5">
<button className="flex w-full items-center justify-center gap-2 rounded-md border border-dashed border-primary/30 px-2 py-1.5 text-sm text-primary hover:bg-primary/5 transition-colors">
<span>+</span>
<span>New Screen</span>
</button>
</div>
</div>
)}
</div>
)}
{pathname.includes('/code') && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">QUICK LINKS</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>📦 Repository</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>🌿 Branches</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>🔀 Pull Requests</span>
</button>
</div>
</div>
)}
{pathname.includes('/deployment') && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">ENVIRONMENTS</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>🟢 Production</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>🟡 Staging</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>🔵 Development</span>
</button>
</div>
</div>
)}
{pathname.includes('/automation') && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">WORKFLOWS</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span> Active Jobs</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span> Scheduled</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors text-muted-foreground">
<span>📋 Logs</span>
</button>
</div>
</div>
)}
{/* Default: Left rail context sections */}
{activeSection === 'inbox' && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">INBOX</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span className="text-muted-foreground">No new items</span>
</button>
</div>
</div>
)}
{activeSection === 'clients' && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">PEOPLE</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Personal Projects</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>VIBN</span>
</button>
</div>
</div>
)}
{activeSection === 'invoices' && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">GROW</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>No growth strategies yet</span>
</button>
</div>
</div>
)}
{activeSection === 'site' && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">SITE</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Pages</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Settings</span>
</button>
</div>
</div>
)}
{activeSection === 'content' && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">CONTENT</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Blog Posts</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Case Studies</span>
</button>
</div>
</div>
)}
{activeSection === 'social' && (
<div className="space-y-2">
<h3 className="px-2 text-xs font-semibold text-muted-foreground">SOCIAL</h3>
<div className="space-y-1">
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Posts</span>
</button>
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent transition-colors">
<span>Analytics</span>
</button>
</div>
</div>
)}
</div>
</ScrollArea>
)}
{/* Footer */}
<div className="flex h-10 items-center justify-between border-t px-4 text-xs text-muted-foreground">
<span>v1.0.0</span>
<button className="hover:text-foreground transition-colors">
Help
</button>
</div>
{/* Resize Handle */}
<div
onMouseDown={startResizing}
className="absolute right-0 top-0 h-full w-1 cursor-col-resize hover:bg-primary/20 active:bg-primary/40 transition-colors"
/>
</aside>
<MCPConnectModal
open={mcpModalOpen}
onOpenChange={setMcpModalOpen}
projectId={projectId}
/>
<ConnectSourcesModal
open={connectModalOpen}
onOpenChange={setConnectModalOpen}
projectId={projectId}
/>
</>
);
}