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
This commit is contained in:
134
frontend/src/pages/ProjectPage.tsx
Normal file
134
frontend/src/pages/ProjectPage.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user