feat: add draft room authentication and UI improvements with exit button

This commit is contained in:
Chris Parsons 2025-10-17 12:45:30 -07:00
parent 137c4eb0fd
commit 269499d584

View file

@ -1,4 +1,4 @@
import { useLoaderData } from "react-router";
import { useLoaderData, Link } from "react-router";
import { eq, and, asc, notInArray } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
@ -6,14 +6,24 @@ import { useDraftSocket } from "~/hooks/useDraftSocket";
import { useEffect, useState } from "react";
import { Card } from "~/components/ui/card";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { getAuth } from "@clerk/react-router/server";
import { getTeamQueue } from "~/models/draft-queue";
export async function loader({ params }: { params: { seasonId: string } }) {
const { seasonId } = params;
export async function loader(args: any) {
const { params } = args;
const { seasonId, leagueId } = params;
const auth = await getAuth(args);
const userId = (auth as any).userId as string | null;
if (!seasonId) {
throw new Response("Season ID is required", { status: 400 });
}
if (!userId) {
throw new Response("You must be logged in to view the draft room", { status: 401 });
}
const db = database();
// Get season details
@ -29,6 +39,19 @@ export async function loader({ params }: { params: { seasonId: string } }) {
throw new Response("Season not found", { status: 404 });
}
// Verify user access: must be team owner or commissioner
const userTeam = season.teams.find((team: any) => team.ownerId === userId);
const isCommissioner = await db.query.commissioners.findFirst({
where: and(
eq(schema.commissioners.leagueId, leagueId),
eq(schema.commissioners.userId, userId)
),
});
if (!userTeam && !isCommissioner) {
throw new Response("You do not have access to this draft room", { status: 403 });
}
// Get draft slots (draft order) - using select instead of query builder
const draftSlots = await db
.select({
@ -82,16 +105,23 @@ export async function loader({ params }: { params: { seasonId: string } }) {
)
.orderBy(asc(schema.participants.name));
// Load user's team queue if they have a team
const userQueue = userTeam ? await getTeamQueue(userTeam.id) : [];
return {
season,
draftSlots,
draftPicks,
availableParticipants,
userTeam,
userQueue,
isCommissioner: !!isCommissioner,
currentUserId: userId,
};
}
export default function DraftRoom() {
const { season, draftSlots, draftPicks, availableParticipants } =
const { season, draftSlots, draftPicks, availableParticipants, userTeam: _userTeam, isCommissioner: _isCommissioner } =
useLoaderData<typeof loader>();
const { isConnected, on, off } = useDraftSocket(season.id);
const [picks, setPicks] = useState(draftPicks);
@ -117,32 +147,45 @@ export default function DraftRoom() {
const currentRound = Math.ceil(currentPick / draftSlots.length);
return (
<div className="container mx-auto p-4">
{/* Header */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h1 className="text-3xl font-bold">
{season.league.name} - {season.year} Draft
</h1>
<div className="flex items-center gap-2">
<div
className={`w-3 h-3 rounded-full ${
isConnected ? "bg-green-500" : "bg-red-500"
}`}
/>
<span className="text-sm font-medium">
{isConnected ? "Live" : "Disconnected"}
</span>
<div className="min-h-screen bg-background">
{/* Standalone Header - No Main Nav */}
<div className="border-b bg-card">
<div className="container mx-auto p-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">
{season.league.name} - {season.year} Draft
</h1>
<div className="flex gap-4 text-sm text-muted-foreground mt-1">
<span>Round: {currentRound}</span>
<span>Pick: {currentPick}</span>
<span className="capitalize">{season.status.replace("_", " ")}</span>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<div
className={`w-3 h-3 rounded-full ${
isConnected ? "bg-green-500" : "bg-red-500"
}`}
/>
<span className="text-sm font-medium">
{isConnected ? "Live" : "Disconnected"}
</span>
</div>
<Button variant="outline" asChild>
<Link to={`/leagues/${season.leagueId}`}>
Exit Draft Room
</Link>
</Button>
</div>
</div>
</div>
<div className="flex gap-4 text-sm text-gray-600">
<span>Round: {currentRound}</span>
<span>Pick: {currentPick}</span>
<span>Status: {season.status}</span>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main Content */}
<div className="container mx-auto p-4">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column: Draft Board */}
<div className="lg:col-span-2">
<Card className="p-4">
@ -226,6 +269,7 @@ export default function DraftRoom() {
</Card>
</div>
</div>
</div>
</div>
);
}