Files
stack/apps/web/src/spa/pages/projects.tsx
T

71 lines
2.2 KiB
TypeScript

import { useEffect, useState, type ReactElement } from 'react';
import { useNavigate } from 'react-router-dom';
import { ProjectCard } from '@/components/projects/project-card';
import { api } from '@/lib/api';
import type { Project } from '@/lib/types';
import { getErrorMessage } from './page-errors';
export function ProjectsPage(): ReactElement {
const navigate = useNavigate();
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
void api<Project[]>('/api/projects')
.then((response) => {
if (cancelled) return;
setProjects(response);
})
.catch((caught: unknown) => {
if (cancelled) return;
setError(getErrorMessage(caught, 'Failed to load projects.'));
})
.finally(() => {
if (cancelled) return;
setLoading(false);
});
return () => {
cancelled = true;
};
}, []);
return (
<div className="flex min-h-screen flex-col px-4 py-6 sm:px-6">
<header className="mb-6 border-b px-1 pb-3">
<h1 className="text-2xl font-semibold">Projects</h1>
</header>
{error ? (
<div role="alert" className="mb-6 rounded-lg border border-error/40 px-4 py-3 text-sm">
{error}
</div>
) : null}
{loading ? (
<p className="py-8 text-center text-sm text-text-muted">Loading projects...</p>
) : projects.length === 0 ? (
<div className="py-12 text-center">
<h2 className="text-lg font-medium text-text-secondary">No projects yet</h2>
<p className="mt-1 text-sm text-text-muted">
Projects will appear here when created via the gateway API
</p>
</div>
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => (
<ProjectCard
key={project.id}
project={project}
onClick={(selectedProject) => navigate(`/projects/${selectedProject.id}`)}
/>
))}
</div>
)}
</div>
);
}