feat(plugins): content-pages + legal-pages (Phase 4.1 + 4.3)

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)
This commit is contained in:
Claude Integration 2026-05-31 10:12:13 +00:00
parent ae8f79b436
commit 68f37f554f
20 changed files with 1116 additions and 0 deletions

View file

@ -0,0 +1,31 @@
import { renderMarkdown } from "@/lib/markdown";
import type { ContentPage } from "@/lib/content-pages";
/**
* Rend une ContentPage en HTML markdown. Server component pur.
* Pas de "use client" le markdown est rendu côté serveur, pas de hydration.
*/
export function ContentPageRenderer({ page }: { page: ContentPage }) {
const html = renderMarkdown(page.body);
return (
<article className="mx-auto max-w-3xl px-6 py-12 sm:px-8 lg:px-12">
<header className="mb-8">
<h1 className="font-serif text-4xl font-medium tracking-tight text-[var(--color-karbe-ink)] sm:text-5xl">
{page.title}
</h1>
<p className="mt-2 text-xs uppercase tracking-wider text-[var(--color-karbe-canopy-700)]/70">
Mis à jour le{" "}
{new Date(page.updatedAt).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</p>
</header>
<div
className="text-base leading-relaxed text-[var(--color-karbe-ink)]/90"
dangerouslySetInnerHTML={{ __html: html }}
/>
</article>
);
}