Show why a participant is ineligible to draft
Closes #72 Upgrades ineligibleReasons from Record<string, string> to a structured {code, message} type so the UI can render the human-readable reason inline below the Ineligible badge in both AvailableParticipantsSection and ParticipantSelectionDialog. API routes surface the message in 400 responses unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5b03b22267
commit
26efcedb0a
10 changed files with 214 additions and 40 deletions
|
|
@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
|||
import { render, screen, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { AvailableParticipantsSection } from "~/components/draft/AvailableParticipantsSection";
|
||||
import type { DraftIneligibilityReason } from "~/lib/draft-eligibility";
|
||||
|
||||
vi.mock("@tanstack/react-virtual", () => ({
|
||||
useVirtualizer: ({
|
||||
|
|
@ -67,6 +68,13 @@ const defaultProps = {
|
|||
// (CSS breakpoints are not applied). Click the first one to open the Sheet.
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
function createReason(
|
||||
code: DraftIneligibilityReason["code"],
|
||||
message: string
|
||||
): DraftIneligibilityReason {
|
||||
return { code, message };
|
||||
}
|
||||
|
||||
function clickFirstSportFilterTrigger(name: string | RegExp) {
|
||||
return user.click(screen.getAllByRole("button", { name })[0]);
|
||||
}
|
||||
|
|
@ -506,4 +514,43 @@ describe("AvailableParticipantsSection", () => {
|
|||
expect(onShowOnlyWatchedChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ineligible reasons", () => {
|
||||
it("renders inline out-of-flex-spots copy for ineligible participants", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
hasTeam={true}
|
||||
canPick={true}
|
||||
eligibility={{
|
||||
eligibleSportIds: new Set(["s2"]),
|
||||
ineligibleReasons: {
|
||||
s1: createReason("no_flex_spots", "Out of flex spots (2/2)"),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getAllByText("Out of flex spots (2/2)").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByTitle("Out of flex spots (2/2)").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("renders inline no-extras copy for ineligible participants", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
hasTeam={true}
|
||||
canPick={true}
|
||||
eligibility={{
|
||||
eligibleSportIds: new Set(["s2"]),
|
||||
ineligibleReasons: {
|
||||
s1: createReason("no_extra_participants", "Not enough NFL participants for remaining teams"),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getAllByText("Not enough NFL participants for remaining teams").length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
59
app/components/__tests__/ParticipantSelectionDialog.test.tsx
Normal file
59
app/components/__tests__/ParticipantSelectionDialog.test.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { ParticipantSelectionDialog } from "~/components/draft/ParticipantSelectionDialog";
|
||||
|
||||
const defaultProps = {
|
||||
open: true,
|
||||
onOpenChange: vi.fn(),
|
||||
title: "Force pick",
|
||||
description: "Choose a participant",
|
||||
participants: [
|
||||
{ id: "1", name: "Player A", sport: { id: "s1", name: "NFL" } },
|
||||
{ id: "2", name: "Player B", sport: { id: "s2", name: "NBA" } },
|
||||
],
|
||||
draftedParticipantIds: new Set<string>(),
|
||||
eligibility: null,
|
||||
uniqueSports: ["NFL", "NBA"],
|
||||
onSelect: vi.fn(),
|
||||
};
|
||||
|
||||
describe("ParticipantSelectionDialog", () => {
|
||||
it("renders inline out-of-flex-spots reason and disables drafting", () => {
|
||||
render(
|
||||
<ParticipantSelectionDialog
|
||||
{...defaultProps}
|
||||
eligibility={{
|
||||
eligibleSportIds: new Set(["s2"]),
|
||||
ineligibleReasons: {
|
||||
s1: {
|
||||
code: "no_flex_spots",
|
||||
message: "Out of flex spots (2/2)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Out of flex spots (2/2)")).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("button", { name: "Draft" })[0]).toBeDisabled();
|
||||
});
|
||||
|
||||
it("renders inline no-extras reason for blocked flex picks", () => {
|
||||
render(
|
||||
<ParticipantSelectionDialog
|
||||
{...defaultProps}
|
||||
eligibility={{
|
||||
eligibleSportIds: new Set(["s2"]),
|
||||
ineligibleReasons: {
|
||||
s1: {
|
||||
code: "no_extra_participants",
|
||||
message: "Not enough NFL participants for remaining teams",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Not enough NFL participants for remaining teams")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@ import { Button } from "~/components/ui/button";
|
|||
import { Badge } from "~/components/ui/badge";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import { MiniDraftGrid, type MiniDraftGridProps } from "~/components/draft/MiniDraftGrid";
|
||||
import type { DraftIneligibilityReason } from "~/lib/draft-eligibility";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
|
|
@ -29,7 +30,7 @@ function getParticipantState(
|
|||
queueMap: Map<string, string>,
|
||||
eligibility: {
|
||||
eligibleSportIds: Set<string>;
|
||||
ineligibleReasons: Record<string, string>;
|
||||
ineligibleReasons: Record<string, DraftIneligibilityReason>;
|
||||
} | null,
|
||||
watchedParticipantIds: Set<string>
|
||||
) {
|
||||
|
|
@ -38,7 +39,7 @@ function getParticipantState(
|
|||
const isEligible = eligibility
|
||||
? eligibility.eligibleSportIds.has(participant.sport.id)
|
||||
: true;
|
||||
const ineligibleReason = eligibility?.ineligibleReasons[participant.sport.id];
|
||||
const ineligibleReason = eligibility?.ineligibleReasons[participant.sport.id] ?? null;
|
||||
const isWatched = watchedParticipantIds.has(participant.id);
|
||||
return { isDrafted, isInQueue, isEligible, ineligibleReason, isWatched };
|
||||
}
|
||||
|
|
@ -151,7 +152,7 @@ interface AvailableParticipantsSectionProps {
|
|||
queue: Array<{ id: string; participantId: string }>;
|
||||
eligibility: {
|
||||
eligibleSportIds: Set<string>;
|
||||
ineligibleReasons: Record<string, string>;
|
||||
ineligibleReasons: Record<string, DraftIneligibilityReason>;
|
||||
} | null;
|
||||
canPick: boolean;
|
||||
hasTeam: boolean;
|
||||
|
|
@ -547,11 +548,16 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
<Badge
|
||||
variant="destructive"
|
||||
className="text-xs"
|
||||
title={ineligibleReason}
|
||||
title={ineligibleReason?.message}
|
||||
>
|
||||
Ineligible
|
||||
</Badge>
|
||||
)}
|
||||
{!isDrafted && !isEligible && ineligibleReason && (
|
||||
<p className="text-xs text-destructive/90">
|
||||
{ineligibleReason.message}
|
||||
</p>
|
||||
)}
|
||||
{isInQueue && !isDrafted && isEligible && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
Queued
|
||||
|
|
@ -578,7 +584,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
size="sm"
|
||||
className="min-h-[44px] min-w-[44px]"
|
||||
onClick={() => onAddToQueue(participant.id)}
|
||||
title={!isEligible ? ineligibleReason : "Add to queue"}
|
||||
title={!isEligible ? ineligibleReason?.message : "Add to queue"}
|
||||
disabled={!isEligible}
|
||||
>
|
||||
<ListPlus className="h-4 w-4" />
|
||||
|
|
@ -607,7 +613,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
!canPick
|
||||
? "Not your turn"
|
||||
: !isEligible
|
||||
? ineligibleReason
|
||||
? ineligibleReason?.message
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
|
|
@ -631,7 +637,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
: "hover:bg-muted/50"
|
||||
}`}
|
||||
title={
|
||||
!isEligible && !isDrafted ? ineligibleReason : undefined
|
||||
!isEligible && !isDrafted ? ineligibleReason?.message : undefined
|
||||
}
|
||||
>
|
||||
<span className="text-center text-muted-foreground tabular-nums p-3 px-0">
|
||||
|
|
@ -659,7 +665,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
<Badge
|
||||
variant="destructive"
|
||||
className="text-xs"
|
||||
title={ineligibleReason}
|
||||
title={ineligibleReason?.message}
|
||||
>
|
||||
Ineligible
|
||||
</Badge>
|
||||
|
|
@ -669,6 +675,11 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
Queued
|
||||
</Badge>
|
||||
)}
|
||||
{!isDrafted && !isEligible && ineligibleReason && (
|
||||
<p className="mt-1 text-xs text-destructive/90">
|
||||
{ineligibleReason.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{hasTeam && (
|
||||
|
|
@ -693,7 +704,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
onClick={() => onAddToQueue(participant.id)}
|
||||
title={
|
||||
!isEligible
|
||||
? ineligibleReason
|
||||
? ineligibleReason?.message
|
||||
: "Add to queue"
|
||||
}
|
||||
disabled={!isEligible}
|
||||
|
|
@ -722,7 +733,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
|
|||
!canPick
|
||||
? "Not your turn"
|
||||
: !isEligible
|
||||
? ineligibleReason
|
||||
? ineligibleReason?.message
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect, useMemo } from "react";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import type { DraftIneligibilityReason } from "~/lib/draft-eligibility";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -25,7 +26,7 @@ interface ParticipantSelectionDialogProps {
|
|||
allowParticipantId?: string;
|
||||
eligibility: {
|
||||
eligibleSportIds: Set<string>;
|
||||
ineligibleReasons: Record<string, string>;
|
||||
ineligibleReasons: Record<string, DraftIneligibilityReason>;
|
||||
} | null;
|
||||
/** Marks a row with "Current" badge and "Keep" button label */
|
||||
currentParticipantId?: string;
|
||||
|
|
@ -121,7 +122,7 @@ export function ParticipantSelectionDialog({
|
|||
? eligibility.eligibleSportIds.has(participant.sport.id)
|
||||
: true;
|
||||
const ineligibleReason =
|
||||
eligibility?.ineligibleReasons[participant.sport.id];
|
||||
eligibility?.ineligibleReasons[participant.sport.id] ?? null;
|
||||
|
||||
return (
|
||||
<tr
|
||||
|
|
@ -131,7 +132,7 @@ export function ParticipantSelectionDialog({
|
|||
? "hover:bg-muted/50"
|
||||
: "bg-destructive/10 opacity-60"
|
||||
}`}
|
||||
title={!isEligible ? ineligibleReason : undefined}
|
||||
title={!isEligible ? ineligibleReason?.message : undefined}
|
||||
>
|
||||
<td className="p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -149,12 +150,17 @@ export function ParticipantSelectionDialog({
|
|||
<Badge
|
||||
variant="destructive"
|
||||
className="text-xs"
|
||||
title={ineligibleReason}
|
||||
title={ineligibleReason?.message}
|
||||
>
|
||||
Ineligible
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{!isEligible && ineligibleReason && (
|
||||
<p className="mt-1 text-xs text-destructive/90">
|
||||
{ineligibleReason.message}
|
||||
</p>
|
||||
)}
|
||||
</td>
|
||||
<td className="p-3 text-muted-foreground">
|
||||
{participant.sport.name}
|
||||
|
|
@ -164,7 +170,7 @@ export function ParticipantSelectionDialog({
|
|||
size="sm"
|
||||
onClick={() => onSelect(participant.id)}
|
||||
disabled={!isEligible}
|
||||
title={!isEligible ? ineligibleReason : undefined}
|
||||
title={!isEligible ? ineligibleReason?.message : undefined}
|
||||
>
|
||||
{isCurrentPick ? "Keep" : "Draft"}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -117,8 +117,14 @@ describe("calculateDraftEligibility", () => {
|
|||
expect(eligibility.flexPicksAvailable).toBe(2);
|
||||
// Can only draft from NHL (not yet drafted)
|
||||
expect(eligibility.eligibleSportIds).toEqual(new Set(["nhl"]));
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nba"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toMatchObject({
|
||||
code: "no_flex_spots",
|
||||
message: "Out of flex spots (2/2)",
|
||||
});
|
||||
expect(eligibility.ineligibleReasons["nba"]).toMatchObject({
|
||||
code: "no_flex_spots",
|
||||
message: "Out of flex spots (2/2)",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -181,9 +187,10 @@ describe("calculateDraftEligibility", () => {
|
|||
// 2 NFL participants left, 2 teams still need NFL
|
||||
// For flex pick: 2 > 2 is FALSE, so blocked
|
||||
expect(eligibility.eligibleSportIds).not.toContain("nfl");
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toContain(
|
||||
"Would prevent other teams from getting NFL"
|
||||
);
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toMatchObject({
|
||||
code: "no_extra_participants",
|
||||
message: "Not enough NFL participants for remaining teams",
|
||||
});
|
||||
expect(eligibility.sportAvailability["nfl"].canDraftAsFlex).toBe(false);
|
||||
});
|
||||
|
||||
|
|
@ -286,9 +293,9 @@ describe("calculateDraftEligibility", () => {
|
|||
// Even though NFL has 3 > 4 (global constraint would block flex),
|
||||
// team can't draft NFL because flex picks are exhausted
|
||||
expect(eligibility.eligibleSportIds).toEqual(new Set([]));
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nba"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nhl"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nfl"].code).toBe("no_flex_spots");
|
||||
expect(eligibility.ineligibleReasons["nba"].code).toBe("no_flex_spots");
|
||||
expect(eligibility.ineligibleReasons["nhl"].code).toBe("no_flex_spots");
|
||||
});
|
||||
|
||||
it("allows pick when both constraints satisfied", () => {
|
||||
|
|
@ -401,9 +408,10 @@ describe("calculateDraftEligibility", () => {
|
|||
// 3 participants, 5 teams need it, 3 < 5
|
||||
expect(eligibility.sportAvailability["nfl"].canDraftAsFirst).toBe(false);
|
||||
expect(eligibility.eligibleSportIds).not.toContain("nfl");
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toContain(
|
||||
"Only 3 participants left for 5 teams"
|
||||
);
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toMatchObject({
|
||||
code: "unavailable",
|
||||
message: "Only 3 NFL participants left for 5 teams",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -580,8 +588,8 @@ describe("calculateDraftEligibility", () => {
|
|||
|
||||
// Can only draft NHL (not yet picked)
|
||||
expect(eligibility.eligibleSportIds).toEqual(new Set(["nhl"]));
|
||||
expect(eligibility.ineligibleReasons["nfl"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nba"]).toContain("All flex picks used");
|
||||
expect(eligibility.ineligibleReasons["nfl"].code).toBe("no_flex_spots");
|
||||
expect(eligibility.ineligibleReasons["nba"].code).toBe("no_flex_spots");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,20 @@ export interface SportAvailability {
|
|||
canDraftAsFlex: boolean; // undraftedCount > teamsStillNeeding
|
||||
}
|
||||
|
||||
export type DraftIneligibilityCode =
|
||||
| "no_flex_spots"
|
||||
| "no_extra_participants"
|
||||
| "unavailable";
|
||||
|
||||
export interface DraftIneligibilityReason {
|
||||
code: DraftIneligibilityCode;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface DraftEligibility {
|
||||
teamId: string;
|
||||
eligibleSportIds: Set<string>;
|
||||
ineligibleReasons: Record<string, string>; // sportId -> reason
|
||||
ineligibleReasons: Record<string, DraftIneligibilityReason>; // sportId -> reason
|
||||
flexPicksUsed: number;
|
||||
flexPicksAvailable: number;
|
||||
picksBySport: Record<string, number>;
|
||||
|
|
@ -56,6 +66,34 @@ interface TeamData {
|
|||
id: string;
|
||||
}
|
||||
|
||||
function getFlexSpotsMessage(
|
||||
flexPicksUsed: number,
|
||||
flexPicksAvailable: number
|
||||
): DraftIneligibilityReason {
|
||||
return {
|
||||
code: "no_flex_spots",
|
||||
message: `Out of flex spots (${flexPicksUsed}/${flexPicksAvailable})`,
|
||||
};
|
||||
}
|
||||
|
||||
function getNoExtrasMessage(sportName: string): DraftIneligibilityReason {
|
||||
return {
|
||||
code: "no_extra_participants",
|
||||
message: `Not enough ${sportName} participants for remaining teams`,
|
||||
};
|
||||
}
|
||||
|
||||
function getUnavailableMessage(
|
||||
sportName: string,
|
||||
undraftedCount: number,
|
||||
teamsStillNeeding: number
|
||||
): DraftIneligibilityReason {
|
||||
return {
|
||||
code: "unavailable",
|
||||
message: `Only ${undraftedCount} ${sportName} participant${undraftedCount === 1 ? "" : "s"} left for ${teamsStillNeeding} team${teamsStillNeeding === 1 ? "" : "s"}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate which sports a team is eligible to draft from based on:
|
||||
* - Team's flex pick usage
|
||||
|
|
@ -132,7 +170,7 @@ export function calculateDraftEligibility(
|
|||
|
||||
// Step 3: Combine constraints to determine eligibility
|
||||
const eligibleSportIds = new Set<string>();
|
||||
const ineligibleReasons: Record<string, string> = {};
|
||||
const ineligibleReasons: Record<string, DraftIneligibilityReason> = {};
|
||||
|
||||
for (const sport of seasonSports) {
|
||||
const sportId = sport.id;
|
||||
|
|
@ -140,21 +178,21 @@ export function calculateDraftEligibility(
|
|||
const currentTeamHasSport = (picksBySport[sportId] || 0) > 0;
|
||||
|
||||
let isEligible = false;
|
||||
let reason = "";
|
||||
let reason: DraftIneligibilityReason | null = null;
|
||||
|
||||
if (!currentTeamHasSport) {
|
||||
// Team hasn't drafted from this sport yet (would be first pick)
|
||||
if (availability.canDraftAsFirst) {
|
||||
isEligible = true;
|
||||
} else {
|
||||
reason = `Only ${availability.undraftedCount} participants left for ${availability.teamsStillNeeding} teams needing ${sport.name}`;
|
||||
reason = getUnavailableMessage(sport.name, availability.undraftedCount, availability.teamsStillNeeding);
|
||||
}
|
||||
} else {
|
||||
// Team has drafted from this sport (would be flex pick)
|
||||
if (!hasFlexesRemaining) {
|
||||
reason = `All flex picks used (${flexPicksUsed}/${flexPicksAvailable})`;
|
||||
reason = getFlexSpotsMessage(flexPicksUsed, flexPicksAvailable);
|
||||
} else if (!availability.canDraftAsFlex) {
|
||||
reason = `Would prevent other teams from getting ${sport.name} (${availability.undraftedCount} left for ${availability.teamsStillNeeding} teams)`;
|
||||
reason = getNoExtrasMessage(sport.name);
|
||||
} else {
|
||||
isEligible = true;
|
||||
}
|
||||
|
|
@ -162,7 +200,7 @@ export function calculateDraftEligibility(
|
|||
|
||||
if (isEligible) {
|
||||
eligibleSportIds.add(sportId);
|
||||
} else {
|
||||
} else if (reason) {
|
||||
ineligibleReasons[sportId] = reason;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,14 +288,19 @@ describe("draft.force-manual-pick action", () => {
|
|||
const { calculateDraftEligibility } = await import("~/lib/draft-eligibility");
|
||||
vi.mocked(calculateDraftEligibility).mockReturnValue({
|
||||
eligibleSportIds: new Set(),
|
||||
ineligibleReasons: { [SPORT_ID]: "Sport limit reached" },
|
||||
ineligibleReasons: {
|
||||
[SPORT_ID]: {
|
||||
code: "no_extra_participants",
|
||||
message: "Not enough NFL participants for remaining teams",
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
|
||||
const response = await action({ request: defaultPickRequest(), params: {}, context: ctx });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const data = await response.json();
|
||||
expect(data.error).toMatch(/sport limit reached/i);
|
||||
expect(data.error).toMatch(/not enough nfl participants/i);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export async function action(args: ActionFunctionArgs) {
|
|||
|
||||
const sportId = participant.sportsSeason.sport.id;
|
||||
if (!eligibility.eligibleSportIds.has(sportId)) {
|
||||
const reason = eligibility.ineligibleReasons[sportId] || "Cannot draft from this sport";
|
||||
const reason = eligibility.ineligibleReasons[sportId]?.message || "Cannot draft from this sport";
|
||||
return Response.json({ error: reason }, { status: 400 });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export async function action(args: ActionFunctionArgs) {
|
|||
|
||||
const sportId = participant.sportsSeason.sport.id;
|
||||
if (!eligibility.eligibleSportIds.has(sportId)) {
|
||||
const reason = eligibility.ineligibleReasons[sportId] || "Cannot draft from this sport";
|
||||
const reason = eligibility.ineligibleReasons[sportId]?.message || "Cannot draft from this sport";
|
||||
return Response.json({ error: reason }, { status: 400 });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export async function action(args: ActionFunctionArgs) {
|
|||
|
||||
const sportId = participant.sportsSeason.sport.id;
|
||||
if (!eligibility.eligibleSportIds.has(sportId)) {
|
||||
const reason = eligibility.ineligibleReasons[sportId] || "Cannot draft from this sport";
|
||||
const reason = eligibility.ineligibleReasons[sportId]?.message || "Cannot draft from this sport";
|
||||
return Response.json({ error: reason }, { status: 400 });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue