From faecfd8ecc6156378b837e7085c004e5e68de2a5 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Fri, 15 May 2026 18:47:36 -0700 Subject: [PATCH] Rename queue page to Set Pre-Draft Queue, remove VORP, fix re-add race condition (#436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename queue page to Set Pre-Draft Queue, remove VORP, fix re-add race condition - Rename page title, h1, button, error message, and tests from "Pre-Draft Rankings" to "Set Pre-Draft Queue" - Remove VORP values and "Sorted by VORP" labels from the all-players list - Fix bug where removing a player then immediately re-adding would fail with "already in queue": track in-flight removes in a ref and guard handleAdd against firing while a remove is still in-flight https://claude.ai/code/session_01TGAV9A2c72PNkL1ffY7Xeq * Address code review findings on Set Pre-Draft Queue page - Rename component function from PreDraftRankings to SetPreDraftQueue - Guard handleRemove against temp IDs — items added optimistically but not yet written to the DB have no server-side record to delete; skip the API call and just drop them from local state - Surface a toast when handleAdd is blocked by a pending remove instead of silently no-oping - Group both refs before their shared useEffect for readability - Drop justify-between from the All Players desktop heading (sole child) - Update test describe blocks to "Set Pre-Draft Queue Access" https://claude.ai/code/session_01TGAV9A2c72PNkL1ffY7Xeq --------- Co-authored-by: Claude --- app/components/league/DraftInfoCard.tsx | 2 +- .../$leagueId.draft-queue.$seasonId.tsx | 38 +++++++++++-------- .../__tests__/draft-queue-access.test.ts | 12 +++--- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/app/components/league/DraftInfoCard.tsx b/app/components/league/DraftInfoCard.tsx index e2b7d5b..a7d3bab 100644 --- a/app/components/league/DraftInfoCard.tsx +++ b/app/components/league/DraftInfoCard.tsx @@ -121,7 +121,7 @@ function DraftInfoCardVariant({ ) : queueBuilderHref ? ( ) : null} diff --git a/app/routes/leagues/$leagueId.draft-queue.$seasonId.tsx b/app/routes/leagues/$leagueId.draft-queue.$seasonId.tsx index 2899cc6..b48f32e 100644 --- a/app/routes/leagues/$leagueId.draft-queue.$seasonId.tsx +++ b/app/routes/leagues/$leagueId.draft-queue.$seasonId.tsx @@ -13,7 +13,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import type { Route } from "./+types/$leagueId.draft-queue.$seasonId"; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { - return [{ title: `Pre-Draft Rankings — ${data?.season?.league?.name ?? "League"} - Brackt` }]; + return [{ title: `Set Pre-Draft Queue — ${data?.season?.league?.name ?? "League"} - Brackt` }]; } export async function loader(args: Route.LoaderArgs) { @@ -41,7 +41,7 @@ export async function loader(args: Route.LoaderArgs) { } if (!userId) { - throw new Response("You must be logged in to set pre-draft rankings", { status: 401 }); + throw new Response("You must be logged in to set pre-draft queue", { status: 401 }); } const userTeam = season.teams.find((t) => t.ownerId === userId); @@ -64,7 +64,7 @@ export async function loader(args: Route.LoaderArgs) { type QueueItem = { id: string; participantId: string }; -export default function PreDraftRankings() { +export default function SetPreDraftQueue() { const { season, userTeam, availableParticipants, userQueue } = useLoaderData(); const { leagueId } = useParams<{ leagueId: string }>(); @@ -79,6 +79,9 @@ export default function PreDraftRankings() { // Always-current ref so reorder callback stays stable (no localQueue dependency) const queueRef = useRef(localQueue); + // Track in-flight removes to prevent add racing ahead of a pending remove + const pendingRemovesRef = useRef(new Set()); + useEffect(() => { queueRef.current = localQueue; }, [localQueue]); @@ -91,6 +94,10 @@ export default function PreDraftRankings() { const handleAdd = useCallback( async (participantId: string) => { if (queuedParticipantIds.has(participantId)) return; + if (pendingRemovesRef.current.has(participantId)) { + toast("Player is being removed — try again in a moment"); + return; + } const tempId = `temp-${Date.now()}-${participantId}`; setLocalQueue((prev) => [...prev, { id: tempId, participantId }]); @@ -118,7 +125,15 @@ export default function PreDraftRankings() { const handleRemove = useCallback( async (queueId: string) => { - setLocalQueue((prev) => prev.filter((item) => item.id !== queueId)); + const item = queueRef.current.find((q) => q.id === queueId); + const participantId = item?.participantId; + + setLocalQueue((prev) => prev.filter((q) => q.id !== queueId)); + + // Temp IDs haven't been written to the DB yet — nothing to delete server-side. + if (queueId.startsWith("temp-")) return; + + if (participantId) pendingRemovesRef.current.add(participantId); const formData = new FormData(); formData.append("queueId", queueId); @@ -134,6 +149,8 @@ export default function PreDraftRankings() { } catch { toast.error("Network error — failed to remove from rankings"); revalidate(); + } finally { + if (participantId) pendingRemovesRef.current.delete(participantId); } }, [userTeam.id, revalidate] @@ -194,11 +211,6 @@ export default function PreDraftRankings() {

{p.name}

{p.sport.name}

- {p.vorpValue !== null && p.vorpValue !== undefined && ( - - {Number(p.vorpValue).toFixed(1)} - - )} {inQueue ? (