* Refactor league settings into per-section components (#347) Extract all settings sections from the monolithic 1009-line route file into individual components under app/components/league/settings/. Route file drops to ~300 lines. Separates draft-order dirty state from general settings dirty state, deduplicates section-change handling, and fixes several bugs found during review (typo in pointsFor5th, wrong mock in tests, lint violations). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Stop hook loop: suppress output on typecheck success The Stop hook was producing stdout on every run, causing Claude Code to feed it back as context and rewake the model each turn. Now emits a systemMessage JSON only on failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Typescript fix * Remove type asserting --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
190 lines
7.1 KiB
TypeScript
190 lines
7.1 KiB
TypeScript
import { Swords, CalendarIcon } from "lucide-react";
|
|
import { format } from "date-fns";
|
|
import { Label } from "~/components/ui/label";
|
|
import { Input } from "~/components/ui/input";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Badge } from "~/components/ui/badge";
|
|
import { Calendar } from "~/components/ui/calendar";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
|
import { cn } from "~/lib/utils";
|
|
import { TimerModeSelector } from "~/components/league/TimerModeSelector";
|
|
import { DraftSpeedPicker } from "~/components/league/DraftSpeedPicker";
|
|
import { OvernightPauseSettings } from "~/components/league/OvernightPauseSettings";
|
|
import { StepperInput } from "~/components/league/StepperInput";
|
|
import { SettingsSection, SettingsStatusPill } from "./SettingsSection";
|
|
|
|
export function DraftSetupSection({
|
|
active,
|
|
canEditDraftRounds,
|
|
minRounds,
|
|
recommendedRounds,
|
|
flexSpots,
|
|
draftRounds,
|
|
onDraftRoundsChange,
|
|
draftDate,
|
|
onDraftDateChange,
|
|
draftTime,
|
|
onDraftTimeChange,
|
|
timerMode,
|
|
onTimerModeChange,
|
|
draftSpeed,
|
|
onDraftSpeedChange,
|
|
overnightMode,
|
|
onOvernightModeChange,
|
|
overnightStart,
|
|
onOvernightStartChange,
|
|
overnightEnd,
|
|
onOvernightEndChange,
|
|
overnightTimezone,
|
|
onOvernightTimezoneChange,
|
|
commishTimezone,
|
|
}: {
|
|
active: boolean;
|
|
canEditDraftRounds: boolean;
|
|
minRounds: number;
|
|
recommendedRounds: number;
|
|
flexSpots: number;
|
|
draftRounds: number;
|
|
onDraftRoundsChange: (v: number) => void;
|
|
draftDate: Date | undefined;
|
|
onDraftDateChange: (v: Date | undefined) => void;
|
|
draftTime: string;
|
|
onDraftTimeChange: (v: string) => void;
|
|
timerMode: "chess_clock" | "standard";
|
|
onTimerModeChange: (v: "chess_clock" | "standard") => void;
|
|
draftSpeed: string;
|
|
onDraftSpeedChange: (v: string) => void;
|
|
overnightMode: "none" | "league" | "per_user";
|
|
onOvernightModeChange: (v: "none" | "league" | "per_user") => void;
|
|
overnightStart: string;
|
|
onOvernightStartChange: (v: string) => void;
|
|
overnightEnd: string;
|
|
onOvernightEndChange: (v: string) => void;
|
|
overnightTimezone: string;
|
|
onOvernightTimezoneChange: (v: string) => void;
|
|
commishTimezone: string | null;
|
|
}) {
|
|
return (
|
|
<SettingsSection
|
|
id="draft-setup"
|
|
icon={Swords}
|
|
title="Draft Settings"
|
|
description="Configure the draft format, timing, overnight protections, and pick order."
|
|
status={<SettingsStatusPill tone={canEditDraftRounds ? "success" : "locked"}>{canEditDraftRounds ? "Editable" : "Locked"}</SettingsStatusPill>}
|
|
className={active ? undefined : "hidden"}
|
|
>
|
|
<input type="hidden" name="draftRounds" value={draftRounds} />
|
|
<input type="hidden" name="draftTimerMode" value={timerMode} />
|
|
<input type="hidden" name="draftSpeed" value={draftSpeed} />
|
|
<input type="hidden" name="overnightPauseMode" value={overnightMode} />
|
|
<input type="hidden" name="overnightPauseStart" value={overnightStart} />
|
|
<input type="hidden" name="overnightPauseEnd" value={overnightEnd} />
|
|
<input type="hidden" name="overnightPauseTimezone" value={overnightTimezone} />
|
|
{draftDate && draftTime ? (
|
|
<input
|
|
type="hidden"
|
|
name="draftDateTime"
|
|
value={new Date(`${format(draftDate, "yyyy-MM-dd")}T${draftTime}`).toISOString()}
|
|
/>
|
|
) : (
|
|
<input type="hidden" name="draftDateTime" value="" />
|
|
)}
|
|
|
|
<div className="space-y-8">
|
|
<div className="space-y-8">
|
|
<div className="rounded-lg border p-5 sm:p-6">
|
|
<div className="mb-5 flex items-start justify-between gap-3">
|
|
<div>
|
|
<Label>Draft Rounds</Label>
|
|
<p className="text-sm text-muted-foreground">Minimum {minRounds}; recommended {recommendedRounds}.</p>
|
|
</div>
|
|
<Badge variant="outline">{flexSpots} flex</Badge>
|
|
</div>
|
|
<StepperInput
|
|
value={draftRounds}
|
|
min={canEditDraftRounds ? Math.max(1, minRounds) : draftRounds}
|
|
max={canEditDraftRounds ? 50 : draftRounds}
|
|
onChange={onDraftRoundsChange}
|
|
decrementLabel="Decrease draft rounds"
|
|
incrementLabel="Increase draft rounds"
|
|
/>
|
|
{!canEditDraftRounds && (
|
|
<p className="mt-3 text-sm text-muted-foreground">Round count cannot be changed after draft starts.</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="rounded-lg border p-5 sm:p-6">
|
|
<Label htmlFor="draftDate">Draft Date & Time</Label>
|
|
<div className="mt-5 grid gap-3 sm:grid-cols-2">
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
className={cn("justify-start text-left font-normal", !draftDate && "text-muted-foreground")}
|
|
disabled={!canEditDraftRounds}
|
|
>
|
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
{draftDate ? format(draftDate, "PPP") : <span>Pick a date</span>}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0">
|
|
<Calendar
|
|
mode="single"
|
|
selected={draftDate}
|
|
onSelect={(date) => onDraftDateChange(date)}
|
|
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<Input
|
|
type="time"
|
|
value={draftTime}
|
|
onChange={(e) => onDraftTimeChange(e.target.value)}
|
|
disabled={!canEditDraftRounds}
|
|
/>
|
|
</div>
|
|
<p className="mt-4 text-sm text-muted-foreground">
|
|
Set this before starting the draft room.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-8 rounded-lg border p-5 sm:p-6">
|
|
<div className="space-y-4">
|
|
<Label>Timer Mode</Label>
|
|
<TimerModeSelector
|
|
value={timerMode}
|
|
onChange={onTimerModeChange}
|
|
disabled={!canEditDraftRounds}
|
|
/>
|
|
</div>
|
|
<div className="border-t pt-8 space-y-4">
|
|
<Label>Draft Speed</Label>
|
|
<DraftSpeedPicker
|
|
timerMode={timerMode}
|
|
value={draftSpeed}
|
|
onChange={onDraftSpeedChange}
|
|
disabled={!canEditDraftRounds}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-lg border p-5 sm:p-6">
|
|
<OvernightPauseSettings
|
|
show={true}
|
|
mode={overnightMode}
|
|
onModeChange={onOvernightModeChange}
|
|
start={overnightStart}
|
|
onStartChange={onOvernightStartChange}
|
|
end={overnightEnd}
|
|
onEndChange={onOvernightEndChange}
|
|
timezone={overnightTimezone}
|
|
onTimezoneChange={onOvernightTimezoneChange}
|
|
commishTimezone={commishTimezone}
|
|
disabled={!canEditDraftRounds}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</SettingsSection>
|
|
);
|
|
}
|