1
0
Fork 0

fix(locker): revert to onClick and add error feedback to slot picker

- Revert onPointerDown back to onClick for item selection
- Add error state to display server action failures to user
- Add pending state to disable buttons during submission
- Update onPick to return ActionResult for error handling
This commit is contained in:
Jason Fraley 2026-08-02 19:00:05 -04:00
parent bf6b29690f
commit fc27f11ab8
2 changed files with 33 additions and 21 deletions

View file

@ -62,7 +62,7 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
}
async function handlePick(slotName: string, assetId: number | null) {
if (!equippedLoadout) return;
if (!equippedLoadout) return { success: false, error: "No loadout equipped." };
setBusy(true);
const res = await setLoadoutSlot(equippedLoadout.id, slotName, assetId);
setBusy(false);
@ -70,6 +70,7 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
setPickerSlot(null);
router.refresh();
}
return res;
}
const pickerLayout = pickerSlot ? getWardrobeSlot(pickerSlot) : null;

View file

@ -1,6 +1,6 @@
"use client";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import type { Asset } from "@/payload-types";
import { Button } from "@/components/ui/button";
import {
@ -22,7 +22,7 @@ interface WardrobeSlotPickerProps {
items: LockerGridItem[];
currentValue: number | Asset | null;
busy: boolean;
onPick: (assetId: number | null) => void;
onPick: (assetId: number | null) => Promise<{ success: boolean; error?: string }>;
}
function resolveCurrentId(value: number | Asset | null): number | null {
@ -40,6 +40,9 @@ export function WardrobeSlotPicker({
busy,
onPick,
}: WardrobeSlotPickerProps) {
const [error, setError] = useState<string | null>(null);
const [pending, setPending] = useState(false);
const options = useMemo(() => {
const types = new Set(slot.assetTypes);
const seen = new Set<number>();
@ -53,6 +56,21 @@ export function WardrobeSlotPicker({
const currentId = resolveCurrentId(currentValue);
async function handlePick(assetId: number | null) {
setError(null);
setPending(true);
try {
const res = await onPick(assetId);
if (!res.success) {
setError(res.error ?? "Failed to equip item.");
}
} catch (e) {
setError(e instanceof Error ? e.message : "An unexpected error occurred.");
} finally {
setPending(false);
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[85vh] overflow-y-auto">
@ -63,6 +81,12 @@ export function WardrobeSlotPicker({
</DialogDescription>
</DialogHeader>
{error && (
<div className="rounded-md border border-red-500/50 bg-red-500/10 px-3 py-2 text-sm text-red-400">
{error}
</div>
)}
<div className="flex flex-col gap-1.5">
{options.length === 0 && (
<p className="text-sm text-muted-foreground py-2">
@ -75,11 +99,8 @@ export function WardrobeSlotPicker({
<button
key={entry.assetId}
type="button"
disabled={busy}
onPointerDown={(e) => {
e.preventDefault();
onPick(entry.assetId);
}}
disabled={busy || pending}
onClick={() => handlePick(entry.assetId)}
className={cn(
"flex items-center gap-3 rounded-md border px-3 py-2 text-left text-sm transition-colors",
selected
@ -103,23 +124,13 @@ export function WardrobeSlotPicker({
<DialogFooter>
<Button
variant="outline"
disabled={busy}
onPointerDown={(e) => {
e.preventDefault();
onOpenChange(false);
}}
disabled={busy || pending}
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
{currentId != null && (
<Button
variant="ghost"
disabled={busy}
onPointerDown={(e) => {
e.preventDefault();
onPick(null);
}}
>
<Button variant="ghost" disabled={busy || pending} onClick={() => handlePick(null)}>
Clear Slot
</Button>
)}