fix(panel): player inventory not loading due to API response mismatch
Some checks failed
Deploy to Production / test (push) Failing after 32s

Frontend expected { items } but API returns { inventory } with nested
item objects. Fixes response key, aligns InventoryEntry type to actual
API shape, and separates error handling so a failed inventory fetch
shows an error instead of silently displaying "No items yet".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
syntaxbullet
2026-04-05 15:55:25 +02:00
parent 838fbe1b50
commit b645f55f57

View File

@@ -11,33 +11,41 @@ interface UserData {
className: string | null; className: string | null;
} }
interface InventoryItem { interface InventoryEntry {
itemId: string; itemId: number;
name: string; quantity: string;
quantity: number; item: {
rarity: string; name: string;
rarity: string;
};
} }
export default function PlayerDashboard({ userId }: { userId: string }) { export default function PlayerDashboard({ userId }: { userId: string }) {
const [user, setUser] = useState<UserData | null>(null); const [user, setUser] = useState<UserData | null>(null);
const [inventory, setInventory] = useState<InventoryItem[]>([]); const [inventory, setInventory] = useState<InventoryEntry[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [inventoryError, setInventoryError] = useState(false);
useEffect(() => { useEffect(() => {
async function load() { async function load() {
try { try {
const [userData, invData] = await Promise.all([ const userData = await get<UserData>(`/api/users/${userId}`);
get<UserData>(`/api/users/${userId}`),
get<{ items: InventoryItem[] }>(`/api/users/${userId}/inventory`).catch(() => ({ items: [] })),
]);
setUser(userData); setUser(userData);
setInventory(invData.items ?? []); } catch {
} catch (e) {
setError("Failed to load profile"); setError("Failed to load profile");
} finally {
setLoading(false); setLoading(false);
return;
} }
try {
const data = await get<{ inventory: InventoryEntry[] }>(`/api/users/${userId}/inventory`);
setInventory(data.inventory ?? []);
} catch {
setInventoryError(true);
}
setLoading(false);
} }
load(); load();
}, [userId]); }, [userId]);
@@ -66,7 +74,7 @@ export default function PlayerDashboard({ userId }: { userId: string }) {
<StatCard label="Level" value={String(user.level)} accent="primary" subtitle={user.className ?? undefined} /> <StatCard label="Level" value={String(user.level)} accent="primary" subtitle={user.className ?? undefined} />
<StatCard label="Gold" value={formatNumber(user.balance)} accent="gold" /> <StatCard label="Gold" value={formatNumber(user.balance)} accent="gold" />
<StatCard label="XP" value={formatNumber(user.xp)} accent="info" /> <StatCard label="XP" value={formatNumber(user.xp)} accent="info" />
<StatCard label="Items" value={String(inventory.length)} accent="success" /> <StatCard label="Items" value={inventoryError ? "—" : String(inventory.length)} accent="success" />
</div> </div>
<div className="bg-card rounded-xl"> <div className="bg-card rounded-xl">
@@ -74,19 +82,21 @@ export default function PlayerDashboard({ userId }: { userId: string }) {
<span className="text-sm font-display font-semibold">Inventory</span> <span className="text-sm font-display font-semibold">Inventory</span>
<span className="text-xs text-text-disabled font-label">({inventory.length})</span> <span className="text-xs text-text-disabled font-label">({inventory.length})</span>
</div> </div>
{inventory.length === 0 ? ( {inventoryError ? (
<div className="px-5 py-6 text-center text-sm text-destructive/80">Failed to load inventory</div>
) : inventory.length === 0 ? (
<div className="px-5 py-6 text-center text-sm text-text-tertiary">No items yet</div> <div className="px-5 py-6 text-center text-sm text-text-tertiary">No items yet</div>
) : ( ) : (
<div className="px-2 pb-2 space-y-0.5"> <div className="px-2 pb-2 space-y-0.5">
{inventory.slice(0, 10).map((item, i) => ( {inventory.slice(0, 10).map((entry) => (
<div key={i} className="flex items-center justify-between px-3 py-3 hover:bg-raised/30 transition-colors rounded-lg"> <div key={entry.itemId} className="flex items-center justify-between px-3 py-3 hover:bg-raised/30 transition-colors rounded-lg">
<div className="text-sm font-medium">{item.name}</div> <div className="text-sm font-medium">{entry.item.name}</div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className={`text-[10px] font-semibold px-1.5 py-0.5 rounded ${rarityColor(item.rarity)}`}> <span className={`text-[10px] font-semibold px-1.5 py-0.5 rounded ${rarityColor(entry.item.rarity)}`}>
{item.rarity} {entry.item.rarity}
</span> </span>
{item.quantity > 1 && ( {Number(entry.quantity) > 1 && (
<span className="text-xs text-text-tertiary font-mono">x{item.quantity}</span> <span className="text-xs text-text-tertiary font-mono">x{entry.quantity}</span>
)} )}
</div> </div>
</div> </div>