* Show leagues where user is a member regardless of team assignment The homepage now uses a union query to show leagues where the user either has a team in the current season OR is a commissioner. Previously, commissioners without a team could not see their leagues on the homepage. https://claude.ai/code/session_01E3ugKTfatkEc5TcDGXvHiW * Fix stale empty state card title in home route Update 'No Active Leagues' to 'No Leagues' to match the updated description which is now about membership rather than active seasons. https://claude.ai/code/session_01E3ugKTfatkEc5TcDGXvHiW * Fix union import: use two queries with JS deduplication drizzle-orm 0.36.x does not export a standalone union() function. Replace with two awaited queries merged via a Map (dedup by id), then sorted by createdAt descending in JS. https://claude.ai/code/session_01E3ugKTfatkEc5TcDGXvHiW --------- Co-authored-by: Claude <noreply@anthropic.com>
148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
import { useEffect } from "react";
|
|
import { Link, useSearchParams } from "react-router";
|
|
import { toast } from "sonner";
|
|
import { getAuth } from "@clerk/react-router/server";
|
|
|
|
import type { Route } from "./+types/home";
|
|
import { findLeaguesWithActiveSeasonsByUserId } from "~/models/league";
|
|
import { findSeasonById } from "~/models/season";
|
|
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) {
|
|
return { leagues: [], isLoggedIn: false };
|
|
}
|
|
|
|
// Fetch leagues where user has a team in the current season
|
|
const leagues = await findLeaguesWithActiveSeasonsByUserId(userId);
|
|
|
|
// Fetch season details for each league's current season
|
|
const leaguesWithSeasons = await Promise.all(
|
|
leagues.map(async (league) => {
|
|
const season = league.currentSeasonId
|
|
? await findSeasonById(league.currentSeasonId)
|
|
: null;
|
|
return {
|
|
...league,
|
|
currentSeason: season,
|
|
};
|
|
})
|
|
);
|
|
|
|
return {
|
|
leagues: leaguesWithSeasons,
|
|
isLoggedIn: true,
|
|
};
|
|
}
|
|
|
|
export default function Home({ loaderData }: Route.ComponentProps) {
|
|
const { leagues, isLoggedIn } = 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>
|
|
|
|
{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>
|
|
);
|
|
}
|