feat(announcements): add locale audience targeting and published-home news
This commit is contained in:
@@ -14,6 +14,7 @@ import { requirePermissionForRoute } from "@/lib/route-guards"
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
type SearchParamsInput = Record<string, string | string[] | undefined>
|
||||
const SUPPORTED_LOCALES = ["de", "en", "es", "fr"] as const
|
||||
|
||||
function readFirstValue(value: string | string[] | undefined): string | null {
|
||||
if (Array.isArray(value)) {
|
||||
@@ -49,6 +50,22 @@ function readNullableDate(formData: FormData, field: string): Date | null {
|
||||
return parsed
|
||||
}
|
||||
|
||||
function readLocaleSelections(formData: FormData, field: string): string[] {
|
||||
const values = formData.getAll(field)
|
||||
const locales = new Set<string>()
|
||||
|
||||
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)
|
||||
|
||||
@@ -94,6 +111,7 @@ async function createAnnouncementAction(formData: FormData) {
|
||||
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"),
|
||||
@@ -125,6 +143,7 @@ async function updateAnnouncementAction(formData: FormData) {
|
||||
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"),
|
||||
@@ -277,6 +296,20 @@ export default async function AnnouncementsPage({
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-neutral-600">Target locales (empty = all locales)</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<label
|
||||
key={`create-locale-${locale}`}
|
||||
className="inline-flex items-center gap-2 text-sm"
|
||||
>
|
||||
<input name="targetLocales" type="checkbox" value={locale} className="size-4" />
|
||||
{locale.toUpperCase()}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<label className="space-y-1">
|
||||
<span className="text-xs text-neutral-600">Starts at</span>
|
||||
@@ -390,6 +423,26 @@ export default async function AnnouncementsPage({
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-3 space-y-1">
|
||||
<p className="text-xs text-neutral-600">Target locales (empty = all locales)</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<label
|
||||
key={`${announcement.id}-locale-${locale}`}
|
||||
className="inline-flex items-center gap-2 text-sm"
|
||||
>
|
||||
<input
|
||||
name="targetLocales"
|
||||
type="checkbox"
|
||||
value={locale}
|
||||
defaultChecked={announcement.targetLocales.includes(locale)}
|
||||
className="size-4"
|
||||
/>
|
||||
{locale.toUpperCase()}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap items-center justify-between gap-3">
|
||||
<label className="inline-flex items-center gap-2 text-sm text-neutral-700">
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user