import { data, redirect } from "react-router"; import type { LoaderFunctionArgs } from "react-router"; import { getAuth } from "@clerk/react-router/server"; import { useEffect, useState } from "react"; import { findLeagueById, findCurrentSeason, findTeamsBySeasonId, isCommissioner, findDraftSlotsBySeasonId, getDraftPicks, getSeasonTimers, findSeasonWithSportsSeasons, getTeamQueue, getTeamForPick, } from "~/models"; import { DraftGrid } from "~/components/draft/DraftGrid"; import { PlayerList } from "~/components/draft/PlayerList"; import { DraftQueue } from "~/components/draft/DraftQueue"; import { CommissionerControls } from "~/components/draft/CommissionerControls"; import { useDraftSocket } from "~/hooks/useDraftSocket"; export async function loader(args: LoaderFunctionArgs) { const { userId } = await getAuth(args); if (!userId) { throw redirect("/"); } const { leagueId } = args.params; if (!leagueId) { throw new Response("League ID is required", { status: 400 }); } // Get league and season const league = await findLeagueById(leagueId); if (!league) { throw new Response("League not found", { status: 404 }); } const season = await findCurrentSeason(leagueId); if (!season) { throw new Response("No active season found", { status: 404 }); } // Get season with sports and participants const seasonWithSports = await findSeasonWithSportsSeasons(season.id); const participants = seasonWithSports?.seasonSports?.flatMap( (ss: any) => { const sportName = ss.sportsSeason?.sport?.name || "Unknown"; return (ss.sportsSeason?.participants || []).map((p: any) => ({ ...p, sport: sportName, })); } ) || []; // Get teams and draft order const teams = await findTeamsBySeasonId(season.id); const draftSlots = await findDraftSlotsBySeasonId(season.id); // Sort teams by draft order const teamsWithOrder = teams.map((team) => { const slot = draftSlots.find((s: { teamId: string }) => s.teamId === team.id); return { ...team, draftOrder: slot?.draftOrder || 0, }; }).sort((a, b) => a.draftOrder - b.draftOrder); // Check user permissions const isUserCommissioner = await isCommissioner(leagueId, userId); const userTeam = teams.find((team) => team.ownerId === userId); // Get draft data const draftPicks = await getDraftPicks(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) : []; // Calculate current pick info const totalPicks = season.draftRounds * teams.length; const currentPickNumber = season.currentPickNumber || 1; const isDraftComplete = currentPickNumber > totalPicks; return data({ league, season, teams: teamsWithOrder, draftSlots, isCommissioner: isUserCommissioner, userTeam, draftPicks, participants, queueItems, timers, currentPickNumber, totalPicks, isDraftComplete, userId, }); } export default function DraftRoom({ loaderData }: { loaderData: Awaited>["data"] }) { const { league, season, teams, draftPicks: initialDraftPicks, participants, queueItems: initialQueueItems, timers: initialTimers, isCommissioner, userTeam, currentPickNumber: initialPickNumber, totalPicks, isDraftComplete: initialIsDraftComplete, draftSlots, } = loaderData; // Local state for real-time updates const [draftPicks, setDraftPicks] = useState(initialDraftPicks); const [queueItems, setQueueItems] = useState(initialQueueItems); const [timers, setTimers] = useState(initialTimers); const [currentPickNumber, setCurrentPickNumber] = useState(initialPickNumber); const [isDraftComplete, setIsDraftComplete] = useState(initialIsDraftComplete); const [isDraftStarted, setIsDraftStarted] = useState(!!season.draftStartedAt); const [isDraftPaused, setIsDraftPaused] = useState(season.draftPaused || false); // Socket connection const { on, off } = useDraftSocket(season.id); // Calculate current team const draftOrder = draftSlots.map((slot: any) => ({ teamId: slot.teamId, draftOrder: slot.draftOrder, })); const currentTeamId = getTeamForPick(currentPickNumber, draftOrder); const currentTeam = teams.find((t: any) => t.id === currentTeamId); const currentTeamName = currentTeam?.name || "Unknown"; // Socket event handlers useEffect(() => { // Listen for pick made const handlePickMade = (data: any) => { setDraftPicks((prev: any) => [...prev, data.pick]); setCurrentPickNumber(data.nextPickNumber); setIsDraftComplete(data.isDraftComplete); }; // Listen for draft started const handleDraftStarted = (data: any) => { setIsDraftStarted(true); setIsDraftPaused(false); setTimers(data.timers); }; // Listen for draft paused const handleDraftPaused = (data: any) => { setIsDraftPaused(data.isPaused); }; // Listen for draft completed const handleDraftCompleted = () => { setIsDraftComplete(true); }; // Listen for queue updates const handleQueueUpdated = (data: any) => { if (userTeam && data.teamId === userTeam.id) { setQueueItems(data.queue); } }; on("pick-made", handlePickMade); on("draft-started", handleDraftStarted); on("draft-paused", handleDraftPaused); on("draft-completed", handleDraftCompleted); on("queue-updated", handleQueueUpdated); return () => { off("pick-made", handlePickMade); off("draft-started", handleDraftStarted); off("draft-paused", handleDraftPaused); off("draft-completed", handleDraftCompleted); off("queue-updated", handleQueueUpdated); }; }, [on, off, userTeam]); // Commissioner action handlers const handleStartDraft = async () => { try { const formData = new FormData(); formData.append("action", "start-draft"); formData.append("seasonId", season.id); const response = await fetch("/api/draft-commissioner", { method: "POST", body: formData, }); if (!response.ok) { const error = await response.json(); console.error("Failed to start draft:", error); } } catch (error) { console.error("Error starting draft:", error); } }; const handlePauseDraft = async () => { try { const formData = new FormData(); formData.append("action", "pause-draft"); formData.append("seasonId", season.id); formData.append("pause", "true"); const response = await fetch("/api/draft-commissioner", { method: "POST", body: formData, }); if (!response.ok) { const error = await response.json(); console.error("Failed to pause draft:", error); } } catch (error) { console.error("Error pausing draft:", error); } }; const handleResumeDraft = async () => { try { const formData = new FormData(); formData.append("action", "pause-draft"); formData.append("seasonId", season.id); formData.append("pause", "false"); const response = await fetch("/api/draft-commissioner", { method: "POST", body: formData, }); if (!response.ok) { const error = await response.json(); console.error("Failed to resume draft:", error); } } catch (error) { console.error("Error resuming draft:", error); } }; const handleForcePick = async () => { if (!currentTeamId) return; try { const formData = new FormData(); formData.append("action", "force-pick"); formData.append("seasonId", season.id); formData.append("teamId", currentTeamId); const response = await fetch("/api/draft-commissioner", { method: "POST", body: formData, }); if (!response.ok) { const error = await response.json(); console.error("Failed to force pick:", error); } } catch (error) { console.error("Error forcing pick:", error); } }; return (
{/* Header */}

{league.name} Draft

{season.year} Season • Pick {currentPickNumber} of {totalPicks}

{isCommissioner && ( Commissioner )} {userTeam && ( {userTeam.name} )}
{/* Main Content */}
{isDraftComplete ? (

Draft Complete!

All picks have been made. The season is ready to begin.

) : (
{/* Draft Board - Full Width */}

Draft Board

{/* 2x2 Grid Below */}
{/* Available Players */}

Available Players

{/* Your Queue */}

Your Queue

{/* Draft Timer */}

Draft Timer

Timer will be implemented in Phase 8
{/* Commissioner Controls */} {isCommissioner ? (

Commissioner Controls

) : (

Draft Info

Current Pick: {currentPickNumber} of {totalPicks}
Draft Rounds: {season.draftRounds}
Current Team: {currentTeamName}
)}
)}
); }