From 05e6182bcfb23429fd8fbc84fea8f5983272883c Mon Sep 17 00:00:00 2001 From: vikingowl Date: Wed, 12 Nov 2025 23:11:50 +0100 Subject: [PATCH] fix: Fix Svelte 5 reactivity issues in matches page and update API handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix toast notification imports: change from showToast to toast.success/error - Remove hover preloading from app.html and Tabs component - Fix match rounds API handling with safe parsing for incomplete data - Fix pagination timestamp calculation (API returns Unix timestamp, not ISO string) - Refactor matches page state management to fix reactivity issues: - Replace separate state variables with single matchesState object - Completely replace state object on updates to trigger reactivity - Fix infinite loop in intersection observer effect - Add keyed each blocks for proper list rendering - Remove client-side filtering (temporarily) to isolate reactivity issues - Add error state handling with nextPageTime in matches loader 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/app.html | 2 +- src/lib/api/matches.ts | 67 +++-- .../components/match/ShareCodeInput.svelte | 12 +- .../components/player/TrackPlayerModal.svelte | 10 +- src/lib/components/ui/Tabs.svelte | 1 - src/routes/matches/+page.svelte | 248 +++++++----------- src/routes/matches/+page.ts | 1 + 7 files changed, 142 insertions(+), 199 deletions(-) diff --git a/src/app.html b/src/app.html index aedc548..4158c07 100644 --- a/src/app.html +++ b/src/app.html @@ -9,7 +9,7 @@ %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/lib/api/matches.ts b/src/lib/api/matches.ts index f2c7873..e42d932 100644 --- a/src/lib/api/matches.ts +++ b/src/lib/api/matches.ts @@ -1,6 +1,6 @@ import { apiClient } from './client'; import { - parseMatchRounds, + parseMatchRoundsSafe, parseMatchWeapons, parseMatchChat, parseMatchParseResponse @@ -69,13 +69,22 @@ export const matchesAPI = { * Get match round-by-round statistics * @param matchId - Match ID * @returns Round statistics and economy data + * @throws Error if data is invalid or demo not parsed yet */ async getMatchRounds(matchId: string | number): Promise { const url = `/match/${matchId}/rounds`; - const data = await apiClient.get(url); + const data = await apiClient.get(url); - // Validate with Zod schema - return parseMatchRounds(data); + // Validate with Zod schema using safe parse + // This handles cases where the demo hasn't been parsed yet + const result = parseMatchRoundsSafe(data); + + if (!result.success) { + // If validation fails, it's likely the demo hasn't been parsed yet + throw new Error('Demo not parsed yet or invalid response format'); + } + + return result.data; }, /** @@ -117,30 +126,33 @@ export const matchesAPI = { const limit = params?.limit || 50; // CRITICAL: API returns a plain array, not a wrapped object - // We request limit + 1 to detect if there are more pages + // NOTE: Backend has a hard limit of 20 matches per request + // We assume hasMore = true if we get exactly the limit we requested const data = await apiClient.get(url, { params: { - limit: limit + 1, // Request one extra to check if there are more + limit: limit, map: params?.map, player_id: params?.player_id } }); - // Check if there are more matches (if we got the extra one) - const hasMore = data.length > limit; + // Handle null or empty response + if (!data || !Array.isArray(data)) { + console.warn('[API] getMatches received null or invalid data'); + return transformMatchesListResponse([], false, undefined); + } - // Remove the extra match if we have more - const matchesToReturn = hasMore ? data.slice(0, limit) : data; + // If we got exactly the limit, assume there might be more + // If we got less, we've reached the end + const hasMore = data.length === limit; - // If there are more matches, use the timestamp of the last match for pagination - // This timestamp is used in the next request: /matches/next/{timestamp} - const lastMatch = - matchesToReturn.length > 0 ? matchesToReturn[matchesToReturn.length - 1] : undefined; - const nextPageTime = - hasMore && lastMatch ? Math.floor(new Date(lastMatch.date).getTime() / 1000) : undefined; + // Get the timestamp from the LAST match BEFORE transformation + // The legacy API format has `date` as a Unix timestamp (number) + const lastLegacyMatch = data.length > 0 ? data[data.length - 1] : undefined; + const nextPageTime = hasMore && lastLegacyMatch ? lastLegacyMatch.date : undefined; // Transform legacy API response to new format - return transformMatchesListResponse(matchesToReturn, hasMore, nextPageTime); + return transformMatchesListResponse(data, hasMore, nextPageTime); }, /** @@ -153,28 +165,25 @@ export const matchesAPI = { const limit = params?.limit || 20; // API returns a plain array, not a wrapped object + // Backend has a hard limit of 20 matches per request const data = await apiClient.getCancelable(url, 'match-search', { params: { - limit: limit + 1, // Request one extra to check if there are more + limit: limit, map: params?.map, player_id: params?.player_id } }); - // Check if there are more matches (if we got the extra one) - const hasMore = data.length > limit; + // If we got exactly the limit, assume there might be more + const hasMore = data.length === limit; - // Remove the extra match if we have more - const matchesToReturn = hasMore ? data.slice(0, limit) : data; - - // If there are more matches, use the timestamp of the last match for pagination - const lastMatch = - matchesToReturn.length > 0 ? matchesToReturn[matchesToReturn.length - 1] : undefined; - const nextPageTime = - hasMore && lastMatch ? Math.floor(new Date(lastMatch.date).getTime() / 1000) : undefined; + // Get the timestamp from the LAST match BEFORE transformation + // The legacy API format has `date` as a Unix timestamp (number) + const lastLegacyMatch = data.length > 0 ? data[data.length - 1] : undefined; + const nextPageTime = hasMore && lastLegacyMatch ? lastLegacyMatch.date : undefined; // Transform legacy API response to new format - return transformMatchesListResponse(matchesToReturn, hasMore, nextPageTime); + return transformMatchesListResponse(data, hasMore, nextPageTime); }, /** diff --git a/src/lib/components/match/ShareCodeInput.svelte b/src/lib/components/match/ShareCodeInput.svelte index a647d64..b642b6c 100644 --- a/src/lib/components/match/ShareCodeInput.svelte +++ b/src/lib/components/match/ShareCodeInput.svelte @@ -1,7 +1,7 @@