feat(housekeeping): cut over administration to ase

This commit is contained in:
Simo committed 2026-08-30 20:35:22 +02:00
1 parent c9e35cf602
commit 2b8f73a91d
457 files changed
+589 -55936

No files matched your search

-24
View File
@@ -1,24 +0,0 @@
import { redirect } from "next/navigation";
import { AuditClient } from "@/app/admin/import/audit/audit-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioAuditPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="space-y-6 p-4">
<div>
<h1 className="text-2xl font-bold tracking-tight">Catalog Audit</h1>
<p className="mt-1 text-muted-foreground">
Scan your entire furniture catalog for missing entries, broken
references, missing files, and items available from clone sources.
</p>
</div>
<AuditClient />
</div>
);
}
-17
View File
@@ -1,17 +0,0 @@
import { redirect } from "next/navigation";
import { ImportBadgesClient } from "@/app/admin/import/badges/import-badges-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioBadgesPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="p-4">
<ImportBadgesClient />
</div>
);
}
-17
View File
@@ -1,17 +0,0 @@
import { redirect } from "next/navigation";
import { ImportCloneClient } from "@/app/admin/import/clone/import-clone-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioClonePage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="p-4">
<ImportCloneClient />
</div>
);
}
-17
View File
@@ -1,17 +0,0 @@
import { redirect } from "next/navigation";
import { ImportClothingClient } from "@/app/admin/import/clothing/import-clothing-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioClothingPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="p-4">
<ImportClothingClient />
</div>
);
}
-17
View File
@@ -1,17 +0,0 @@
import { redirect } from "next/navigation";
import { ImportEffectsClient } from "@/app/admin/import/effects/import-effects-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioEffectsPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="p-4">
<ImportEffectsClient />
</div>
);
}
-30
View File
@@ -1,30 +0,0 @@
import { redirect } from "next/navigation";
import { StudioClient } from "@/components/admin/studio/studio-client";
import { buildFurniImportSource } from "@/lib/habbo-gamedata-hotel";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { getTreeFlat } from "@/lib/services/catalog-tree";
import { getHabboGamedataHotel } from "@/lib/services/habbo-gamedata-hotel";
import { siteSettings } from "@/lib/services/site-settings";
export default async function StudioFurniPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
const [hotel, catalogTree, translateEnabled] = await Promise.all([
getHabboGamedataHotel(),
getTreeFlat(),
siteSettings.getBool("furnidata_translate_enabled", true),
]);
return (
<StudioClient
source={buildFurniImportSource(hotel)}
initialTree={catalogTree}
defaultTranslate={translateEnabled}
/>
);
}
-77
View File
@@ -1,77 +0,0 @@
"use client";
import {
BadgeCheck,
Cat,
ClipboardCheck,
Copy,
Dumbbell,
Image as ImageIcon,
type LucideIcon,
Puzzle,
RefreshCw,
Shirt,
Upload,
Wrench,
} from "lucide-react";
import { usePathname } from "next/navigation";
import Link from "@/components/link";
import { cn } from "@/lib/utils";
const TABS: { href: string; label: string; icon: LucideIcon }[] = [
{ href: "/admin/studio/furni", label: "Furni", icon: Puzzle },
{ href: "/admin/studio/upload", label: "Upload", icon: Upload },
{ href: "/admin/studio/badges", label: "Badges", icon: BadgeCheck },
{ href: "/admin/studio/clothing", label: "Clothing", icon: Shirt },
{ href: "/admin/studio/effects", label: "Effects", icon: Dumbbell },
{ href: "/admin/studio/pets", label: "Pets", icon: Cat },
{ href: "/admin/studio/clone", label: "Clone", icon: Copy },
{ href: "/admin/studio/sync", label: "Sync", icon: RefreshCw },
{
href: "/admin/studio/repair-icons",
label: "Repair Icons",
icon: ImageIcon,
},
{ href: "/admin/studio/audit", label: "Audit", icon: ClipboardCheck },
{
href: "/admin/studio/maintenance",
label: "Catalogus onderhoud",
icon: Wrench,
},
];
export default function StudioLayout({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname() ?? "";
return (
<div
className="flex h-[calc(100dvh-7rem)] min-h-[520px] flex-col overflow-hidden rounded-xl border border-[var(--admin-border)]"
style={{ backgroundColor: "var(--admin-canvas)" }}
>
<div className="flex shrink-0 items-center gap-1 overflow-x-auto border-b border-[var(--admin-border)] bg-[var(--admin-surface)] px-2 py-1.5">
{TABS.map(({ href, label, icon: Icon }) => {
const active = pathname === href || pathname.startsWith(`${href}/`);
return (
<Link
key={href}
href={href}
className={cn(
"flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors",
active
? "bg-[var(--admin-accent)] text-[var(--admin-accent-foreground)]"
: "text-[var(--admin-text-muted)] hover:bg-[var(--admin-accent)]/10 hover:text-[var(--admin-text)]",
)}
>
<Icon size={13} />
{label}
</Link>
);
})}
</div>
<div className="min-h-0 flex-1 overflow-auto">{children}</div>
</div>
);
}
@@ -1,9 +0,0 @@
import { CatalogMaintenancePanel } from "@/components/admin/catalog/catalog-maintenance-panel";
export default function StudioCatalogMaintenancePage() {
return (
<div className="min-h-0">
<CatalogMaintenancePanel />
</div>
);
}
-5
View File
@@ -1,5 +0,0 @@
import { redirect } from "next/navigation";
export default function StudioIndexPage() {
redirect("/admin/studio/furni");
}
-17
View File
@@ -1,17 +0,0 @@
import { redirect } from "next/navigation";
import { ImportPetsClient } from "@/app/admin/import/pets/import-pets-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioPetsPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="p-4">
<ImportPetsClient />
</div>
);
}
@@ -1,27 +0,0 @@
import { redirect } from "next/navigation";
import { RepairIconsClient } from "@/app/admin/import/repair-icons/repair-icons-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioRepairIconsPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="space-y-6 p-4">
<div>
<h1 className="text-2xl font-bold tracking-tight">
Repair Missing Icons
</h1>
<p className="mt-1 text-muted-foreground">
Scan all existing furniture and download any missing icon files. Icons
are extracted from local .nitro bundles or fetched from configured
clone sources.
</p>
</div>
<RepairIconsClient />
</div>
);
}
-26
View File
@@ -1,26 +0,0 @@
import { redirect } from "next/navigation";
import { SyncAllClient } from "@/app/admin/import/sync/sync-all-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioSyncPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="space-y-6 p-4">
<div>
<h1 className="text-2xl font-bold tracking-tight">Sync All Sources</h1>
<p className="mt-1 text-muted-foreground">
Scan all configured clone sources and automatically import any
furniture that is not yet present in your hotel. Missing .nitro files,
icons, database entries, catalog items, and FurnitureData.json will be
added automatically.
</p>
</div>
<SyncAllClient />
</div>
);
}
-27
View File
@@ -1,27 +0,0 @@
import { redirect } from "next/navigation";
import { UploadFurniClient } from "@/app/admin/import/furni/upload/upload-furni-client";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function StudioUploadPage(_props: {
params: Promise<{ locale: string }>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
}
return (
<div className="space-y-6 p-4">
<div>
<h1 className="text-2xl font-bold tracking-tight">
Upload .nitro Furniture
</h1>
<p className="mt-1 text-muted-foreground">
Upload a .nitro bundle and optional icon to import furniture into the
hotel. The database entry, catalog item, and FurnitureData.json will
be updated automatically.
</p>
</div>
<UploadFurniClient />
</div>
);
}