6f41fa7cbe
- Install @phosphor-icons/react package - Create centralized Icon component with size/weight/color variants - Create icon registry with 34 icons across 5 categories - Replace all raw Unicode symbols with proper icon components - Add icons to navigation, buttons, status indicators, git operations - Add icon CSS with consistent sizing and spacing - Fix type definitions for Phosphor icon compatibility Quality gates: typecheck ✓, lint ✓, build ✓ (375KB bundle)
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
|
import { getDashboardSummary, type DashboardSummary } from "../api/dashboard";
|
|
import { Icon } from "../components/icon";
|
|
|
|
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">
|
|
<Icon name="refresh" size="sm" />
|
|
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">
|
|
<Icon name="add" size="sm" />
|
|
New Project
|
|
</button>
|
|
<button className="secondary-button" type="button">
|
|
<Icon name="add" size="sm" />
|
|
Add Repository
|
|
</button>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|