merge: align dev branch with main
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { getDashboardSummary, type DashboardSummary } from "../api/dashboard";
|
||||
import { getUserSessions } from "../api/sessions";
|
||||
import { listProjects } from "../api/projects";
|
||||
import { listToolTypes } from "../api/tool-types";
|
||||
import type { Session as SessionApi } from "../types/session";
|
||||
import type { Project } from "../types/project";
|
||||
import type { ToolType } from "../types/tool-type";
|
||||
import {
|
||||
DashboardSummary as DashboardSummaryComponent,
|
||||
ActiveSessionsList,
|
||||
ProjectsSection,
|
||||
QuickCreateForm,
|
||||
RecentSessionsSection,
|
||||
} from "../components/features/dashboard";
|
||||
import { LoadingState, ErrorState } from "../components/ui";
|
||||
import { useDashboardActions } from "../hooks/use-dashboard-actions";
|
||||
import { listRepositories } from "../api/git-repositories";
|
||||
import type { GitRepository } from "../types/git-repository";
|
||||
|
||||
type HomeStatus = "loading" | "ready" | "error";
|
||||
type SessionView = SessionApi;
|
||||
|
||||
export const HomePage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [status, setStatus] = useState<HomeStatus>("loading");
|
||||
const [summary, setSummary] = useState<DashboardSummary | null>(null);
|
||||
const [sessions, setSessions] = useState<SessionView[]>([]);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [repositories, setRepositories] = useState<GitRepository[]>([]);
|
||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||
const safeSessions = Array.isArray(sessions) ? sessions : [];
|
||||
|
||||
const loadHome = useCallback(async () => {
|
||||
setStatus("loading");
|
||||
try {
|
||||
const [dashboard, sessionData, projectData, toolTypeData] =
|
||||
await Promise.all([
|
||||
getDashboardSummary(),
|
||||
getUserSessions(),
|
||||
listProjects(),
|
||||
listToolTypes(),
|
||||
]);
|
||||
setSummary(dashboard);
|
||||
setSessions(sessionData as SessionView[]);
|
||||
setProjects(projectData);
|
||||
setToolTypes(toolTypeData);
|
||||
setStatus("ready");
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void loadHome();
|
||||
}, [loadHome]);
|
||||
|
||||
const loadRepos = useCallback(async (projectId: string) => {
|
||||
try {
|
||||
setRepositories(await listRepositories(projectId));
|
||||
} catch {
|
||||
setRepositories([]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const activeSessions = useMemo(
|
||||
() =>
|
||||
safeSessions.filter((s) =>
|
||||
["running", "building", "pending"].includes(s.status),
|
||||
),
|
||||
[safeSessions],
|
||||
);
|
||||
const recentSessions = useMemo(
|
||||
() =>
|
||||
safeSessions
|
||||
.filter((s) => ["stopped", "error"].includes(s.status))
|
||||
.slice(0, 5),
|
||||
[safeSessions],
|
||||
);
|
||||
|
||||
const actions = useDashboardActions(loadHome);
|
||||
|
||||
return (
|
||||
<section className="stack home-page">
|
||||
<header className="home-hero card">
|
||||
<div className="stack-sm">
|
||||
<p className="eyebrow">Workspace overview</p>
|
||||
<h1>Home</h1>
|
||||
<p className="muted">
|
||||
Open sessions, available projects, and the fastest path back into
|
||||
work.
|
||||
</p>
|
||||
</div>
|
||||
<div className="home-hero-actions">
|
||||
<button
|
||||
className="primary-button"
|
||||
type="button"
|
||||
onClick={() => navigate("/projects")}
|
||||
>
|
||||
New Project
|
||||
</button>
|
||||
<button
|
||||
className="secondary-button"
|
||||
type="button"
|
||||
onClick={() => navigate("/settings")}
|
||||
>
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{status === "loading" && <LoadingState message="Loading overview..." />}
|
||||
{status === "error" && (
|
||||
<ErrorState
|
||||
message="Unable to load your workspace overview."
|
||||
onRetry={() => void loadHome()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{status === "ready" && summary && (
|
||||
<>
|
||||
<DashboardSummaryComponent
|
||||
summary={summary}
|
||||
activeSessionsCount={activeSessions.length}
|
||||
/>
|
||||
|
||||
<section className="card stack home-section">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<p className="eyebrow">Open sessions</p>
|
||||
<h2>{activeSessions.length}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<ActiveSessionsList
|
||||
sessions={activeSessions}
|
||||
actionBusy={actions.actionBusy}
|
||||
onOpen={actions.handleOpen}
|
||||
onStop={actions.handleStop}
|
||||
onDelete={actions.handleDelete}
|
||||
onRecreateTunnel={actions.handleRecreateTunnel}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="card stack home-section">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<p className="eyebrow">Available projects</p>
|
||||
<h2>{projects.length}</h2>
|
||||
</div>
|
||||
<button
|
||||
className="secondary-button"
|
||||
type="button"
|
||||
onClick={() => navigate("/projects")}
|
||||
>
|
||||
View all
|
||||
</button>
|
||||
</div>
|
||||
<ProjectsSection
|
||||
projects={projects}
|
||||
onOpenProject={(id) => navigate(`/projects/${id}`)}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="card stack home-section">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<p className="eyebrow">Quick create</p>
|
||||
<h2>Start a session</h2>
|
||||
</div>
|
||||
</div>
|
||||
<QuickCreateForm
|
||||
projects={projects}
|
||||
repositories={repositories}
|
||||
toolTypes={toolTypes}
|
||||
saveState={actions.saveState}
|
||||
onSubmit={actions.handleCreate}
|
||||
onProjectChange={loadRepos}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<RecentSessionsSection
|
||||
sessions={recentSessions}
|
||||
onOpen={actions.handleOpen}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { HomePage as DashboardPage };
|
||||
Reference in New Issue
Block a user