Files
orchard/frontend/src/pages/ProjectPage.tsx
Mondo Diaz ff7df9eb3f Rename terminology to industry standard terms
- Grove → Project
- Tree → Package
- Fruit → Artifact
- Graft → Tag
- Cultivate → Upload
- Harvest → Download

Updated across:
- Backend models, schemas, and routes
- Frontend types, API client, and components
- README documentation
- API endpoints now use /project/:project/packages pattern
2025-12-08 10:38:44 -06:00

135 lines
4.2 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import { Project, Package } from '../types';
import { getProject, listPackages, createPackage } from '../api';
import './Home.css';
function ProjectPage() {
const { projectName } = useParams<{ projectName: string }>();
const [project, setProject] = useState<Project | null>(null);
const [packages, setPackages] = useState<Package[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
const [newPackage, setNewPackage] = useState({ name: '', description: '' });
const [creating, setCreating] = useState(false);
useEffect(() => {
if (projectName) {
loadData();
}
}, [projectName]);
async function loadData() {
try {
setLoading(true);
const [projectData, packagesData] = await Promise.all([
getProject(projectName!),
listPackages(projectName!),
]);
setProject(projectData);
setPackages(packagesData);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load data');
} finally {
setLoading(false);
}
}
async function handleCreatePackage(e: React.FormEvent) {
e.preventDefault();
try {
setCreating(true);
await createPackage(projectName!, newPackage);
setNewPackage({ name: '', description: '' });
setShowForm(false);
loadData();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create package');
} finally {
setCreating(false);
}
}
if (loading) {
return <div className="loading">Loading...</div>;
}
if (!project) {
return <div className="error-message">Project not found</div>;
}
return (
<div className="home">
<nav className="breadcrumb">
<Link to="/">Projects</Link> / <span>{project.name}</span>
</nav>
<div className="page-header">
<div>
<h1>{project.name}</h1>
{project.description && <p className="description">{project.description}</p>}
</div>
<button className="btn btn-primary" onClick={() => setShowForm(!showForm)}>
{showForm ? 'Cancel' : '+ New Package'}
</button>
</div>
{error && <div className="error-message">{error}</div>}
{showForm && (
<form className="form card" onSubmit={handleCreatePackage}>
<h3>Create New Package</h3>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
value={newPackage.name}
onChange={(e) => setNewPackage({ ...newPackage, name: e.target.value })}
placeholder="releases"
required
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
id="description"
type="text"
value={newPackage.description}
onChange={(e) => setNewPackage({ ...newPackage, description: e.target.value })}
placeholder="Optional description"
/>
</div>
<button type="submit" className="btn btn-primary" disabled={creating}>
{creating ? 'Creating...' : 'Create Package'}
</button>
</form>
)}
{packages.length === 0 ? (
<div className="empty-state">
<p>No packages yet. Create your first package to start uploading artifacts!</p>
</div>
) : (
<div className="project-grid">
{packages.map((pkg) => (
<Link to={`/project/${projectName}/${pkg.name}`} key={pkg.id} className="project-card card">
<h3>{pkg.name}</h3>
{pkg.description && <p>{pkg.description}</p>}
<div className="project-meta">
<span className="date">
Created {new Date(pkg.created_at).toLocaleDateString()}
</span>
</div>
</Link>
))}
</div>
)}
</div>
);
}
export default ProjectPage;