Wrap next/link in a shared Link component that ships prefetch=false by default, so no route is ever prefetched (viewport or hover) anymore, and drop the DNS prefetch hint. Removes hidden background requests that were the source of intermittent issues.
274 lines
7.4 KiB
TypeScript
274 lines
7.4 KiB
TypeScript
"use client";
|
|
|
|
import { useRouter } from "next/navigation";
|
|
import { useTranslations } from "next-intl";
|
|
import { useState } from "react";
|
|
import { updateItemsBase } from "@/actions/items-base";
|
|
import Link from "@/components/link";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { useServerAction } from "@/hooks/use-server-action";
|
|
|
|
export type ItemsBaseEditValues = {
|
|
id: number;
|
|
spriteId: number;
|
|
publicName: string;
|
|
itemName: string;
|
|
type: string;
|
|
width: number;
|
|
length: number;
|
|
stackHeight: string;
|
|
allowStack: number;
|
|
allowSit: number;
|
|
allowLay: number;
|
|
allowWalk: number;
|
|
allowGift: number;
|
|
allowTrade: number;
|
|
allowRecycle: number;
|
|
allowMarketplaceSell: number;
|
|
allowInventoryStack: number;
|
|
interactionType: string;
|
|
interactionModesCount: number;
|
|
vendingIds: string;
|
|
multiheight: string;
|
|
customparams: string;
|
|
};
|
|
|
|
const ALLOW_FLAGS: Array<{ key: keyof ItemsBaseEditValues; label: string }> = [
|
|
{ key: "allowStack", label: "Stack" },
|
|
{ key: "allowSit", label: "Sit" },
|
|
{ key: "allowLay", label: "Lay" },
|
|
{ key: "allowWalk", label: "Walk" },
|
|
{ key: "allowGift", label: "Gift" },
|
|
{ key: "allowTrade", label: "Trade" },
|
|
{ key: "allowRecycle", label: "Recycle" },
|
|
{ key: "allowMarketplaceSell", label: "Marketplace" },
|
|
{ key: "allowInventoryStack", label: "Inv. stack" },
|
|
];
|
|
|
|
export function ItemsBaseEditForm({
|
|
initial,
|
|
canEdit,
|
|
}: {
|
|
initial: ItemsBaseEditValues;
|
|
canEdit: boolean;
|
|
}) {
|
|
const t = useTranslations("pages.admin.items");
|
|
const router = useRouter();
|
|
const { run, isPending } = useServerAction();
|
|
const [form, setForm] = useState(initial);
|
|
|
|
function setField<K extends keyof ItemsBaseEditValues>(
|
|
key: K,
|
|
value: ItemsBaseEditValues[K],
|
|
) {
|
|
setForm((prev) => ({ ...prev, [key]: value }));
|
|
}
|
|
|
|
function handleSave() {
|
|
if (!canEdit || isPending) return;
|
|
const { id, ...fields } = form;
|
|
run(() => updateItemsBase({ id, fields }), {
|
|
successMessage: t("saved"),
|
|
onSuccess: () => router.refresh(),
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-sm text-muted-foreground m-0 font-mono">
|
|
#{form.id}
|
|
</p>
|
|
<h2 className="text-xl font-bold m-0">
|
|
{form.publicName || form.itemName || t("untitled")}
|
|
</h2>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" asChild>
|
|
<Link href="/admin/items">{t("back")}</Link>
|
|
</Button>
|
|
{canEdit ? (
|
|
<Button onClick={handleSave} disabled={isPending}>
|
|
{t("save")}
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm">{t("sectionIdentity")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div>
|
|
<Label htmlFor="publicName">{t("publicName")}</Label>
|
|
<Input
|
|
id="publicName"
|
|
value={form.publicName}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("publicName", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="itemName">{t("itemName")}</Label>
|
|
<Input
|
|
id="itemName"
|
|
value={form.itemName}
|
|
disabled={!canEdit}
|
|
className="font-mono"
|
|
onChange={(e) => setField("itemName", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<Label htmlFor="type">{t("type")}</Label>
|
|
<Input
|
|
id="type"
|
|
value={form.type}
|
|
disabled={!canEdit}
|
|
maxLength={3}
|
|
onChange={(e) => setField("type", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="spriteId">{t("spriteId")}</Label>
|
|
<Input
|
|
id="spriteId"
|
|
type="number"
|
|
value={form.spriteId}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("spriteId", Number(e.target.value))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm">{t("sectionDims")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div className="grid grid-cols-3 gap-3">
|
|
<div>
|
|
<Label htmlFor="width">{t("width")}</Label>
|
|
<Input
|
|
id="width"
|
|
type="number"
|
|
value={form.width}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("width", Number(e.target.value))}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="length">{t("length")}</Label>
|
|
<Input
|
|
id="length"
|
|
type="number"
|
|
value={form.length}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("length", Number(e.target.value))}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="stackHeight">{t("stackHeight")}</Label>
|
|
<Input
|
|
id="stackHeight"
|
|
value={form.stackHeight}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("stackHeight", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="interactionType">{t("interactionType")}</Label>
|
|
<Input
|
|
id="interactionType"
|
|
value={form.interactionType}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("interactionType", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="interactionModesCount">
|
|
{t("interactionModes")}
|
|
</Label>
|
|
<Input
|
|
id="interactionModesCount"
|
|
type="number"
|
|
value={form.interactionModesCount}
|
|
disabled={!canEdit}
|
|
onChange={(e) =>
|
|
setField("interactionModesCount", Number(e.target.value))
|
|
}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm">{t("sectionFlags")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
|
{ALLOW_FLAGS.map(({ key, label }) => (
|
|
<label
|
|
key={key}
|
|
className="flex items-center gap-2 text-sm rounded-md border px-2 py-1.5"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
checked={Number(form[key]) === 1}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField(key, e.target.checked ? 1 : 0)}
|
|
/>
|
|
{label}
|
|
</label>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm">{t("sectionExtra")}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div>
|
|
<Label htmlFor="vendingIds">{t("vendingIds")}</Label>
|
|
<Input
|
|
id="vendingIds"
|
|
value={form.vendingIds}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("vendingIds", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="multiheight">{t("multiheight")}</Label>
|
|
<Input
|
|
id="multiheight"
|
|
value={form.multiheight}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("multiheight", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="customparams">{t("customparams")}</Label>
|
|
<Input
|
|
id="customparams"
|
|
value={form.customparams}
|
|
disabled={!canEdit}
|
|
onChange={(e) => setField("customparams", e.target.value)}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|