Plugin content-pages : - Modèle Prisma ContentPage (slug PK, title, body markdown, category, published) - lib/content-pages.ts : helpers upsert/get/list/unpublish - lib/markdown.ts : mini-renderer markdown server-side sans deps externe (h1-h3, paragraphes, gras/italique, liens, listes ul/ol, hr, blockquote, échappement HTML) - ContentPageRenderer server component, applique le theme Guyane (font-serif) - 5 pages seedées : /a-propos, /faq, /comment-ca-marche, /pour-comites-entreprise, /devenir-loueur - Routes publiques + force-dynamic + guard requirePluginOr404 Plugin legal-pages : - Réutilise le même modèle ContentPage, catégorie 'legal' - 3 pages seedées : /cgv, /mentions-legales, /politique-de-confidentialite (contenu de base, à valider par avocat avant prod réelle) Admin : - /admin/content-pages : table par catégorie, statut publié/dépublié - /admin/content-pages/[slug] : éditeur markdown + toggle publié - PATCH /api/admin/content-pages/[slug] Hooks plugin : - onEnable seed + republish toutes les pages - onDisable dépublie toute la catégorie sans la supprimer (preserve les edits)
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import Link from "next/link";
|
|
import { requireRole } from "@/lib/authorization";
|
|
import { UserRole } from "@/generated/prisma/enums";
|
|
import { listContentPages } from "@/lib/content-pages";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const CATEGORY_LABEL: Record<string, string> = {
|
|
general: "Général",
|
|
legal: "Légales",
|
|
};
|
|
|
|
export default async function ContentPagesAdminPage() {
|
|
await requireRole([UserRole.ADMIN]);
|
|
const pages = await listContentPages();
|
|
|
|
const byCategory = pages.reduce<Record<string, typeof pages>>((acc, p) => {
|
|
(acc[p.category] ??= []).push(p);
|
|
return acc;
|
|
}, {});
|
|
|
|
return (
|
|
<div className="mx-auto max-w-5xl px-4 py-8 sm:px-6 lg:px-8">
|
|
<h1 className="text-2xl font-semibold">Pages éditoriales</h1>
|
|
<p className="mt-2 text-sm text-gray-600">
|
|
Pages markdown affichées dans le site public. La catégorie « Général »
|
|
est gérée par le plugin <code>content-pages</code>, la catégorie « Légales »
|
|
par <code>legal-pages</code>. Désactiver le plugin dépublie ses pages
|
|
sans les supprimer.
|
|
</p>
|
|
|
|
<div className="mt-6 space-y-8">
|
|
{Object.entries(byCategory).map(([cat, list]) => (
|
|
<section key={cat}>
|
|
<h2 className="mb-2 text-sm font-semibold uppercase tracking-wide text-gray-500">
|
|
{CATEGORY_LABEL[cat] ?? cat}
|
|
</h2>
|
|
<ul className="divide-y divide-gray-200 rounded-lg border border-gray-200 bg-white">
|
|
{list.map((p) => (
|
|
<li key={p.slug} className="flex items-center justify-between gap-4 px-4 py-3">
|
|
<div>
|
|
<div className="font-medium">{p.title}</div>
|
|
<div className="text-xs text-gray-500">
|
|
<code>/{p.slug}</code> · {p.published ? "publié" : "dépublié"} ·
|
|
mis à jour le {new Date(p.updatedAt).toLocaleDateString("fr-FR")}
|
|
</div>
|
|
</div>
|
|
<Link
|
|
href={`/admin/content-pages/${encodeURIComponent(p.slug)}`}
|
|
className="rounded-full bg-gray-900 px-3 py-1 text-xs font-semibold text-white hover:bg-gray-800"
|
|
>
|
|
Éditer
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|