VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import {
LayoutDashboard,
Activity,
Box,
Map,
FileCode,
BarChart3,
Settings,
HelpCircle,
} from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
interface ProjectSidebarProps {
projectId: string;
}
const menuItems = [
{
title: "Overview",
icon: LayoutDashboard,
href: "/overview",
description: "Project dashboard and stats",
},
{
title: "Sessions",
icon: Activity,
href: "/sessions",
description: "AI coding sessions",
},
{
title: "Features",
icon: Box,
href: "/features",
description: "Feature planning and tracking",
},
{
title: "API Map",
icon: Map,
href: "/api-map",
description: "Auto-generated API docs",
},
{
title: "Architecture",
icon: FileCode,
href: "/architecture",
description: "Living architecture docs",
},
{
title: "Analytics",
icon: BarChart3,
href: "/analytics",
description: "Costs and metrics",
},
];
export function ProjectSidebar({ projectId }: ProjectSidebarProps) {
const pathname = usePathname();
return (
<div className="flex h-full flex-col">
{/* Project Header */}
<div className="flex h-14 items-center justify-between border-b px-4">
<div>
<h2 className="text-lg font-semibold">AI Proxy</h2>
<p className="text-xs text-muted-foreground">Project Dashboard</p>
</div>
</div>
{/* Navigation */}
<ScrollArea className="flex-1 px-3 py-4">
<div className="space-y-1">
{menuItems.map((item) => {
const href = `/${projectId}${item.href}`;
const isActive = pathname === href;
return (
<Link
key={item.href}
href={href}
className={cn(
"group flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-all hover:bg-accent",
isActive
? "bg-accent text-accent-foreground font-medium"
: "text-muted-foreground hover:text-accent-foreground"
)}
title={item.description}
>
<item.icon className="h-4 w-4 shrink-0" />
<span className="truncate">{item.title}</span>
</Link>
);
})}
</div>
<Separator className="my-4" />
{/* Settings Section */}
<div className="space-y-1">
<Link
href={`/${projectId}/settings`}
className="flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground transition-all hover:bg-accent hover:text-accent-foreground"
>
<Settings className="h-4 w-4" />
<span>Settings</span>
</Link>
</div>
</ScrollArea>
{/* Footer */}
<div className="flex h-14 items-center justify-between border-t px-4">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span>v1.0.0</span>
<Separator orientation="vertical" className="h-4" />
<span>Powered by AI</span>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8">
<HelpCircle className="h-4 w-4" />
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,143 @@
"use client";
import { useState, useCallback, useEffect, useRef, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
interface ResizableSidebarProps {
children: ReactNode;
defaultWidth?: number;
minWidth?: number;
maxWidth?: number;
}
export function ResizableSidebar({
children,
defaultWidth = 250,
minWidth = 200,
maxWidth = 400,
}: ResizableSidebarProps) {
const [width, setWidth] = useState(defaultWidth);
const [isCollapsed, setIsCollapsed] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const [showPeek, setShowPeek] = useState(false);
const sidebarRef = useRef<HTMLDivElement>(null);
const initialXRef = useRef<number>(0);
const initialWidthRef = useRef<number>(defaultWidth);
const startResizing = useCallback((e: React.MouseEvent) => {
setIsResizing(true);
initialXRef.current = e.clientX;
initialWidthRef.current = width;
e.preventDefault();
}, [width]);
const stopResizing = useCallback(() => {
setIsResizing(false);
}, []);
const resize = useCallback(
(e: MouseEvent) => {
if (isResizing) {
const deltaX = e.clientX - initialXRef.current;
const newWidth = initialWidthRef.current + deltaX;
const clampedWidth = Math.min(Math.max(newWidth, minWidth), maxWidth);
setWidth(clampedWidth);
}
},
[isResizing, minWidth, maxWidth]
);
useEffect(() => {
if (isResizing) {
window.addEventListener("mousemove", resize);
window.addEventListener("mouseup", stopResizing);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
}
return () => {
window.removeEventListener("mousemove", resize);
window.removeEventListener("mouseup", stopResizing);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
}, [isResizing, resize, stopResizing]);
const handleMouseEnter = () => {
if (isCollapsed) {
setShowPeek(true);
}
};
const handleMouseLeave = () => {
if (isCollapsed) {
setTimeout(() => setShowPeek(false), 300);
}
};
return (
<>
{/* Peek trigger when collapsed */}
{isCollapsed && (
<div
className="absolute left-0 top-0 z-40 h-full w-2"
onMouseEnter={handleMouseEnter}
/>
)}
<aside
ref={sidebarRef}
style={{ width: isCollapsed ? 0 : `${width}px` }}
className={cn(
"relative flex flex-col border-r bg-card transition-all duration-300",
isCollapsed && "w-0 overflow-hidden"
)}
>
{!isCollapsed && children}
{/* Resize Handle */}
{!isCollapsed && (
<div
onMouseDown={startResizing}
className={cn(
"absolute right-0 top-0 h-full w-1 cursor-col-resize transition-colors hover:bg-primary/20",
isResizing && "bg-primary/40"
)}
/>
)}
{/* Collapse/Expand Button */}
<Button
variant="outline"
size="icon"
onClick={() => setIsCollapsed(!isCollapsed)}
className={cn(
"absolute -right-3 top-4 z-10 h-6 w-6 rounded-full shadow-sm",
isCollapsed && "left-2"
)}
>
{isCollapsed ? (
<ChevronRight className="h-4 w-4" />
) : (
<ChevronLeft className="h-4 w-4" />
)}
</Button>
</aside>
{/* Peek Sidebar (when collapsed) */}
{isCollapsed && showPeek && (
<div
className="absolute left-0 top-0 z-30 h-full w-64 border-r bg-card shadow-lg"
style={{ width: `${defaultWidth}px` }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
</div>
)}
</>
);
}