نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی

مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت.
تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
mjalmoudy94
2026-10-13 00:39:28 +03:30
commit 717e53a7ce
185 changed files with 39910 additions and 0 deletions
+150
View File
@@ -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>
);
}