Paul-CICD/packages/client/src/pages/Projects.tsx

120 lines
3.7 KiB
TypeScript

import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Plus, Trash2, ChevronRight } from "lucide-react";
interface Project {
id: number;
name: string;
trigger_branch: string;
created_at: number;
}
export default function Projects() {
const [projects, setProjects] = useState<Project[]>([]);
const [creating, setCreating] = useState(false);
const [newName, setNewName] = useState("");
const navigate = useNavigate();
const load = () =>
fetch("/api/projects")
.then((r) => r.json())
.then(setProjects);
useEffect(() => {
load();
}, []);
const create = async () => {
if (!newName.trim()) return;
await fetch("/api/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: newName.trim() }),
});
setNewName("");
setCreating(false);
load();
};
const remove = async (e: React.MouseEvent, id: number) => {
e.stopPropagation();
if (!confirm("Delete this project and all its builds?")) return;
await fetch(`/api/projects/${id}`, { method: "DELETE" });
load();
};
return (
<div>
<div className="flex items-center justify-between mb-6">
<h1 className="text-xl font-semibold">Projects</h1>
<button
onClick={() => setCreating(true)}
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-500 text-white text-sm px-3 py-2 rounded-md transition-colors"
>
<Plus className="w-4 h-4" /> New Project
</button>
</div>
{creating && (
<div className="mb-4 flex gap-2">
<input
autoFocus
value={newName}
onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") create();
if (e.key === "Escape") setCreating(false);
}}
placeholder="Project name"
className="flex-1 bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
/>
<button
onClick={create}
className="bg-blue-600 hover:bg-blue-500 text-white text-sm px-4 py-2 rounded-md transition-colors"
>
Create
</button>
<button
onClick={() => setCreating(false)}
className="text-gray-400 hover:text-gray-200 text-sm px-3 py-2 rounded-md transition-colors"
>
Cancel
</button>
</div>
)}
{projects.length === 0 && !creating ? (
<p className="text-gray-500 text-sm">
No projects yet. Create one to get started.
</p>
) : (
<ul className="space-y-2">
{projects.map((p) => (
<li
key={p.id}
onClick={() => navigate(`/projects/${p.id}`)}
className="flex items-center justify-between bg-gray-900 border border-gray-800 hover:border-gray-600 rounded-lg px-4 py-3 cursor-pointer transition-colors group"
>
<div>
<p className="font-medium text-sm">{p.name}</p>
<p className="text-xs text-gray-500 mt-0.5">
branch: {p.trigger_branch}
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={(e) => remove(e, p.id)}
className="text-gray-600 hover:text-red-400 transition-colors opacity-0 group-hover:opacity-100"
>
<Trash2 className="w-4 h-4" />
</button>
<ChevronRight className="w-4 h-4 text-gray-600" />
</div>
</li>
))}
</ul>
)}
</div>
);
}