نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import * as React from "react";
|
||||
import type { Project } from "@agy/contracts";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardHint,
|
||||
CardTitle,
|
||||
EmptyState,
|
||||
ErrorState,
|
||||
Field,
|
||||
LoadingState,
|
||||
} from "@agy/ui";
|
||||
import { ApiError, api } from "@/lib/api";
|
||||
import { useSession } from "@/lib/session";
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const { activeOrganization } = useSession();
|
||||
const [projects, setProjects] = React.useState<Project[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [name, setName] = React.useState("");
|
||||
const [description, setDescription] = React.useState("");
|
||||
const [creating, setCreating] = React.useState(false);
|
||||
const [formError, setFormError] = React.useState<string | null>(null);
|
||||
|
||||
const load = React.useCallback(async () => {
|
||||
if (!activeOrganization) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
setProjects(await api.listProjects(activeOrganization.id));
|
||||
} catch (err) {
|
||||
setError(err instanceof ApiError ? err.message : "دریافت پروژهها ناموفق بود.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [activeOrganization]);
|
||||
|
||||
React.useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
const onCreate = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (!activeOrganization) return;
|
||||
setFormError(null);
|
||||
setCreating(true);
|
||||
try {
|
||||
const created = await api.createProject(activeOrganization.id, {
|
||||
name: name.trim(),
|
||||
description: description.trim(),
|
||||
});
|
||||
setProjects((current) => [created, ...current]);
|
||||
setName("");
|
||||
setDescription("");
|
||||
} catch (err) {
|
||||
setFormError(err instanceof ApiError ? err.message : "ساخت پروژه ناموفق بود.");
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!activeOrganization) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="سازمانی انتخاب نشده است"
|
||||
description="برای دیدن پروژهها ابتدا یک سازمان بسازید."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<header className="flex flex-col gap-1">
|
||||
<h1 className="text-lg font-medium text-slate-100">پروژهها</h1>
|
||||
<p className="text-sm text-slate-400">
|
||||
هر پروژه میتواند یک مخزن خصوصی، یک محیط اجرای ایزوله و یک دامنهٔ تست داشته باشد.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<Card>
|
||||
<CardTitle>ساخت پروژه</CardTitle>
|
||||
<CardHint>نام پروژه به یک slug یکتا در سازمان تبدیل میشود.</CardHint>
|
||||
<form onSubmit={onCreate} className="mt-4 flex flex-col gap-4 sm:flex-row sm:items-end">
|
||||
<div className="flex-1">
|
||||
<Field
|
||||
label="نام پروژه"
|
||||
required
|
||||
minLength={2}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="مثلاً فروشگاه آنلاین"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Field
|
||||
label="توضیح (اختیاری)"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" loading={creating}>
|
||||
ساخت پروژه
|
||||
</Button>
|
||||
</form>
|
||||
{formError && (
|
||||
<div className="mt-3">
|
||||
<ErrorState title="ساخت پروژه ناموفق" message={formError} />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{loading ? (
|
||||
<LoadingState label="در حال دریافت پروژهها…" />
|
||||
) : error ? (
|
||||
<ErrorState message={error} onRetry={load} />
|
||||
) : projects.length === 0 ? (
|
||||
<EmptyState
|
||||
title="هنوز پروژهای نساختهاید"
|
||||
description="اولین پروژه را با فرم بالا بسازید؛ سپس میتوانید مخزن Git و دامنهٔ تست بسازید."
|
||||
/>
|
||||
) : (
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{projects.map((project) => (
|
||||
<Card key={project.id} className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-sm font-medium text-slate-100">{project.name}</p>
|
||||
<p className="text-xs text-slate-500 ltr-inline">{project.slug}</p>
|
||||
</div>
|
||||
{project.description && (
|
||||
<p className="text-xs text-slate-400">{project.description}</p>
|
||||
)}
|
||||
<Link href={`/projects/${project.id}`} className="mt-auto">
|
||||
<Button variant="secondary" size="sm" className="w-full">
|
||||
جزئیات پروژه
|
||||
</Button>
|
||||
</Link>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user