┌─ TS ───────────────────────────────────────────────────────────────────────┐
│ import type { LeaderboardData, PlayerProfileData, BestRun } from "./types.js"; │
│ import { │
│ buildLeaderboardURL, │
│ buildPlayerProfileURL, │
│ buildStaticLeaderboardPath, │
│ buildStaticPlayerLeaderboardPath, │
│ buildStaticPlayerProfilePath, │
│ buildStaticTotalRunsLeaderboardPath, │
│ buildStaticAccountLeaderboardPath, │
│ buildStaticAccountTotalRunsPath, │
│ buildUnifiedLeaderboardPath, │
│ } from "./utils.js"; │
│ │
│ // api base configuration (always use relative paths for Netlify/SSR) │
│ const API_BASE = ""; │
│ │
│ // generic api request handler with error handling │
│ async function apiRequest<T>(url: string, origin?: string): Promise<T> { │
│ let fullUrl: string; │
│ if (url.startsWith("http")) { │
│ fullUrl = url; │
│ } else if (origin) { │
│ // SSR context - need full URL │
│ fullUrl = `${origin}${url}`; │
│ } else { │
│ // Client-side context - relative URL works │
│ fullUrl = `${API_BASE}${url}`; │
│ } │
│ const response = await fetch(fullUrl); │
│ │
│ if (!response.ok) { │
│ if (response.status === 404) { │
│ throw new Error("Player not found"); │
│ } │
│ │
│ throw new Error( │
│ `Failed to load data: ${response.status} ${response.statusText}`, │
│ ); │
│ } │
│ │
│ return response.json(); │
│ } │
│ │
│ // leaderboard api functions │
│ export async function fetchGlobalLeaderboard( │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ const url = `${API_BASE}${buildStaticLeaderboardPath("global", "", dungeonId, pa │
│ ge, seasonId)}`; │
│ console.log("Fetching global leaderboard:", url); │
│ return apiRequest<LeaderboardData>(url, origin); │
│ } │
│ │
│ export async function fetchRegionalLeaderboard( │
│ region: string, │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ const url = `${API_BASE}${buildStaticLeaderboardPath(region, "all", dungeonId, p │
│ age, seasonId)}`; │
│ console.log("Fetching regional leaderboard:", url); │
│ return apiRequest<LeaderboardData>(url, origin); │
│ } │
│ │
│ export async function fetchRealmLeaderboard( │
│ region: string, │
│ realmSlug: string, │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ const url = `${API_BASE}${buildStaticLeaderboardPath(region, realmSlug, dungeonI │
│ d, page, seasonId)}`; │
│ console.log("Fetching realm leaderboard:", url); │
│ return apiRequest<LeaderboardData>(url, origin); │
│ } │
│ │
│ export async function fetchPlayerLeaderboard( │
│ scope: "global" | "regional" | "realm" = "global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { realmSlug?: string; classKey?: string; seasonId?: number }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticPlayerLeaderboardPath(scope, region, page, │
│ opts)}`; │
│ console.log("Fetching player leaderboard:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ export async function fetchTotalRunsLeaderboard( │
│ scope: "global" | "regional" | "realm" = "global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { realmSlug?: string; classKey?: string }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticTotalRunsLeaderboardPath(scope, region, pag │
│ e, opts)}`; │
│ console.log("Fetching total runs leaderboard:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // Unified leaderboard fetcher for the merged Players page. Picks the right │
│ // JSON based on (view, sort, season, scope) and lets the caller render. │
│ export async function fetchUnifiedLeaderboard( │
│ view: "player" | "character", │
│ sort: "time" | "runs", │
│ season: number | "all-time", │
│ scope: "global" | "regional" | "realm" | "class", │
│ opts?: { │
│ region?: string; │
│ realmSlug?: string; │
│ classKey?: string; │
│ page?: number; │
│ }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildUnifiedLeaderboardPath(view, sort, season, scope, │
│ opts)}`; │
│ console.log("Fetching unified leaderboard:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // account-grouped cross-season total runs (Player toggle on total-runs page) │
│ export async function fetchAccountTotalRunsLeaderboard( │
│ scope: "global" | "regional" | "realm" = "global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { realmSlug?: string }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticAccountTotalRunsPath(scope, region, page, o │
│ pts)}`; │
│ console.log("Fetching account total-runs leaderboard:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // account-grouped player leaderboard │
│ export async function fetchAccountLeaderboard( │
│ scope: "global" | "regional" | "realm" = "global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { seasonId?: number; realmSlug?: string }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticAccountLeaderboardPath(scope, region, page, │
│ opts)}`; │
│ console.log("Fetching account leaderboard:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // player profile API functions │
│ export async function fetchPlayerProfile( │
│ region: string, │
│ realmSlug: string, │
│ playerName: string, │
│ origin?: string, │
│ ): Promise<PlayerProfileData> { │
│ const url = `${API_BASE}${buildStaticPlayerProfilePath(region, realmSlug, player │
│ Name)}`; │
│ console.log("Fetching player profile:", url); │
│ return apiRequest<PlayerProfileData>(url, origin); │
│ } │
│ │
│ // fetchPlayerBestRuns removed - best runs are now included in fetchPlayerProfile │
│ response │
│ │
│ // leaderboard router function - determines which API to call based on filters │
│ export async function fetchLeaderboard( │
│ region: string, │
│ realm: string, │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ if (region === "global") { │
│ return fetchGlobalLeaderboard( │
│ dungeonId, │
│ page, │
│ teamFilter, │
│ origin, │
│ seasonId, │
│ ); │
│ } else if (realm === "all") { │
│ return fetchRegionalLeaderboard( │
│ region, │
│ dungeonId, │
│ page, │
│ teamFilter, │
│ origin, │
│ seasonId, │
│ ); │
│ } else { │
│ return fetchRealmLeaderboard( │
│ region, │
│ realm, │
│ dungeonId, │
│ page, │
│ teamFilter, │
│ origin, │
│ seasonId, │
│ ); │
│ } │
│ } │
│ │
│ // url helpers are centralized in lib/utils.ts and re-exported here for convenienc │
│ e │
│ export { buildLeaderboardURL, buildPlayerProfileURL }; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ TS ─────────────────────────────────┐
│ import type { LeaderboardData, PlayerProfile │
│ Data, BestRun } from "./types.js"; │
│ import { │
│ buildLeaderboardURL, │
│ buildPlayerProfileURL, │
│ buildStaticLeaderboardPath, │
│ buildStaticPlayerLeaderboardPath, │
│ buildStaticPlayerProfilePath, │
│ buildStaticTotalRunsLeaderboardPath, │
│ buildStaticAccountLeaderboardPath, │
│ buildStaticAccountTotalRunsPath, │
│ buildUnifiedLeaderboardPath, │
│ } from "./utils.js"; │
│ │
│ // api base configuration (always use relati │
│ ve paths for Netlify/SSR) │
│ const API_BASE = ""; │
│ │
│ // generic api request handler with error ha │
│ ndling │
│ async function apiRequest<T>(url: string, or │
│ igin?: string): Promise<T> { │
│ let fullUrl: string; │
│ if (url.startsWith("http")) { │
│ fullUrl = url; │
│ } else if (origin) { │
│ // SSR context - need full URL │
│ fullUrl = `${origin}${url}`; │
│ } else { │
│ // Client-side context - relative URL wo │
│ rks │
│ fullUrl = `${API_BASE}${url}`; │
│ } │
│ const response = await fetch(fullUrl); │
│ │
│ if (!response.ok) { │
│ if (response.status === 404) { │
│ throw new Error("Player not found"); │
│ } │
│ │
│ throw new Error( │
│ `Failed to load data: ${response.statu │
│ s} ${response.statusText}`, │
│ ); │
│ } │
│ │
│ return response.json(); │
│ } │
│ │
│ // leaderboard api functions │
│ export async function fetchGlobalLeaderboard │
│ ( │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ const url = `${API_BASE}${buildStaticLeade │
│ rboardPath("global", "", dungeonId, page, se │
│ asonId)}`; │
│ console.log("Fetching global leaderboard:" │
│ , url); │
│ return apiRequest<LeaderboardData>(url, or │
│ igin); │
│ } │
│ │
│ export async function fetchRegionalLeaderboa │
│ rd( │
│ region: string, │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ const url = `${API_BASE}${buildStaticLeade │
│ rboardPath(region, "all", dungeonId, page, s │
│ easonId)}`; │
│ console.log("Fetching regional leaderboard │
│ :", url); │
│ return apiRequest<LeaderboardData>(url, or │
│ igin); │
│ } │
│ │
│ export async function fetchRealmLeaderboard( │
│ region: string, │
│ realmSlug: string, │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ const url = `${API_BASE}${buildStaticLeade │
│ rboardPath(region, realmSlug, dungeonId, pag │
│ e, seasonId)}`; │
│ console.log("Fetching realm leaderboard:", │
│ url); │
│ return apiRequest<LeaderboardData>(url, or │
│ igin); │
│ } │
│ │
│ export async function fetchPlayerLeaderboard │
│ ( │
│ scope: "global" | "regional" | "realm" = " │
│ global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { realmSlug?: string; classKey?: st │
│ ring; seasonId?: number }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticPlaye │
│ rLeaderboardPath(scope, region, page, opts)} │
│ `; │
│ console.log("Fetching player leaderboard:" │
│ , url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ export async function fetchTotalRunsLeaderbo │
│ ard( │
│ scope: "global" | "regional" | "realm" = " │
│ global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { realmSlug?: string; classKey?: st │
│ ring }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticTotal │
│ RunsLeaderboardPath(scope, region, page, opt │
│ s)}`; │
│ console.log("Fetching total runs leaderboa │
│ rd:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // Unified leaderboard fetcher for the merge │
│ d Players page. Picks the right │
│ // JSON based on (view, sort, season, scope) │
│ and lets the caller render. │
│ export async function fetchUnifiedLeaderboar │
│ d( │
│ view: "player" | "character", │
│ sort: "time" | "runs", │
│ season: number | "all-time", │
│ scope: "global" | "regional" | "realm" | " │
│ class", │
│ opts?: { │
│ region?: string; │
│ realmSlug?: string; │
│ classKey?: string; │
│ page?: number; │
│ }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildUnifiedLead │
│ erboardPath(view, sort, season, scope, opts) │
│ }`; │
│ console.log("Fetching unified leaderboard: │
│ ", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // account-grouped cross-season total runs ( │
│ Player toggle on total-runs page) │
│ export async function fetchAccountTotalRunsL │
│ eaderboard( │
│ scope: "global" | "regional" | "realm" = " │
│ global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { realmSlug?: string }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticAccou │
│ ntTotalRunsPath(scope, region, page, opts)}` │
│ ; │
│ console.log("Fetching account total-runs l │
│ eaderboard:", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // account-grouped player leaderboard │
│ export async function fetchAccountLeaderboar │
│ d( │
│ scope: "global" | "regional" | "realm" = " │
│ global", │
│ region?: string, │
│ page: number = 1, │
│ pageSize: number = 25, │
│ opts?: { seasonId?: number; realmSlug?: st │
│ ring }, │
│ origin?: string, │
│ ): Promise<any> { │
│ const url = `${API_BASE}${buildStaticAccou │
│ ntLeaderboardPath(scope, region, page, opts) │
│ }`; │
│ console.log("Fetching account leaderboard: │
│ ", url); │
│ return apiRequest(url, origin); │
│ } │
│ │
│ // player profile API functions │
│ export async function fetchPlayerProfile( │
│ region: string, │
│ realmSlug: string, │
│ playerName: string, │
│ origin?: string, │
│ ): Promise<PlayerProfileData> { │
│ const url = `${API_BASE}${buildStaticPlaye │
│ rProfilePath(region, realmSlug, playerName)} │
│ `; │
│ console.log("Fetching player profile:", ur │
│ l); │
│ return apiRequest<PlayerProfileData>(url, │
│ origin); │
│ } │
│ │
│ // fetchPlayerBestRuns removed - best runs a │
│ re now included in fetchPlayerProfile respon │
│ se │
│ │
│ // leaderboard router function - determines │
│ which API to call based on filters │
│ export async function fetchLeaderboard( │
│ region: string, │
│ realm: string, │
│ dungeonId: number, │
│ page: number = 1, │
│ teamFilter: boolean = true, │
│ origin?: string, │
│ seasonId: number = 1, │
│ ): Promise<LeaderboardData> { │
│ if (region === "global") { │
│ return fetchGlobalLeaderboard( │
│ dungeonId, │
│ page, │
│ teamFilter, │
│ origin, │
│ seasonId, │
│ ); │
│ } else if (realm === "all") { │
│ return fetchRegionalLeaderboard( │
│ region, │
│ dungeonId, │
│ page, │
│ teamFilter, │
│ origin, │
│ seasonId, │
│ ); │
│ } else { │
│ return fetchRealmLeaderboard( │
│ region, │
│ realm, │
│ dungeonId, │
│ page, │
│ teamFilter, │
│ origin, │
│ seasonId, │
│ ); │
│ } │
│ } │
│ │
│ // url helpers are centralized in lib/utils. │
│ ts and re-exported here for convenience │
│ export { buildLeaderboardURL, buildPlayerPro │
│ fileURL }; │
└──────────────────────────────────────────────┘