Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
93 lines
3.7 KiB
TypeScript
93 lines
3.7 KiB
TypeScript
import Link from "next/link";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { Newspaper } from "lucide-react";
|
|
import { StatusCard } from "@/components/admin/dashboard";
|
|
import { deleteArticle } from "@/actions/admin-articles";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function AdminArticles({ searchParams }: { searchParams: Promise<{ error?: string }> }) {
|
|
const { error } = await searchParams;
|
|
const t = await getTranslations("pages.admin.articles");
|
|
const articles = await prisma.websiteArticles
|
|
.findMany({
|
|
orderBy: { createdAt: "desc" },
|
|
select: { id: true, title: true, slug: true, createdAt: true },
|
|
take: 50,
|
|
})
|
|
.catch(() => []);
|
|
|
|
const latest = articles[0]?.createdAt ? articles[0].createdAt.toISOString().slice(0, 10) : "—";
|
|
|
|
return (
|
|
<main>
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
|
|
<Newspaper size={20} className="text-[var(--admin-accent)]" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">{t("title")}</h1>
|
|
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">{t("subtitle")}</p>
|
|
</div>
|
|
<Link href="/admin/articles/new" className="btn btn-primary">
|
|
+ {t("newArticle")}
|
|
</Link>
|
|
</div>
|
|
|
|
{error ? <p className="text-xs text-[var(--admin-error)] dark:text-[var(--admin-error)] mb-4">Error: {error}</p> : null}
|
|
|
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
|
|
<StatusCard label={t("articlesShown")} value={articles.length} icon="📰" />
|
|
<StatusCard label={t("latest")} value={latest} hint={t("mostRecentPost")} icon="🕒" />
|
|
</div>
|
|
|
|
<section className="mt-6">
|
|
<h2 className="admin-section-title">{t("recentArticles")}</h2>
|
|
{articles.length === 0 ? (
|
|
<div className="admin-empty">{t("noArticles")}</div>
|
|
) : (
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>{t("colTitle")}</th>
|
|
<th>{t("colSlug")}</th>
|
|
<th>{t("colCreated")}</th>
|
|
<th>{t("colActions")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{articles.map((a) => (
|
|
<tr key={String(a.id)}>
|
|
<td>
|
|
<Link href={`/admin/articles/${a.id}`}>{a.title}</Link>
|
|
</td>
|
|
<td className="text-sm theme-text-muted dark:theme-text-muted whitespace-nowrap">
|
|
<code>{a.slug}</code>
|
|
</td>
|
|
<td className="text-sm theme-text-muted dark:theme-text-muted whitespace-nowrap">
|
|
{a.createdAt ? a.createdAt.toISOString().slice(0, 10) : "—"}
|
|
</td>
|
|
<td>
|
|
<div className="flex gap-2">
|
|
<Link href={`/admin/articles/${a.id}`} className="btn btn-primary">
|
|
{t("edit")}
|
|
</Link>
|
|
<form action={deleteArticle} className="m-0">
|
|
<input type="hidden" name="id" value={String(a.id)} />
|
|
<button type="submit" className="btn btn-danger">
|
|
{t("delete")}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|