diff --git a/src/app/admin/import/furni/upload/page.tsx b/src/app/admin/import/furni/upload/page.tsx new file mode 100644 index 0000000000..915d13e820 --- /dev/null +++ b/src/app/admin/import/furni/upload/page.tsx @@ -0,0 +1,26 @@ +import { redirect } from "next/navigation"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { UploadFurniClient } from "./upload-furni-client"; + +export default async function UploadFurniPage() { + const { session, permissions } = await getAdminContext(); + if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) { + redirect("/admin"); + } + + return ( +
+
+

+ Upload .nitro Furniture +

+

+ 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. +

+
+ +
+ ); +} diff --git a/src/app/admin/import/furni/upload/upload-furni-client.tsx b/src/app/admin/import/furni/upload/upload-furni-client.tsx new file mode 100644 index 0000000000..483960be66 --- /dev/null +++ b/src/app/admin/import/furni/upload/upload-furni-client.tsx @@ -0,0 +1,331 @@ +"use client"; + +import { AlertCircle, Check, Loader2, Upload } from "lucide-react"; +import { useRef, useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { adminFetch } from "@/lib/admin-fetch"; + +interface UploadResult { + itemId: number; + catalogItemId: number | null; + classname: string; + warnings: string[]; +} + +export function UploadFurniClient() { + const nitroRef = useRef(null); + const iconRef = useRef(null); + + const [classname, setClassname] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [itemType, setItemType] = useState<"s" | "i">("s"); + const [xdim, setXdim] = useState(1); + const [ydim, setYdim] = useState(1); + const [canstandon, setCanstandon] = useState(false); + const [cansiton, setCansiton] = useState(false); + const [canlayon, setCanlayon] = useState(false); + const [interactionType, setInteractionType] = useState("default"); + const [customparams, setCustomparams] = useState(""); + + const [nitroFile, setNitroFile] = useState(null); + const [iconFile, setIconFile] = useState(null); + + const [uploading, setUploading] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(""); + + function resetForm() { + setClassname(""); + setName(""); + setDescription(""); + setItemType("s"); + setXdim(1); + setYdim(1); + setCanstandon(false); + setCansiton(false); + setCanlayon(false); + setInteractionType("default"); + setCustomparams(""); + setNitroFile(null); + setIconFile(null); + setResult(null); + setError(""); + if (nitroRef.current) nitroRef.current.value = ""; + if (iconRef.current) iconRef.current.value = ""; + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setUploading(true); + setError(""); + setResult(null); + + try { + const fd = new FormData(); + fd.set("classname", classname.trim()); + fd.set("name", name.trim()); + fd.set("description", description.trim()); + fd.set("itemType", itemType); + fd.set("xdim", String(xdim)); + fd.set("ydim", String(ydim)); + fd.set("canstandon", String(canstandon)); + fd.set("cansiton", String(cansiton)); + fd.set("canlayon", String(canlayon)); + fd.set("interactionType", interactionType.trim()); + fd.set("customparams", customparams.trim()); + if (nitroFile) fd.set("nitro", nitroFile); + if (iconFile) fd.set("icon", iconFile); + + const res = await adminFetch("/api/admin/import/furni/upload", { + method: "POST", + body: fd, + }); + const data = await res.json(); + + if (!res.ok) { + setError(data.error || "Upload failed"); + toast.error(data.error || "Upload failed"); + return; + } + + setResult(data); + toast.success(`${data.classname} imported successfully`); + } catch { + const msg = "Network error"; + setError(msg); + toast.error(msg); + } finally { + setUploading(false); + } + } + + return ( +
+ {!result ? ( +
+
+
Furniture Details
+ +
+
+ + setClassname(e.target.value)} + required + /> +
+
+ + setName(e.target.value)} + required + /> +
+
+ +
+ +