add otp swagger1
This commit is contained in:
171
app/(admin)/admin/news/page.tsx
Normal file
171
app/(admin)/admin/news/page.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,22 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {};
|
||||
const nextConfig: NextConfig = {
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/api/:path*",
|
||||
headers: [
|
||||
{ key: "Access-Control-Allow-Origin", value: "*" },
|
||||
{ key: "Access-Control-Allow-Methods", value: "GET,POST,PUT,PATCH,DELETE,OPTIONS" },
|
||||
{
|
||||
key: "Access-Control-Allow-Headers",
|
||||
value: "Content-Type, Authorization, X-Requested-With, x-news-mode, x-news-summary",
|
||||
},
|
||||
{ key: "Access-Control-Max-Age", value: "86400" },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { PrismaClient } from "../lib/generated/prisma";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { db } from "../lib/db";
|
||||
|
||||
async function main() {
|
||||
console.log("🔧 ساخت کاربر ادمین...\n");
|
||||
@@ -11,11 +9,11 @@ async function main() {
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// حذف کاربر قبلی اگر وجود دارد
|
||||
await prisma.user.deleteMany({
|
||||
await db.user.deleteMany({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
const user = await prisma.user.create({
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email,
|
||||
password: hashedPassword,
|
||||
@@ -40,5 +38,5 @@ main()
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
await db.$disconnect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user