import React, { useEffect, useRef, useState } from "react"; import SessionItem from "./SessionItem.jsx"; /** * SessionSidebar — Claude-Code-on-Web parity. * * Shows a scrollable list of coding sessions with status indicators, * timestamps, and a "New Session" button. Additive — does not modify * any existing component. */ export default function SessionSidebar({ repo, activeSessionId, onSelectSession, onNewSession, onDeleteSession, refreshNonce = 0, }) { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(false); const pollRef = useRef(null); const repoFullName = repo?.full_name || (repo ? `${repo.owner}/${repo.name}` : null); // Fetch sessions useEffect(() => { if (!repoFullName) { setSessions([]); return; } let cancelled = false; const fetchSessions = async () => { setLoading(true); try { const token = localStorage.getItem("github_token"); const headers = token ? { Authorization: `Bearer ${token}` } : {}; const res = await fetch(`/api/sessions`, { headers, cache: "no-cache" }); if (!res.ok) return; const data = await res.json(); if (cancelled) return; // Filter to current repo const filtered = (data.sessions || []).filter( (s) => s.repo === repoFullName ); setSessions(filtered); } catch (err) { console.warn("Failed to fetch sessions:", err); } finally { if (!cancelled) setLoading(false); } }; fetchSessions(); // Poll every 15s for status updates pollRef.current = setInterval(fetchSessions, 15000); return () => { cancelled = true; if (pollRef.current) clearInterval(pollRef.current); }; }, [repoFullName, refreshNonce]); const handleDelete = async (sessionId) => { try { const token = localStorage.getItem("github_token"); const headers = token ? { Authorization: `Bearer ${token}` } : {}; await fetch(`/api/sessions/${sessionId}`, { method: "DELETE", headers }); setSessions((prev) => prev.filter((s) => s.id !== sessionId)); // Notify parent so it can clear the chat if this was the active session onDeleteSession?.(sessionId); } catch (err) { console.warn("Failed to delete session:", err); } }; return (
{/* Header */}
SESSIONS
{/* Session list */}
{loading && sessions.length === 0 && (
Loading...
)} {!loading && sessions.length === 0 && (
No sessions yet.
Your first message will create one automatically.
)} {sessions.map((s) => ( onSelectSession?.(s)} onDelete={() => handleDelete(s.id)} /> ))}
); } const animStyles = ` @keyframes session-fade-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } `; const styles = { container: { display: "flex", flexDirection: "column", borderTop: "1px solid #27272A", flex: 1, minHeight: 0, }, header: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "10px 14px 6px", }, label: { fontSize: 10, fontWeight: 700, letterSpacing: "0.08em", color: "#71717A", textTransform: "uppercase", }, newBtn: { width: 22, height: 22, borderRadius: 4, border: "1px dashed #3F3F46", background: "transparent", color: "#A1A1AA", fontSize: 14, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", lineHeight: 1, }, list: { flex: 1, overflowY: "auto", padding: "0 6px 8px", }, empty: { textAlign: "center", color: "#52525B", fontSize: 12, padding: "20px 8px", lineHeight: 1.5, }, };