import { useEffect } from "react"; import { useSearchParams } from "react-router"; import { toast } from "sonner"; import { getAuth } from "@clerk/react-router/server"; import { addDays, subDays } from "date-fns"; import type { Route } from "./+types/home"; import { findLeaguesWithActiveSeasonsByUserId } from "~/models/league"; import { findSeasonById, findCurrentSeasonWithSports } from "~/models/season"; import { findTeamByOwnerAndSeason } from "~/models/team"; import { getDraftedParticipantsBySportsSeason } from "~/models/draft-pick"; import { getUpcomingEventsForDraftedParticipants } from "~/models/scoring-event"; import { getTeamStanding } from "~/models/standings"; import { getSeasonCompletionPercentage } from "~/lib/season-helpers.server"; import { findDraftSlotsBySeasonId } from "~/models/draft-slot"; import { getTeamForPick } from "~/lib/draft-order"; import type { CalendarPanelEvent } from "~/components/sport-season/UpcomingCalendarPanel"; import { UpcomingEventsCard } from "~/components/sport-season/UpcomingEventsCard"; import { MyLeaguesCard } from "~/components/league/MyLeaguesCard"; import { CreateLeagueCard } from "~/components/league/CreateLeagueCard"; import type { LeagueRowProps } from "~/components/league/LeagueRow"; import { toEventSortKey } from "~/lib/date-utils"; export function meta() { return [ { title: "Brackt - Fantasy All of the Sports!" }, { name: "description", content: "Play multi-sport fantasy leagues with your friends!", }, ]; } export async function loader(args: Route.LoaderArgs) { const { userId } = await getAuth(args); if (!userId) { return { leagues: [], isLoggedIn: false, upcomingCalendarEvents: [] }; } const leagues = await findLeaguesWithActiveSeasonsByUserId(userId); const today = new Date(); const dateFromStr = subDays(today, 1).toISOString().split("T")[0]; const dateToStr = addDays(today, 30).toISOString().split("T")[0]; const leaguesWithData = await Promise.all( leagues.map(async (league) => { const [season, seasonWithSports] = await Promise.all([ league.currentSeasonId ? findSeasonById(league.currentSeasonId) : null, findCurrentSeasonWithSports(league.id), ]); const numSports = seasonWithSports?.seasonSports?.length ?? 0; const calendarEvents: CalendarPanelEvent[] = []; let currentRank: number | undefined; let totalPoints: number | undefined; let previousRank: number | undefined; let completionPercentage = 0; let picksUntilMyTurn: number | undefined; let draftPosition: number | undefined; if (league.currentSeasonId && seasonWithSports?.seasonSports) { const myTeam = await findTeamByOwnerAndSeason(userId, league.currentSeasonId); if (myTeam) { // Fetch standing, completion %, and calendar events in parallel const [standing, pct, participantsBySportsSeason] = await Promise.all([ getTeamStanding(myTeam.id, league.currentSeasonId), getSeasonCompletionPercentage(league.currentSeasonId), getDraftedParticipantsBySportsSeason(myTeam.id, league.currentSeasonId), ]); completionPercentage = pct; // Draft slot info — needed for pre-draft position and in-progress picks-until-turn if (season?.status === "draft" || season?.status === "pre_draft") { const draftSlots = await findDraftSlotsBySeasonId(league.currentSeasonId); const mySlot = draftSlots.find((s) => s.teamId === myTeam.id); if (mySlot) { draftPosition = mySlot.draftOrder; } if (season.status === "draft" && season.currentPickNumber) { const currentPick = season.currentPickNumber; let offset = 0; // Snake draft: one full cycle is draftSlots.length * 2 picks while (offset < draftSlots.length * 2) { const slot = getTeamForPick(currentPick + offset, draftSlots); if (slot?.teamId === myTeam.id) { picksUntilMyTurn = offset; break; } offset++; } } } if (standing) { currentRank = standing.currentRank ?? undefined; totalPoints = standing.totalPoints ?? undefined; previousRank = standing.previousRank ?? undefined; } const perSeasonEvents = await Promise.all( seasonWithSports.seasonSports.map(async ({ sportsSeason: ss }) => { const draftedParticipants = participantsBySportsSeason.get(ss.id) ?? []; if (draftedParticipants.length === 0) return []; const events = await getUpcomingEventsForDraftedParticipants( ss.id, ss.scoringPattern ?? "", draftedParticipants, dateFromStr, dateToStr ); return events.map((event) => ({ ...event, sportName: ss.sport.name, sportSeasonName: ss.name, leagueName: league.name, leagueId: league.id, sportsSeasonPageUrl: `/leagues/${league.id}/sports-seasons/${ss.id}`, })); }) ); calendarEvents.push(...perSeasonEvents.flat()); } } return { ...league, currentSeason: season, numSports, currentRank, totalPoints, previousRank, completionPercentage, picksUntilMyTurn, draftPosition, draftDateTime: season?.draftDateTime?.toISOString() ?? null, calendarEvents, }; }) ); const upcomingCalendarEvents = leaguesWithData .flatMap((l) => l.calendarEvents) .toSorted((a, b) => toEventSortKey(a).localeCompare(toEventSortKey(b))); return { leagues: leaguesWithData, isLoggedIn: true, upcomingCalendarEvents, }; } export default function Home({ loaderData }: Route.ComponentProps) { const { leagues, isLoggedIn, upcomingCalendarEvents } = loaderData; const [searchParams, setSearchParams] = useSearchParams(); useEffect(() => { if (searchParams.get("deleted") === "true") { toast.success("League deleted successfully"); setSearchParams({}); } if (searchParams.get("left") === "true") { toast.success("You have left the league. Your team is now available."); setSearchParams({}); } }, [searchParams, setSearchParams]); if (!isLoggedIn) { return (
Please sign in to view your leagues