diff --git a/src/actions/admin-nav-menu.ts b/src/actions/admin-nav-menu.ts new file mode 100644 index 00000000..7460a81f --- /dev/null +++ b/src/actions/admin-nav-menu.ts @@ -0,0 +1,43 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { z } from "zod"; +import { + ADMIN_NAV_CONFIG_KEY, + type AdminNavConfig, + serializeAdminNavConfig, +} from "@/lib/admin-nav-config"; +import { actionOk, adminAction } from "@/lib/foundation/action"; +import { PERMS } from "@/lib/permissions"; +import { siteSettings } from "@/lib/services/site-settings"; + +const schema = z.object({ + groupOrder: z.array(z.string()), + hiddenGroups: z.array(z.string()), + hiddenItems: z.array(z.string()), + itemOrder: z.record(z.string(), z.array(z.string())), +}); + +export const saveAdminNavConfig = adminAction( + { + permission: PERMS.SETTINGS_EDIT, + schema, + rateLimitKey: "admin-nav-config-save", + rateLimitMax: 30, + }, + async (ctx) => { + const config: AdminNavConfig = { + groupOrder: ctx.data.groupOrder, + hiddenGroups: ctx.data.hiddenGroups, + hiddenItems: ctx.data.hiddenItems, + itemOrder: ctx.data.itemOrder, + }; + await siteSettings.update( + ADMIN_NAV_CONFIG_KEY, + serializeAdminNavConfig(config), + ); + revalidatePath("/admin", "layout"); + revalidatePath("/admin/menu"); + return actionOk({ saved: true }); + }, +); diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index bc5806d0..41762864 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -11,6 +11,10 @@ import { LanguageSwitcher } from "@/components/language-switcher"; import { ThemeSwitcher } from "@/components/theme-switcher"; import { requireStaff } from "@/lib/admin/guard"; import { collectNavPermissionSlugs } from "@/lib/admin-nav"; +import { + ADMIN_NAV_CONFIG_KEY, + parseAdminNavConfig, +} from "@/lib/admin-nav-config"; import { setCsrfCookie } from "@/lib/foundation/security"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; @@ -86,6 +90,10 @@ async function Sidebar({ return slug.endsWith(".view") || slug === PERMS.ADMIN_DASHBOARD; }); + const navConfig = parseAdminNavConfig( + await siteSettings.get(ADMIN_NAV_CONFIG_KEY, ""), + ); + return (