refactor(admin): remove legacy menu editor
This commit is contained in:
1 parent
98b0009206
commit
667637f8da
12 files changed
+13
-461
No files matched your search
@@ -1,43 +0,0 @@
|
||||
"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 });
|
||||
},
|
||||
);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,49 +0,0 @@
|
||||
import { ListOrdered } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { AdminPageShell } from "@/components/admin/admin-page-shell";
|
||||
import { ADMIN_NAV_GROUPS } from "@/lib/admin-nav";
|
||||
import {
|
||||
ADMIN_NAV_CONFIG_KEY,
|
||||
parseAdminNavConfig,
|
||||
} from "@/lib/admin-nav-config";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { AdminMenuEditor } from "./admin-menu-editor";
|
||||
|
||||
export const metadata = { title: "Admin menu" };
|
||||
|
||||
export default async function AdminMenuPage() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.SETTINGS_VIEW, session.user.rank)) {
|
||||
redirect("/admin");
|
||||
}
|
||||
const canEdit = canAccess(
|
||||
permissions,
|
||||
PERMS.SETTINGS_EDIT,
|
||||
session.user.rank,
|
||||
);
|
||||
const t = await getTranslations("pages.admin.menu");
|
||||
|
||||
const catalog = ADMIN_NAV_GROUPS.map((g) => ({
|
||||
labelKey: g.labelKey,
|
||||
items: g.items.map((i) => ({ href: i.href, labelKey: i.labelKey })),
|
||||
}));
|
||||
const initialConfig = parseAdminNavConfig(
|
||||
await siteSettings.get(ADMIN_NAV_CONFIG_KEY, ""),
|
||||
);
|
||||
|
||||
return (
|
||||
<AdminPageShell
|
||||
icon={ListOrdered}
|
||||
title={t("title")}
|
||||
subtitle={t("subtitle")}
|
||||
>
|
||||
<AdminMenuEditor
|
||||
catalog={catalog}
|
||||
initialConfig={initialConfig}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
</AdminPageShell>
|
||||
);
|
||||
}
|
||||
@@ -612,8 +612,8 @@ describe("housekeeping foundation completion contracts", () => {
|
||||
HOUSEKEEPING_MIGRATION_MATRIX,
|
||||
);
|
||||
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(139);
|
||||
expect(discovered).toHaveLength(139);
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(138);
|
||||
expect(discovered).toHaveLength(138);
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,7 @@ describe("discoverLegacyPages", () => {
|
||||
it("discovers the exact legacy administration inventory", () => {
|
||||
const pages = discoverLegacyPages();
|
||||
|
||||
expect(pages).toHaveLength(139);
|
||||
expect(pages).toHaveLength(138);
|
||||
expect(pages).toContainEqual({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/users/:id/edit",
|
||||
|
||||
@@ -4,10 +4,10 @@ import { HOUSEKEEPING_MIGRATION_MATRIX } from "./matrix";
|
||||
import { validateMigrationEntries } from "./validate-matrix";
|
||||
|
||||
describe("HOUSEKEEPING_MIGRATION_MATRIX", () => {
|
||||
it("covers all 139 legacy pages exactly once", () => {
|
||||
it("covers all 138 legacy pages exactly once", () => {
|
||||
const discovered = discoverLegacyPages();
|
||||
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(139);
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(138);
|
||||
expect(
|
||||
validateMigrationEntries(discovered, HOUSEKEEPING_MIGRATION_MATRIX),
|
||||
).toEqual([]);
|
||||
|
||||
@@ -13,14 +13,13 @@ const SYSTEM_PREFIXES = [
|
||||
"/admin/housekeeping",
|
||||
"/admin/logs",
|
||||
"/admin/maintenance",
|
||||
"/admin/menu",
|
||||
"/admin/permissions",
|
||||
"/admin/settings",
|
||||
] as const;
|
||||
|
||||
describe("systemMigrationEntries", () => {
|
||||
it("covers all 20 System pages exactly once", () => {
|
||||
expect(systemMigrationEntries).toHaveLength(20);
|
||||
it("covers all 19 System pages exactly once", () => {
|
||||
expect(systemMigrationEntries).toHaveLength(19);
|
||||
expect(
|
||||
validateMigrationEntries(
|
||||
ownedLegacyPages(SYSTEM_PREFIXES),
|
||||
@@ -37,11 +36,6 @@ describe("systemMigrationEntries", () => {
|
||||
decision: "REMOVE",
|
||||
targetPath: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
legacyPath: "/admin/menu",
|
||||
decision: "REMOVE",
|
||||
targetPath: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -346,28 +346,6 @@ export const systemMigrationEntries: readonly MigrationEntry[] = [
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Maintenance changes global login availability and splash content"],
|
||||
}),
|
||||
plannedSystemEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/menu",
|
||||
sourceFile: "src/app/admin/menu/page.tsx",
|
||||
targetPath: null,
|
||||
decision: "REMOVE",
|
||||
capabilities: {
|
||||
read: [PERMS.SETTINGS_VIEW],
|
||||
mutate: [PERMS.SETTINGS_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["ADMIN_NAV_GROUPS", "siteSettings.get"],
|
||||
mutations: ["saveAdminNavConfig", "siteSettings.update"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
requiredTests: ["integration", "e2e"],
|
||||
notes: [
|
||||
"Remove the manual legacy menu editor because foundation manifests own navigation",
|
||||
],
|
||||
}),
|
||||
plannedSystemEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/permissions",
|
||||
|
||||
@@ -64,10 +64,7 @@ describe("admin-nav-config", () => {
|
||||
hiddenItems: ["/admin/photos", "/admin", "/admin/menu"],
|
||||
});
|
||||
expect(applied.map((g) => g.labelKey)).toEqual(["overview", "content"]);
|
||||
expect(applied[0]?.items.map((i) => i.href)).toEqual([
|
||||
"/admin",
|
||||
"/admin/menu",
|
||||
]);
|
||||
expect(applied[0]?.items.map((i) => i.href)).toEqual(["/admin"]);
|
||||
expect(applied[1]?.items.map((i) => i.href)).toEqual(["/admin/articles"]);
|
||||
expect(ADMIN_NAV_PINNED_HREFS.has("/admin")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { AdminNavGroup } from "@/lib/admin-nav";
|
||||
/** website_settings key storing JSON overlay for the admin sidebar. */
|
||||
export const ADMIN_NAV_CONFIG_KEY = "admin_nav_config";
|
||||
|
||||
/** Hrefs that cannot be hidden (dashboard + menu editor). */
|
||||
export const ADMIN_NAV_PINNED_HREFS = new Set(["/admin", "/admin/menu"]);
|
||||
/** Hrefs that cannot be hidden (dashboard). */
|
||||
export const ADMIN_NAV_PINNED_HREFS = new Set(["/admin"]);
|
||||
|
||||
export type AdminNavConfig = {
|
||||
/** Group labelKeys in display order. Missing groups append in catalog order. */
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
KeyRound,
|
||||
Languages,
|
||||
LayoutDashboard,
|
||||
ListOrdered,
|
||||
type LucideIcon,
|
||||
Megaphone,
|
||||
Monitor,
|
||||
@@ -541,12 +540,6 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
|
||||
icon: Cog,
|
||||
permission: PERMS.SETTINGS_VIEW,
|
||||
},
|
||||
{
|
||||
href: "/admin/menu",
|
||||
labelKey: "adminMenu",
|
||||
icon: ListOrdered,
|
||||
permission: PERMS.SETTINGS_VIEW,
|
||||
},
|
||||
{
|
||||
href: "/admin/theme",
|
||||
labelKey: "theme",
|
||||
|
||||
@@ -75,10 +75,10 @@ describe("staff smoke contract", () => {
|
||||
expect(existsSync("src/lib/admin/ops-online-users.ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("ships dynamic admin menu overlay", () => {
|
||||
it("keeps sidebar configuration without exposing the retired menu editor", () => {
|
||||
expect(existsSync("src/lib/admin-nav-config.ts")).toBe(true);
|
||||
expect(existsSync("src/app/admin/menu/page.tsx")).toBe(true);
|
||||
expect(readFileSync("src/lib/admin-nav.ts", "utf8")).toContain(
|
||||
expect(existsSync("src/app/admin/menu/page.tsx")).toBe(false);
|
||||
expect(readFileSync("src/lib/admin-nav.ts", "utf8")).not.toContain(
|
||||
"/admin/menu",
|
||||
);
|
||||
expect(
|
||||
|
||||
Reference in new issue
Block a user