fix: Fix player profile loading with API transformer and improve UI layout

- Add LegacyPlayerProfile transformer to handle API response format mismatch
- Transform avatar hashes to full Steam CDN URLs
- Map team IDs correctly (API 1/2 -> Schema 2/3)
- Calculate aggregate stats (avg_kills, avg_deaths, win_rate) from matches
- Reduce featured matches on homepage from 6 to 3
- Show 4 recent matches on player profile instead of 10
- Display recent matches in 4-column grid on desktop (side-by-side)

Fixes "Player not found" error for all player profiles.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-05 00:43:50 +01:00
parent 62bfdc8090
commit a861b1c1b6
5 changed files with 152 additions and 10 deletions

View File

@@ -161,3 +161,101 @@ export function transformMatchDetail(legacy: LegacyMatchDetail): Match {
players: legacy.stats?.map(transformPlayerStats)
};
}
/**
* Legacy player profile format from API
*/
export interface LegacyPlayerProfile {
steamid64: string;
name: string;
avatar: string; // Hash, not full URL
vac: boolean;
vac_date: number; // Unix timestamp
game_ban: boolean;
game_ban_date: number; // Unix timestamp
tracked: boolean;
match_stats?: {
win: number;
loss: number;
};
matches?: Array<{
match_id: string;
map: string;
date: number;
score: [number, number];
duration: number;
match_result: number;
max_rounds: number;
parsed: boolean;
vac: boolean;
game_ban: boolean;
stats: {
team_id: number;
kills: number;
deaths: number;
assists: number;
headshot: number;
mvp: number;
score: number;
rank: Record<string, unknown>;
multi_kills?: Record<string, number>;
dmg?: Record<string, unknown>;
};
}>;
}
/**
* Transform legacy player profile to schema-compatible format
*/
export function transformPlayerProfile(legacy: LegacyPlayerProfile) {
// Unix timestamp -62135596800 represents "no date" (year 0)
const hasVacDate = legacy.vac_date && legacy.vac_date > 0;
const hasGameBanDate = legacy.game_ban_date && legacy.game_ban_date > 0;
return {
id: legacy.steamid64,
name: legacy.name,
avatar: `https://avatars.steamstatic.com/${legacy.avatar}_full.jpg`,
steam_updated: new Date().toISOString(), // Not provided by API
vac_count: legacy.vac ? 1 : 0,
vac_date: hasVacDate ? new Date(legacy.vac_date * 1000).toISOString() : null,
game_ban_count: legacy.game_ban ? 1 : 0,
game_ban_date: hasGameBanDate ? new Date(legacy.game_ban_date * 1000).toISOString() : null,
wins: legacy.match_stats?.win,
losses: legacy.match_stats?.loss,
matches: legacy.matches?.map((match) => ({
match_id: match.match_id,
map: match.map || 'unknown',
date: new Date(match.date * 1000).toISOString(),
score_team_a: match.score[0],
score_team_b: match.score[1],
duration: match.duration,
match_result: match.match_result,
max_rounds: match.max_rounds,
demo_parsed: match.parsed,
vac_present: match.vac,
gameban_present: match.game_ban,
tick_rate: 64, // Not provided by API
stats: {
id: legacy.steamid64,
name: legacy.name,
avatar: `https://avatars.steamstatic.com/${legacy.avatar}_full.jpg`,
// Fix team_id: API returns 1/2, but schema expects min 2
// Map: 1 -> 2 (Terrorists), 2 -> 3 (Counter-Terrorists)
team_id:
match.stats.team_id === 1 ? 2 : match.stats.team_id === 2 ? 3 : match.stats.team_id,
kills: match.stats.kills,
deaths: match.stats.deaths,
assists: match.stats.assists,
headshot: match.stats.headshot,
mvp: match.stats.mvp,
score: match.stats.score,
kast: 0,
mk_2: match.stats.multi_kills?.duo,
mk_3: match.stats.multi_kills?.triple,
mk_4: match.stats.multi_kills?.quad,
mk_5: match.stats.multi_kills?.ace
}
}))
};
}