import { useEffect, useRef, useState } from "react"; import { Link, useSearchParams } from "react-router"; import { toast } from "sonner"; import type { Route } from "./+types/$leagueId"; import { Link2 } from "lucide-react"; import { Button } from "~/components/ui/button"; import { GradientIcon } from "~/components/ui/GradientIcon"; import { CommissionersPanel } from "~/components/league/CommissionersPanel"; import { TeamsPanel } from "~/components/league/TeamsPanel"; import { DraftInfoCard } from "~/components/league/DraftInfoCard"; import { SportsSeasonsSummary } from "~/components/league/SportsSeasonsSummary"; import { UpcomingEventsCard } from "~/components/sport-season/UpcomingEventsCard"; import { buildTiedRankChecker, getDisplayRank } from "~/lib/standings-display"; import { StandingsPreview, type StandingsPreviewEntry } from "~/components/league/StandingsPreview"; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [{ title: `${data?.league?.name ?? "League"} - Brackt` }]; } export { loader } from "./$leagueId.server"; export default function LeagueHome({ loaderData }: Route.ComponentProps) { const { league, season, teams, commissioners, currentUserId, isUserCommissioner, ownerMap, commissionerMap, availableTeamCount, sportsCount, isDraftOrderSet, draftSlots, sportsSeasons, standings, origin, upcomingCalendarEvents, recentActivity, } = loaderData; const myTeam = teams.find((t) => t.ownerId === currentUserId); // 1-based pick position; undefined if myTeam isn't in the slots (order not yet set, or no team) const userDraftPosition = myTeam ? draftSlots.findIndex((s) => s.team.id === myTeam.id) + 1 || undefined : undefined; const [searchParams, setSearchParams] = useSearchParams(); const [copied, setCopied] = useState(false); // Guard against double-firing toasts (e.g. React Strict Mode double-invoke) const processedParamsRef = useRef(null); useEffect(() => { const paramString = searchParams.toString(); if (!paramString || paramString === processedParamsRef.current) return; if (searchParams.get("updated") === "true") { processedParamsRef.current = paramString; toast.success("Team updated successfully"); setSearchParams({}); } else if (searchParams.get("joined") === "true") { processedParamsRef.current = paramString; toast.success("Welcome to the league! You've been assigned a team."); setSearchParams({}); } else if (searchParams.get("left") === "true") { processedParamsRef.current = paramString; toast.success("You have left the league. Your team is now available."); setSearchParams({}); } }, [searchParams, setSearchParams]); const handleCopyInviteLink = async () => { if (!season?.inviteCode) return; const inviteUrl = `${origin}/i/${season.inviteCode}`; try { await navigator.clipboard.writeText(inviteUrl); setCopied(true); toast.success("Invite link copied to clipboard!"); setTimeout(() => setCopied(false), 2000); } catch { toast.error("Failed to copy invite link"); } }; // Pre-compute standings map to avoid O(n²) lookups in render const standingsMap = new Map(standings.map((s) => [s.teamId, s])); const isTiedRank = buildTiedRankChecker(standings.map((s) => s.currentRank)); // Pre-compute sorted standings list const sortedTeams = [...teams].toSorted((a, b) => { const rankA = standingsMap.get(a.id)?.currentRank ?? Infinity; const rankB = standingsMap.get(b.id)?.currentRank ?? Infinity; return rankA - rankB; }); const standingsEntries: StandingsPreviewEntry[] = sortedTeams.map((team) => { const standing = standingsMap.get(team.id); return { teamId: team.id, teamName: team.name, ownerName: team.ownerId ? ownerMap[team.ownerId] : null, displayRank: getDisplayRank(standing, standings.length, standing ? isTiedRank(standing.currentRank) : false), currentRank: standing?.currentRank, points: standing ? (standing.actualPoints ?? standing.totalPoints) : 0, href: season ? `/leagues/${league.id}/standings/${season.id}/teams/${team.id}` : undefined, rankChange: standing?.rankChange, pointChange: standing?.sevenDayPointChange, }; }); // Pre-compute sorted sports seasons: active → upcoming → completed, // season_standings last within active, then alphabetical by sport name const sortedSportsSeasons = [...sportsSeasons].toSorted((a, b) => { const statusOrder = { active: 0, upcoming: 1, completed: 2 } as const; const statusDiff = statusOrder[a.status] - statusOrder[b.status]; if (statusDiff !== 0) return statusDiff; if (a.status === "active") { const aSeasonLong = a.scoringPattern === "season_standings" ? 1 : 0; const bSeasonLong = b.scoringPattern === "season_standings" ? 1 : 0; if (aSeasonLong !== bSeasonLong) return aSeasonLong - bSeasonLong; } return a.sport.name.localeCompare(b.sport.name); }); const isDraftOrPreDraft = season?.status === "pre_draft" || season?.status === "draft"; const isActiveOrCompleted = season?.status === "active" || season?.status === "completed"; return (

{league.name}

{season && (

{season.year} Season •{" "} {season.status.replace("_", " ")}

)}
{myTeam && ( )} {isUserCommissioner && ( )}
{season?.status === "draft" && (
Draft is live! Your league draft is currently in progress.
)}
{/* Left Column - 2/3 width on desktop */}
{/* Draft Info card - pre_draft/draft only */} {season && isDraftOrPreDraft && ( )} {/* Standings Panel - active/completed seasons */} {season && isActiveOrCompleted && ( )} {/* Sports Seasons Section */} {season && sortedSportsSeasons.length > 0 && ( ({ id: ss.id, sportName: ss.sport.name, seasonName: ss.name, status: ss.status, scoringPattern: ss.scoringPattern, upcomingParticipantEvents: ss.upcomingParticipantEvents, draftedParticipants: ss.draftedParticipantsWithPoints, }))} /> )}
{/* Right Column - 1/3 width on desktop */}
{/* Teams - shown above invite link when league is full */} {season && isDraftOrPreDraft && availableTeamCount === 0 && ( )} {/* Invite Link - bare, pre_draft commissioner only */} {isUserCommissioner && season?.status === "pre_draft" && availableTeamCount > 0 && (
Invite Link

{availableTeamCount} spot{availableTeamCount !== 1 ? "s" : ""} remaining

e.currentTarget.select()} />

Anyone with this link can join your league

)} {/* Teams - shown below invite link when league is not full */} {season && isDraftOrPreDraft && availableTeamCount > 0 && ( )} {upcomingCalendarEvents.length > 0 && ( )} {/* Draft historical view - active/completed only */} {season && isActiveOrCompleted && ( )}
); }