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
|
||||
|
||||
@@ -52,7 +52,7 @@ export default async function LocaleLayout({ children, params }: LocaleLayoutPro
|
||||
<NextIntlClientProvider locale={locale}>
|
||||
<Providers>
|
||||
<PublicHeaderBanner banner={banner} />
|
||||
<PublicAnnouncements placement="global_top" />
|
||||
<PublicAnnouncements placement="global_top" locale={locale} />
|
||||
<PublicSiteHeader />
|
||||
<main>{children}</main>
|
||||
<PublicSiteFooter />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getPublishedPageBySlugForLocale, listPosts } from "@cms/db"
|
||||
import { getPublishedPageBySlugForLocale, listPostsForLocale } from "@cms/db"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { PublicAnnouncements } from "@/components/public-announcements"
|
||||
import { PublicPageView } from "@/components/public-page-view"
|
||||
@@ -15,14 +15,14 @@ export default async function HomePage({ params }: HomePageProps) {
|
||||
|
||||
const [homePage, posts, t] = await Promise.all([
|
||||
getPublishedPageBySlugForLocale("home", locale),
|
||||
listPosts(),
|
||||
listPostsForLocale(locale),
|
||||
getTranslations("Home"),
|
||||
])
|
||||
|
||||
return (
|
||||
<section>
|
||||
{homePage ? <PublicPageView page={homePage} /> : null}
|
||||
<PublicAnnouncements placement="homepage" />
|
||||
<PublicAnnouncements placement="homepage" locale={locale} />
|
||||
|
||||
<section className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-6 py-6 pb-16">
|
||||
<header className="space-y-3">
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from "next/link"
|
||||
|
||||
type PublicAnnouncementsProps = {
|
||||
placement: "global_top" | "homepage"
|
||||
locale?: string
|
||||
}
|
||||
|
||||
function AnnouncementCard({ announcement }: { announcement: PublicAnnouncement }) {
|
||||
@@ -22,8 +23,8 @@ function AnnouncementCard({ announcement }: { announcement: PublicAnnouncement }
|
||||
)
|
||||
}
|
||||
|
||||
export async function PublicAnnouncements({ placement }: PublicAnnouncementsProps) {
|
||||
const announcements = await listActiveAnnouncements(placement)
|
||||
export async function PublicAnnouncements({ placement, locale }: PublicAnnouncementsProps) {
|
||||
const announcements = await listActiveAnnouncements(placement, new Date(), locale)
|
||||
|
||||
if (announcements.length === 0) {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user