feat: add current season tracking and active league listing by user
This commit is contained in:
parent
226cdd0ea7
commit
5f32ec05af
7 changed files with 503 additions and 9 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { eq } from "drizzle-orm";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { database } from "~/database/context";
|
||||
import * as schema from "~/database/schema";
|
||||
|
||||
|
|
@ -47,6 +47,13 @@ export async function deleteLeague(id: string): Promise<void> {
|
|||
await db.delete(schema.leagues).where(eq(schema.leagues.id, id));
|
||||
}
|
||||
|
||||
export async function setCurrentSeason(
|
||||
leagueId: string,
|
||||
seasonId: string | null
|
||||
): Promise<League> {
|
||||
return await updateLeague(leagueId, { currentSeasonId: seasonId });
|
||||
}
|
||||
|
||||
export async function listLeagues(options?: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
|
|
@ -58,3 +65,26 @@ export async function listLeagues(options?: {
|
|||
orderBy: (leagues, { desc }) => [desc(leagues.createdAt)],
|
||||
});
|
||||
}
|
||||
|
||||
export async function findLeaguesWithActiveSeasonsByUserId(
|
||||
userId: string
|
||||
): Promise<League[]> {
|
||||
const db = database();
|
||||
|
||||
// Query to find all leagues where user has a team in the current season
|
||||
const leagues = await db
|
||||
.selectDistinct({
|
||||
id: schema.leagues.id,
|
||||
name: schema.leagues.name,
|
||||
createdBy: schema.leagues.createdBy,
|
||||
currentSeasonId: schema.leagues.currentSeasonId,
|
||||
createdAt: schema.leagues.createdAt,
|
||||
updatedAt: schema.leagues.updatedAt,
|
||||
})
|
||||
.from(schema.leagues)
|
||||
.innerJoin(schema.teams, eq(schema.teams.seasonId, schema.leagues.currentSeasonId))
|
||||
.where(eq(schema.teams.ownerId, userId))
|
||||
.orderBy(desc(schema.leagues.createdAt));
|
||||
|
||||
return leagues;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
import { useEffect } from "react";
|
||||
import { useSearchParams } from "react-router";
|
||||
import { Link, useSearchParams } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import { getAuth } from "@clerk/react-router/server";
|
||||
|
||||
import type { Route } from "./+types/home";
|
||||
import { Welcome } from "../welcome/welcome";
|
||||
import { expressValueContext } from "~/contexts/express";
|
||||
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 [
|
||||
|
|
@ -16,22 +25,118 @@ export function meta() {
|
|||
];
|
||||
}
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
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 {
|
||||
message: context.get(expressValueContext) || "No message from Express",
|
||||
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");
|
||||
// Remove the query parameter
|
||||
setSearchParams({});
|
||||
}
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
return <Welcome message={loaderData.message} />;
|
||||
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 Active Leagues</CardTitle>
|
||||
<CardDescription>
|
||||
You don't have any teams in leagues with active seasons 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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Form, Link, redirect } from "react-router";
|
||||
import { getAuth } from "@clerk/react-router/server";
|
||||
import { createLeague } from "~/models/league";
|
||||
import { createLeague, setCurrentSeason } from "~/models/league";
|
||||
import { createSeason } from "~/models/season";
|
||||
import { createManyTeams } from "~/models/team";
|
||||
import { createCommissioner } from "~/models/commissioner";
|
||||
|
|
@ -70,6 +70,9 @@ export async function action(args: Route.ActionArgs) {
|
|||
status: "pre_draft",
|
||||
});
|
||||
|
||||
// Set this as the current season for the league
|
||||
await setCurrentSeason(league.id, season.id);
|
||||
|
||||
// Create teams with placeholder names
|
||||
const teams = Array.from({ length: teamCountNum }, (_, i) => ({
|
||||
seasonId: season.id,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export const leagues = pgTable("leagues", {
|
|||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
createdBy: varchar("created_by", { length: 255 }).notNull(), // Clerk user ID
|
||||
currentSeasonId: uuid("current_season_id"), // References the active season
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
|
|
|||
1
drizzle/0006_needy_junta.sql
Normal file
1
drizzle/0006_needy_junta.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "leagues" ADD COLUMN "current_season_id" uuid;
|
||||
347
drizzle/meta/0006_snapshot.json
Normal file
347
drizzle/meta/0006_snapshot.json
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
{
|
||||
"id": "afd92b21-6a89-4b8c-80ac-414a320db897",
|
||||
"prevId": "6839ce43-d04c-4246-9b00-7820935e1166",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.commissioners": {
|
||||
"name": "commissioners",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"league_id": {
|
||||
"name": "league_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"commissioners_league_id_leagues_id_fk": {
|
||||
"name": "commissioners_league_id_leagues_id_fk",
|
||||
"tableFrom": "commissioners",
|
||||
"tableTo": "leagues",
|
||||
"columnsFrom": [
|
||||
"league_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.leagues": {
|
||||
"name": "leagues",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_by": {
|
||||
"name": "created_by",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"current_season_id": {
|
||||
"name": "current_season_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.seasons": {
|
||||
"name": "seasons",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"league_id": {
|
||||
"name": "league_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"year": {
|
||||
"name": "year",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "season_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pre_draft'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"seasons_league_id_leagues_id_fk": {
|
||||
"name": "seasons_league_id_leagues_id_fk",
|
||||
"tableFrom": "seasons",
|
||||
"tableTo": "leagues",
|
||||
"columnsFrom": [
|
||||
"league_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.teams": {
|
||||
"name": "teams",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"season_id": {
|
||||
"name": "season_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"owner_id": {
|
||||
"name": "owner_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"teams_season_id_seasons_id_fk": {
|
||||
"name": "teams_season_id_seasons_id_fk",
|
||||
"tableFrom": "teams",
|
||||
"tableTo": "seasons",
|
||||
"columnsFrom": [
|
||||
"season_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"clerk_id": {
|
||||
"name": "clerk_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image_url": {
|
||||
"name": "image_url",
|
||||
"type": "varchar(512)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_clerk_id_unique": {
|
||||
"name": "users_clerk_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"clerk_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.season_status": {
|
||||
"name": "season_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"pre_draft",
|
||||
"draft",
|
||||
"active",
|
||||
"completed"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,13 @@
|
|||
"when": 1760169537260,
|
||||
"tag": "0005_abnormal_thunderbird",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1760241924414,
|
||||
"tag": "0006_needy_junta",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue