import { deletePage, getPageById, listPageTranslations, updatePage, upsertPageTranslation, } from "@cms/db" import { Button } from "@cms/ui/button" import Link from "next/link" import { redirect } from "next/navigation" import { AdminShell } from "@/components/admin-shell" import { PageBlockEditor } from "@/components/pages/page-block-editor" import { requirePermissionForRoute } from "@/lib/route-guards" export const dynamic = "force-dynamic" type SearchParamsInput = Record const SUPPORTED_LOCALES = ["de", "en", "es", "fr"] as const type SupportedLocale = (typeof SUPPORTED_LOCALES)[number] type PageProps = { params: Promise<{ id: string }> searchParams: Promise } function readFirstValue(value: string | string[] | undefined): string | null { if (Array.isArray(value)) { return value[0] ?? null } return value ?? null } function readInputString(formData: FormData, field: string): string { const value = formData.get(field) return typeof value === "string" ? value.trim() : "" } function readNullableString(formData: FormData, field: string): string | null { const value = readInputString(formData, field) return value.length > 0 ? value : null } function redirectWithState(pageId: string, params: { notice?: string; error?: string }) { const query = new URLSearchParams() if (params.notice) { query.set("notice", params.notice) } if (params.error) { query.set("error", params.error) } const value = query.toString() redirect(value ? `/pages/${pageId}?${value}` : `/pages/${pageId}`) } function normalizeLocale(input: string | null): SupportedLocale { if (input && SUPPORTED_LOCALES.includes(input as SupportedLocale)) { return input as SupportedLocale } return "en" } export default async function PageEditorPage({ params, searchParams }: PageProps) { const role = await requirePermissionForRoute({ nextPath: "/pages", permission: "pages:read", scope: "team", }) const resolvedParams = await params const pageId = resolvedParams.id const [resolvedSearchParams, pageRecord, translations] = await Promise.all([ searchParams, getPageById(pageId), listPageTranslations(pageId), ]) if (!pageRecord) { redirect("/pages?error=Page+not+found") } const page = pageRecord const notice = readFirstValue(resolvedSearchParams.notice) const error = readFirstValue(resolvedSearchParams.error) const selectedLocale = normalizeLocale(readFirstValue(resolvedSearchParams.locale)) const selectedTranslation = translations.find((entry) => entry.locale === selectedLocale) async function updatePageAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/pages", permission: "pages:write", scope: "team", }) try { await updatePage({ id: pageId, title: readInputString(formData, "title"), slug: readInputString(formData, "slug"), status: readInputString(formData, "status"), summary: readNullableString(formData, "summary"), content: readInputString(formData, "content"), seoTitle: readNullableString(formData, "seoTitle"), seoDescription: readNullableString(formData, "seoDescription"), }) } catch { redirectWithState(pageId, { error: "Failed to update page. Validate values and try again.", }) } redirectWithState(pageId, { notice: "Page updated.", }) } async function deletePageAction() { "use server" await requirePermissionForRoute({ nextPath: "/pages", permission: "pages:write", scope: "team", }) try { await deletePage(pageId) } catch { redirectWithState(pageId, { error: "Failed to delete page. Remove linked navigation references first.", }) } redirect("/pages?notice=Page+deleted") } async function upsertPageTranslationAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/pages", permission: "pages:write", scope: "team", }) const locale = normalizeLocale(readInputString(formData, "locale")) try { await upsertPageTranslation({ pageId, locale, title: readInputString(formData, "title"), summary: readNullableString(formData, "summary"), content: readInputString(formData, "content"), seoTitle: readNullableString(formData, "seoTitle"), seoDescription: readNullableString(formData, "seoDescription"), }) } catch { redirect(`/pages/${pageId}?error=Failed+to+save+translation.&locale=${locale}`) } redirect(`/pages/${pageId}?notice=Translation+saved.&locale=${locale}`) } return ( {notice ? (
{notice}
) : null} {error ? (
{error}
) : null}

{page.title}

ID: {page.id}

Back to pages

Translations

Add locale-specific page content. Missing locales fall back to base page fields.

{SUPPORTED_LOCALES.map((locale) => { const isActive = locale === selectedLocale const hasTranslation = translations.some((entry) => entry.locale === locale) return ( {locale.toUpperCase()} {hasTranslation ? "saved" : "missing"} ) })}
{translations.length > 0 ? (
{translations.map((translation) => ( ))}
Locale Title Updated
{translation.locale.toUpperCase()} {translation.title} {translation.updatedAt.toLocaleDateString("en-US")}
) : null}

Danger Zone

Deleting this page is permanent and may break linked navigation items.

) }