Add cached-first TeamSnap reads
This commit is contained in:
1
PLAN.md
1
PLAN.md
@@ -18,6 +18,7 @@
|
|||||||
- Client-side clip and assignment reads now persist locally and revalidate against server ETags.
|
- Client-side clip and assignment reads now persist locally and revalidate against server ETags.
|
||||||
- Normalized playback media is cacheable for offline clip playback.
|
- Normalized playback media is cacheable for offline clip playback.
|
||||||
- Auth and session responses remain `no-store` so cached data is limited to app-owned clip state.
|
- Auth and session responses remain `no-store` so cached data is limited to app-owned clip state.
|
||||||
|
- TeamSnap read queries now use cached-first stale-while-revalidate behavior on the client.
|
||||||
|
|
||||||
## Storage Status
|
## Storage Status
|
||||||
- Backend media persists in the `backend-media` named Docker volume.
|
- Backend media persists in the `backend-media` named Docker volume.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Walkup is a collaborative baseball walk-up song app built as a React PWA with a
|
|||||||
|
|
||||||
## Frontend Responsibilities
|
## Frontend Responsibilities
|
||||||
- TeamSnap SDK bootstrap with server-issued access tokens
|
- TeamSnap SDK bootstrap with server-issued access tokens
|
||||||
- Team/game browsing from TeamSnap
|
- Team/game browsing from TeamSnap with cached-first revalidation
|
||||||
- Song upload and clip creation
|
- Song upload and clip creation
|
||||||
- Game assignments and gameday console
|
- Game assignments and gameday console
|
||||||
- PWA install/offline shell
|
- PWA install/offline shell
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Walkup is a baseball walk-up song app with a React PWA frontend and a FastAPI ba
|
|||||||
- `frontend/` contains the React application.
|
- `frontend/` contains the React application.
|
||||||
- The app uses React Router for navigation and TanStack Query for server state.
|
- The app uses React Router for navigation and TanStack Query for server state.
|
||||||
- TeamSnap data is loaded through the official JavaScript SDK from the browser after the backend provides an access token.
|
- TeamSnap data is loaded through the official JavaScript SDK from the browser after the backend provides an access token.
|
||||||
|
- Read-only TeamSnap queries use a stale-while-revalidate cache in the browser so the UI can render immediately from stored data and update when a fresh response arrives.
|
||||||
- The UI includes player, gameday, and library views for clip management and gameday playback.
|
- The UI includes player, gameday, and library views for clip management and gameday playback.
|
||||||
- The app is shipped as a PWA with install and offline-prep behavior.
|
- The app is shipped as a PWA with install and offline-prep behavior.
|
||||||
- Normalized playback media is cached by the service worker, and the backend marks those files cacheable while keeping auth/session responses `no-store`.
|
- Normalized playback media is cached by the service worker, and the backend marks those files cacheable while keeping auth/session responses `no-store`.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { api } from "../api/client";
|
|||||||
import type { TeamSnapEvent, TeamSnapMember } from "../api/types";
|
import type { TeamSnapEvent, TeamSnapMember } from "../api/types";
|
||||||
import { queryClient } from "../lib/queryClient";
|
import { queryClient } from "../lib/queryClient";
|
||||||
import { findCurrentPlayer, findNextGame, sortGames } from "../lib/teamsnapHelpers";
|
import { findCurrentPlayer, findNextGame, sortGames } from "../lib/teamsnapHelpers";
|
||||||
import { teamsnapClient } from "../lib/teamsnap";
|
import { teamSnapQueryKeys, teamsnapClient } from "../lib/teamsnap";
|
||||||
import { useSession } from "./useSession";
|
import { useSession } from "./useSession";
|
||||||
|
|
||||||
const TEAM_STORAGE_KEY = "walkup.selectedTeamId";
|
const TEAM_STORAGE_KEY = "walkup.selectedTeamId";
|
||||||
@@ -24,10 +24,13 @@ const WalkupContext = createContext<WalkupContextValue | null>(null);
|
|||||||
function useBuildWalkupContext() {
|
function useBuildWalkupContext() {
|
||||||
const sessionQuery = useSession();
|
const sessionQuery = useSession();
|
||||||
const isTeamSnap = sessionQuery.data?.authenticated === true && sessionQuery.data?.provider === "teamsnap";
|
const isTeamSnap = sessionQuery.data?.authenticated === true && sessionQuery.data?.provider === "teamsnap";
|
||||||
|
const teamSnapCacheScope = isTeamSnap
|
||||||
|
? String(sessionQuery.data?.external_user_id ?? sessionQuery.data?.external_team_id ?? "teamsnap")
|
||||||
|
: "anonymous";
|
||||||
const [selectedTeamId, setSelectedTeamId] = useState(readStoredTeamId);
|
const [selectedTeamId, setSelectedTeamId] = useState(readStoredTeamId);
|
||||||
const teamsQuery = useQuery({
|
const teamsQuery = useQuery({
|
||||||
queryKey: ["teamsnap", "teams"],
|
queryKey: teamSnapQueryKeys.teams(teamSnapCacheScope),
|
||||||
queryFn: () => teamsnapClient.loadTeams(),
|
queryFn: () => teamsnapClient.loadTeams(teamSnapCacheScope),
|
||||||
enabled: isTeamSnap,
|
enabled: isTeamSnap,
|
||||||
networkMode: "always",
|
networkMode: "always",
|
||||||
retry: 0,
|
retry: 0,
|
||||||
@@ -53,15 +56,15 @@ function useBuildWalkupContext() {
|
|||||||
}, [resolvedTeamId, selectedTeam, selectedTeamId, teams.length]);
|
}, [resolvedTeamId, selectedTeam, selectedTeamId, teams.length]);
|
||||||
|
|
||||||
const membersQuery = useQuery({
|
const membersQuery = useQuery({
|
||||||
queryKey: ["teamsnap", "members", resolvedTeamId],
|
queryKey: teamSnapQueryKeys.members(teamSnapCacheScope, resolvedTeamId),
|
||||||
queryFn: () => teamsnapClient.loadMembers(resolvedTeamId),
|
queryFn: () => teamsnapClient.loadMembers(resolvedTeamId, teamSnapCacheScope),
|
||||||
enabled: isTeamSnap && Boolean(resolvedTeamId),
|
enabled: isTeamSnap && Boolean(resolvedTeamId),
|
||||||
networkMode: "always",
|
networkMode: "always",
|
||||||
retry: 0,
|
retry: 0,
|
||||||
});
|
});
|
||||||
const eventsQuery = useQuery({
|
const eventsQuery = useQuery({
|
||||||
queryKey: ["teamsnap", "events", resolvedTeamId],
|
queryKey: teamSnapQueryKeys.events(teamSnapCacheScope, resolvedTeamId),
|
||||||
queryFn: () => teamsnapClient.loadEvents(resolvedTeamId),
|
queryFn: () => teamsnapClient.loadEvents(resolvedTeamId, teamSnapCacheScope),
|
||||||
enabled: isTeamSnap && Boolean(resolvedTeamId),
|
enabled: isTeamSnap && Boolean(resolvedTeamId),
|
||||||
networkMode: "always",
|
networkMode: "always",
|
||||||
retry: 0,
|
retry: 0,
|
||||||
@@ -112,6 +115,7 @@ function useBuildWalkupContext() {
|
|||||||
isTeamSnap,
|
isTeamSnap,
|
||||||
sessionQuery,
|
sessionQuery,
|
||||||
teamsQuery,
|
teamsQuery,
|
||||||
|
teamSnapCacheScope,
|
||||||
selectedTeam,
|
selectedTeam,
|
||||||
selectedTeamId,
|
selectedTeamId,
|
||||||
hasSelectedTeam: Boolean(resolvedTeamId),
|
hasSelectedTeam: Boolean(resolvedTeamId),
|
||||||
|
|||||||
86
frontend/src/lib/teamSnapCache.ts
Normal file
86
frontend/src/lib/teamSnapCache.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { QueryKey } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { queryClient } from "./queryClient";
|
||||||
|
import { readCachedValue, writeCachedValue } from "./offlineCache";
|
||||||
|
|
||||||
|
const inFlightRequests = new Map<string, Promise<unknown>>();
|
||||||
|
|
||||||
|
export function normalizeTeamSnapCacheScope(scope?: string | number | null): string {
|
||||||
|
const value = scope == null ? "" : String(scope).trim();
|
||||||
|
return value || "anonymous";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCacheParts(scope: string | number | null | undefined, resource: string, parts: readonly unknown[]): readonly unknown[] {
|
||||||
|
return ["teamsnap", normalizeTeamSnapCacheScope(scope), resource, ...parts];
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheKey(parts: readonly unknown[]): string {
|
||||||
|
return JSON.stringify(parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFreshFetch<T>(
|
||||||
|
cacheParts: readonly unknown[],
|
||||||
|
queryKey: QueryKey,
|
||||||
|
fetchFresh: () => Promise<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
const key = cacheKey(cacheParts);
|
||||||
|
const existing = inFlightRequests.get(key);
|
||||||
|
if (existing) {
|
||||||
|
return existing as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = fetchFresh()
|
||||||
|
.then((data) => {
|
||||||
|
writeCachedValue(cacheParts, data);
|
||||||
|
queryClient.setQueryData(queryKey, data);
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
inFlightRequests.delete(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
inFlightRequests.set(key, request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function staleWhileRevalidateTeamSnap<T>({
|
||||||
|
cacheParts,
|
||||||
|
queryKey,
|
||||||
|
fetchFresh,
|
||||||
|
}: {
|
||||||
|
cacheParts: readonly unknown[];
|
||||||
|
queryKey: QueryKey;
|
||||||
|
fetchFresh: () => Promise<T>;
|
||||||
|
}): Promise<T> {
|
||||||
|
const cached = readCachedValue<T>(cacheParts);
|
||||||
|
if (cached) {
|
||||||
|
void startFreshFetch(cacheParts, queryKey, fetchFresh).catch(() => undefined);
|
||||||
|
return cached.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return startFreshFetch(cacheParts, queryKey, fetchFresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const teamSnapQueryKeys = {
|
||||||
|
me(scope?: string | number | null): QueryKey {
|
||||||
|
return buildCacheParts(scope, "me", []);
|
||||||
|
},
|
||||||
|
teams(scope?: string | number | null): QueryKey {
|
||||||
|
return buildCacheParts(scope, "teams", []);
|
||||||
|
},
|
||||||
|
members(scope: string | number | null | undefined, teamId: string): QueryKey {
|
||||||
|
return buildCacheParts(scope, "members", [teamId]);
|
||||||
|
},
|
||||||
|
events(scope: string | number | null | undefined, teamId: string): QueryKey {
|
||||||
|
return buildCacheParts(scope, "events", [teamId]);
|
||||||
|
},
|
||||||
|
availabilities(scope: string | number | null | undefined, teamId: string, eventId?: string): QueryKey {
|
||||||
|
return buildCacheParts(scope, "availabilities", [teamId, eventId ?? ""]);
|
||||||
|
},
|
||||||
|
assignments(scope: string | number | null | undefined, teamId: string, eventId?: string): QueryKey {
|
||||||
|
return buildCacheParts(scope, "assignments", [teamId, eventId ?? ""]);
|
||||||
|
},
|
||||||
|
eventLineup(scope: string | number | null | undefined, teamId: string, eventId: string): QueryKey {
|
||||||
|
return buildCacheParts(scope, "eventLineup", [teamId, eventId]);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -11,6 +11,9 @@ import type {
|
|||||||
TeamSnapTeam,
|
TeamSnapTeam,
|
||||||
TeamSnapUser,
|
TeamSnapUser,
|
||||||
} from "../api/types";
|
} from "../api/types";
|
||||||
|
import { staleWhileRevalidateTeamSnap, teamSnapQueryKeys } from "./teamSnapCache";
|
||||||
|
|
||||||
|
export { teamSnapQueryKeys } from "./teamSnapCache";
|
||||||
|
|
||||||
type TeamSnapSdk = {
|
type TeamSnapSdk = {
|
||||||
auth?: (token: string) => Promise<void> | void;
|
auth?: (token: string) => Promise<void> | void;
|
||||||
@@ -106,81 +109,126 @@ async function ensureAuthorized(): Promise<TeamSnapSdk> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const teamsnapClient = {
|
export const teamsnapClient = {
|
||||||
async loadMe(): Promise<TeamSnapUser | null> {
|
async loadMe(cacheScope?: string | number | null): Promise<TeamSnapUser | null> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<TeamSnapUser | null>({
|
||||||
if (sdk.loadMe) {
|
cacheParts: teamSnapQueryKeys.me(cacheScope),
|
||||||
return sdk.loadMe();
|
queryKey: teamSnapQueryKeys.me(cacheScope),
|
||||||
}
|
fetchFresh: async () => {
|
||||||
return null;
|
const sdk = await ensureAuthorized();
|
||||||
|
if (sdk.loadMe) {
|
||||||
|
return sdk.loadMe();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadTeams(): Promise<TeamSnapTeam[]> {
|
async loadTeams(cacheScope?: string | number | null): Promise<TeamSnapTeam[]> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<TeamSnapTeam[]>({
|
||||||
if (sdk.loadTeams) {
|
cacheParts: teamSnapQueryKeys.teams(cacheScope),
|
||||||
const teams = await sdk.loadTeams();
|
queryKey: teamSnapQueryKeys.teams(cacheScope),
|
||||||
return teams.filter((team) => team.isRetired !== true);
|
fetchFresh: async () => {
|
||||||
}
|
const sdk = await ensureAuthorized();
|
||||||
return [];
|
if (sdk.loadTeams) {
|
||||||
|
const teams = await sdk.loadTeams();
|
||||||
|
return teams.filter((team) => team.isRetired !== true);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadMembers(teamId: string): Promise<TeamSnapMember[]> {
|
async loadMembers(teamId: string, cacheScope?: string | number | null): Promise<TeamSnapMember[]> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<TeamSnapMember[]>({
|
||||||
if (sdk.loadMembers) {
|
cacheParts: teamSnapQueryKeys.members(cacheScope, teamId),
|
||||||
return sdk.loadMembers({ teamId });
|
queryKey: teamSnapQueryKeys.members(cacheScope, teamId),
|
||||||
}
|
fetchFresh: async () => {
|
||||||
return [];
|
const sdk = await ensureAuthorized();
|
||||||
|
if (sdk.loadMembers) {
|
||||||
|
return sdk.loadMembers({ teamId });
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadEvents(teamId: string): Promise<TeamSnapEvent[]> {
|
async loadEvents(teamId: string, cacheScope?: string | number | null): Promise<TeamSnapEvent[]> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<TeamSnapEvent[]>({
|
||||||
if (sdk.loadEvents) {
|
cacheParts: teamSnapQueryKeys.events(cacheScope, teamId),
|
||||||
return sdk.loadEvents({ teamId });
|
queryKey: teamSnapQueryKeys.events(cacheScope, teamId),
|
||||||
}
|
fetchFresh: async () => {
|
||||||
return [];
|
const sdk = await ensureAuthorized();
|
||||||
|
if (sdk.loadEvents) {
|
||||||
|
return sdk.loadEvents({ teamId });
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadAvailabilities(teamId: string, eventId?: string): Promise<TeamSnapAvailability[]> {
|
async loadAvailabilities(teamId: string, eventId?: string, cacheScope?: string | number | null): Promise<TeamSnapAvailability[]> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<TeamSnapAvailability[]>({
|
||||||
if (sdk.loadAvailabilities) {
|
cacheParts: teamSnapQueryKeys.availabilities(cacheScope, teamId, eventId),
|
||||||
return sdk.loadAvailabilities(eventId ? { teamId, eventId } : { teamId });
|
queryKey: teamSnapQueryKeys.availabilities(cacheScope, teamId, eventId),
|
||||||
}
|
fetchFresh: async () => {
|
||||||
return [];
|
const sdk = await ensureAuthorized();
|
||||||
|
if (sdk.loadAvailabilities) {
|
||||||
|
return sdk.loadAvailabilities(eventId ? { teamId, eventId } : { teamId });
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadAssignments(teamId: string, eventId?: string): Promise<TeamSnapAssignment[]> {
|
async loadAssignments(teamId: string, eventId?: string, cacheScope?: string | number | null): Promise<TeamSnapAssignment[]> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<TeamSnapAssignment[]>({
|
||||||
if (sdk.loadAssignments) {
|
cacheParts: teamSnapQueryKeys.assignments(cacheScope, teamId, eventId),
|
||||||
return sdk.loadAssignments(eventId ? { teamId, eventId } : { teamId });
|
queryKey: teamSnapQueryKeys.assignments(cacheScope, teamId, eventId),
|
||||||
}
|
fetchFresh: async () => {
|
||||||
return [];
|
const sdk = await ensureAuthorized();
|
||||||
|
if (sdk.loadAssignments) {
|
||||||
|
return sdk.loadAssignments(eventId ? { teamId, eventId } : { teamId });
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async loadEventLineupData(teamId: string, eventId: string): Promise<{
|
async loadEventLineupData(teamId: string, eventId: string, cacheScope?: string | number | null): Promise<{
|
||||||
eventLineup: TeamSnapEventLineup | null;
|
eventLineup: TeamSnapEventLineup | null;
|
||||||
entries: TeamSnapEventLineupEntry[];
|
entries: TeamSnapEventLineupEntry[];
|
||||||
}> {
|
}> {
|
||||||
const sdk = await ensureAuthorized();
|
return staleWhileRevalidateTeamSnap<{
|
||||||
if (!sdk.loadEventLineups) {
|
eventLineup: TeamSnapEventLineup | null;
|
||||||
return { eventLineup: null, entries: [] };
|
entries: TeamSnapEventLineupEntry[];
|
||||||
}
|
}>({
|
||||||
|
cacheParts: teamSnapQueryKeys.eventLineup(cacheScope, teamId, eventId),
|
||||||
|
queryKey: teamSnapQueryKeys.eventLineup(cacheScope, teamId, eventId),
|
||||||
|
fetchFresh: async () => {
|
||||||
|
const sdk = await ensureAuthorized();
|
||||||
|
if (!sdk.loadEventLineups) {
|
||||||
|
return { eventLineup: null, entries: [] };
|
||||||
|
}
|
||||||
|
|
||||||
const eventLineups = await sdk.loadEventLineups(eventId);
|
const eventLineups = await sdk.loadEventLineups(eventId);
|
||||||
const eventLineup = eventLineups.length ? eventLineups[eventLineups.length - 1] : null;
|
const eventLineup = eventLineups.length ? eventLineups[eventLineups.length - 1] : null;
|
||||||
|
|
||||||
const eventLineupWithLinks = eventLineup as TeamSnapEventLineup & {
|
const eventLineupWithLinks = eventLineup as TeamSnapEventLineup & {
|
||||||
loadItems?: (linkName: string) => Promise<TeamSnapEventLineupEntry[]>;
|
loadItems?: (linkName: string) => Promise<TeamSnapEventLineupEntry[]>;
|
||||||
} | null;
|
} | null;
|
||||||
if (!eventLineupWithLinks?.loadItems) {
|
if (!eventLineupWithLinks?.loadItems) {
|
||||||
return { eventLineup, entries: [] };
|
return { eventLineup, entries: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rawEntries = await eventLineupWithLinks.loadItems("eventLineupEntries");
|
const rawEntries = await eventLineupWithLinks.loadItems("eventLineupEntries");
|
||||||
const entries = rawEntries
|
const entries = rawEntries
|
||||||
.filter((item): item is TeamSnapEventLineupEntry => item.type === "eventLineupEntry")
|
.filter((item): item is TeamSnapEventLineupEntry => item.type === "eventLineupEntry")
|
||||||
.sort((left, right) => {
|
.sort((left, right) => {
|
||||||
const leftSequence = Number(left.sequence ?? Number.MAX_SAFE_INTEGER);
|
const leftSequence = Number(left.sequence ?? Number.MAX_SAFE_INTEGER);
|
||||||
const rightSequence = Number(right.sequence ?? Number.MAX_SAFE_INTEGER);
|
const rightSequence = Number(right.sequence ?? Number.MAX_SAFE_INTEGER);
|
||||||
return leftSequence - rightSequence;
|
return leftSequence - rightSequence;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { eventLineup, entries };
|
return { eventLineup, entries };
|
||||||
} catch {
|
} catch {
|
||||||
return { eventLineup, entries: [] };
|
return { eventLineup, entries: [] };
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ClipSummaryRow } from "../components/ClipSummaryRow";
|
|||||||
import { useClipPlayback } from "../hooks/useClipPlayback";
|
import { useClipPlayback } from "../hooks/useClipPlayback";
|
||||||
import { useWalkupContext } from "../hooks/useWalkupContext";
|
import { useWalkupContext } from "../hooks/useWalkupContext";
|
||||||
import { loadPreparedGame } from "../lib/offlinePrep";
|
import { loadPreparedGame } from "../lib/offlinePrep";
|
||||||
import { teamsnapClient } from "../lib/teamsnap";
|
import { teamSnapQueryKeys, teamsnapClient } from "../lib/teamsnap";
|
||||||
import {
|
import {
|
||||||
formatGameDate,
|
formatGameDate,
|
||||||
formatGameTitle,
|
formatGameTitle,
|
||||||
@@ -124,16 +124,16 @@ export function GamedayPage() {
|
|||||||
const assignmentList = assignmentsQuery.data ?? preparedGame?.assignments ?? [];
|
const assignmentList = assignmentsQuery.data ?? preparedGame?.assignments ?? [];
|
||||||
|
|
||||||
const eventLineupQuery = useQuery({
|
const eventLineupQuery = useQuery({
|
||||||
queryKey: ["teamsnap", "eventLineup", teamId, resolvedSelectedGameId],
|
queryKey: teamSnapQueryKeys.eventLineup(walkup.teamSnapCacheScope, teamId, resolvedSelectedGameId),
|
||||||
queryFn: () => teamsnapClient.loadEventLineupData(teamId, resolvedSelectedGameId),
|
queryFn: () => teamsnapClient.loadEventLineupData(teamId, resolvedSelectedGameId, walkup.teamSnapCacheScope),
|
||||||
enabled: Boolean(teamId && resolvedSelectedGameId),
|
enabled: Boolean(teamId && resolvedSelectedGameId),
|
||||||
networkMode: "always",
|
networkMode: "always",
|
||||||
retry: 0,
|
retry: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const availabilityQuery = useQuery({
|
const availabilityQuery = useQuery({
|
||||||
queryKey: ["teamsnap", "availabilities", teamId, resolvedSelectedGameId],
|
queryKey: teamSnapQueryKeys.availabilities(walkup.teamSnapCacheScope, teamId, resolvedSelectedGameId),
|
||||||
queryFn: () => teamsnapClient.loadAvailabilities(teamId, resolvedSelectedGameId),
|
queryFn: () => teamsnapClient.loadAvailabilities(teamId, resolvedSelectedGameId, walkup.teamSnapCacheScope),
|
||||||
enabled: Boolean(teamId && resolvedSelectedGameId),
|
enabled: Boolean(teamId && resolvedSelectedGameId),
|
||||||
networkMode: "always",
|
networkMode: "always",
|
||||||
retry: 0,
|
retry: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user