Squash merge feature/library-reorganization

This commit is contained in:
Codex
2026-04-22 06:46:23 -05:00
parent 7f4a4beb5a
commit fe2a04343c
72 changed files with 14520 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "../api/client";
export function useSession() {
return useQuery({
queryKey: ["session"],
queryFn: api.getSession,
});
}

View File

@@ -0,0 +1,135 @@
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
import { useQuery } from "@tanstack/react-query";
import { api } from "../api/client";
import type { TeamSnapEvent, TeamSnapMember } from "../api/types";
import { queryClient } from "../lib/queryClient";
import { findCurrentPlayer, findNextGame, sortGames } from "../lib/teamsnapHelpers";
import { teamsnapClient } from "../lib/teamsnap";
import { useSession } from "./useSession";
const TEAM_STORAGE_KEY = "walkup.selectedTeamId";
function readStoredTeamId(): string {
if (typeof window === "undefined") {
return "";
}
return window.localStorage.getItem(TEAM_STORAGE_KEY) ?? "";
}
type WalkupContextValue = ReturnType<typeof useBuildWalkupContext>;
const WalkupContext = createContext<WalkupContextValue | null>(null);
function useBuildWalkupContext() {
const sessionQuery = useSession();
const isTeamSnap = sessionQuery.data?.authenticated === true && sessionQuery.data?.provider === "teamsnap";
const [selectedTeamId, setSelectedTeamId] = useState(readStoredTeamId);
const teamsQuery = useQuery({
queryKey: ["teamsnap", "teams"],
queryFn: () => teamsnapClient.loadTeams(),
enabled: isTeamSnap,
});
const teams = teamsQuery.data ?? [];
const selectedTeam = teams.find((team) => String(team.id) === selectedTeamId) ?? null;
const resolvedTeamId = selectedTeam ? String(selectedTeam.id) : "";
useEffect(() => {
if (selectedTeamId && !selectedTeam && teams.length) {
setSelectedTeamId("");
window.localStorage.removeItem(TEAM_STORAGE_KEY);
return;
}
if (!selectedTeamId) {
window.localStorage.removeItem(TEAM_STORAGE_KEY);
return;
}
window.localStorage.setItem(TEAM_STORAGE_KEY, selectedTeamId);
}, [resolvedTeamId, selectedTeam, selectedTeamId, teams.length]);
const membersQuery = useQuery({
queryKey: ["teamsnap", "members", resolvedTeamId],
queryFn: () => teamsnapClient.loadMembers(resolvedTeamId),
enabled: isTeamSnap && Boolean(resolvedTeamId),
});
const eventsQuery = useQuery({
queryKey: ["teamsnap", "events", resolvedTeamId],
queryFn: () => teamsnapClient.loadEvents(resolvedTeamId),
enabled: isTeamSnap && Boolean(resolvedTeamId),
});
const members: TeamSnapMember[] = membersQuery.data ?? [];
const games: TeamSnapEvent[] = sortGames(eventsQuery.data ?? []);
const currentPlayer = findCurrentPlayer(sessionQuery.data?.external_user_id, members);
const nextGame = findNextGame(games);
const currentPlayerId =
sessionQuery.data?.external_team_id === selectedTeamId && sessionQuery.data?.external_player_id
? String(sessionQuery.data.external_player_id)
: currentPlayer
? String(currentPlayer.id)
: "";
useEffect(() => {
if (!isTeamSnap || !resolvedTeamId || !currentPlayer) {
return;
}
const selectedPlayerId = String(currentPlayer.id);
if (
sessionQuery.data?.external_team_id === selectedTeamId &&
sessionQuery.data?.external_player_id === selectedPlayerId
) {
return;
}
void api
.updateWalkupSessionSelection({
external_team_id: resolvedTeamId,
external_player_id: selectedPlayerId,
})
.then(async () => {
await queryClient.invalidateQueries({ queryKey: ["session"] });
})
.catch(() => {
// Keep the UI working even if the session cache update fails.
});
}, [currentPlayer, isTeamSnap, resolvedTeamId, selectedTeamId, sessionQuery.data?.external_player_id, sessionQuery.data?.external_team_id]);
function selectTeam(teamId: string) {
setSelectedTeamId(teamId);
}
return {
isTeamSnap,
sessionQuery,
teamsQuery,
selectedTeam,
selectedTeamId,
hasSelectedTeam: Boolean(resolvedTeamId),
selectTeam,
membersQuery,
members,
currentPlayer,
currentPlayerId,
eventsQuery,
games,
nextGame,
};
}
export function WalkupProvider({ children }: { children: ReactNode }) {
const value = useBuildWalkupContext();
const memoizedValue = useMemo(() => value, [value]);
return <WalkupContext.Provider value={memoizedValue}>{children}</WalkupContext.Provider>;
}
export function useWalkupContext() {
const value = useContext(WalkupContext);
if (!value) {
throw new Error("useWalkupContext must be used within WalkupProvider");
}
return value;
}