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;
|
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user