Show commish-only leagues in a separate dashboard section, fixes #336

Leagues where the user is a commissioner but has no team are now shown
in a "Leagues I Commish" subsection below their member leagues, rather
than silently appearing with no stats. A live draft always takes
priority over the commish-only row so commissioners still see the draft
banner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-05-11 23:51:23 -07:00
parent 27e8ae505a
commit e0ba84669c
3 changed files with 45 additions and 3 deletions

View file

@ -18,6 +18,7 @@ export interface LeagueRowProps {
draftDateTime?: string | null; draftDateTime?: string | null;
picksUntilMyTurn?: number; picksUntilMyTurn?: number;
draftPosition?: number; draftPosition?: number;
isCommishOnly?: boolean;
} }
// ─── Helpers ────────────────────────────────────────────────────────────────── // ─── Helpers ──────────────────────────────────────────────────────────────────
@ -205,10 +206,28 @@ function CompletedRow({
); );
} }
// ─── Commish-only row ─────────────────────────────────────────────────────────
function CommishOnlyRow({ leagueId, leagueName }: Pick<LeagueRowProps, "leagueId" | "leagueName">) {
return (
<Link
to={`/leagues/${leagueId}`}
className="flex items-center gap-3 rounded-lg bg-card px-3 py-3 sm:px-5 sm:py-4 hover:bg-white/[0.06] transition-colors"
>
<LeagueAvatar leagueId={leagueId} leagueName={leagueName} />
<div className="min-w-0">
<p className="font-semibold leading-tight truncate">{leagueName}</p>
<p className="text-xs text-muted-foreground mt-0.5">Commissioner</p>
</div>
</Link>
);
}
// ─── Public export ──────────────────────────────────────────────────────────── // ─── Public export ────────────────────────────────────────────────────────────
export function LeagueRow(props: LeagueRowProps) { export function LeagueRow(props: LeagueRowProps) {
if (props.status === "draft") return <DraftRow {...props} />; if (props.status === "draft") return <DraftRow {...props} />;
if (props.isCommishOnly) return <CommishOnlyRow {...props} />;
if (props.status === "active") return <ActiveRow {...props} />; if (props.status === "active") return <ActiveRow {...props} />;
if (props.status === "pre_draft") return <PreDraftRow {...props} />; if (props.status === "pre_draft") return <PreDraftRow {...props} />;
return <CompletedRow {...props} />; return <CompletedRow {...props} />;

View file

@ -17,7 +17,12 @@ interface MyLeaguesCardProps {
} }
export function MyLeaguesCard({ leagues, viewAllUrl }: MyLeaguesCardProps) { export function MyLeaguesCard({ leagues, viewAllUrl }: MyLeaguesCardProps) {
const sorted = leagues.toSorted((a, b) => { const memberLeagues = leagues.filter((l) => !l.isCommishOnly);
const commishOnlyLeagues = leagues
.filter((l) => l.isCommishOnly)
.toSorted((a, b) => a.leagueName.localeCompare(b.leagueName));
const sortedMember = memberLeagues.toSorted((a, b) => {
const statusDiff = STATUS_ORDER[a.status] - STATUS_ORDER[b.status]; const statusDiff = STATUS_ORDER[a.status] - STATUS_ORDER[b.status];
if (statusDiff !== 0) return statusDiff; if (statusDiff !== 0) return statusDiff;
if (a.status === "active") { if (a.status === "active") {
@ -35,11 +40,13 @@ export function MyLeaguesCard({ leagues, viewAllUrl }: MyLeaguesCardProps) {
</Link> </Link>
) : undefined; ) : undefined;
const isEmpty = sortedMember.length === 0 && commishOnlyLeagues.length === 0;
return ( return (
<Card className="bg-[#181b22] gap-2"> <Card className="bg-[#181b22] gap-2">
<SectionCardHeader icon={Shield} title="My Leagues" right={right} /> <SectionCardHeader icon={Shield} title="My Leagues" right={right} />
<CardContent className="px-3 sm:px-6"> <CardContent className="px-3 sm:px-6">
{sorted.length === 0 ? ( {isEmpty ? (
<div className="py-4 text-center"> <div className="py-4 text-center">
<p className="text-sm text-muted-foreground mb-3"> <p className="text-sm text-muted-foreground mb-3">
You aren&apos;t a member of any leagues yet. You aren&apos;t a member of any leagues yet.
@ -53,9 +60,20 @@ export function MyLeaguesCard({ leagues, viewAllUrl }: MyLeaguesCardProps) {
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{sorted.map((league) => ( {sortedMember.map((league) => (
<LeagueRow key={league.leagueId} {...league} /> <LeagueRow key={league.leagueId} {...league} />
))} ))}
{commishOnlyLeagues.length > 0 && (
<>
{sortedMember.length > 0 && <div className="border-t border-border/40 pt-1" />}
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground px-1">
Leagues I Commish
</p>
{commishOnlyLeagues.map((league) => (
<LeagueRow key={league.leagueId} {...league} />
))}
</>
)}
</div> </div>
)} )}
</CardContent> </CardContent>

View file

@ -63,9 +63,12 @@ export async function loader(args: Route.LoaderArgs) {
let completionPercentage = 0; let completionPercentage = 0;
let picksUntilMyTurn: number | undefined; let picksUntilMyTurn: number | undefined;
let draftPosition: number | undefined; let draftPosition: number | undefined;
// False when there's no current season — a commish with no season has no team by definition.
let hasTeam = false;
if (league.currentSeasonId && seasonWithSports?.seasonSports) { if (league.currentSeasonId && seasonWithSports?.seasonSports) {
const myTeam = await findTeamByOwnerAndSeason(userId, league.currentSeasonId); const myTeam = await findTeamByOwnerAndSeason(userId, league.currentSeasonId);
hasTeam = !!myTeam;
if (myTeam) { if (myTeam) {
// Fetch standing, completion %, and calendar events in parallel // Fetch standing, completion %, and calendar events in parallel
@ -153,6 +156,7 @@ export async function loader(args: Route.LoaderArgs) {
draftPosition, draftPosition,
draftDateTime: season?.draftDateTime?.toISOString() ?? null, draftDateTime: season?.draftDateTime?.toISOString() ?? null,
calendarEvents, calendarEvents,
hasTeam,
}; };
}) })
); );
@ -209,6 +213,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
picksUntilMyTurn: league.picksUntilMyTurn, picksUntilMyTurn: league.picksUntilMyTurn,
draftPosition: league.draftPosition, draftPosition: league.draftPosition,
draftDateTime: league.draftDateTime, draftDateTime: league.draftDateTime,
isCommishOnly: !league.hasTeam,
}; };
}); });