diff --git a/prisma/migrations/20260531180000_add_content_pages/migration.sql b/prisma/migrations/20260531180000_add_content_pages/migration.sql new file mode 100644 index 0000000..4306682 --- /dev/null +++ b/prisma/migrations/20260531180000_add_content_pages/migration.sql @@ -0,0 +1,16 @@ +-- Plugin content-pages + legal-pages : table ContentPage + +CREATE TABLE "ContentPage" ( + "slug" TEXT PRIMARY KEY, + "title" TEXT NOT NULL, + "body" TEXT NOT NULL, + "lang" TEXT NOT NULL DEFAULT 'fr', + "category" TEXT NOT NULL DEFAULT 'general', + "published" BOOLEAN NOT NULL DEFAULT true, + "lastEditedBy" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL +); + +CREATE INDEX "ContentPage_category_idx" ON "ContentPage" ("category"); +CREATE INDEX "ContentPage_published_idx" ON "ContentPage" ("published"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a7d8c9e..4fd694e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -280,3 +280,19 @@ model Plugin { @@index([category]) @@index([enabled]) } + +model ContentPage { + slug String @id + title String + body String + lang String @default("fr") + // 'general' (about, faq, ...) ou 'legal' (cgv, mentions, ...) + category String @default("general") + published Boolean @default(true) + lastEditedBy String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([category]) + @@index([published]) +} diff --git a/src/app/a-propos/page.tsx b/src/app/a-propos/page.tsx new file mode 100644 index 0000000..e4a36b1 --- /dev/null +++ b/src/app/a-propos/page.tsx @@ -0,0 +1,18 @@ +import { notFound } from "next/navigation"; +import { getContentPage } from "@/lib/content-pages"; +import { isPluginEnabled } from "@/lib/plugins/server"; +import { ContentPageRenderer } from "@/components/ContentPageRenderer"; + +export const dynamic = "force-dynamic"; + +export async function generateMetadata() { + const page = await getContentPage("a-propos"); + return { title: page?.title ?? "À propos" }; +} + +export default async function AboutPage() { + if (!(await isPluginEnabled("content-pages"))) notFound(); + const page = await getContentPage("a-propos"); + if (!page) notFound(); + return ; +} diff --git a/src/app/admin/content-pages/[slug]/_components/EditorForm.tsx b/src/app/admin/content-pages/[slug]/_components/EditorForm.tsx new file mode 100644 index 0000000..64e3818 --- /dev/null +++ b/src/app/admin/content-pages/[slug]/_components/EditorForm.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +type Page = { + slug: string; + title: string; + body: string; + category: string; + published: boolean; +}; + +export default function EditorForm({ page }: { page: Page }) { + const router = useRouter(); + const [title, setTitle] = useState(page.title); + const [body, setBody] = useState(page.body); + const [published, setPublished] = useState(page.published); + const [busy, setBusy] = useState(false); + const [msg, setMsg] = useState(null); + const [err, setErr] = useState(null); + + async function save() { + setBusy(true); + setMsg(null); + setErr(null); + try { + const res = await fetch(`/api/admin/content-pages/${encodeURIComponent(page.slug)}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title, body, published }), + }); + if (!res.ok) { + const j = await res.json().catch(() => ({})); + throw new Error(j?.error || `HTTP ${res.status}`); + } + setMsg("Sauvegardé."); + router.refresh(); + } catch (e) { + setErr(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + } + + return ( +
+ + +