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;
picksUntilMyTurn?: number;
draftPosition?: number;
isCommishOnly?: boolean;
}
// ─── 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 ────────────────────────────────────────────────────────────
export function LeagueRow(props: LeagueRowProps) {
if (props.status === "draft") return <DraftRow {...props} />;
if (props.isCommishOnly) return <CommishOnlyRow {...props} />;
if (props.status === "active") return <ActiveRow {...props} />;
if (props.status === "pre_draft") return <PreDraftRow {...props} />;
return <CompletedRow {...props} />;

View file

@ -17,7 +17,12 @@ interface 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];
if (statusDiff !== 0) return statusDiff;
if (a.status === "active") {
@ -35,11 +40,13 @@ export function MyLeaguesCard({ leagues, viewAllUrl }: MyLeaguesCardProps) {
</Link>
) : undefined;
const isEmpty = sortedMember.length === 0 && commishOnlyLeagues.length === 0;
return (
<Card className="bg-[#181b22] gap-2">
<SectionCardHeader icon={Shield} title="My Leagues" right={right} />
<CardContent className="px-3 sm:px-6">
{sorted.length === 0 ? (
{isEmpty ? (
<div className="py-4 text-center">
<p className="text-sm text-muted-foreground mb-3">
You aren&apos;t a member of any leagues yet.
@ -53,9 +60,20 @@ export function MyLeaguesCard({ leagues, viewAllUrl }: MyLeaguesCardProps) {
</div>
) : (
<div className="space-y-3">
{sorted.map((league) => (
{sortedMember.map((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>
)}
</CardContent>

View file

@ -63,9 +63,12 @@ export async function loader(args: Route.LoaderArgs) {
let completionPercentage = 0;
let picksUntilMyTurn: 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) {
const myTeam = await findTeamByOwnerAndSeason(userId, league.currentSeasonId);
hasTeam = !!myTeam;
if (myTeam) {
// Fetch standing, completion %, and calendar events in parallel
@ -153,6 +156,7 @@ export async function loader(args: Route.LoaderArgs) {
draftPosition,
draftDateTime: season?.draftDateTime?.toISOString() ?? null,
calendarEvents,
hasTeam,
};
})
);
@ -209,6 +213,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
picksUntilMyTurn: league.picksUntilMyTurn,
draftPosition: league.draftPosition,
draftDateTime: league.draftDateTime,
isCommishOnly: !league.hasTeam,
};
});