1
0
Fork 0
polaris-task-force/src/components/frontend/locker/LockerCharacterPanel.tsx

134 lines
4.3 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import type { Loadout } from "@/payload-types";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { setLoadoutSlot, equipLoadout } from "@/app/(frontend)/locker/actions";
import {
getLoadoutSlot,
getWardrobeSlot,
type LoadoutSlots,
type LockerGridItem,
} from "@/lib/locker";
import { Wardrobe } from "./Wardrobe";
import { WardrobeSlotPicker } from "./WardrobeSlotPicker";
import { cn } from "@/lib/utils";
interface LockerCharacterPanelProps {
loadouts: Loadout[];
items: LockerGridItem[];
}
export function LockerCharacterPanel({ loadouts, items }: LockerCharacterPanelProps) {
const router = useRouter();
const equippedLoadout = loadouts.find((loadout) => loadout.equipped) ?? null;
const [pickerSlot, setPickerSlot] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const slots = (equippedLoadout?.slots ?? {}) as LoadoutSlots;
const pickerLayout = pickerSlot ? getWardrobeSlot(pickerSlot) : null;
const pickerConfig = pickerSlot ? getLoadoutSlot(pickerSlot) : null;
async function changeLoadout(id: string) {
setBusy(true);
const result = await equipLoadout(Number(id));
setBusy(false);
if (result.success) router.refresh();
}
async function pickSlot(assetId: number | null) {
if (!equippedLoadout || !pickerSlot) return { success: false, error: "No loadout equipped." };
setBusy(true);
const result = await setLoadoutSlot(equippedLoadout.id, pickerSlot, assetId);
setBusy(false);
if (result.success) {
setPickerSlot(null);
router.refresh();
}
return result;
}
return (
<section
className="flex min-w-0 flex-col border border-border/70 bg-card/35"
aria-label="Character equipment"
>
<div className="flex items-center justify-between gap-3 border-b border-border/60 bg-muted/20 px-3 py-2">
<div>
<p className="font-mono text-[10px] uppercase tracking-[0.2em] text-primary/80">
CHARACTER
</p>
<p className="text-xs text-muted-foreground/70">EQUIPMENT STATUS</p>
</div>
<span
className={cn(
"size-2 rounded-full",
equippedLoadout ? "bg-emerald-400" : "bg-muted-foreground/40",
)}
aria-label={equippedLoadout ? "Loadout equipped" : "No loadout equipped"}
/>
</div>
<div className="border-b border-border/50 p-2">
<Select
value={equippedLoadout ? String(equippedLoadout.id) : ""}
onValueChange={changeLoadout}
disabled={busy || loadouts.length === 0}
>
<SelectTrigger className="h-8 rounded-sm border-border/60 bg-background/40 text-xs uppercase tracking-wide">
<SelectValue placeholder="SELECT LOADOUT" />
</SelectTrigger>
<SelectContent>
{loadouts.map((loadout) => (
<SelectItem key={loadout.id} value={String(loadout.id)}>
{loadout.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="p-2">
<Wardrobe
loadout={equippedLoadout ?? ({ name: "Empty", slots: {} } as unknown as Loadout)}
items={items}
onSlotClick={setPickerSlot}
/>
</div>
<div className="flex items-center justify-between border-t border-border/50 px-3 py-2 font-mono text-[10px] uppercase tracking-wide text-muted-foreground/60">
<span>Click a slot to equip</span>
<Button
variant="ghost"
size="sm"
className="h-6 rounded-sm px-2 text-[10px]"
onClick={() => setPickerSlot("primary")}
disabled={!equippedLoadout || busy}
>
EDIT
</Button>
</div>
{pickerLayout && pickerConfig && (
<WardrobeSlotPicker
open={!!pickerSlot}
onOpenChange={(open) => {
if (!open) setPickerSlot(null);
}}
slot={pickerConfig}
items={items}
currentValue={slots[pickerSlot as keyof LoadoutSlots] ?? null}
busy={busy}
onPick={pickSlot}
/>
)}
</section>
);
}