fix(panel): player inventory not loading due to API response mismatch
Some checks failed
Deploy to Production / test (push) Failing after 32s
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:
@@ -11,33 +11,41 @@ interface UserData {
|
||||
className: string | null;
|
||||
}
|
||||
|
||||
interface InventoryItem {
|
||||
itemId: string;
|
||||
interface InventoryEntry {
|
||||
itemId: number;
|
||||
quantity: string;
|
||||
item: {
|
||||
name: string;
|
||||
quantity: number;
|
||||
rarity: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function PlayerDashboard({ userId }: { userId: string }) {
|
||||
const [user, setUser] = useState<UserData | null>(null);
|
||||
const [inventory, setInventory] = useState<InventoryItem[]>([]);
|
||||
const [inventory, setInventory] = useState<InventoryEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [inventoryError, setInventoryError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
try {
|
||||
const [userData, invData] = await Promise.all([
|
||||
get<UserData>(`/api/users/${userId}`),
|
||||
get<{ items: InventoryItem[] }>(`/api/users/${userId}/inventory`).catch(() => ({ items: [] })),
|
||||
]);
|
||||
const userData = await get<UserData>(`/api/users/${userId}`);
|
||||
setUser(userData);
|
||||
setInventory(invData.items ?? []);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
setError("Failed to load profile");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await get<{ inventory: InventoryEntry[] }>(`/api/users/${userId}/inventory`);
|
||||
setInventory(data.inventory ?? []);
|
||||
} catch {
|
||||
setInventoryError(true);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
load();
|
||||
}, [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="Gold" value={formatNumber(user.balance)} accent="gold" />
|
||||
<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 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-xs text-text-disabled font-label">({inventory.length})</span>
|
||||
</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-2 pb-2 space-y-0.5">
|
||||
{inventory.slice(0, 10).map((item, i) => (
|
||||
<div key={i} 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>
|
||||
{inventory.slice(0, 10).map((entry) => (
|
||||
<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">{entry.item.name}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-[10px] font-semibold px-1.5 py-0.5 rounded ${rarityColor(item.rarity)}`}>
|
||||
{item.rarity}
|
||||
<span className={`text-[10px] font-semibold px-1.5 py-0.5 rounded ${rarityColor(entry.item.rarity)}`}>
|
||||
{entry.item.rarity}
|
||||
</span>
|
||||
{item.quantity > 1 && (
|
||||
<span className="text-xs text-text-tertiary font-mono">x{item.quantity}</span>
|
||||
{Number(entry.quantity) > 1 && (
|
||||
<span className="text-xs text-text-tertiary font-mono">x{entry.quantity}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user