feat: enhance autodraft functionality with detailed settings and commissioner controls (#61)
This commit is contained in:
parent
d53bd271c6
commit
40167cfa5e
7 changed files with 224 additions and 46 deletions
|
|
@ -82,6 +82,14 @@ function toAutodraftState(
|
||||||
return queueOnly ? "all_queue" : "all_picks";
|
return queueOnly ? "all_queue" : "all_picks";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAutodraftLabel(
|
||||||
|
isEnabled: boolean,
|
||||||
|
mode: AutodraftMode,
|
||||||
|
queueOnly: boolean
|
||||||
|
): string {
|
||||||
|
return OPTIONS[toAutodraftState(isEnabled, mode, queueOnly)].label;
|
||||||
|
}
|
||||||
|
|
||||||
interface AutodraftSettingsProps {
|
interface AutodraftSettingsProps {
|
||||||
seasonId: string;
|
seasonId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,11 @@ interface DraftGridSectionProps {
|
||||||
>;
|
>;
|
||||||
currentPick: number;
|
currentPick: number;
|
||||||
teamTimers: Record<string, number | undefined>;
|
teamTimers: Record<string, number | undefined>;
|
||||||
autodraftStatus: Record<string, boolean>;
|
autodraftStatus: Record<string, { isEnabled: boolean; mode: "next_pick" | "while_on"; queueOnly: boolean }>;
|
||||||
connectedTeams: Set<string>;
|
connectedTeams: Set<string>;
|
||||||
isCommissioner: boolean;
|
isCommissioner: boolean;
|
||||||
onAdjustTimeBankOpen?: (teamId: string) => void;
|
onAdjustTimeBankOpen?: (teamId: string) => void;
|
||||||
|
onSetAutodraftOpen?: (teamId: string) => void;
|
||||||
onForceAutopick: (pickNumber: number, teamId: string) => void;
|
onForceAutopick: (pickNumber: number, teamId: string) => void;
|
||||||
onForceManualPickOpen: (pickNumber: number, teamId: string) => void;
|
onForceManualPickOpen: (pickNumber: number, teamId: string) => void;
|
||||||
onReplacePick?: (pickNumber: number, teamId: string) => void;
|
onReplacePick?: (pickNumber: number, teamId: string) => void;
|
||||||
|
|
@ -67,6 +68,7 @@ export const DraftGridSection = memo(function DraftGridSection({
|
||||||
connectedTeams,
|
connectedTeams,
|
||||||
isCommissioner,
|
isCommissioner,
|
||||||
onAdjustTimeBankOpen,
|
onAdjustTimeBankOpen,
|
||||||
|
onSetAutodraftOpen,
|
||||||
onForceAutopick,
|
onForceAutopick,
|
||||||
onForceManualPickOpen,
|
onForceManualPickOpen,
|
||||||
onReplacePick,
|
onReplacePick,
|
||||||
|
|
@ -91,7 +93,7 @@ export const DraftGridSection = memo(function DraftGridSection({
|
||||||
<div className="w-8 flex-shrink-0 sticky left-0 z-[6] bg-background/95" />
|
<div className="w-8 flex-shrink-0 sticky left-0 z-[6] bg-background/95" />
|
||||||
{draftSlots.map((slot) => {
|
{draftSlots.map((slot) => {
|
||||||
const teamTime = teamTimers[slot.team.id];
|
const teamTime = teamTimers[slot.team.id];
|
||||||
const isAutodraft = autodraftStatus[slot.team.id] || false;
|
const isAutodraft = autodraftStatus[slot.team.id]?.isEnabled || false;
|
||||||
const isConnected = connectedTeams.has(slot.team.id);
|
const isConnected = connectedTeams.has(slot.team.id);
|
||||||
|
|
||||||
const headerInner = (
|
const headerInner = (
|
||||||
|
|
@ -117,7 +119,7 @@ export const DraftGridSection = memo(function DraftGridSection({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isCommissioner && onAdjustTimeBankOpen && (
|
{isCommissioner && (onAdjustTimeBankOpen || onSetAutodraftOpen) && (
|
||||||
<button
|
<button
|
||||||
className="absolute top-0 right-0 md:hidden p-1 min-w-[32px] min-h-[32px] flex items-center justify-center rounded hover:bg-muted"
|
className="absolute top-0 right-0 md:hidden p-1 min-w-[32px] min-h-[32px] flex items-center justify-center rounded hover:bg-muted"
|
||||||
onClick={() => setMobileSheet({ type: "team", teamId: slot.team.id })}
|
onClick={() => setMobileSheet({ type: "team", teamId: slot.team.id })}
|
||||||
|
|
@ -128,7 +130,7 @@ export const DraftGridSection = memo(function DraftGridSection({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isCommissioner && onAdjustTimeBankOpen) {
|
if (isCommissioner && (onAdjustTimeBankOpen || onSetAutodraftOpen)) {
|
||||||
return (
|
return (
|
||||||
<ContextMenu key={slot.id}>
|
<ContextMenu key={slot.id}>
|
||||||
<ContextMenuTrigger asChild>
|
<ContextMenuTrigger asChild>
|
||||||
|
|
@ -137,11 +139,16 @@ export const DraftGridSection = memo(function DraftGridSection({
|
||||||
</div>
|
</div>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
{onAdjustTimeBankOpen && (
|
||||||
onClick={() => onAdjustTimeBankOpen(slot.team.id)}
|
<ContextMenuItem onClick={() => onAdjustTimeBankOpen(slot.team.id)}>
|
||||||
>
|
Adjust Time Bank...
|
||||||
Adjust Time Bank...
|
</ContextMenuItem>
|
||||||
</ContextMenuItem>
|
)}
|
||||||
|
{onSetAutodraftOpen && (
|
||||||
|
<ContextMenuItem onClick={() => onSetAutodraftOpen(slot.team.id)}>
|
||||||
|
Set Autodraft...
|
||||||
|
</ContextMenuItem>
|
||||||
|
)}
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
|
|
@ -316,15 +323,30 @@ export const DraftGridSection = memo(function DraftGridSection({
|
||||||
<SheetTitle>Commissioner Actions</SheetTitle>
|
<SheetTitle>Commissioner Actions</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="flex flex-col gap-3 p-4">
|
<div className="flex flex-col gap-3 p-4">
|
||||||
{mobileSheet?.type === "team" && onAdjustTimeBankOpen && (
|
{mobileSheet?.type === "team" && (
|
||||||
<Button
|
<>
|
||||||
onClick={() => {
|
{onAdjustTimeBankOpen && (
|
||||||
onAdjustTimeBankOpen(mobileSheet.teamId);
|
<Button
|
||||||
setMobileSheet(null);
|
onClick={() => {
|
||||||
}}
|
onAdjustTimeBankOpen(mobileSheet.teamId);
|
||||||
>
|
setMobileSheet(null);
|
||||||
Adjust Time Bank...
|
}}
|
||||||
</Button>
|
>
|
||||||
|
Adjust Time Bank...
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{onSetAutodraftOpen && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
onSetAutodraftOpen(mobileSheet.teamId);
|
||||||
|
setMobileSheet(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Set Autodraft...
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{mobileSheet?.type === "current-cell" && (
|
{mobileSheet?.type === "current-cell" && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, asc } from "drizzle-orm";
|
||||||
import { getSocketIO } from "~/server/socket";
|
import { getSocketIO } from "~/server/socket";
|
||||||
|
import { isUserAdminByClerkId } from "~/models/user";
|
||||||
|
import { getTeamForPick } from "~/lib/draft-order";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -26,15 +28,38 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
||||||
const db = database();
|
const db = database();
|
||||||
|
|
||||||
// Verify user owns this team
|
// Fetch team and season in parallel
|
||||||
const team = await db.query.teams.findFirst({
|
const [team, season] = await Promise.all([
|
||||||
where: eq(schema.teams.id, teamId),
|
db.query.teams.findFirst({ where: eq(schema.teams.id, teamId) }),
|
||||||
});
|
db.query.seasons.findFirst({ where: eq(schema.seasons.id, seasonId) }),
|
||||||
|
]);
|
||||||
|
|
||||||
if (!team || team.ownerId !== userId) {
|
if (!team || !season) {
|
||||||
return Response.json({ error: "Unauthorized" }, { status: 403 });
|
return Response.json({ error: "Team or season not found" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isOwner = team.ownerId === userId;
|
||||||
|
|
||||||
|
let isActingAsCommissioner = false;
|
||||||
|
if (!isOwner) {
|
||||||
|
const [commissioner, userIsAdmin] = await Promise.all([
|
||||||
|
db.query.commissioners.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(schema.commissioners.leagueId, season.leagueId),
|
||||||
|
eq(schema.commissioners.userId, userId)
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
isUserAdminByClerkId(userId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!commissioner && !userIsAdmin) {
|
||||||
|
return Response.json({ error: "Unauthorized" }, { status: 403 });
|
||||||
|
}
|
||||||
|
isActingAsCommissioner = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedSource: "commissioner" | "user" = isActingAsCommissioner ? "commissioner" : "user";
|
||||||
|
|
||||||
// Check if autodraft settings exist
|
// Check if autodraft settings exist
|
||||||
const existingSettings = await db.query.autodraftSettings.findFirst({
|
const existingSettings = await db.query.autodraftSettings.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
|
|
@ -77,7 +102,38 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
isEnabled,
|
isEnabled,
|
||||||
mode,
|
mode,
|
||||||
queueOnly,
|
queueOnly,
|
||||||
|
source: resolvedSource,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If commissioner enables autodraft for the team currently on the clock, fire immediately
|
||||||
|
if (isEnabled && isActingAsCommissioner) {
|
||||||
|
import("~/models/draft-utils").then(async ({ executeAutoPick }) => {
|
||||||
|
const freshSeason = await db.query.seasons.findFirst({
|
||||||
|
where: eq(schema.seasons.id, seasonId),
|
||||||
|
});
|
||||||
|
if (!freshSeason || freshSeason.status !== "draft") return;
|
||||||
|
|
||||||
|
const currentPickNumber = freshSeason.currentPickNumber ?? 1;
|
||||||
|
const draftSlots = await db.query.draftSlots.findMany({
|
||||||
|
where: eq(schema.draftSlots.seasonId, seasonId),
|
||||||
|
orderBy: asc(schema.draftSlots.draftOrder),
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentSlot = getTeamForPick(currentPickNumber, draftSlots);
|
||||||
|
if (!currentSlot || currentSlot.teamId !== teamId) return;
|
||||||
|
|
||||||
|
// Team is on the clock — trigger autopick
|
||||||
|
await executeAutoPick({
|
||||||
|
seasonId,
|
||||||
|
teamId,
|
||||||
|
pickNumber: currentPickNumber,
|
||||||
|
triggeredBy: "commissioner",
|
||||||
|
db,
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error("[AutodraftUpdate] Mid-turn autopick failed:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Response.json({ success: true, settings });
|
return Response.json({ success: true, settings });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import { getTeamForPick } from "~/lib/draft-order";
|
||||||
import { useDraftNotifications } from "~/hooks/useDraftNotifications";
|
import { useDraftNotifications } from "~/hooks/useDraftNotifications";
|
||||||
import { useMediaQuery } from "~/hooks/useMediaQuery";
|
import { useMediaQuery } from "~/hooks/useMediaQuery";
|
||||||
import { NotificationSettings } from "~/components/NotificationSettings";
|
import { NotificationSettings } from "~/components/NotificationSettings";
|
||||||
|
import { AutodraftSettings, getAutodraftLabel } from "~/components/AutodraftSettings";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { formatClockTime, getTimerColorClass } from "~/lib/draft-timer";
|
import { formatClockTime, getTimerColorClass } from "~/lib/draft-timer";
|
||||||
import { Users, LayoutGrid, ListChecks, Settings, ListOrdered } from "lucide-react";
|
import { Users, LayoutGrid, ListChecks, Settings, ListOrdered } from "lucide-react";
|
||||||
|
|
@ -35,6 +36,12 @@ import type { Route } from "./+types/$leagueId.draft.$seasonId";
|
||||||
|
|
||||||
type QueueItem = typeof schema.draftQueue.$inferSelect;
|
type QueueItem = typeof schema.draftQueue.$inferSelect;
|
||||||
|
|
||||||
|
type AutodraftStatusEntry = {
|
||||||
|
isEnabled: boolean;
|
||||||
|
mode: "next_pick" | "while_on";
|
||||||
|
queueOnly: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const MOBILE_TABS_BASE = [
|
const MOBILE_TABS_BASE = [
|
||||||
{ id: "available" as const, label: "Available", Icon: Users },
|
{ id: "available" as const, label: "Available", Icon: Users },
|
||||||
{ id: "board" as const, label: "Board", Icon: LayoutGrid },
|
{ id: "board" as const, label: "Board", Icon: LayoutGrid },
|
||||||
|
|
@ -492,9 +499,13 @@ export default function DraftRoom() {
|
||||||
// Sync autodraft settings — teams may have toggled autodraft while
|
// Sync autodraft settings — teams may have toggled autodraft while
|
||||||
// we were away.
|
// we were away.
|
||||||
setAutodraftStatus(() => {
|
setAutodraftStatus(() => {
|
||||||
const status: Record<string, boolean> = {};
|
const status: Record<string, AutodraftStatusEntry> = {};
|
||||||
autodraftSettings.forEach((setting: any) => {
|
autodraftSettings.forEach((setting: any) => {
|
||||||
status[setting.teamId] = setting.isEnabled;
|
status[setting.teamId] = {
|
||||||
|
isEnabled: setting.isEnabled,
|
||||||
|
mode: setting.mode,
|
||||||
|
queueOnly: setting.queueOnly,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
|
|
@ -528,10 +539,14 @@ export default function DraftRoom() {
|
||||||
const isMobile = useMediaQuery("(max-width: 767px)");
|
const isMobile = useMediaQuery("(max-width: 767px)");
|
||||||
|
|
||||||
// Track autodraft status for all teams
|
// Track autodraft status for all teams
|
||||||
const [autodraftStatus, setAutodraftStatus] = useState<Record<string, boolean>>(() => {
|
const [autodraftStatus, setAutodraftStatus] = useState<Record<string, AutodraftStatusEntry>>(() => {
|
||||||
const status: Record<string, boolean> = {};
|
const status: Record<string, AutodraftStatusEntry> = {};
|
||||||
autodraftSettings.forEach((setting: any) => {
|
autodraftSettings.forEach((setting: any) => {
|
||||||
status[setting.teamId] = setting.isEnabled;
|
status[setting.teamId] = {
|
||||||
|
isEnabled: setting.isEnabled,
|
||||||
|
mode: setting.mode,
|
||||||
|
queueOnly: setting.queueOnly,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
|
|
@ -579,6 +594,8 @@ export default function DraftRoom() {
|
||||||
// Time bank adjustment dialog state
|
// Time bank adjustment dialog state
|
||||||
const [timeBankDialogOpen, setTimeBankDialogOpen] = useState(false);
|
const [timeBankDialogOpen, setTimeBankDialogOpen] = useState(false);
|
||||||
const [timeBankTeamId, setTimeBankTeamId] = useState<string | null>(null);
|
const [timeBankTeamId, setTimeBankTeamId] = useState<string | null>(null);
|
||||||
|
const [commissionerAutodraftDialogOpen, setCommissionerAutodraftDialogOpen] = useState(false);
|
||||||
|
const [commissionerAutodraftTeamId, setCommissionerAutodraftTeamId] = useState<string | null>(null);
|
||||||
const [timeBankAmount, setTimeBankAmount] = useState("1");
|
const [timeBankAmount, setTimeBankAmount] = useState("1");
|
||||||
const [timeBankUnit, setTimeBankUnit] = useState<"seconds" | "minutes" | "hours">("minutes");
|
const [timeBankUnit, setTimeBankUnit] = useState<"seconds" | "minutes" | "hours">("minutes");
|
||||||
const [timeBankDirection, setTimeBankDirection] = useState<"add" | "remove">("add");
|
const [timeBankDirection, setTimeBankDirection] = useState<"add" | "remove">("add");
|
||||||
|
|
@ -743,18 +760,32 @@ export default function DraftRoom() {
|
||||||
setIsDraftComplete(true);
|
setIsDraftComplete(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAutodraftUpdated = (data: { teamId: string; isEnabled: boolean; mode: "next_pick" | "while_on"; queueOnly: boolean }) => {
|
const handleAutodraftUpdated = (data: {
|
||||||
|
teamId: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
mode: "next_pick" | "while_on";
|
||||||
|
queueOnly: boolean;
|
||||||
|
source?: "commissioner" | "user";
|
||||||
|
}) => {
|
||||||
setAutodraftStatus((prev) => ({
|
setAutodraftStatus((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[data.teamId]: data.isEnabled,
|
[data.teamId]: { isEnabled: data.isEnabled, mode: data.mode, queueOnly: data.queueOnly },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Update user's local state if it's their team
|
// Update user's local state if it's their team
|
||||||
if (userTeam && data.teamId === userTeam.id) {
|
if (userTeam && data.teamId === userTeam.id) {
|
||||||
// Detect server-side auto-disable: was enabled with queueOnly, now disabled
|
if (data.source === "commissioner") {
|
||||||
const prev = userAutodraftRef.current;
|
if (!data.isEnabled) {
|
||||||
if (!data.isEnabled && prev.isEnabled && prev.queueOnly && prev.mode === "while_on") {
|
toast.info("Commissioner turned off your autodraft");
|
||||||
toast.info("Autodraft disabled — your queue is empty");
|
} else {
|
||||||
|
toast.info(`Commissioner changed your autodraft to "${getAutodraftLabel(data.isEnabled, data.mode, data.queueOnly)}"`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Detect server-side auto-disable: was enabled with queueOnly, now disabled
|
||||||
|
const prev = userAutodraftRef.current;
|
||||||
|
if (!data.isEnabled && prev.isEnabled && prev.queueOnly && prev.mode === "while_on") {
|
||||||
|
toast.info("Autodraft disabled — your queue is empty");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserAutodraft({
|
setUserAutodraft({
|
||||||
|
|
@ -1235,6 +1266,16 @@ export default function DraftRoom() {
|
||||||
setTimeBankDialogOpen(true);
|
setTimeBankDialogOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleSetAutodraftOpen = useCallback((teamId: string) => {
|
||||||
|
setCommissionerAutodraftTeamId(teamId);
|
||||||
|
setCommissionerAutodraftDialogOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCommissionerAutodraftUpdate = useCallback(() => {
|
||||||
|
setCommissionerAutodraftDialogOpen(false);
|
||||||
|
setCommissionerAutodraftTeamId(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleConfirmAdjustTimeBank = async () => {
|
const handleConfirmAdjustTimeBank = async () => {
|
||||||
if (!timeBankTeamId || isAdjustingTimeBank) return;
|
if (!timeBankTeamId || isAdjustingTimeBank) return;
|
||||||
|
|
||||||
|
|
@ -1452,6 +1493,7 @@ export default function DraftRoom() {
|
||||||
isCommissioner,
|
isCommissioner,
|
||||||
ownerMap,
|
ownerMap,
|
||||||
onAdjustTimeBankOpen: isCommissioner ? handleAdjustTimeBankOpen : undefined,
|
onAdjustTimeBankOpen: isCommissioner ? handleAdjustTimeBankOpen : undefined,
|
||||||
|
onSetAutodraftOpen: isCommissioner ? handleSetAutodraftOpen : undefined,
|
||||||
onForceAutopick: handleForceAutopick,
|
onForceAutopick: handleForceAutopick,
|
||||||
onForceManualPickOpen: handleForceManualPickOpen,
|
onForceManualPickOpen: handleForceManualPickOpen,
|
||||||
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
|
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
|
||||||
|
|
@ -1990,6 +2032,37 @@ export default function DraftRoom() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Commissioner Autodraft Override Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={commissionerAutodraftDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setCommissionerAutodraftDialogOpen(open);
|
||||||
|
if (!open) setCommissionerAutodraftTeamId(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Set Autodraft</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{commissionerAutodraftTeamId
|
||||||
|
? `Change autodraft for ${draftSlots.find((s) => s.team.id === commissionerAutodraftTeamId)?.team.name ?? "this team"}`
|
||||||
|
: "Set a team's autodraft settings"}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
{commissionerAutodraftTeamId && (
|
||||||
|
<AutodraftSettings
|
||||||
|
seasonId={season.id}
|
||||||
|
teamId={commissionerAutodraftTeamId}
|
||||||
|
isEnabled={autodraftStatus[commissionerAutodraftTeamId]?.isEnabled ?? false}
|
||||||
|
mode={autodraftStatus[commissionerAutodraftTeamId]?.mode ?? "next_pick"}
|
||||||
|
queueOnly={autodraftStatus[commissionerAutodraftTeamId]?.queueOnly ?? false}
|
||||||
|
isMyTurn={false}
|
||||||
|
onUpdate={handleCommissionerAutodraftUpdate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Connection Overlay - blocks interaction until socket connects */}
|
{/* Connection Overlay - blocks interaction until socket connects */}
|
||||||
<ConnectionOverlay
|
<ConnectionOverlay
|
||||||
isConnected={isConnected}
|
isConnected={isConnected}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ vi.mock("~/server/socket", () => ({
|
||||||
vi.mock("@clerk/react-router/server", () => ({
|
vi.mock("@clerk/react-router/server", () => ({
|
||||||
getAuth: vi.fn(),
|
getAuth: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
vi.mock("~/models/user", () => ({
|
||||||
|
isUserAdminByClerkId: vi.fn().mockResolvedValue(false),
|
||||||
|
}));
|
||||||
|
|
||||||
describe("Autodraft Settings API", () => {
|
describe("Autodraft Settings API", () => {
|
||||||
let mockDb: any;
|
let mockDb: any;
|
||||||
|
|
@ -39,6 +42,12 @@ describe("Autodraft Settings API", () => {
|
||||||
teams: {
|
teams: {
|
||||||
findFirst: vi.fn(),
|
findFirst: vi.fn(),
|
||||||
},
|
},
|
||||||
|
seasons: {
|
||||||
|
findFirst: vi.fn().mockResolvedValue({ id: "season-123", leagueId: "league-1" }),
|
||||||
|
},
|
||||||
|
commissioners: {
|
||||||
|
findFirst: vi.fn().mockResolvedValue(null),
|
||||||
|
},
|
||||||
autodraftSettings: {
|
autodraftSettings: {
|
||||||
findFirst: vi.fn(),
|
findFirst: vi.fn(),
|
||||||
},
|
},
|
||||||
|
|
@ -115,6 +124,7 @@ describe("Autodraft Settings API", () => {
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
mode: "next_pick",
|
mode: "next_pick",
|
||||||
queueOnly: false,
|
queueOnly: false,
|
||||||
|
source: "user",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -179,6 +189,7 @@ describe("Autodraft Settings API", () => {
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
mode: "while_on",
|
mode: "while_on",
|
||||||
queueOnly: false,
|
queueOnly: false,
|
||||||
|
source: "user",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -233,6 +244,7 @@ describe("Autodraft Settings API", () => {
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
mode: "next_pick",
|
mode: "next_pick",
|
||||||
queueOnly: true,
|
queueOnly: true,
|
||||||
|
source: "user",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -418,6 +430,7 @@ describe("Autodraft Settings API", () => {
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
mode: "next_pick",
|
mode: "next_pick",
|
||||||
queueOnly: true,
|
queueOnly: true,
|
||||||
|
source: "user",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -708,26 +708,31 @@ describe("full mobile reconnection scenario", () => {
|
||||||
|
|
||||||
describe("autodraft and timer sync on revalidation completion", () => {
|
describe("autodraft and timer sync on revalidation completion", () => {
|
||||||
it("syncs autodraftStatus from loader data on revalidation", () => {
|
it("syncs autodraftStatus from loader data on revalidation", () => {
|
||||||
let autodraftStatus: Record<string, boolean> = {
|
type AutodraftStatusEntry = { isEnabled: boolean; mode: "next_pick" | "while_on"; queueOnly: boolean };
|
||||||
"team-1": false,
|
let autodraftStatus: Record<string, AutodraftStatusEntry> = {
|
||||||
"team-2": false,
|
"team-1": { isEnabled: false, mode: "next_pick", queueOnly: false },
|
||||||
|
"team-2": { isEnabled: false, mode: "next_pick", queueOnly: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate what happens on revalidation completion
|
// Simulate what happens on revalidation completion
|
||||||
const autodraftSettings = [
|
const autodraftSettings = [
|
||||||
{ teamId: "team-1", isEnabled: true },
|
{ teamId: "team-1", isEnabled: true, mode: "while_on" as const, queueOnly: false },
|
||||||
{ teamId: "team-2", isEnabled: false },
|
{ teamId: "team-2", isEnabled: false, mode: "next_pick" as const, queueOnly: false },
|
||||||
{ teamId: "team-3", isEnabled: true },
|
{ teamId: "team-3", isEnabled: true, mode: "next_pick" as const, queueOnly: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
autodraftStatus = {};
|
autodraftStatus = {};
|
||||||
autodraftSettings.forEach((setting) => {
|
autodraftSettings.forEach((setting) => {
|
||||||
autodraftStatus[setting.teamId] = setting.isEnabled;
|
autodraftStatus[setting.teamId] = {
|
||||||
|
isEnabled: setting.isEnabled,
|
||||||
|
mode: setting.mode,
|
||||||
|
queueOnly: setting.queueOnly,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(autodraftStatus["team-1"]).toBe(true);
|
expect(autodraftStatus["team-1"].isEnabled).toBe(true);
|
||||||
expect(autodraftStatus["team-2"]).toBe(false);
|
expect(autodraftStatus["team-2"].isEnabled).toBe(false);
|
||||||
expect(autodraftStatus["team-3"]).toBe(true);
|
expect(autodraftStatus["team-3"].isEnabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("syncs teamTimers from loader data on revalidation", () => {
|
it("syncs teamTimers from loader data on revalidation", () => {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ interface ServerToClientEvents {
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
mode: "next_pick" | "while_on";
|
mode: "next_pick" | "while_on";
|
||||||
queueOnly: boolean;
|
queueOnly: boolean;
|
||||||
|
source?: "commissioner" | "user";
|
||||||
}) => void;
|
}) => void;
|
||||||
"team-connected": (data: { teamId: string }) => void;
|
"team-connected": (data: { teamId: string }) => void;
|
||||||
"team-disconnected": (data: { teamId: string }) => void;
|
"team-disconnected": (data: { teamId: string }) => void;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue