Add editable localized marquee settings
This commit is contained in:
@@ -34,6 +34,14 @@ export {
|
||||
type ContactProtectionFormValues,
|
||||
type PublicContactProtectionSettings,
|
||||
} from "./contact-protection";
|
||||
export {
|
||||
MARQUEE_SETTINGS_KEY,
|
||||
buildDefaultMarqueeSettings,
|
||||
parseMarqueeSettingsValue,
|
||||
splitMarqueeRowItems,
|
||||
type MarqueeLocaleSettings,
|
||||
type MarqueeSettings,
|
||||
} from "./marquee-settings";
|
||||
import {
|
||||
DEFAULT_SITE_NAME,
|
||||
SITE_NAME_KEY,
|
||||
@@ -68,6 +76,12 @@ import {
|
||||
type ContactProtectionFormValues,
|
||||
type PublicContactProtectionSettings,
|
||||
} from "./contact-protection";
|
||||
import {
|
||||
MARQUEE_SETTINGS_KEY,
|
||||
buildDefaultMarqueeSettings,
|
||||
parseMarqueeSettingsValue,
|
||||
type MarqueeSettings,
|
||||
} from "./marquee-settings";
|
||||
|
||||
export async function getMaintenanceMode(): Promise<boolean> {
|
||||
try {
|
||||
@@ -203,6 +217,32 @@ export async function updateContactProtectionSettings(
|
||||
});
|
||||
}
|
||||
|
||||
export async function getMarqueeSettings(): Promise<MarqueeSettings> {
|
||||
try {
|
||||
const config = await prisma.appConfig.findUnique({
|
||||
where: { key: MARQUEE_SETTINGS_KEY },
|
||||
select: { value: true },
|
||||
});
|
||||
|
||||
return parseMarqueeSettingsValue(config?.value);
|
||||
} catch {
|
||||
return buildDefaultMarqueeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateMarqueeSettings(settings: MarqueeSettings): Promise<void> {
|
||||
await prisma.appConfig.upsert({
|
||||
where: { key: MARQUEE_SETTINGS_KEY },
|
||||
update: {
|
||||
value: JSON.stringify(settings),
|
||||
},
|
||||
create: {
|
||||
key: MARQUEE_SETTINGS_KEY,
|
||||
value: JSON.stringify(settings),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSiteSettingsMediaBindings(): Promise<SiteSettingsMediaBindings> {
|
||||
try {
|
||||
const usages = await prisma.mediaUsage.findMany({
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
export const MARQUEE_SETTINGS_KEY = "marquee_settings";
|
||||
|
||||
export type MarqueeLocaleSettings = {
|
||||
row1: string;
|
||||
row2: string;
|
||||
row3: string;
|
||||
row4: string;
|
||||
};
|
||||
|
||||
export type MarqueeSettings = {
|
||||
locales: {
|
||||
ar: MarqueeLocaleSettings;
|
||||
en: MarqueeLocaleSettings;
|
||||
de: MarqueeLocaleSettings;
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_ROW_1 = [
|
||||
"Next.js",
|
||||
"React",
|
||||
"WordPress",
|
||||
"Docker",
|
||||
"Technical SEO",
|
||||
"Tailwind CSS",
|
||||
"Google Analytics",
|
||||
"Framer Motion",
|
||||
"Cloudflare",
|
||||
"Performance",
|
||||
].join("\n");
|
||||
|
||||
const DEFAULT_ROW_2 = [
|
||||
"TypeScript",
|
||||
"Node.js",
|
||||
"WooCommerce",
|
||||
"Schema Markup",
|
||||
"DevOps",
|
||||
"Responsive Design",
|
||||
"Core Web Vitals",
|
||||
"Plausible Analytics",
|
||||
"Design Systems",
|
||||
"Server Infrastructure",
|
||||
].join("\n");
|
||||
|
||||
const DEFAULT_ROW_3 = [
|
||||
"JavaScript",
|
||||
"REST APIs",
|
||||
"Git",
|
||||
"Linux",
|
||||
"Search Console",
|
||||
"Conversion Tracking",
|
||||
"UI Engineering",
|
||||
"Accessibility",
|
||||
"Component Architecture",
|
||||
"Web Performance",
|
||||
].join("\n");
|
||||
|
||||
const DEFAULT_ROW_4 = [
|
||||
"Frontend Strategy",
|
||||
"CMS Integration",
|
||||
"Motion Design",
|
||||
"Content Architecture",
|
||||
"E-Commerce",
|
||||
"SEO Audits",
|
||||
"Deployments",
|
||||
"Scalable UI",
|
||||
"Analytics Setup",
|
||||
"Product Pages",
|
||||
].join("\n");
|
||||
|
||||
function buildDefaultLocaleSettings(): MarqueeLocaleSettings {
|
||||
return {
|
||||
row1: DEFAULT_ROW_1,
|
||||
row2: DEFAULT_ROW_2,
|
||||
row3: DEFAULT_ROW_3,
|
||||
row4: DEFAULT_ROW_4,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildDefaultMarqueeSettings(): MarqueeSettings {
|
||||
return {
|
||||
locales: {
|
||||
ar: buildDefaultLocaleSettings(),
|
||||
en: buildDefaultLocaleSettings(),
|
||||
de: buildDefaultLocaleSettings(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeLocaleSettings(value: unknown, defaults: MarqueeLocaleSettings): MarqueeLocaleSettings {
|
||||
const candidate = value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
||||
|
||||
return {
|
||||
row1: typeof candidate.row1 === "string" && candidate.row1.trim() ? candidate.row1.trim() : defaults.row1,
|
||||
row2: typeof candidate.row2 === "string" && candidate.row2.trim() ? candidate.row2.trim() : defaults.row2,
|
||||
row3: typeof candidate.row3 === "string" && candidate.row3.trim() ? candidate.row3.trim() : defaults.row3,
|
||||
row4: typeof candidate.row4 === "string" && candidate.row4.trim() ? candidate.row4.trim() : defaults.row4,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMarqueeSettingsValue(rawValue?: string | null): MarqueeSettings {
|
||||
const defaults = buildDefaultMarqueeSettings();
|
||||
|
||||
if (!rawValue) {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(rawValue) as { locales?: Record<string, unknown> };
|
||||
const locales = parsed && typeof parsed === "object" ? parsed.locales : undefined;
|
||||
|
||||
return {
|
||||
locales: {
|
||||
ar: normalizeLocaleSettings(locales?.ar, defaults.locales.ar),
|
||||
en: normalizeLocaleSettings(locales?.en, defaults.locales.en),
|
||||
de: normalizeLocaleSettings(locales?.de, defaults.locales.de),
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
|
||||
export function splitMarqueeRowItems(rawValue: string): string[] {
|
||||
return rawValue
|
||||
.split(/\r?\n|,/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ShieldAlert,
|
||||
SwatchBook,
|
||||
Tags,
|
||||
Type,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
@@ -18,6 +19,7 @@ type RootNavigationCopy = {
|
||||
portfolio: string;
|
||||
media: string;
|
||||
siteSettings: string;
|
||||
marquee?: string;
|
||||
smtp?: string;
|
||||
contactProtection?: string;
|
||||
};
|
||||
@@ -33,7 +35,7 @@ export type RootNavItem = {
|
||||
|
||||
export function getRootNavigation(
|
||||
copy: RootNavigationCopy,
|
||||
active: "overview" | "maintenance" | "ui-kit" | "portfolio" | "media" | "site-settings" | "smtp",
|
||||
active: "overview" | "maintenance" | "ui-kit" | "portfolio" | "media" | "site-settings" | "smtp" | "marquee",
|
||||
smtpChild?: "settings" | "contact-protection",
|
||||
portfolioChild?: "overview" | "projects" | "new-project" | "categories",
|
||||
): RootNavItem[] {
|
||||
@@ -68,6 +70,12 @@ export function getRootNavigation(
|
||||
icon: Globe2,
|
||||
active: active === "site-settings",
|
||||
},
|
||||
{
|
||||
label: copy.marquee ?? "Marquee",
|
||||
href: "/root/marquee",
|
||||
icon: Type,
|
||||
active: active === "marquee",
|
||||
},
|
||||
{
|
||||
label: copy.smtp ?? "SMTP",
|
||||
href: "/root/smtp",
|
||||
|
||||
Reference in New Issue
Block a user