* 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>
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { ClipboardList } from "lucide-react";
|
|
import { Label } from "~/components/ui/label";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Checkbox } from "~/components/ui/checkbox";
|
|
import { cn } from "~/lib/utils";
|
|
import { SportIcon } from "~/components/league/SportIcon";
|
|
import { SettingsSection, SettingsStatusPill } from "./SettingsSection";
|
|
|
|
type SportsSeason = {
|
|
id: string;
|
|
name: string;
|
|
year: number;
|
|
scoringType: string;
|
|
sport: { id: string; name: string; slug: string; type: string; iconUrl: string | null };
|
|
};
|
|
|
|
const BRACKT_SLUG = "brackt";
|
|
|
|
export function SportsSection({
|
|
active,
|
|
canEditSports,
|
|
selectedSports,
|
|
displaySportsSeasons,
|
|
onSportToggle,
|
|
onClearAll,
|
|
}: {
|
|
active: boolean;
|
|
canEditSports: boolean;
|
|
selectedSports: Set<string>;
|
|
displaySportsSeasons: SportsSeason[];
|
|
onSportToggle: (id: string) => void;
|
|
onClearAll: () => void;
|
|
}) {
|
|
return (
|
|
<SettingsSection
|
|
id="sports"
|
|
icon={ClipboardList}
|
|
title="Sports"
|
|
description="Manage draftable sports seasons for this league. Sports lock when the draft starts."
|
|
status={<SettingsStatusPill tone={canEditSports ? "success" : "locked"}>{canEditSports ? "Editable" : "Locked"}</SettingsStatusPill>}
|
|
className={active ? undefined : "hidden"}
|
|
>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<Label>{selectedSports.size} selected</Label>
|
|
{canEditSports && selectedSports.size > 0 && (
|
|
<Button type="button" variant="ghost" size="sm" onClick={onClearAll}>
|
|
Clear all
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<div className="space-y-2">
|
|
{displaySportsSeasons.length > 0 ? (
|
|
displaySportsSeasons.map((ss) => {
|
|
const selected = selectedSports.has(ss.id);
|
|
return (
|
|
<label
|
|
key={ss.id}
|
|
htmlFor={`sport-${ss.id}`}
|
|
className={cn(
|
|
"mb-2 flex cursor-pointer items-center gap-3 rounded-md border p-3 text-sm transition-colors last:mb-0",
|
|
selected ? "border-primary bg-primary/10 text-primary" : "border-border bg-background hover:border-primary/50",
|
|
!canEditSports && "cursor-not-allowed opacity-65"
|
|
)}
|
|
>
|
|
<Checkbox
|
|
id={`sport-${ss.id}`}
|
|
name="sportsSeasons"
|
|
value={ss.id}
|
|
checked={selected}
|
|
onCheckedChange={() => onSportToggle(ss.id)}
|
|
disabled={!canEditSports}
|
|
/>
|
|
<SportIcon sport={ss.sport} />
|
|
<span className="min-w-0 flex-1">
|
|
<span className="block truncate font-medium">
|
|
{ss.sport.slug === BRACKT_SLUG ? "Brackt" : `${ss.sport.name} - ${ss.name} (${ss.year})`}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">{ss.scoringType.replace("_", " ")}</span>
|
|
</span>
|
|
</label>
|
|
);
|
|
})
|
|
) : (
|
|
<p className="p-3 text-sm text-muted-foreground">No sports seasons available.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</SettingsSection>
|
|
);
|
|
}
|