feat: implement auth, projects, and frontend foundation
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { getDashboardSummary, type DashboardSummary } from "../api/dashboard";
|
||||
|
||||
const CARDS = [
|
||||
{ label: "Projects", key: "projects" },
|
||||
{ label: "Repositories", key: "repositories" },
|
||||
{ label: "SSH Keys", key: "sshKeys" }
|
||||
] as const;
|
||||
|
||||
type DashboardStatus = "loading" | "ready" | "error";
|
||||
|
||||
export const DashboardPage = () => {
|
||||
const [status, setStatus] = useState<DashboardStatus>("loading");
|
||||
const [summary, setSummary] = useState<DashboardSummary | null>(null);
|
||||
|
||||
const loadSummary = useCallback(async () => {
|
||||
setStatus("loading");
|
||||
try {
|
||||
const data = await getDashboardSummary();
|
||||
setSummary(data);
|
||||
setStatus("ready");
|
||||
} catch {
|
||||
setSummary(null);
|
||||
setStatus("error");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void loadSummary();
|
||||
}, [loadSummary]);
|
||||
|
||||
const cards = useMemo(() => CARDS, []);
|
||||
const isEmpty =
|
||||
status === "ready" &&
|
||||
summary !== null &&
|
||||
summary.projects === 0 &&
|
||||
summary.repositories === 0 &&
|
||||
summary.sshKeys === 0 &&
|
||||
summary.recentActivity.length === 0;
|
||||
|
||||
return (
|
||||
<section className="stack">
|
||||
<h1>Dashboard</h1>
|
||||
<p className="muted">Your workspace overview will appear here.</p>
|
||||
|
||||
{status === "loading" && <p className="muted">Loading dashboard...</p>}
|
||||
|
||||
{status === "error" && (
|
||||
<div className="card stack">
|
||||
<p>Dashboard is unavailable</p>
|
||||
<button className="secondary-button" onClick={() => void loadSummary()} type="button">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="card-grid">
|
||||
{cards.map((card) => (
|
||||
<article className="card" key={card.label}>
|
||||
<p className="card-label">{card.label}</p>
|
||||
<p className="card-value">{summary ? String(summary[card.key]) : "-"}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isEmpty && <p className="muted">No activity yet</p>}
|
||||
|
||||
<div className="quick-actions">
|
||||
<button className="primary-button" type="button">
|
||||
New Project
|
||||
</button>
|
||||
<button className="secondary-button" type="button">
|
||||
Add Repository
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user