brackt/app/components/league/settings/LeagueBasicsSection.tsx
Chris Parsons 05fe1493a3
Refactor league settings into per-section components (#379)
* 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>
2026-05-05 14:19:50 -07:00

123 lines
4.3 KiB
TypeScript

import { Trophy } from "lucide-react";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Button } from "~/components/ui/button";
import { StepperInput } from "~/components/league/StepperInput";
import { SettingsSection, SettingsStatusPill } from "./SettingsSection";
export function LeagueBasicsSection({
active,
season,
leagueName,
onLeagueNameChange,
isPublicDraftBoard,
onIsPublicDraftBoardChange,
teamCountValue,
onTeamCountValueChange,
teamsWithOwners,
canEditTeamCount,
minTeamCount,
inviteUrl,
copied,
onCopyInviteLink,
}: {
active: boolean;
season: { inviteCode: string | null } | null;
leagueName: string;
onLeagueNameChange: (v: string) => void;
isPublicDraftBoard: boolean;
onIsPublicDraftBoardChange: (v: boolean) => void;
teamCountValue: number;
onTeamCountValueChange: (v: number) => void;
teamsWithOwners: number;
canEditTeamCount: boolean;
minTeamCount: number;
inviteUrl: string;
copied: boolean;
onCopyInviteLink: () => void;
}) {
return (
<SettingsSection
id="league-basics"
icon={Trophy}
title="League Basics"
description="High-level league identity and public visibility controls."
status={<SettingsStatusPill tone="success">Always editable</SettingsStatusPill>}
className={active ? undefined : "hidden"}
>
<input type="hidden" name="teamCount" value={teamCountValue} />
<div className="space-y-5">
<div className="space-y-2">
<Label htmlFor="name">League Name</Label>
<Input
id="name"
name="name"
type="text"
value={leagueName}
onChange={(e) => onLeagueNameChange(e.target.value)}
placeholder="Enter league name"
required
minLength={3}
maxLength={50}
/>
</div>
<label
htmlFor="isPublicDraftBoard"
className="flex cursor-pointer items-start gap-3 rounded-lg border p-4"
>
<input
type="checkbox"
id="isPublicDraftBoard"
name="isPublicDraftBoard"
checked={isPublicDraftBoard}
onChange={(e) => onIsPublicDraftBoardChange(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-border"
/>
<span>
<span className="block font-medium">Public draft board</span>
<span className="text-sm text-muted-foreground">Allow anyone with the link to view the board without logging in.</span>
</span>
</label>
<div className="rounded-lg border p-4">
<div className="mb-3">
<Label>Number of Teams</Label>
<p className="text-sm text-muted-foreground">Minimum {minTeamCount} based on teams with owners.</p>
</div>
<StepperInput
value={teamCountValue}
min={canEditTeamCount ? minTeamCount : teamCountValue}
max={canEditTeamCount ? 16 : teamCountValue}
onChange={onTeamCountValueChange}
decrementLabel="Decrease team count"
incrementLabel="Increase team count"
/>
{!canEditTeamCount && (
<p className="mt-3 text-sm text-muted-foreground">Locked after the draft starts.</p>
)}
</div>
{season?.inviteCode && (
<div className="rounded-lg border p-4">
<div className="mb-3">
<h3 className="font-medium">Invite Link</h3>
<p className="text-sm text-muted-foreground">
{/* eslint-disable-next-line no-nested-ternary */}
{(() => {
const open = teamCountValue - teamsWithOwners;
return open > 0
? `${open} open spot${open === 1 ? "" : "s"} available.`
: "No open team spots right now.";
})()}
</p>
</div>
<div className="flex flex-col gap-2 sm:flex-row">
<Input readOnly value={inviteUrl} onClick={(e) => e.currentTarget.select()} className="text-sm" />
<Button type="button" variant={copied ? "default" : "outline"} onClick={onCopyInviteLink}>
{copied ? "Copied" : "Copy"}
</Button>
</div>
</div>
)}
</div>
</SettingsSection>
);
}