import { createAnnouncement, deleteAnnouncement, listAnnouncements, updateAnnouncement, } from "@cms/db" import { Button } from "@cms/ui/button" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" import { AdminShell } from "@/components/admin-shell" import { requirePermissionForRoute } from "@/lib/route-guards" export const dynamic = "force-dynamic" type SearchParamsInput = Record const SUPPORTED_LOCALES = ["de", "en", "es", "fr"] as const 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 readNullableDate(formData: FormData, field: string): Date | null { const value = readInputString(formData, field) if (!value) { return null } const parsed = new Date(value) if (Number.isNaN(parsed.getTime())) { return null } return parsed } function readLocaleSelections(formData: FormData, field: string): string[] { const values = formData.getAll(field) const locales = new Set() for (const value of values) { if ( typeof value === "string" && SUPPORTED_LOCALES.includes(value as (typeof SUPPORTED_LOCALES)[number]) ) { locales.add(value) } } return Array.from(locales) } function readInt(formData: FormData, field: string, fallback = 100): number { const value = readInputString(formData, field) if (!value) { return fallback } const parsed = Number.parseInt(value, 10) if (!Number.isFinite(parsed)) { return fallback } return parsed } 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 ? `/announcements?${value}` : "/announcements") } async function createAnnouncementAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/announcements", permission: "banner:write", scope: "global", }) try { await createAnnouncement({ title: readInputString(formData, "title"), message: readInputString(formData, "message"), placement: readInputString(formData, "placement"), targetLocales: readLocaleSelections(formData, "targetLocales"), priority: readInt(formData, "priority", 100), ctaLabel: readNullableString(formData, "ctaLabel"), ctaHref: readNullableString(formData, "ctaHref"), startsAt: readNullableDate(formData, "startsAt"), endsAt: readNullableDate(formData, "endsAt"), isVisible: readInputString(formData, "isVisible") === "true", }) } catch { redirectWithState({ error: "Failed to create announcement." }) } revalidatePath("/announcements") revalidatePath("/") redirectWithState({ notice: "Announcement created." }) } async function updateAnnouncementAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/announcements", permission: "banner:write", scope: "global", }) try { await updateAnnouncement({ id: readInputString(formData, "id"), title: readInputString(formData, "title"), message: readInputString(formData, "message"), placement: readInputString(formData, "placement"), targetLocales: readLocaleSelections(formData, "targetLocales"), priority: readInt(formData, "priority", 100), ctaLabel: readNullableString(formData, "ctaLabel"), ctaHref: readNullableString(formData, "ctaHref"), startsAt: readNullableDate(formData, "startsAt"), endsAt: readNullableDate(formData, "endsAt"), isVisible: readInputString(formData, "isVisible") === "true", }) } catch { redirectWithState({ error: "Failed to update announcement." }) } revalidatePath("/announcements") revalidatePath("/") redirectWithState({ notice: "Announcement updated." }) } async function deleteAnnouncementAction(formData: FormData) { "use server" await requirePermissionForRoute({ nextPath: "/announcements", permission: "banner:write", scope: "global", }) try { await deleteAnnouncement(readInputString(formData, "id")) } catch { redirectWithState({ error: "Failed to delete announcement." }) } revalidatePath("/announcements") revalidatePath("/") redirectWithState({ notice: "Announcement deleted." }) } function dateInputValue(value: Date | null): string { if (!value) { return "" } return value.toISOString().slice(0, 10) } export default async function AnnouncementsPage({ searchParams, }: { searchParams: Promise }) { const role = await requirePermissionForRoute({ nextPath: "/announcements", permission: "banner:read", scope: "global", }) const [resolvedSearchParams, announcements] = await Promise.all([ searchParams, listAnnouncements(200), ]) const notice = readFirstValue(resolvedSearchParams.notice) const error = readFirstValue(resolvedSearchParams.error) return ( {notice ? (
{notice}
) : null} {error ? (
{error}
) : null}

Create Announcement