import { useEffect, useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Plus, MessageSquare, Trash2, ChevronDown, ChevronRight, RotateCcw, Menu, Code } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api/client'; import type { Project as ProjectType, Session } from '@/api/types'; import { Button } from '@/components/ui/button'; import { sessionEvents } from '@/hooks/sessionEvents'; import { useSessions } from '@/hooks/useSessions'; import { isCoderSessionName } from '@/lib/coder-session'; import { useSidebarDrawer } from '@/hooks/useSidebarDrawer'; import { useViewport } from '@/hooks/useViewport'; export function Project() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { sessions, create, remove } = useSessions(id); const [project, setProject] = useState(null); const [creating, setCreating] = useState(false); const [archivedSessions, setArchivedSessions] = useState(null); const [showArchived, setShowArchived] = useState(false); const { setOpen: setDrawerOpen } = useSidebarDrawer(); const { isMobile } = useViewport(); useEffect(() => { if (!id) return; api.projects .list() .then((list) => setProject(list.find((p) => p.id === id) ?? null)) .catch(() => {}); }, [id]); useEffect(() => { if (!id) return; api.sessions.listForProject(id, 'archived') .then(setArchivedSessions) .catch(() => {}); }, [id]); useEffect(() => { return sessionEvents.subscribe((event) => { if (event.type === 'session_archived' && event.project_id === id) { setArchivedSessions((prev) => { if (!prev) return prev; if (prev.some((s) => s.id === event.session_id)) return prev; const session = sessions?.find((s) => s.id === event.session_id); if (!session) return prev; return [{ ...session, status: 'archived' as const }, ...prev]; }); } if (event.type === 'session_deleted' && event.project_id === id) { setArchivedSessions((prev) => prev ? prev.filter((s) => s.id !== event.session_id) : prev ); } }); }, [id, sessions]); async function handleNew() { if (!id || creating) return; setCreating(true); try { const s = await create({}); // Server publishes session_created via WS; let useUserEvents deliver it. navigate(`/session/${s.id}`); } finally { setCreating(false); } } async function handleUnarchive(sessionId: string) { try { await api.sessions.unarchive(sessionId); setArchivedSessions((prev) => prev ? prev.filter((s) => s.id !== sessionId) : prev ); } catch (err) { toast.error(err instanceof Error ? err.message : 'failed to unarchive'); } } return (
{isMobile && ( )}

{project?.name ?? '…'}

{project?.path}
{sessions === null && (
Loading…
)} {sessions && sessions.length === 0 && (
No sessions yet. Click New session to start.
)} {sessions && sessions.length > 0 && (
    {sessions.map((s) => (
  • {isCoderSessionName(s.name) ? ( ) : ( )} {s.name} {s.model}
  • ))}
)} {/* Archived sessions */} {archivedSessions && archivedSessions.length > 0 && (
{showArchived && (
    {archivedSessions.map((s) => (
  • {s.name}
  • ))}
)}
)}
); }