From ab3437cd731cb886a184635a0f9a915e7997379a Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:56:07 -0800 Subject: [PATCH] Display team owner names instead of team names in draft UI (#24) * Show manager username instead of team name on draft board Both the read-only draft board and the active draft room now display the manager's username (falling back to displayName if no username is set, and to team name if the team is unassigned) in the column headers of the draft grid. https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1 * Address code review: extract ownerMap helper, fix TeamsDraftedGrid - Extract ownerMap building into app/lib/owner-map.ts to eliminate duplication across the two loaders - Guard against null username AND displayName so the map only contains real string values - Apply username display to TeamsDraftedGrid (the "Teams" tab in the active draft room), which was missed in the initial change https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1 --------- Co-authored-by: Claude --- app/components/DraftGrid.tsx | 4 +- app/components/draft/DraftGridSection.tsx | 4 +- app/components/draft/TeamsDraftedGrid.tsx | 4 +- app/lib/owner-map.ts | 39 +++++++++++++++++++ .../$leagueId.draft-board.$seasonId.tsx | 7 +++- .../leagues/$leagueId.draft.$seasonId.tsx | 7 ++++ 6 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 app/lib/owner-map.ts diff --git a/app/components/DraftGrid.tsx b/app/components/DraftGrid.tsx index bf98a3c..d0acfd8 100644 --- a/app/components/DraftGrid.tsx +++ b/app/components/DraftGrid.tsx @@ -25,6 +25,7 @@ interface DraftGridProps { onForceManualPick?: (pickNumber: number, teamId: string) => void; autodraftStatus?: Record; connectedTeams?: Set; + ownerMap?: Record; } export function DraftGrid({ @@ -39,6 +40,7 @@ export function DraftGrid({ onForceManualPick, autodraftStatus = {}, connectedTeams = new Set(), + ownerMap = {}, }: DraftGridProps) { const totalTeams = draftSlots.length; @@ -58,7 +60,7 @@ export function DraftGrid({
- {slot.team.name} + {ownerMap[slot.team.id] || slot.team.name}
{teamTimers && formatTime && (
void; onReplacePick?: (pickNumber: number, teamId: string) => void; onRollbackToPick?: (pickNumber: number) => void; + ownerMap?: Record; } export function DraftGridSection({ @@ -57,6 +58,7 @@ export function DraftGridSection({ onForceManualPickOpen, onReplacePick, onRollbackToPick, + ownerMap = {}, }: DraftGridSectionProps) { const currentTeamId = useMemo( () => draftGrid.flat().find((c) => c.pickNumber === currentPick)?.teamId ?? null, @@ -86,7 +88,7 @@ export function DraftGridSection({ : "" }`} > - {slot.team.name} + {ownerMap[slot.team.id] || slot.team.name}
; + ownerMap?: Record; picks: Array<{ id: string; team: { @@ -38,6 +39,7 @@ export function TeamsDraftedGrid({ picks, sports, season, + ownerMap = {}, }: TeamsDraftedGridProps) { // Calculate picks by team and sport const picksByTeamAndSport = useMemo(() => { @@ -97,7 +99,7 @@ export function TeamsDraftedGrid({ >
- {slot.team.name} + {ownerMap[slot.team.id] || slot.team.name}
{flexUsed} of {season.numFlexPicks} flex diff --git a/app/lib/owner-map.ts b/app/lib/owner-map.ts new file mode 100644 index 0000000..70e118e --- /dev/null +++ b/app/lib/owner-map.ts @@ -0,0 +1,39 @@ +import { findUserByClerkId } from "~/models/user"; + +/** + * Builds a map of teamId -> manager username (or displayName fallback). + * Accepts any array of objects that have a `team` with `id` and `ownerId`. + */ +export async function buildOwnerMap( + slots: Array<{ team: { id: string; ownerId: string | null } }> +): Promise> { + const ownerIds = slots + .map((s) => s.team.ownerId) + .filter((id): id is string => id !== null); + const uniqueOwnerIds = [...new Set(ownerIds)]; + + const ownerEntries = await Promise.all( + uniqueOwnerIds.map(async (ownerId) => { + const user = await findUserByClerkId(ownerId); + const name = user?.username || user?.displayName || null; + return name ? { ownerId, name } : null; + }) + ); + + const ownerByClerkId = new Map( + ownerEntries + .filter((e): e is NonNullable => e !== null) + .map((e) => [e.ownerId, e.name]) + ); + + const ownerMap: Record = {}; + for (const slot of slots) { + const name = slot.team.ownerId + ? ownerByClerkId.get(slot.team.ownerId) + : undefined; + if (name) { + ownerMap[slot.team.id] = name; + } + } + return ownerMap; +} diff --git a/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx b/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx index ffc56c5..2fa61e2 100644 --- a/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.draft-board.$seasonId.tsx @@ -6,6 +6,7 @@ import * as schema from "~/database/schema"; import { DraftGrid } from "~/components/DraftGrid"; import { useDraftSocket } from "~/hooks/useDraftSocket"; import { useState, useEffect } from "react"; +import { buildOwnerMap } from "~/lib/owner-map"; export async function loader(args: any) { const { params } = args; @@ -102,15 +103,18 @@ export async function loader(args: any) { .where(eq(schema.draftPicks.seasonId, seasonId)) .orderBy(asc(schema.draftPicks.pickNumber)); + const ownerMap = await buildOwnerMap(draftSlots); + return { season, draftSlots, draftPicks, + ownerMap, }; } export default function DraftBoard() { - const { season, draftSlots, draftPicks: initialPicks } = useLoaderData(); + const { season, draftSlots, draftPicks: initialPicks, ownerMap } = useLoaderData(); const { isConnected, on, off } = useDraftSocket(season.id); const [picks, setPicks] = useState(initialPicks); const [currentPick, setCurrentPick] = useState(season.currentPickNumber || 1); @@ -186,6 +190,7 @@ export default function DraftBoard() { draftSlots={draftSlots} draftGrid={draftGrid} currentPick={currentPick} + ownerMap={ownerMap} />
diff --git a/app/routes/leagues/$leagueId.draft.$seasonId.tsx b/app/routes/leagues/$leagueId.draft.$seasonId.tsx index 1c75430..db207e2 100644 --- a/app/routes/leagues/$leagueId.draft.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.draft.$seasonId.tsx @@ -11,6 +11,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "~/components/ui/dialog"; import { getAuth } from "@clerk/react-router/server"; import { getTeamQueue } from "~/models/draft-queue"; +import { buildOwnerMap } from "~/lib/owner-map"; import { DraftSidebar } from "~/components/DraftSidebar"; import { TeamsDraftedGrid } from "~/components/draft/TeamsDraftedGrid"; import { QueueSection } from "~/components/draft/QueueSection"; @@ -166,6 +167,8 @@ export async function loader(args: any) { ? (autodraftSettings.find((s) => s.teamId === userTeam.id) ?? null) : null; + const ownerMap = await buildOwnerMap(draftSlots); + return { season, draftSlots, @@ -179,6 +182,7 @@ export async function loader(args: any) { isCommissioner: !!isCommissioner, currentUserId: userId, numFlexPicks: Math.max(0, season.draftRounds - seasonSports.length), + ownerMap, }; } @@ -196,6 +200,7 @@ export default function DraftRoom() { isCommissioner, currentUserId, numFlexPicks, + ownerMap, } = useLoaderData(); const { revalidate } = useRevalidator(); const { isConnected, connectionError, isReconnecting, on, off } = useDraftSocket(season.id, userTeam?.id); @@ -1224,6 +1229,7 @@ export default function DraftRoom() { autodraftStatus={autodraftStatus} connectedTeams={connectedTeams} isCommissioner={isCommissioner} + ownerMap={ownerMap} onAdjustTimeBankOpen={isCommissioner ? handleAdjustTimeBankOpen : undefined} onForceAutopick={handleForceAutopick} onForceManualPickOpen={(pickNumber, teamId) => { @@ -1244,6 +1250,7 @@ export default function DraftRoom() { picks={picks} sports={seasonSportsData} season={{ numFlexPicks }} + ownerMap={ownerMap} />