import { useEffect, useMemo, useState } from "react"; import { useShallow } from "zustand/react/shallow"; import { useAppState } from "../../../store/appState"; import { useProjectActions } from "../../../hooks/useProjectActions"; import { useProjects } from "../../../hooks/useProjects"; import { useProjectSave } from "../../../hooks/useSaveState"; import { ProjectStatusIndicator } from "../../ui/StatusIndicator"; import Button from "../../ui/Button"; import OverflowMenu from "../../ui/OverflowMenu"; import ConfirmRemoveModal from "../ConfirmRemoveModal"; import OverviewTab from "./OverviewTab"; import SessionsTab from "./SessionsTab"; import AutomationTab from "./AutomationTab"; import ConfigTab from "./ConfigTab"; import FilesTab from "./FilesTab"; import { formatUptime } from "./format"; const TABS = [ { id: "overview", label: "Overview" }, { id: "sessions", label: "Sessions" }, { id: "automation", label: "Automation" }, { id: "config", label: "Config" }, { id: "files", label: "Files" }, ] as const; export type ProjectHomeTabId = (typeof TABS)[number]["id"]; interface Props { projectId: string; active: boolean; } /** * The project promoted from a sidebar card to a first-class main-area view. * Everything that used to spray out of `ProjectCard` as a modal lives here. */ export default function ProjectHome({ projectId, active }: Props) { const { projects, remove } = useProjects(); const project = projects.find((p) => p.id === projectId); const [tab, setTab] = useState("overview"); const [confirmRemove, setConfirmRemove] = useState(false); const { runningSince, progress } = useAppState( useShallow((s) => ({ runningSince: s.runningSince[projectId], progress: s.containerProgress[projectId], })), ); // Re-render once a minute so the uptime line stays honest. const [, setTick] = useState(0); useEffect(() => { if (!active || runningSince === undefined) return; const timer = setInterval(() => setTick((t) => t + 1), 60_000); return () => clearInterval(timer); }, [active, runningSince]); const actions = useProjectActions( project ?? ({ id: projectId, name: "", container_id: null } as never), ); const { save, saveState } = useProjectSave( project ?? ({ id: projectId, name: "" } as never), ); const uptime = useMemo(() => formatUptime(runningSince), [runningSince]); if (!project) { return (

This project is no longer available.

); } const isRunning = project.status === "running"; const isTransitioning = project.status === "starting" || project.status === "stopping"; const isStopped = project.status === "stopped" || project.status === "error"; return (
{/* Header */}

{project.name}

{isRunning && uptime && ( · {uptime} )} {isTransitioning && progress && ( · {progress} )}
{isRunning ? ( ) : ( )} {isRunning && ( <> )} {isTransitioning && ( )} setConfirmRemove(true), danger: true, }, ]} />
{/* Tabs */}
{TABS.map((t) => ( ))}
{/* Panel */}
{tab === "overview" && ( )} {tab === "sessions" && } {tab === "automation" && } {tab === "config" && ( )} {tab === "files" && }
{confirmRemove && ( setConfirmRemove(false)} onConfirm={async () => { setConfirmRemove(false); try { await remove(project.id); } catch (e) { useAppState.getState().pushToast({ kind: "error", message: `Could not remove “${project.name}”`, detail: String(e), }); } }} /> )}
); }