From 045e0f2cab372d446e13dd58a90ee42d1b4977f1 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Thu, 16 Oct 2025 01:05:20 -0700 Subject: [PATCH] feat: add draft room route and navigation button in league view --- app/routes.ts | 1 + app/routes/leagues/$leagueId.draft.tsx | 180 +++++++++++++++++++++++++ app/routes/leagues/$leagueId.tsx | 9 ++ 3 files changed, 190 insertions(+) create mode 100644 app/routes/leagues/$leagueId.draft.tsx diff --git a/app/routes.ts b/app/routes.ts index 1e08d0c..2426d2e 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -6,6 +6,7 @@ export default [ route("leagues/new", "routes/leagues/new.tsx"), route("leagues/:leagueId", "routes/leagues/$leagueId.tsx"), route("leagues/:leagueId/settings", "routes/leagues/$leagueId.settings.tsx"), + route("leagues/:leagueId/draft", "routes/leagues/$leagueId.draft.tsx"), route("teams/:teamId/settings", "routes/teams/$teamId.settings.tsx"), route("api/webhooks/clerk", "routes/api/webhooks/clerk.ts"), route("user-profile", "routes/user-profile.tsx"), diff --git a/app/routes/leagues/$leagueId.draft.tsx b/app/routes/leagues/$leagueId.draft.tsx new file mode 100644 index 0000000..1d19219 --- /dev/null +++ b/app/routes/leagues/$leagueId.draft.tsx @@ -0,0 +1,180 @@ +import { data, redirect } from "react-router"; +import type { LoaderFunctionArgs } from "react-router"; +import { getAuth } from "@clerk/react-router/server"; +import { + findLeagueById, + findCurrentSeason, + findTeamsBySeasonId, + isCommissioner, + findDraftSlotsBySeasonId, + getDraftPicks, + getSeasonTimers, +} from "~/models"; + +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 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); + const timers = await getSeasonTimers(season.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, + timers, + currentPickNumber, + totalPicks, + isDraftComplete, + userId, + }); +} + +export default function DraftRoom({ loaderData }: { loaderData: Awaited>["data"] }) { + const { + league, + season, + isCommissioner, + userTeam, + currentPickNumber, + totalPicks, + isDraftComplete, + } = loaderData; + + 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. +

+
+ ) : ( +
+ {/* Left Column - Draft Grid & Player List */} +
+ {/* Draft Grid Placeholder */} +
+

Draft Board

+
+ Draft grid will be implemented in Phase 5 +
+
+ + {/* Player List Placeholder */} +
+

Available Players

+
+ Player list will be implemented in Phase 6 +
+
+
+ + {/* Right Column - Queue & Controls */} +
+ {/* Queue Placeholder */} +
+

Your Queue

+
+ Queue will be implemented in Phase 7 +
+
+ + {/* Timer Placeholder */} +
+

Draft Timer

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

Commissioner Controls

+
+ Controls will be implemented in Phase 9 +
+
+ )} +
+
+ )} +
+
+ ); +} diff --git a/app/routes/leagues/$leagueId.tsx b/app/routes/leagues/$leagueId.tsx index aef86b6..6d3393a 100644 --- a/app/routes/leagues/$leagueId.tsx +++ b/app/routes/leagues/$leagueId.tsx @@ -372,6 +372,15 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) { )} + {season && ( + + + + + + )}