172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import { revalidatePath } from "next/cache";
|
||
import { db } from "@/lib/db";
|
||
import { requireAdmin } from "@/lib/session";
|
||
|
||
function toDateTimeLocalValue(date: Date) {
|
||
const offsetMs = date.getTimezoneOffset() * 60 * 1000;
|
||
return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);
|
||
}
|
||
|
||
async function createNews(formData: FormData) {
|
||
"use server";
|
||
|
||
await requireAdmin();
|
||
|
||
const icon = String(formData.get("icon") ?? "").trim();
|
||
const title = String(formData.get("title") ?? "").trim();
|
||
const description = String(formData.get("description") ?? "").trim();
|
||
const newsTimeValue = String(formData.get("newsTime") ?? "").trim();
|
||
const newsTime = new Date(newsTimeValue);
|
||
|
||
if (!icon || !title || !description || Number.isNaN(newsTime.getTime())) {
|
||
return;
|
||
}
|
||
|
||
await db.fantasyNews.create({
|
||
data: {
|
||
icon,
|
||
title,
|
||
description,
|
||
newsTime,
|
||
},
|
||
});
|
||
|
||
revalidatePath("/admin/news");
|
||
revalidatePath("/");
|
||
}
|
||
|
||
async function deleteNews(formData: FormData) {
|
||
"use server";
|
||
|
||
await requireAdmin();
|
||
|
||
const id = String(formData.get("id") ?? "");
|
||
if (!id) return;
|
||
|
||
await db.fantasyNews.delete({ where: { id } });
|
||
|
||
revalidatePath("/admin/news");
|
||
revalidatePath("/");
|
||
}
|
||
|
||
export default async function AdminNewsPage() {
|
||
await requireAdmin();
|
||
|
||
const news = await db.fantasyNews.findMany({
|
||
orderBy: [{ newsTime: "desc" }, { createdAt: "desc" }],
|
||
});
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold">مدیریت اخبار</h1>
|
||
<p className="text-sm text-gray-500 mt-1">اخبار فانتزی که در API اخبار نمایش داده میشوند.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<form action={createNews} className="bg-white rounded-2xl shadow p-6 grid grid-cols-1 gap-4">
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700">
|
||
آیکن
|
||
<input
|
||
name="icon"
|
||
required
|
||
defaultValue="info"
|
||
className="rounded-xl border border-gray-300 px-4 py-2.5 outline-none focus:border-green-700"
|
||
placeholder="info"
|
||
/>
|
||
</label>
|
||
|
||
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700 col-span-2">
|
||
عنوان
|
||
<input
|
||
name="title"
|
||
required
|
||
className="rounded-xl border border-gray-300 px-4 py-2.5 outline-none focus:border-green-700"
|
||
placeholder="مثلاً خبر ترکیب تیمها"
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700">
|
||
متن خبر
|
||
<textarea
|
||
name="description"
|
||
required
|
||
rows={5}
|
||
className="rounded-xl border border-gray-300 px-4 py-3 outline-none focus:border-green-700 resize-y"
|
||
placeholder="متن کامل خبر را وارد کنید"
|
||
/>
|
||
</label>
|
||
|
||
<div className="flex items-end justify-between gap-4">
|
||
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700 w-72">
|
||
زمان خبر
|
||
<input
|
||
name="newsTime"
|
||
type="datetime-local"
|
||
required
|
||
defaultValue={toDateTimeLocalValue(new Date())}
|
||
className="rounded-xl border border-gray-300 px-4 py-2.5 outline-none focus:border-green-700"
|
||
/>
|
||
</label>
|
||
|
||
<button
|
||
type="submit"
|
||
className="bg-green-700 text-white px-6 py-2.5 rounded-xl hover:bg-green-800 transition font-medium"
|
||
>
|
||
ثبت خبر
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
<div className="bg-white rounded-2xl shadow overflow-hidden">
|
||
<table className="w-full text-sm">
|
||
<thead className="bg-gray-100 text-gray-600">
|
||
<tr>
|
||
<th className="text-right px-5 py-4">زمان</th>
|
||
<th className="text-right px-5 py-4">عنوان</th>
|
||
<th className="text-right px-5 py-4">متن</th>
|
||
<th className="px-5 py-4"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{news.map((item) => (
|
||
<tr key={item.id} className="border-t align-top hover:bg-gray-50 transition">
|
||
<td className="px-5 py-4 text-gray-500 whitespace-nowrap">
|
||
{item.newsTime.toLocaleString("fa-IR")}
|
||
</td>
|
||
<td className="px-5 py-4 font-medium">
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-gray-400">{item.icon}</span>
|
||
<span>{item.title}</span>
|
||
</div>
|
||
</td>
|
||
<td className="px-5 py-4 text-gray-600 leading-7 max-w-xl">
|
||
{item.description}
|
||
</td>
|
||
<td className="px-5 py-4">
|
||
<form action={deleteNews}>
|
||
<input type="hidden" name="id" value={item.id} />
|
||
<button type="submit" className="text-red-600 hover:underline text-xs">
|
||
حذف
|
||
</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
{news.length === 0 && (
|
||
<tr>
|
||
<td colSpan={4} className="text-center py-10 text-gray-400">
|
||
هنوز خبری ثبت نشده است
|
||
</td>
|
||
</tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|