From c820dfbdad8a0cdcdf6c54924490e100ed455a32 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Thu, 16 Oct 2025 01:36:14 -0700 Subject: [PATCH] feat: add draft timer display with team-specific countdown UI --- app/components/draft/DraftGrid.tsx | 50 ++++++++++++++++++++------ app/components/draft/DraftTimer.tsx | 37 +++++++++++++++++++ app/routes/leagues/$leagueId.draft.tsx | 16 ++++++++- 3 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 app/components/draft/DraftTimer.tsx diff --git a/app/components/draft/DraftGrid.tsx b/app/components/draft/DraftGrid.tsx index 384e226..5206db8 100644 --- a/app/components/draft/DraftGrid.tsx +++ b/app/components/draft/DraftGrid.tsx @@ -1,4 +1,5 @@ import { useMemo } from "react"; +import { DraftTimer } from "./DraftTimer"; interface Team { id: string; @@ -23,6 +24,11 @@ interface Participant { sport: string; } +interface Timer { + teamId: string; + timeRemaining: number; +} + interface DraftGridProps { teams: Team[]; draftPicks: DraftPick[]; @@ -30,6 +36,7 @@ interface DraftGridProps { draftRounds: number; currentPickNumber: number; userTeamId?: string; + timers?: Timer[]; } export function DraftGrid({ @@ -39,6 +46,7 @@ export function DraftGrid({ draftRounds, currentPickNumber, userTeamId, + timers = [], }: DraftGridProps) { // Create a map of participant IDs to participant data for quick lookup const participantMap = useMemo(() => { @@ -50,6 +58,11 @@ export function DraftGrid({ return new Map(draftPicks.map((p) => [p.pickNumber, p])); }, [draftPicks]); + // Create a map of team IDs to timers + const timerMap = useMemo(() => { + return new Map(timers.map((t) => [t.teamId, t])); + }, [timers]); + // Calculate pick number for a given round and team const getPickNumber = (round: number, teamIndex: number): number => { const isOddRound = round % 2 === 1; @@ -66,18 +79,33 @@ export function DraftGrid({
Round
- {teams.map((team) => ( -
-
- {team.name} + {teams.map((team) => { + const timer = timerMap.get(team.id); + // Determine if this team is on the clock + const currentPick = Math.ceil(currentPickNumber / teams.length); + const pickInRound = ((currentPickNumber - 1) % teams.length) + 1; + const isOddRound = currentPick % 2 === 1; + const currentTeamIndex = isOddRound ? pickInRound - 1 : teams.length - pickInRound; + const isOnClock = teams[currentTeamIndex]?.id === team.id; + + return ( +
+
+ {team.name} +
+ {timer && ( +
+ +
+ )}
-
- ))} + ); + })}
{/* Draft Rounds */} diff --git a/app/components/draft/DraftTimer.tsx b/app/components/draft/DraftTimer.tsx new file mode 100644 index 0000000..d8ef01f --- /dev/null +++ b/app/components/draft/DraftTimer.tsx @@ -0,0 +1,37 @@ +interface DraftTimerProps { + timeRemaining: number; // in seconds + isActive?: boolean; +} + +export function DraftTimer({ timeRemaining, isActive = false }: DraftTimerProps) { + // Format time as M:SS or H:MM:SS + const formatTime = (seconds: number): string => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + + if (hours > 0) { + return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`; + } + return `${minutes}:${String(secs).padStart(2, "0")}`; + }; + + // Determine color based on time remaining + const getColorClass = (): string => { + if (timeRemaining > 60) { + return "text-green-600 dark:text-green-400"; + } else if (timeRemaining > 30) { + return "text-yellow-600 dark:text-yellow-400"; + } else if (timeRemaining > 10) { + return "text-red-600 dark:text-red-400"; + } else { + return "text-red-600 dark:text-red-400 animate-pulse"; + } + }; + + return ( +
+ {formatTime(timeRemaining)} +
+ ); +} diff --git a/app/routes/leagues/$leagueId.draft.tsx b/app/routes/leagues/$leagueId.draft.tsx index 954ac6b..9aba38e 100644 --- a/app/routes/leagues/$leagueId.draft.tsx +++ b/app/routes/leagues/$leagueId.draft.tsx @@ -69,7 +69,19 @@ export async function loader(args: LoaderFunctionArgs) { // Get draft data const draftPicks = await getDraftPicks(season.id); - const timers = await getSeasonTimers(season.id); + let timers = await getSeasonTimers(season.id); + + // If no timers exist, create default timers for display + if (timers.length === 0) { + timers = teams.map((team) => ({ + id: team.id, + seasonId: season.id, + teamId: team.id, + timeRemaining: season.draftInitialTime, + createdAt: new Date(), + updatedAt: new Date(), + })); + } // Get user's queue if they have a team const queueItems = userTeam ? await getTeamQueue(userTeam.id) : []; @@ -105,6 +117,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited