import { hasPermission } from "@cms/content/rbac" import { createPost, deletePost, listPosts, updatePost } from "@cms/db" import { Button } from "@cms/ui/button" import { revalidatePath } from "next/cache" import Link from "next/link" import { redirect } from "next/navigation" import { AdminShell } from "@/components/admin-shell" import { translateMessage } from "@/i18n/messages" import { getAdminMessages, resolveAdminLocale } from "@/i18n/server" import { requirePermissionForRoute } from "@/lib/route-guards" export const dynamic = "force-dynamic" type SearchParamsInput = Record function readFirstValue(value: string | string[] | undefined): string | null { if (Array.isArray(value)) { return value[0] ?? null } return value ?? null } function readRequiredField(formData: FormData, field: string): string { const value = formData.get(field) if (typeof value !== "string") { return "" } return value.trim() } function readOptionalField(formData: FormData, field: string): string | undefined { const value = readRequiredField(formData, field) return value.length > 0 ? value : undefined } async function requireNewsWritePermission() { await requirePermissionForRoute({ nextPath: "/", permission: "news:write", scope: "team", }) } function redirectWithState(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 ? `/?${value}` : "/") } async function getDashboardTranslator() { const locale = await resolveAdminLocale() const messages = await getAdminMessages(locale) return (key: string, fallback: string) => translateMessage(messages, key, fallback) } async function createPostAction(formData: FormData) { "use server" await requireNewsWritePermission() const t = await getDashboardTranslator() const status = readRequiredField(formData, "status") try { await createPost({ title: readRequiredField(formData, "title"), slug: readRequiredField(formData, "slug"), excerpt: readOptionalField(formData, "excerpt"), body: readRequiredField(formData, "body"), status: status === "published" ? "published" : "draft", }) } catch { redirectWithState({ error: t("dashboard.posts.errors.createFailed", "Create failed. Please check your input."), }) } revalidatePath("/") redirectWithState({ notice: t("dashboard.posts.success.created", "Post created.") }) } async function updatePostAction(formData: FormData) { "use server" await requireNewsWritePermission() const t = await getDashboardTranslator() const id = readRequiredField(formData, "id") const status = readRequiredField(formData, "status") if (!id) { redirectWithState({ error: t("dashboard.posts.errors.updateMissingId", "Update failed. Missing post id."), }) } try { await updatePost(id, { title: readRequiredField(formData, "title"), slug: readRequiredField(formData, "slug"), excerpt: readOptionalField(formData, "excerpt"), body: readRequiredField(formData, "body"), status: status === "published" ? "published" : "draft", }) } catch { redirectWithState({ error: t("dashboard.posts.errors.updateFailed", "Update failed. Please check your input."), }) } revalidatePath("/") redirectWithState({ notice: t("dashboard.posts.success.updated", "Post updated.") }) } async function deletePostAction(formData: FormData) { "use server" await requireNewsWritePermission() const t = await getDashboardTranslator() const id = readRequiredField(formData, "id") if (!id) { redirectWithState({ error: t("dashboard.posts.errors.deleteMissingId", "Delete failed. Missing post id."), }) } try { await deletePost(id) } catch { redirectWithState({ error: t("dashboard.posts.errors.deleteFailed", "Delete failed.") }) } revalidatePath("/") redirectWithState({ notice: t("dashboard.posts.success.deleted", "Post deleted.") }) } export default async function AdminHomePage({ searchParams, }: { searchParams: Promise }) { const role = await requirePermissionForRoute({ nextPath: "/", permission: "news:read", scope: "team", }) const [resolvedSearchParams, locale, posts] = await Promise.all([ searchParams, resolveAdminLocale(), listPosts(), ]) const messages = await getAdminMessages(locale) const t = (key: string, fallback: string) => translateMessage(messages, key, fallback) const notice = readFirstValue(resolvedSearchParams.notice) const error = readFirstValue(resolvedSearchParams.error) const canCreatePost = hasPermission(role, "news:write", "team") return ( {t("dashboard.actions.openRoadmap", "Open roadmap and progress")} {t("settings.title", "Settings")} } > {notice ? (
{notice}
) : null} {error ? (
{error}
) : null}

{t("dashboard.posts.title", "Posts CRUD Sandbox")}

{t("dashboard.notices.crudSandboxTag", "MVP0 functional test")}

{canCreatePost ? (

{t("dashboard.posts.createTitle", "Create post")}