Files
headquarter/apps/web/src/pages/SessionsPage.tsx
T
Developer e434c439c9 refactor: rename files to PascalCase components and kebab-case APIs (Task 4.4)
- Rename all component files to PascalCase matching exported names
- Move components into feature directories (git/, session/, project/, terminal/, workspace/, ui/, layout/)
- Rename all page files to PascalCase with Page suffix
- Rename all API files to kebab-case
- Update all imports across codebase with corrected relative depths
- Preserve git history via git mv

Quality gates: tsc (pass), eslint (pass), 66/74 tests pass (8 pre-existing failures)
Refs: repo-restructure Task 4.4
2026-06-02 22:58:10 +00:00

215 lines
5.4 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { listProjects } from "../api/projects";
import { getUserSessions } from "../api/sessions";
import { listToolTypes } from "../api/tool-types";
import { getUserConfig } from "../api/settings";
import { Icon } from "../components/ui/Icon";
import { LoadingState, ErrorState } from "../components/ui";
import { CreateSessionForm, SessionList } from "../components/features/session";
import type { Project } from "../types/project";
import type { Session } from "../types/session";
import type { ToolType } from "../types/tool-type";
type SessionsStatus = "loading" | "ready" | "error";
export const SessionsPage = () => {
const navigate = useNavigate();
const [status, setStatus] = useState<SessionsStatus>("loading");
const [sessions, setSessions] = useState<Session[]>([]);
const [lastSessionId, setLastSessionId] = useState<string | null>(null);
const [projects, setProjects] = useState<Project[]>([]);
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
const loadSessions = useCallback(async () => {
setStatus("loading");
try {
const [sessionsData, config] = await Promise.all([
getUserSessions(),
getUserConfig(),
]);
setSessions(sessionsData);
setLastSessionId(config.last_session_id ?? null);
setStatus("ready");
} catch {
setStatus("error");
}
}, []);
useEffect(() => {
void loadSessions();
}, [loadSessions]);
useEffect(() => {
const loadProjects = async () => {
try {
setProjects(await listProjects());
} catch {
/* ignore */
}
};
void loadProjects();
}, []);
useEffect(() => {
const loadToolTypes = async () => {
try {
setToolTypes(await listToolTypes());
} catch {
/* ignore */
}
};
void loadToolTypes();
}, []);
const activeSessions = useMemo(
() =>
sessions.filter((s) =>
["running", "building", "pending"].includes(s.status),
),
[sessions],
);
const recentSessions = useMemo(
() =>
sessions
.filter((s) => ["stopped", "error"].includes(s.status))
.slice(0, 5),
[sessions],
);
const lastSession = useMemo(
() => sessions.find((s) => s.id === lastSessionId) ?? null,
[sessions, lastSessionId],
);
const handleOpen = useCallback(
(session: Session) => {
if (session.tool_type_interfaces?.includes("terminal")) {
navigate(`/instances/${session.id}/terminal`);
} else {
navigate(`/projects/${session.project_id}`);
}
},
[navigate],
);
const handleResumeLast = useCallback(() => {
if (!lastSession) return;
const project = projects.find((p) => p.name === lastSession.project_name);
if (project) navigate(`/projects/${project.id}`);
}, [lastSession, projects, navigate]);
return (
<section className="stack sessions-page">
<div className="page-header">
<h1>Sessions</h1>
</div>
{status === "loading" && <LoadingState message="Loading sessions..." />}
{status === "error" && (
<ErrorState
message="Failed to load sessions"
onRetry={() => void loadSessions()}
/>
)}
{status === "ready" && (
<>
{/* Last Session */}
{lastSession && (
<div className="last-session-section">
<h2>Last Session</h2>
<div className="card last-session-card">
<div className="last-session-info">
<h3>
{lastSession.display_name ||
lastSession.tool_type_name ||
"Unnamed Session"}
</h3>
<p className="muted">
{lastSession.tool_type_name} · {lastSession.project_name} ·{" "}
{lastSession.repository_name}
</p>
{lastSession.url && (
<p className="session-url">
<a
href={lastSession.url}
target="_blank"
rel="noopener noreferrer"
>
{lastSession.url}
</a>
</p>
)}
<span className={`status-badge ${lastSession.status}`}>
{lastSession.status}
</span>
</div>
<div className="last-session-actions">
{lastSession.url ? (
<a
href={lastSession.url}
target="_blank"
rel="noopener noreferrer"
className="primary-button"
>
<Icon name="external" size="sm" /> Open
</a>
) : (
<button
className="primary-button"
onClick={handleResumeLast}
type="button"
>
<Icon name="play" size="sm" /> Resume
</button>
)}
</div>
</div>
</div>
)}
{/* Active Sessions */}
<div className="active-sessions-section">
<h2>
Active Sessions
{activeSessions.length > 0 && (
<span className="badge">{activeSessions.length}</span>
)}
</h2>
<SessionList
sessions={activeSessions}
variant="active"
onSessionChange={loadSessions}
onOpen={handleOpen}
/>
</div>
{/* Recent Sessions */}
{recentSessions.length > 0 && (
<div className="recent-sessions-section">
<h2>Recent Sessions</h2>
<SessionList
sessions={recentSessions}
variant="recent"
onSessionChange={loadSessions}
onOpen={handleOpen}
/>
</div>
)}
{/* Create Session */}
<CreateSessionForm
projects={projects}
toolTypes={toolTypes}
onCreated={loadSessions}
/>
</>
)}
</section>
);
};