brackt/app/routes/home.tsx

216 lines
7.2 KiB
TypeScript
Raw Normal View History

import { useEffect } from "react";
import { Link, useSearchParams } from "react-router";
import { toast } from "sonner";
import { getAuth } from "@clerk/react-router/server";
2026-03-12 10:12:38 -07:00
import { addDays } from "date-fns";
import type { Route } from "./+types/home";
import { findLeaguesWithActiveSeasonsByUserId } from "~/models/league";
2026-03-12 10:12:38 -07:00
import { findSeasonById, findCurrentSeasonWithSports } from "~/models/season";
import { findTeamByOwnerAndSeason } from "~/models/team";
import { getDraftedParticipantsBySportsSeason } from "~/models/draft-pick";
import { getUpcomingEventsForDraftedParticipants } from "~/models/scoring-event";
import type { CalendarPanelEvent } from "~/components/sport-season/UpcomingCalendarPanel";
import { UpcomingCalendarPanel } from "~/components/sport-season/UpcomingCalendarPanel";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
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) {
2026-03-12 10:12:38 -07:00
return { leagues: [], isLoggedIn: false, upcomingCalendarEvents: [] };
}
// Fetch leagues where user has a team in the current season
const leagues = await findLeaguesWithActiveSeasonsByUserId(userId);
2026-03-12 10:12:38 -07:00
const today = new Date();
const calendarDateFrom = today;
const calendarDateTo = addDays(today, 30);
// Fetch season details and calendar events in parallel per league
const leaguesWithData = await Promise.all(
leagues.map(async (league) => {
2026-03-12 10:12:38 -07:00
const [season, seasonWithSports] = await Promise.all([
league.currentSeasonId ? findSeasonById(league.currentSeasonId) : null,
findCurrentSeasonWithSports(league.id),
]);
// Build calendar events for this league
const calendarEvents: CalendarPanelEvent[] = [];
if (league.currentSeasonId && seasonWithSports?.seasonSports) {
const myTeam = await findTeamByOwnerAndSeason(userId, league.currentSeasonId);
if (myTeam) {
const participantsBySportsSeason = await getDraftedParticipantsBySportsSeason(
myTeam.id,
league.currentSeasonId
);
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,
calendarDateFrom,
calendarDateTo
);
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,
2026-03-12 10:12:38 -07:00
calendarEvents,
};
})
);
2026-03-12 10:12:38 -07:00
const upcomingCalendarEvents = leaguesWithData
.flatMap((l) => l.calendarEvents)
.sort((a, b) => {
const dateA = a.eventDate ?? (a.earliestGameTime ? a.earliestGameTime.split("T")[0] : "");
const dateB = b.eventDate ?? (b.earliestGameTime ? b.earliestGameTime.split("T")[0] : "");
return dateA.localeCompare(dateB);
});
return {
2026-03-12 10:12:38 -07:00
leagues: leaguesWithData,
isLoggedIn: true,
2026-03-12 10:12:38 -07:00
upcomingCalendarEvents,
};
}
export default function Home({ loaderData }: Route.ComponentProps) {
2026-03-12 10:12:38 -07:00
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 (
<div className="container mx-auto py-16 px-4 text-center">
<h1 className="text-4xl font-bold mb-4">Welcome to Brackt</h1>
<p className="text-xl text-muted-foreground mb-8">
Please sign in to view your leagues
</p>
</div>
);
}
return (
<div className="container mx-auto py-8 px-4">
<div className="flex items-center justify-between mb-8">
<h1 className="text-4xl font-bold">My Leagues</h1>
<Button asChild>
<Link to="/leagues/new">Create New League</Link>
</Button>
</div>
2026-03-12 10:12:38 -07:00
{upcomingCalendarEvents.length > 0 && (
<div className="mb-8">
<UpcomingCalendarPanel events={upcomingCalendarEvents} showLeague={true} />
</div>
)}
{leagues.length === 0 ? (
<Card>
<CardHeader>
<CardTitle>No Leagues</CardTitle>
<CardDescription>
You aren't a member of any leagues yet
</CardDescription>
</CardHeader>
<CardContent>
<Button asChild>
<Link to="/leagues/new">Create a New League</Link>
</Button>
</CardContent>
</Card>
) : (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{leagues.map((league) => (
<Link key={league.id} to={`/leagues/${league.id}`}>
<Card className="hover:border-primary transition-colors cursor-pointer h-full">
<CardHeader>
<CardTitle>{league.name}</CardTitle>
<CardDescription>
Created {new Date(league.createdAt).toLocaleDateString()}
</CardDescription>
</CardHeader>
<CardContent>
{league.currentSeason ? (
<div className="space-y-2">
<div>
<p className="text-sm text-muted-foreground">
Current Season
</p>
<p className="font-medium">
{league.currentSeason.year}
</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Status</p>
<p className="font-medium capitalize">
{league.currentSeason.status.replace("_", " ")}
</p>
</div>
</div>
) : (
<p className="text-sm text-muted-foreground">
No active season
</p>
)}
</CardContent>
</Card>
</Link>
))}
</div>
)}
</div>
);
}