Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,13 @@
span {
@apply text-muted-foreground;
}
h1 {
@apply scroll-m-20 text-4xl tracking-tight text-balance;
}
h2 {
@apply scroll-m-20 text-3xl tracking-tight first:mt-0;
}
p {
@apply leading-7 [&:not(:first-child)]:mt-6 text-muted-foreground;
}
}
17 changes: 4 additions & 13 deletions app/projects/[id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { notFound } from 'next/navigation';

import ContentWrapper from '@/components/content-wrapper';
import PersistentTerminal from '@/components/persistent-terminal';
import ProjectSecondarySidebar from '@/components/project-secondary-sidebar';
import PrimarySidebar from '@/components/primary-sidebar';
import ProjectSidebar from '@/components/project-sidebar';
import { TerminalProvider } from '@/components/terminal-provider';
import { auth } from '@/lib/auth';
Expand Down Expand Up @@ -40,24 +40,15 @@ export default async function ProjectLayout({
notFound();
}

// Get all user projects for sidebar
const projects = await prisma.project.findMany({
where: {
userId: session.user.id,
},
orderBy: {
createdAt: 'desc',
},
});


return (
<TerminalProvider>
<div className="h-screen flex text-foreground overflow-hidden">
{/* Primary Sidebar - VSCode style */}
<ProjectSidebar projects={projects} currentProjectId={id} userId={session.user.id} />
<PrimarySidebar currentProjectId={id} userId={session.user.id} />

{/* Secondary Sidebar - Project Settings */}
<ProjectSecondarySidebar
<ProjectSidebar
project={project}
sandboxes={project.sandboxes}
envVars={project.environments}
Expand Down
16 changes: 2 additions & 14 deletions app/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ReactNode } from 'react';
import { redirect } from 'next/navigation';

import ProjectSidebar from '@/components/project-sidebar';
import PrimarySidebar from '@/components/primary-sidebar';
import { auth } from '@/lib/auth';
import { prisma } from '@/lib/db';

export default async function SettingsLayout({
children,
Expand All @@ -16,21 +15,10 @@ export default async function SettingsLayout({
redirect('/login');
}

// Get user's projects for sidebar
const projects = await prisma.project.findMany({
where: {
userId: session.user.id,
},
orderBy: {
createdAt: 'desc',
},
});

return (
<div className="h-screen flex bg-[#1e1e1e] text-white overflow-hidden">
{/* Primary Sidebar - VSCode style */}
<ProjectSidebar
projects={projects}
<PrimarySidebar
currentProjectId="" // No current project in settings
userId={session.user.id}
/>
Expand Down
173 changes: 173 additions & 0 deletions components/primary-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
'use client';

import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Circle, FolderOpen, GitBranch, Home, Plus, Settings } from 'lucide-react';
import Link from 'next/link';

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';

interface Project {
id: string;
name: string;
status: string;
updatedAt: string;
}

interface PrimarySidebarProps {
currentProjectId: string;
userId: string;
}

export default function PrimarySidebar({
currentProjectId,
}: PrimarySidebarProps) {
// Fetch projects list with TanStack Query, polling every 5 seconds
const { data: projects } = useQuery<Project[]>({
queryKey: ['projects'],
queryFn: async () => {
const response = await fetch('/api/projects');
if (!response.ok) {
throw new Error('Failed to fetch projects');
}
return response.json();
},
refetchInterval: 5000, // Poll every 5 seconds
staleTime: 4000, // Data is fresh for 4 seconds
retry: 2,
});
const [isExpanded, setIsExpanded] = useState(false);

const getStatusColor = (status: string) => {
switch (status) {
case 'READY':
case 'DEPLOYED':
return 'text-green-600 dark:text-green-500';
case 'INITIALIZING':
case 'DEPLOYING':
return 'text-yellow-600 dark:text-yellow-500';
case 'ERROR':
return 'text-destructive';
default:
return 'text-muted-foreground';
}
};

return (
<div
className={cn(
'bg-sidebar-background border-r border-sidebar-border flex flex-col transition-all duration-200',
isExpanded ? 'w-52' : 'w-12'
)}
onMouseEnter={() => setIsExpanded(true)}
onMouseLeave={() => setIsExpanded(false)}
>
<TooltipProvider>
{/* Header */}
<div className="h-12 flex items-center justify-center border-b border-sidebar-border">
<FolderOpen className="h-5 w-5 text-muted-foreground" />
{isExpanded && <span className="ml-2 text-sm font-medium text-foreground">Projects</span>}
</div>

{/* Navigation Items */}
<div className="flex-1 py-2 overflow-y-auto">
{/* Home Link */}
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/projects"
className="group flex items-center h-8 px-3 hover:bg-sidebar-accent transition-colors"
>
<Home className="h-4 w-4 text-muted-foreground group-hover:text-sidebar-foreground shrink-0 transition-colors" />
{isExpanded && (
<span className="ml-3 text-sm text-foreground truncate">All Projects</span>
)}
</Link>
</TooltipTrigger>
{!isExpanded && (
<TooltipContent side="right">
<p>All Projects</p>
</TooltipContent>
)}
</Tooltip>

{/* New Project */}
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/projects/new"
className="group flex items-center h-8 px-3 hover:bg-sidebar-accent transition-colors mb-2"
>
<Plus className="h-4 w-4 text-muted-foreground group-hover:text-sidebar-foreground shrink-0 transition-colors" />
{isExpanded && (
<span className="ml-3 text-sm text-foreground truncate">New Project</span>
)}
</Link>
</TooltipTrigger>
{!isExpanded && (
<TooltipContent side="right">
<p>New Project</p>
</TooltipContent>
)}
</Tooltip>

<div className="border-t border-border my-2" />

{/* Project List */}
<div className="space-y-1">
{projects?.map((project) => (
<Tooltip key={project.id}>
<TooltipTrigger asChild>
<Link
href={`/projects/${project.id}`}
className={cn(
'group flex items-center h-8 px-3 hover:bg-sidebar-accent transition-colors',
currentProjectId === project.id && 'bg-sidebar-accent'
)}
>
<div className="flex items-center shrink-0">
<Circle
className={cn('h-2 w-2 mr-2', getStatusColor(project.status))}
fill="currentColor"
/>
<GitBranch className="h-4 w-4 text-muted-foreground group-hover:text-sidebar-foreground transition-colors" />
</div>
{isExpanded && (
<span className="ml-3 text-sm text-foreground truncate">{project.name}</span>
)}
</Link>
</TooltipTrigger>
{!isExpanded && (
<TooltipContent side="right">
<p>{project.name}</p>
</TooltipContent>
)}
</Tooltip>
))}
</div>
</div>

{/* Settings */}
<div className="p-2">
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/settings"
className="group flex items-center h-8 px-2 hover:bg-sidebar-accent rounded transition-colors"
>
<Settings className="h-4 w-4 text-muted-foreground group-hover:text-sidebar-foreground flex-shrink-0 transition-colors" />
{isExpanded && <span className="ml-3 text-sm text-foreground">Settings</span>}
</Link>
</TooltipTrigger>
{!isExpanded && (
<TooltipContent side="right">
<p>Settings</p>
</TooltipContent>
)}
</Tooltip>
</div>
</TooltipProvider>
</div>
);
}
Loading
Loading