/** * Player headshot URL construction (Session 19; Wave 2A refactor). * * The PURE URL logic lives in `playerHeadshotUrl.js` (CommonJS) so it's * requireable by the plain-JS Jest suite AND importable here (allowJs) — same * single-source-of-truth doctrine as `vyndrTokens.js` / `playerName.js`. This * file adds the TypeScript surface (types + the `headshotFromPlayer` helper). * * Each league hosts its own CDN; we don't proxy through ESPN as the primary * because (a) ESPN rate-limits image hotlinking and (b) the league CDNs are the * same sources PrizePicks and Sleeper use, so coverage is closer to 100%. * * Fallback chain inside the resolver: * 1. `cachedPhotoUrl` — a backend-stored URL (soccer). We DO NOT construct * soccer URLs (no central CDN for player headshots). * 2. League CDN with `playerId` — official source. * 3. ESPN CDN with `espnId` — NBA/WNBA (and dormant NFL/NHL) headshots. * 4. `/images/player-silhouette.svg` — the sentinel the consuming component * swaps for a team-colored MONOGRAM (never a gray blob, never a broken * image). `` also degrades a 404 to the monogram. */ import { getHeadshotUrl as coreGetHeadshotUrl, PLAYER_SILHOUETTE as CORE_SILHOUETTE, } from '@/lib/playerHeadshotUrl'; export type HeadshotSport = 'nba' | 'wnba' | 'mlb' | 'soccer' | 'soccer_wc' | string; export interface HeadshotInput { sport: HeadshotSport; /** League-specific ID (NBA stats.com ID, WNBA player ID, MLB people ID). */ playerId?: string | number | null; /** ESPN athlete ID — the NBA/WNBA (and dormant NFL/NHL) headshot source. */ espnId?: string | number | null; /** Wave 2B — a RESOLVED absolute headshot URL (ESPN's exact href); wins over a constructed one. */ headshotUrl?: string | null; /** Pre-cached photo URL (used by soccer where each league has no central CDN). */ cachedPhotoUrl?: string | null; } export const PLAYER_SILHOUETTE: string = CORE_SILHOUETTE; export function getHeadshotUrl(input: HeadshotInput): string { return coreGetHeadshotUrl(input); } /** * Convenience wrapper for the common case where the caller has a player object * with mixed ID fields. Pulls the first non-empty ID out of the union before * delegating to `getHeadshotUrl`. */ export function headshotFromPlayer(player: { sport?: string; nba_id?: string | number | null; wnba_id?: string | number | null; mlb_id?: string | number | null; espn_id?: string | number | null; photo_url?: string | null; }): string { const sport = String(player.sport || '').toLowerCase(); const leagueId = sport === 'nba' ? player.nba_id : sport === 'wnba' ? player.wnba_id : sport === 'mlb' ? player.mlb_id : null; return getHeadshotUrl({ sport, playerId: leagueId, espnId: player.espn_id, cachedPhotoUrl: player.photo_url, }); }