Add admin site settings sections and locale defaults
This commit is contained in:
@@ -12,10 +12,16 @@ import {
|
||||
SITE_SETTINGS_FAVICON_FIELD_KEY,
|
||||
SITE_SETTINGS_LOGO_DARK_FIELD_KEY,
|
||||
SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY,
|
||||
getSiteSettings,
|
||||
updateSiteSettings,
|
||||
} from "@/lib/app-config";
|
||||
import { getAdminAppPath, toInternalAdminPath } from "@/lib/admin-routing";
|
||||
import { PAGE_TITLE_TOKEN, type SiteSettings } from "@/lib/site-settings";
|
||||
import {
|
||||
PAGE_TITLE_TOKEN,
|
||||
normalizeSiteDefaultLocale,
|
||||
normalizeSitePrimaryColor,
|
||||
type SiteSettings,
|
||||
} from "@/lib/site-settings";
|
||||
import { clearAdminSessionCookie, isAdminAuthenticated } from "@/lib/admin-auth";
|
||||
import { replaceEntityMediaUsages } from "@/lib/media";
|
||||
import { resolveMediaSelection } from "@/lib/media-service";
|
||||
@@ -91,12 +97,17 @@ async function revalidateSiteSettingsPages() {
|
||||
}
|
||||
|
||||
export async function saveSiteSettingsAction(formData: FormData) {
|
||||
return saveSiteBrandSettingsAction(formData);
|
||||
}
|
||||
|
||||
export async function saveSiteBrandSettingsAction(formData: FormData) {
|
||||
await ensureAdmin();
|
||||
|
||||
const createdMediaAssetIds: string[] = [];
|
||||
const uploadedPaths: string[] = [];
|
||||
|
||||
try {
|
||||
const currentSettings = await getSiteSettings();
|
||||
const siteLogoLightMedia = parseJsonObject(formData.get("siteLogoLightMedia"), "siteLogoLightMedia");
|
||||
const siteLogoDarkMedia = parseJsonObject(formData.get("siteLogoDarkMedia"), "siteLogoDarkMedia");
|
||||
const faviconMedia = parseJsonObject(formData.get("faviconMedia"), "faviconMedia");
|
||||
@@ -106,41 +117,12 @@ export async function saveSiteSettingsAction(formData: FormData) {
|
||||
);
|
||||
|
||||
const parsedSettings: SiteSettings = {
|
||||
locales: {
|
||||
ar: {
|
||||
siteName: String(formData.get("siteNameAr") ?? "").trim(),
|
||||
titleTemplate: String(formData.get("titleTemplateAr") ?? "").trim(),
|
||||
siteDescription: String(formData.get("siteDescriptionAr") ?? "").trim(),
|
||||
subhead: String(formData.get("subheadAr") ?? "").trim(),
|
||||
},
|
||||
en: {
|
||||
siteName: String(formData.get("siteNameEn") ?? "").trim(),
|
||||
titleTemplate: String(formData.get("titleTemplateEn") ?? "").trim(),
|
||||
siteDescription: String(formData.get("siteDescriptionEn") ?? "").trim(),
|
||||
subhead: String(formData.get("subheadEn") ?? "").trim(),
|
||||
},
|
||||
de: {
|
||||
siteName: String(formData.get("siteNameDe") ?? "").trim(),
|
||||
titleTemplate: String(formData.get("titleTemplateDe") ?? "").trim(),
|
||||
siteDescription: String(formData.get("siteDescriptionDe") ?? "").trim(),
|
||||
subhead: String(formData.get("subheadDe") ?? "").trim(),
|
||||
},
|
||||
...currentSettings,
|
||||
brand: {
|
||||
primaryColor: normalizeSitePrimaryColor(formData.get("primaryColor")),
|
||||
},
|
||||
};
|
||||
|
||||
for (const locale of routing.locales) {
|
||||
if (!parsedSettings.locales[locale].siteName) {
|
||||
throw new Error(`Site Name fuer ${locale} ist erforderlich.`);
|
||||
}
|
||||
|
||||
if (
|
||||
!parsedSettings.locales[locale].titleTemplate ||
|
||||
!parsedSettings.locales[locale].titleTemplate.includes(PAGE_TITLE_TOKEN)
|
||||
) {
|
||||
throw new Error(`Title Template fuer ${locale} muss {pageTitle} enthalten.`);
|
||||
}
|
||||
}
|
||||
|
||||
const siteLogoLightSelection = siteLogoLightMedia
|
||||
? await resolveMediaSelection({
|
||||
media: mediaFieldInputSchema.parse(siteLogoLightMedia),
|
||||
@@ -278,7 +260,7 @@ export async function saveSiteSettingsAction(formData: FormData) {
|
||||
});
|
||||
|
||||
await revalidateSiteSettingsPages();
|
||||
redirect(withMessage(getAdminAppPath("/site-settings"), "success", "Einstellungen gespeichert."));
|
||||
redirect(withMessage(getAdminAppPath("/site-settings/brand"), "success", "Einstellungen gespeichert."));
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) {
|
||||
throw error;
|
||||
@@ -291,6 +273,66 @@ export async function saveSiteSettingsAction(formData: FormData) {
|
||||
? error.message
|
||||
: "Einstellungen konnten nicht gespeichert werden.";
|
||||
|
||||
redirect(withMessage(getAdminAppPath("/site-settings"), "error", message));
|
||||
redirect(withMessage(getAdminAppPath("/site-settings/brand"), "error", message));
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSiteLocalizationSettingsAction(formData: FormData) {
|
||||
await ensureAdmin();
|
||||
|
||||
try {
|
||||
const currentSettings = await getSiteSettings();
|
||||
const parsedSettings: SiteSettings = {
|
||||
...currentSettings,
|
||||
defaultLocale: normalizeSiteDefaultLocale(formData.get("defaultLocale")),
|
||||
locales: {
|
||||
ar: {
|
||||
siteName: String(formData.get("siteNameAr") ?? "").trim(),
|
||||
titleTemplate: String(formData.get("titleTemplateAr") ?? "").trim(),
|
||||
siteDescription: String(formData.get("siteDescriptionAr") ?? "").trim(),
|
||||
subhead: String(formData.get("subheadAr") ?? "").trim(),
|
||||
},
|
||||
en: {
|
||||
siteName: String(formData.get("siteNameEn") ?? "").trim(),
|
||||
titleTemplate: String(formData.get("titleTemplateEn") ?? "").trim(),
|
||||
siteDescription: String(formData.get("siteDescriptionEn") ?? "").trim(),
|
||||
subhead: String(formData.get("subheadEn") ?? "").trim(),
|
||||
},
|
||||
de: {
|
||||
siteName: String(formData.get("siteNameDe") ?? "").trim(),
|
||||
titleTemplate: String(formData.get("titleTemplateDe") ?? "").trim(),
|
||||
siteDescription: String(formData.get("siteDescriptionDe") ?? "").trim(),
|
||||
subhead: String(formData.get("subheadDe") ?? "").trim(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const locale of routing.locales) {
|
||||
if (!parsedSettings.locales[locale].siteName) {
|
||||
throw new Error(`Site Name fuer ${locale} ist erforderlich.`);
|
||||
}
|
||||
|
||||
if (
|
||||
!parsedSettings.locales[locale].titleTemplate ||
|
||||
!parsedSettings.locales[locale].titleTemplate.includes(PAGE_TITLE_TOKEN)
|
||||
) {
|
||||
throw new Error(`Title Template fuer ${locale} muss {pageTitle} enthalten.`);
|
||||
}
|
||||
}
|
||||
|
||||
await updateSiteSettings(parsedSettings);
|
||||
await revalidateSiteSettingsPages();
|
||||
redirect(withMessage(getAdminAppPath("/site-settings/localization"), "success", "Einstellungen gespeichert."));
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Einstellungen konnten nicht gespeichert werden.";
|
||||
|
||||
redirect(withMessage(getAdminAppPath("/site-settings/localization"), "error", message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { MediaKind } from "@prisma/client";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { SiteSettingsForm } from "@/components/admin/site-settings-form";
|
||||
import { AdminDashboardShell } from "@/components/admin/admin-dashboard-shell";
|
||||
import { clearAdminSessionCookie, isAdminAuthenticated } from "@/lib/admin-auth";
|
||||
import { getAdminAppPath } from "@/lib/admin-routing";
|
||||
import {
|
||||
getSiteSettings,
|
||||
getSiteSettingsMediaBindings,
|
||||
} from "@/lib/app-config";
|
||||
import { getMediaOptions } from "@/lib/media";
|
||||
|
||||
import { saveSiteBrandSettingsAction } from "../actions";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const copy = {
|
||||
title: "Brand",
|
||||
subtitle: "Primaerfarbe, Logos, Favicon und OG Assets verwalten.",
|
||||
overview: "Uebersicht",
|
||||
maintenance: "Wartungsmodus",
|
||||
uiKit: "UI Kit",
|
||||
media: "Media",
|
||||
siteSettings: "Settings",
|
||||
brandSettings: "Brand",
|
||||
localizationSettings: "Localization",
|
||||
smtp: "SMTP",
|
||||
portfolio: "Portfolio",
|
||||
logout: "Ausloggen",
|
||||
backToSite: "Zur Website",
|
||||
};
|
||||
|
||||
export default async function AdminSiteBrandSettingsPage() {
|
||||
if (!(await isAdminAuthenticated())) {
|
||||
redirect(getAdminAppPath("/"));
|
||||
}
|
||||
|
||||
async function logoutAction() {
|
||||
"use server";
|
||||
|
||||
await clearAdminSessionCookie();
|
||||
redirect(getAdminAppPath("/"));
|
||||
}
|
||||
|
||||
const [siteSettings, mediaBindings, mediaOptions] = await Promise.all([
|
||||
getSiteSettings(),
|
||||
getSiteSettingsMediaBindings(),
|
||||
getMediaOptions({ kind: MediaKind.IMAGE }),
|
||||
]);
|
||||
|
||||
return (
|
||||
<AdminDashboardShell
|
||||
copy={copy}
|
||||
active="site-settings"
|
||||
siteSettingsChild="brand"
|
||||
logoutAction={logoutAction}
|
||||
headerTitle={copy.title}
|
||||
headerDescription={copy.subtitle}
|
||||
>
|
||||
<MotionFade delay={0.16}>
|
||||
<SiteSettingsForm
|
||||
mode="brand"
|
||||
action={saveSiteBrandSettingsAction}
|
||||
initialSettings={siteSettings}
|
||||
initialBindings={mediaBindings}
|
||||
mediaOptions={mediaOptions}
|
||||
/>
|
||||
</MotionFade>
|
||||
</AdminDashboardShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { SiteSettingsForm } from "@/components/admin/site-settings-form";
|
||||
import { AdminDashboardShell } from "@/components/admin/admin-dashboard-shell";
|
||||
import { clearAdminSessionCookie, isAdminAuthenticated } from "@/lib/admin-auth";
|
||||
import { getAdminAppPath } from "@/lib/admin-routing";
|
||||
import {
|
||||
getDefaultSiteSettingsMediaBindings,
|
||||
getSiteSettings,
|
||||
} from "@/lib/app-config";
|
||||
|
||||
import { saveSiteLocalizationSettingsAction } from "../actions";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const copy = {
|
||||
title: "Localization",
|
||||
subtitle: "Seitennamen, Titelvorlagen und Standardsprache verwalten.",
|
||||
overview: "Uebersicht",
|
||||
maintenance: "Wartungsmodus",
|
||||
uiKit: "UI Kit",
|
||||
media: "Media",
|
||||
siteSettings: "Settings",
|
||||
brandSettings: "Brand",
|
||||
localizationSettings: "Localization",
|
||||
smtp: "SMTP",
|
||||
portfolio: "Portfolio",
|
||||
logout: "Ausloggen",
|
||||
backToSite: "Zur Website",
|
||||
};
|
||||
|
||||
export default async function AdminSiteLocalizationSettingsPage() {
|
||||
if (!(await isAdminAuthenticated())) {
|
||||
redirect(getAdminAppPath("/"));
|
||||
}
|
||||
|
||||
async function logoutAction() {
|
||||
"use server";
|
||||
|
||||
await clearAdminSessionCookie();
|
||||
redirect(getAdminAppPath("/"));
|
||||
}
|
||||
|
||||
const siteSettings = await getSiteSettings();
|
||||
|
||||
return (
|
||||
<AdminDashboardShell
|
||||
copy={copy}
|
||||
active="site-settings"
|
||||
siteSettingsChild="localization"
|
||||
logoutAction={logoutAction}
|
||||
headerTitle={copy.title}
|
||||
headerDescription={copy.subtitle}
|
||||
>
|
||||
<MotionFade delay={0.16}>
|
||||
<SiteSettingsForm
|
||||
mode="localization"
|
||||
action={saveSiteLocalizationSettingsAction}
|
||||
initialSettings={siteSettings}
|
||||
initialBindings={getDefaultSiteSettingsMediaBindings()}
|
||||
mediaOptions={[]}
|
||||
/>
|
||||
</MotionFade>
|
||||
</AdminDashboardShell>
|
||||
);
|
||||
}
|
||||
@@ -1,71 +1,9 @@
|
||||
import { MediaKind } from "@prisma/client";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { SiteSettingsForm } from "@/components/admin/site-settings-form";
|
||||
import { AdminDashboardShell } from "@/components/admin/admin-dashboard-shell";
|
||||
import { clearAdminSessionCookie, isAdminAuthenticated } from "@/lib/admin-auth";
|
||||
import { getAdminAppPath } from "@/lib/admin-routing";
|
||||
import {
|
||||
getSiteSettings,
|
||||
getSiteSettingsMediaBindings,
|
||||
} from "@/lib/app-config";
|
||||
import { getMediaOptions } from "@/lib/media";
|
||||
|
||||
import { saveSiteSettingsAction } from "./actions";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const copy = {
|
||||
title: "Settings",
|
||||
subtitle: "Globale Titel, Copy und Brand Assets verwalten.",
|
||||
overview: "Uebersicht",
|
||||
maintenance: "Wartungsmodus",
|
||||
uiKit: "UI Kit",
|
||||
media: "Media",
|
||||
siteSettings: "Settings",
|
||||
smtp: "SMTP",
|
||||
portfolio: "Portfolio",
|
||||
logout: "Ausloggen",
|
||||
backToSite: "Zur Website",
|
||||
};
|
||||
|
||||
export default async function AdminSiteSettingsPage() {
|
||||
if (!(await isAdminAuthenticated())) {
|
||||
redirect(getAdminAppPath("/"));
|
||||
}
|
||||
|
||||
async function logoutAction() {
|
||||
"use server";
|
||||
|
||||
await clearAdminSessionCookie();
|
||||
redirect(getAdminAppPath("/"));
|
||||
}
|
||||
|
||||
const [siteSettings, mediaBindings, mediaOptions] = await Promise.all([
|
||||
getSiteSettings(),
|
||||
getSiteSettingsMediaBindings(),
|
||||
getMediaOptions({ kind: MediaKind.IMAGE }),
|
||||
]);
|
||||
|
||||
return (
|
||||
<AdminDashboardShell
|
||||
copy={copy}
|
||||
active="site-settings"
|
||||
logoutAction={logoutAction}
|
||||
headerTitle={copy.title}
|
||||
headerDescription={copy.subtitle}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<MotionFade delay={0.16}>
|
||||
<SiteSettingsForm
|
||||
action={saveSiteSettingsAction}
|
||||
initialSettings={siteSettings}
|
||||
initialBindings={mediaBindings}
|
||||
mediaOptions={mediaOptions}
|
||||
/>
|
||||
</MotionFade>
|
||||
</div>
|
||||
</AdminDashboardShell>
|
||||
);
|
||||
export default function AdminSiteSettingsPage() {
|
||||
redirect(getAdminAppPath("/site-settings/brand"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../_admin/site-settings/brand/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../_admin/site-settings/localization/page";
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getSiteSettings } from "@/lib/app-config";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const settings = await getSiteSettings();
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
defaultLocale: settings.defaultLocale,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "no-store, max-age=0",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
+9
-1
@@ -7,8 +7,10 @@ import { QueryToastBridge } from "@/components/admin/query-toast-bridge";
|
||||
import { SoundProvider } from "@/components/sound-provider";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { getSiteSettings } from "@/lib/app-config";
|
||||
import { buildAppMetadata } from "@/lib/metadata";
|
||||
import { getDirection } from "@/lib/locale";
|
||||
import { buildSiteThemeStyleText } from "@/lib/site-theme";
|
||||
import "./globals.css";
|
||||
|
||||
const museo = localFont({
|
||||
@@ -82,10 +84,16 @@ export default async function RootLayout({
|
||||
}>) {
|
||||
noStore();
|
||||
|
||||
const locale = await getLocale().catch(() => "de");
|
||||
const [locale, siteSettings] = await Promise.all([
|
||||
getLocale().catch(() => "de"),
|
||||
getSiteSettings(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<html lang={locale} dir={getDirection(locale)} suppressHydrationWarning>
|
||||
<head>
|
||||
<style id="dynamic-site-theme">{buildSiteThemeStyleText(siteSettings.brand.primaryColor)}</style>
|
||||
</head>
|
||||
<body className={`${museo.variable} ${dubai.variable}`}>
|
||||
<ThemeProvider>
|
||||
<SoundProvider>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../_admin/site-settings/brand/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../_admin/site-settings/localization/page";
|
||||
Reference in New Issue
Block a user