diff --git a/app/components/draft/CommissionerControls.tsx b/app/components/draft/CommissionerControls.tsx
deleted file mode 100644
index d00f7c9..0000000
--- a/app/components/draft/CommissionerControls.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Button } from "~/components/ui/button";
-import { Play, Pause, FastForward } from "lucide-react";
-
-interface CommissionerControlsProps {
- isDraftStarted: boolean;
- isDraftPaused: boolean;
- isDraftComplete: boolean;
- currentTeamName: string;
- onStartDraft: () => void;
- onPauseDraft: () => void;
- onResumeDraft: () => void;
- onForcePick: () => void;
-}
-
-export function CommissionerControls({
- isDraftStarted,
- isDraftPaused,
- isDraftComplete,
- currentTeamName,
- onStartDraft,
- onPauseDraft,
- onResumeDraft,
- onForcePick,
-}: CommissionerControlsProps) {
- if (isDraftComplete) {
- return (
-
- );
- }
-
- if (!isDraftStarted) {
- return (
-
-
- The draft has not started yet. Click below to begin the draft.
-
-
-
- );
- }
-
- return (
-
- {/* Draft Status */}
-
-
- Draft Status:
-
- {isDraftPaused ? "Paused" : "Active"}
-
-
-
- Current Pick:
- {currentTeamName}
-
-
-
- {/* Control Buttons */}
-
- {isDraftPaused ? (
-
- ) : (
-
- )}
-
-
-
-
- {/* Help Text */}
-
-
- Force Pick: Automatically selects the top player from the
- current team's queue, or the highest EV available player if queue is empty.
-
-
-
- );
-}
diff --git a/app/components/draft/DraftGrid.tsx b/app/components/draft/DraftGrid.tsx
deleted file mode 100644
index 5206db8..0000000
--- a/app/components/draft/DraftGrid.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import { useMemo } from "react";
-import { DraftTimer } from "./DraftTimer";
-
-interface Team {
- id: string;
- name: string;
- draftOrder: number;
- ownerId: string | null;
-}
-
-interface DraftPick {
- id: string;
- teamId: string;
- participantId: string;
- pickNumber: number;
- round: number;
- pickInRound: number;
- pickedByType: "owner" | "commissioner" | "auto";
-}
-
-interface Participant {
- id: string;
- name: string;
- sport: string;
-}
-
-interface Timer {
- teamId: string;
- timeRemaining: number;
-}
-
-interface DraftGridProps {
- teams: Team[];
- draftPicks: DraftPick[];
- participants: Participant[];
- draftRounds: number;
- currentPickNumber: number;
- userTeamId?: string;
- timers?: Timer[];
-}
-
-export function DraftGrid({
- teams,
- draftPicks,
- participants,
- draftRounds,
- currentPickNumber,
- userTeamId,
- timers = [],
-}: DraftGridProps) {
- // Create a map of participant IDs to participant data for quick lookup
- const participantMap = useMemo(() => {
- return new Map(participants.map((p) => [p.id, p]));
- }, [participants]);
-
- // Create a map of pick numbers to draft picks
- const pickMap = useMemo(() => {
- return new Map(draftPicks.map((p) => [p.pickNumber, p]));
- }, [draftPicks]);
-
- // Create a map of team IDs to timers
- const timerMap = useMemo(() => {
- return new Map(timers.map((t) => [t.teamId, t]));
- }, [timers]);
-
- // Calculate pick number for a given round and team
- const getPickNumber = (round: number, teamIndex: number): number => {
- const isOddRound = round % 2 === 1;
- const pickInRound = isOddRound ? teamIndex + 1 : teams.length - teamIndex;
- return (round - 1) * teams.length + pickInRound;
- };
-
-
- return (
-
-
- {/* Header Row - Team Names */}
-
-
- Round
-
- {teams.map((team) => {
- const timer = timerMap.get(team.id);
- // Determine if this team is on the clock
- const currentPick = Math.ceil(currentPickNumber / teams.length);
- const pickInRound = ((currentPickNumber - 1) % teams.length) + 1;
- const isOddRound = currentPick % 2 === 1;
- const currentTeamIndex = isOddRound ? pickInRound - 1 : teams.length - pickInRound;
- const isOnClock = teams[currentTeamIndex]?.id === team.id;
-
- return (
-
-
- {team.name}
-
- {timer && (
-
-
-
- )}
-
- );
- })}
-
-
- {/* Draft Rounds */}
- {Array.from({ length: draftRounds }, (_, roundIndex) => {
- const round = roundIndex + 1;
- const isOddRound = round % 2 === 1;
-
- return (
-
- {/* Round Number */}
-
- {round}
-
-
- {/* Picks for this round */}
- {teams.map((team, teamIndex) => {
- const pickNumber = getPickNumber(round, teamIndex);
- const pick = pickMap.get(pickNumber);
- const participant = pick ? participantMap.get(pick.participantId) : null;
- const isCurrentPick = pickNumber === currentPickNumber;
- const isPastPick = pickNumber < currentPickNumber;
- const isUserTeam = team.id === userTeamId;
-
- return (
-
-
- {/* Pick Number */}
-
- {round}.{String(isOddRound ? teamIndex + 1 : teams.length - teamIndex).padStart(2, "0")}
-
-
- {/* Participant or Status */}
- {participant ? (
-
-
- {participant.name}
-
-
- {participant.sport}
-
- {pick && pick.pickedByType !== "owner" && (
-
- ({pick.pickedByType})
-
- )}
-
- ) : isCurrentPick ? (
-
- On the clock
-
- ) : isPastPick ? (
-
- Skipped
-
- ) : (
-
- -
-
- )}
-
-
- );
- })}
-
- );
- })}
-
-
- );
-}
diff --git a/app/components/draft/DraftQueue.tsx b/app/components/draft/DraftQueue.tsx
deleted file mode 100644
index a83eeac..0000000
--- a/app/components/draft/DraftQueue.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { useState } from "react";
-import { Button } from "~/components/ui/button";
-import { Badge } from "~/components/ui/badge";
-import { GripVertical, X } from "lucide-react";
-
-interface Participant {
- id: string;
- name: string;
- sport: string;
- expectedValue: number;
-}
-
-interface QueueItem {
- id: string;
- participantId: string;
- queuePosition: number;
-}
-
-interface DraftQueueProps {
- queueItems: QueueItem[];
- participants: Participant[];
- onReorder?: (participantIds: string[]) => void;
- onRemove?: (queueItemId: string) => void;
-}
-
-export function DraftQueue({
- queueItems,
- participants,
- onReorder,
- onRemove,
-}: DraftQueueProps) {
- const [draggedIndex, setDraggedIndex] = useState(null);
-
- // Create a map for quick participant lookup
- const participantMap = new Map(participants.map((p) => [p.id, p]));
-
- // Sort queue items by position
- const sortedQueue = [...queueItems].sort((a, b) => a.queuePosition - b.queuePosition);
-
- const handleDragStart = (index: number) => {
- setDraggedIndex(index);
- };
-
- const handleDragOver = (e: React.DragEvent, index: number) => {
- e.preventDefault();
- if (draggedIndex === null || draggedIndex === index) return;
-
- // Reorder the items
- const newQueue = [...sortedQueue];
- const draggedItem = newQueue[draggedIndex];
- newQueue.splice(draggedIndex, 1);
- newQueue.splice(index, 0, draggedItem);
-
- // Update positions and notify parent
- const participantIds = newQueue.map((item) => item.participantId);
- onReorder?.(participantIds);
- setDraggedIndex(index);
- };
-
- const handleDragEnd = () => {
- setDraggedIndex(null);
- };
-
- if (sortedQueue.length === 0) {
- return (
-
-
Your queue is empty
-
Add players from the Available Players list
-
- );
- }
-
- return (
-
- {sortedQueue.map((item, index) => {
- const participant = participantMap.get(item.participantId);
- if (!participant) return null;
-
- return (
-
handleDragStart(index)}
- onDragOver={(e) => handleDragOver(e, index)}
- onDragEnd={handleDragEnd}
- className={`flex items-center gap-2 p-3 rounded-lg border bg-card transition-all ${
- draggedIndex === index ? "opacity-50" : "hover:bg-accent cursor-move"
- }`}
- >
- {/* Drag Handle */}
-
-
- {index + 1}
-
-
- {/* Player Info */}
-
-
{participant.name}
-
-
- {participant.sport}
-
-
- EV: {participant.expectedValue.toFixed(1)}
-
-
-
-
- {/* Remove Button */}
- {onRemove && (
-
- )}
-
- );
- })}
-
- );
-}
diff --git a/app/components/draft/DraftTimer.tsx b/app/components/draft/DraftTimer.tsx
deleted file mode 100644
index d8ef01f..0000000
--- a/app/components/draft/DraftTimer.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-interface DraftTimerProps {
- timeRemaining: number; // in seconds
- isActive?: boolean;
-}
-
-export function DraftTimer({ timeRemaining, isActive = false }: DraftTimerProps) {
- // Format time as M:SS or H:MM:SS
- const formatTime = (seconds: number): string => {
- const hours = Math.floor(seconds / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
- const secs = seconds % 60;
-
- if (hours > 0) {
- return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
- }
- return `${minutes}:${String(secs).padStart(2, "0")}`;
- };
-
- // Determine color based on time remaining
- const getColorClass = (): string => {
- if (timeRemaining > 60) {
- return "text-green-600 dark:text-green-400";
- } else if (timeRemaining > 30) {
- return "text-yellow-600 dark:text-yellow-400";
- } else if (timeRemaining > 10) {
- return "text-red-600 dark:text-red-400";
- } else {
- return "text-red-600 dark:text-red-400 animate-pulse";
- }
- };
-
- return (
-
- {formatTime(timeRemaining)}
-
- );
-}
diff --git a/app/components/draft/PlayerList.tsx b/app/components/draft/PlayerList.tsx
deleted file mode 100644
index ce142fe..0000000
--- a/app/components/draft/PlayerList.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import { useState, useMemo } from "react";
-import { Input } from "~/components/ui/input";
-import { Button } from "~/components/ui/button";
-import { Badge } from "~/components/ui/badge";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "~/components/ui/select";
-import { Search, ArrowUpDown } from "lucide-react";
-
-interface Participant {
- id: string;
- name: string;
- sport: string;
- expectedValue: number;
-}
-
-interface DraftPick {
- participantId: string;
-}
-
-interface PlayerListProps {
- participants: Participant[];
- draftPicks: DraftPick[];
- onAddToQueue?: (participantId: string) => void;
- onDraftPlayer?: (participantId: string) => void;
- canDraft: boolean;
-}
-
-export function PlayerList({
- participants,
- draftPicks,
- onAddToQueue,
- onDraftPlayer,
- canDraft,
-}: PlayerListProps) {
- const [searchQuery, setSearchQuery] = useState("");
- const [sportFilter, setSportFilter] = useState("all");
- const [sortBy, setSortBy] = useState<"ev" | "name">("ev");
-
- // Get unique sports
- const sports = useMemo(() => {
- const uniqueSports = new Set(participants.map((p) => p.sport));
- return Array.from(uniqueSports).sort();
- }, [participants]);
-
- // Get drafted participant IDs
- const draftedIds = useMemo(() => {
- return new Set(draftPicks.map((p) => p.participantId));
- }, [draftPicks]);
-
- // Filter and sort participants
- const filteredParticipants = useMemo(() => {
- let filtered = participants.filter((p) => {
- // Filter out drafted players
- if (draftedIds.has(p.id)) return false;
-
- // Filter by search query
- if (searchQuery && !p.name.toLowerCase().includes(searchQuery.toLowerCase())) {
- return false;
- }
-
- // Filter by sport
- if (sportFilter !== "all" && p.sport !== sportFilter) {
- return false;
- }
-
- return true;
- });
-
- // Sort
- if (sortBy === "ev") {
- filtered.sort((a, b) => b.expectedValue - a.expectedValue);
- } else {
- filtered.sort((a, b) => a.name.localeCompare(b.name));
- }
-
- return filtered;
- }, [participants, draftedIds, searchQuery, sportFilter, sortBy]);
-
- return (
-
- {/* Filters */}
-
- {/* Search */}
-
-
- setSearchQuery(e.target.value)}
- className="pl-9"
- />
-
-
- {/* Sport Filter and Sort */}
-
-
-
-
-
-
-
- {/* Player Count */}
-
- {filteredParticipants.length} player{filteredParticipants.length !== 1 ? "s" : ""} available
-
-
- {/* Player Table */}
-
- {filteredParticipants.length === 0 ? (
-
- No players found
-
- ) : (
-
-
-
- | Player |
- Sport |
- EV |
- Actions |
-
-
-
- {filteredParticipants.map((participant) => (
-
- | {participant.name} |
-
-
- {participant.sport}
-
- |
-
- {participant.expectedValue.toFixed(1)}
- |
-
-
- {onAddToQueue && (
-
- )}
- {canDraft && onDraftPlayer && (
-
- )}
-
- |
-
- ))}
-
-
- )}
-
-
- );
-}
diff --git a/app/hooks/useDraftSocket.ts b/app/hooks/useDraftSocket.ts
deleted file mode 100644
index 88f771f..0000000
--- a/app/hooks/useDraftSocket.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { useEffect, useState, useCallback } from "react";
-import { io, Socket } from "socket.io-client";
-
-const SOCKET_URL = typeof window !== "undefined"
- ? window.location.origin
- : "http://localhost:3000";
-
-export interface DraftSocketEvents {
- // Events emitted by server
- "pick-made": (data: any) => void;
- "timer-update": (data: any) => void;
- "current-pick-changed": (data: any) => void;
- "draft-started": (data: any) => void;
- "draft-paused": (data: any) => void;
- "draft-completed": (data: any) => void;
- "queue-updated": (data: any) => void;
- "user-joined": (data: any) => void;
-}
-
-export function useDraftSocket(seasonId: string | null) {
- const [socket, setSocket] = useState(null);
- const [isConnected, setIsConnected] = useState(false);
-
- useEffect(() => {
- if (!seasonId) return;
-
- // Create socket connection
- const newSocket = io(SOCKET_URL, {
- transports: ["websocket", "polling"],
- });
-
- newSocket.on("connect", () => {
- console.log("Socket connected:", newSocket.id);
- setIsConnected(true);
-
- // Join the draft room
- newSocket.emit("join-draft", seasonId);
- });
-
- newSocket.on("disconnect", () => {
- console.log("Socket disconnected");
- setIsConnected(false);
- });
-
- newSocket.on("connect_error", (error) => {
- console.error("Socket connection error:", error);
- setIsConnected(false);
- });
-
- setSocket(newSocket);
-
- // Cleanup on unmount
- return () => {
- if (newSocket) {
- newSocket.emit("leave-draft", seasonId);
- newSocket.close();
- }
- };
- }, [seasonId]);
-
- // Helper to emit events
- const emit = useCallback((event: string, data?: any) => {
- if (socket && isConnected) {
- socket.emit(event, data);
- } else {
- console.warn(`Cannot emit ${event}: socket not connected`);
- }
- }, [socket, isConnected]);
-
- // Helper to listen to events
- const on = useCallback((
- event: K,
- handler: DraftSocketEvents[K]
- ) => {
- if (socket) {
- socket.on(event as string, handler as any);
- }
- }, [socket]);
-
- // Helper to remove event listeners
- const off = useCallback((
- event: K,
- handler?: DraftSocketEvents[K]
- ) => {
- if (socket) {
- if (handler) {
- socket.off(event as string, handler as any);
- } else {
- socket.off(event as string);
- }
- }
- }, [socket]);
-
- return {
- socket,
- isConnected,
- emit,
- on,
- off,
- };
-}
diff --git a/app/models/season.ts b/app/models/season.ts
index 1b4167b..46d9a8c 100644
--- a/app/models/season.ts
+++ b/app/models/season.ts
@@ -139,7 +139,6 @@ export async function findSeasonWithSportsSeasons(
sportsSeason: {
with: {
sport: true,
- participants: true,
},
},
},
diff --git a/app/routes.ts b/app/routes.ts
index 6b7a80b..1e08d0c 100644
--- a/app/routes.ts
+++ b/app/routes.ts
@@ -6,10 +6,8 @@ export default [
route("leagues/new", "routes/leagues/new.tsx"),
route("leagues/:leagueId", "routes/leagues/$leagueId.tsx"),
route("leagues/:leagueId/settings", "routes/leagues/$leagueId.settings.tsx"),
- route("leagues/:leagueId/draft", "routes/leagues/$leagueId.draft.tsx"),
route("teams/:teamId/settings", "routes/teams/$teamId.settings.tsx"),
route("api/webhooks/clerk", "routes/api/webhooks/clerk.ts"),
- route("api/draft-actions", "routes/api/draft-actions.ts"),
route("user-profile", "routes/user-profile.tsx"),
route("how-to-play", "routes/how-to-play.tsx"),
diff --git a/app/routes/api/draft-actions.ts b/app/routes/api/draft-actions.ts
deleted file mode 100644
index 269dacb..0000000
--- a/app/routes/api/draft-actions.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { data, type ActionFunctionArgs } from "react-router";
-import { getAuth } from "@clerk/react-router/server";
-import {
- addToQueue,
- removeFromQueue,
- reorderQueueWithSeason,
- findTeamById,
- getTeamQueue,
-} from "~/models";
-
-export async function action(args: ActionFunctionArgs) {
- const { userId } = await getAuth(args);
- if (!userId) {
- return data({ error: "Unauthorized" }, { status: 401 });
- }
-
- const { request } = args;
-
- const formData = await request.formData();
- const actionType = formData.get("action");
- const teamId = formData.get("teamId");
-
- if (!teamId || typeof teamId !== "string") {
- return data({ error: "Team ID is required" }, { status: 400 });
- }
-
- // Verify user owns this team
- const team = await findTeamById(teamId);
- if (!team || team.ownerId !== userId) {
- return data({ error: "You do not own this team" }, { status: 403 });
- }
-
- try {
- switch (actionType) {
- case "add-to-queue": {
- const participantId = formData.get("participantId");
- const seasonId = formData.get("seasonId");
-
- if (!participantId || typeof participantId !== "string") {
- return data({ error: "Participant ID is required" }, { status: 400 });
- }
- if (!seasonId || typeof seasonId !== "string") {
- return data({ error: "Season ID is required" }, { status: 400 });
- }
-
- // Get current queue to determine position
- const currentQueue = await getTeamQueue(teamId);
- const queuePosition = currentQueue.length + 1;
-
- await addToQueue({
- seasonId,
- teamId,
- participantId,
- queuePosition,
- });
-
- return data({ success: true });
- }
-
- case "remove-from-queue": {
- const queueItemId = formData.get("queueItemId");
-
- if (!queueItemId || typeof queueItemId !== "string") {
- return data({ error: "Queue item ID is required" }, { status: 400 });
- }
-
- await removeFromQueue(queueItemId);
- return data({ success: true });
- }
-
- case "reorder-queue": {
- const participantIds = formData.get("participantIds");
- const seasonId = formData.get("seasonId");
-
- if (!participantIds || typeof participantIds !== "string") {
- return data({ error: "Participant IDs are required" }, { status: 400 });
- }
- if (!seasonId || typeof seasonId !== "string") {
- return data({ error: "Season ID is required" }, { status: 400 });
- }
-
- const idsArray = JSON.parse(participantIds);
- await reorderQueueWithSeason(seasonId, teamId, idsArray);
-
- return data({ success: true });
- }
-
- default:
- return data({ error: "Invalid action" }, { status: 400 });
- }
- } catch (error) {
- console.error("Draft action error:", error);
- return data({ error: "Failed to perform action" }, { status: 500 });
- }
-}
diff --git a/app/routes/api/draft-commissioner.ts b/app/routes/api/draft-commissioner.ts
deleted file mode 100644
index ec311a1..0000000
--- a/app/routes/api/draft-commissioner.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-import { data, type ActionFunctionArgs } from "react-router";
-import { getAuth } from "@clerk/react-router/server";
-import {
- findSeasonById,
- updateSeason,
- updateSeasonStatus,
- findTeamsBySeasonId,
- isCommissioner,
- createDraftPick,
- initializeDraftTimers,
- updateTeamTimer,
- getSeasonTimers,
- autoPickForTeam,
- calculatePickInfo,
- removeFromQueue,
- getTeamQueue,
-} from "~/models";
-import { getSocketIO } from "../../../server/socket.js";
-
-export async function action(args: ActionFunctionArgs) {
- const { userId } = await getAuth(args);
- if (!userId) {
- return data({ error: "Unauthorized" }, { status: 401 });
- }
-
- const { request } = args;
- const formData = await request.formData();
- const actionType = formData.get("action");
- const seasonId = formData.get("seasonId");
-
- if (!seasonId || typeof seasonId !== "string") {
- return data({ error: "Season ID is required" }, { status: 400 });
- }
-
- // Get season
- const season = await findSeasonById(seasonId);
- if (!season) {
- return data({ error: "Season not found" }, { status: 404 });
- }
-
- // Verify user is commissioner
- const isUserCommissioner = await isCommissioner(season.leagueId, userId);
- if (!isUserCommissioner) {
- return data({ error: "Only commissioners can perform this action" }, { status: 403 });
- }
-
- try {
- const io = getSocketIO();
-
- switch (actionType) {
- case "start-draft": {
- // Check if draft already started
- if (season.draftStartedAt) {
- return data({ error: "Draft has already started" }, { status: 400 });
- }
-
- // Get teams
- const teams = await findTeamsBySeasonId(seasonId);
-
- // Initialize timers
- await initializeDraftTimers(seasonId, teams, season.draftInitialTime || 120);
-
- // Update season
- await updateSeason(seasonId, {
- draftStartedAt: new Date(),
- currentPickNumber: 1,
- draftPaused: false,
- });
- await updateSeasonStatus(seasonId, "draft");
-
- // Get updated timers
- const timers = await getSeasonTimers(seasonId);
-
- // Broadcast to all clients
- io.to(`draft-${seasonId}`).emit("draft-started", {
- seasonId,
- startedAt: new Date(),
- timers,
- });
-
- return data({ success: true });
- }
-
- case "pause-draft": {
- const pause = formData.get("pause") === "true";
-
- // Check if draft has started
- if (!season.draftStartedAt) {
- return data({ error: "Draft has not started yet" }, { status: 400 });
- }
-
- // Update season
- await updateSeason(seasonId, { draftPaused: pause });
-
- // Broadcast to all clients
- io.to(`draft-${seasonId}`).emit("draft-paused", {
- seasonId,
- isPaused: pause,
- });
-
- return data({ success: true });
- }
-
- case "force-pick": {
- const teamId = formData.get("teamId");
-
- if (!teamId || typeof teamId !== "string") {
- return data({ error: "Team ID is required" }, { status: 400 });
- }
-
- // Check if draft has started
- if (!season.draftStartedAt) {
- return data({ error: "Draft has not started" }, { status: 400 });
- }
-
- // Check if draft is complete
- const teams = await findTeamsBySeasonId(seasonId);
- const totalPicks = season.draftRounds * teams.length;
- const currentPickNumber = season.currentPickNumber || 1;
-
- if (currentPickNumber > totalPicks) {
- return data({ error: "Draft is already complete" }, { status: 400 });
- }
-
- // Auto-pick for the team
- const participantId = await autoPickForTeam(seasonId, teamId);
-
- if (!participantId) {
- return data({ error: "No available participants to pick" }, { status: 400 });
- }
-
- // Calculate pick info
- const { round, pickInRound } = calculatePickInfo(currentPickNumber, teams.length);
-
- // Create the draft pick
- const pick = await createDraftPick({
- seasonId,
- teamId,
- participantId,
- pickNumber: currentPickNumber,
- round,
- pickInRound,
- pickedByUserId: userId,
- pickedByType: "commissioner",
- timeUsed: 0,
- });
-
- // Remove from queue if it was queued
- const queue = await getTeamQueue(teamId);
- const queueItem = queue.find((item: any) => item.participantId === participantId);
- if (queueItem) {
- await removeFromQueue(queueItem.id);
- }
-
- // Update season to next pick
- const nextPickNumber = currentPickNumber + 1;
- await updateSeason(seasonId, { currentPickNumber: nextPickNumber });
-
- // Add increment time to current team's timer
- if (season.draftIncrementTime) {
- const timers = await getSeasonTimers(seasonId);
- const currentTimer = timers.find((t: any) => t.teamId === teamId);
- if (currentTimer) {
- const newTime = currentTimer.timeRemaining + season.draftIncrementTime;
- await updateTeamTimer(teamId, newTime);
- }
- }
-
- // Check if draft is complete
- if (nextPickNumber > totalPicks) {
- await updateSeasonStatus(seasonId, "active");
- io.to(`draft-${seasonId}`).emit("draft-completed", {
- seasonId,
- });
- }
-
- // Broadcast pick to all clients in the room
- io.to(`draft-${seasonId}`).emit("pick-made", {
- pick,
- nextPickNumber,
- isDraftComplete: nextPickNumber > totalPicks,
- });
-
- // Emit queue update to team owner
- const updatedQueue = await getTeamQueue(teamId);
- io.to(`draft-${seasonId}`).emit("queue-updated", {
- teamId,
- queue: updatedQueue,
- });
-
- return data({ success: true, pick });
- }
-
- default:
- return data({ error: "Invalid action" }, { status: 400 });
- }
- } catch (error) {
- console.error("Commissioner action error:", error);
- return data({
- error: error instanceof Error ? error.message : "Failed to perform action"
- }, { status: 500 });
- }
-}
diff --git a/app/routes/api/draft-pick.ts b/app/routes/api/draft-pick.ts
deleted file mode 100644
index 0293154..0000000
--- a/app/routes/api/draft-pick.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-import { data, type ActionFunctionArgs } from "react-router";
-import { getAuth } from "@clerk/react-router/server";
-import {
- findSeasonById,
- updateSeason,
- updateSeasonStatus,
- findTeamsBySeasonId,
- isCommissioner,
- createDraftPick,
- isParticipantDrafted,
- updateTeamTimer,
- getSeasonTimers,
- calculatePickInfo,
- getTeamForPick,
- findDraftSlotsBySeasonId,
- removeFromQueue,
- getTeamQueue,
-} from "~/models";
-import { getSocketIO } from "../../../server/socket.js";
-
-export async function action(args: ActionFunctionArgs) {
- const { userId } = await getAuth(args);
- if (!userId) {
- return data({ error: "Unauthorized" }, { status: 401 });
- }
-
- const { request } = args;
- const formData = await request.formData();
-
- const seasonId = formData.get("seasonId");
- const teamId = formData.get("teamId");
- const participantId = formData.get("participantId");
-
- if (!seasonId || typeof seasonId !== "string") {
- return data({ error: "Season ID is required" }, { status: 400 });
- }
-
- if (!teamId || typeof teamId !== "string") {
- return data({ error: "Team ID is required" }, { status: 400 });
- }
-
- if (!participantId || typeof participantId !== "string") {
- return data({ error: "Participant ID is required" }, { status: 400 });
- }
-
- try {
- // Get season
- const season = await findSeasonById(seasonId);
- if (!season) {
- return data({ error: "Season not found" }, { status: 404 });
- }
-
- // Check if draft has started
- if (!season.draftStartedAt) {
- return data({ error: "Draft has not started" }, { status: 400 });
- }
-
- // Check if draft is paused
- if (season.draftPaused) {
- return data({ error: "Draft is currently paused" }, { status: 400 });
- }
-
- // Check if draft is complete
- const teams = await findTeamsBySeasonId(seasonId);
- const totalPicks = season.draftRounds * teams.length;
- const currentPickNumber = season.currentPickNumber || 1;
-
- if (currentPickNumber > totalPicks) {
- return data({ error: "Draft is already complete" }, { status: 400 });
- }
-
- // Get draft slots to determine current team
- const draftSlots = await findDraftSlotsBySeasonId(seasonId);
- const draftOrder = draftSlots.map((slot: any) => ({
- teamId: slot.teamId,
- draftOrder: slot.draftOrder,
- }));
-
- const currentTeamId = getTeamForPick(currentPickNumber, draftOrder);
-
- if (!currentTeamId) {
- return data({ error: "Could not determine current team" }, { status: 400 });
- }
-
- // Verify it's the correct team's turn
- if (teamId !== currentTeamId) {
- return data({ error: "It is not this team's turn" }, { status: 400 });
- }
-
- // Verify user has permission (team owner or commissioner)
- const team = teams.find((t: any) => t.id === teamId);
- const isTeamOwner = team?.ownerId === userId;
- const isUserCommissioner = await isCommissioner(season.leagueId, userId);
-
- if (!isTeamOwner && !isUserCommissioner) {
- return data({ error: "You do not have permission to make this pick" }, { status: 403 });
- }
-
- // Check if participant is already drafted
- const isDrafted = await isParticipantDrafted(seasonId, participantId);
- if (isDrafted) {
- return data({ error: "This participant has already been drafted" }, { status: 400 });
- }
-
- // Calculate pick info
- const { round, pickInRound } = calculatePickInfo(currentPickNumber, teams.length);
-
- // Determine picked by type
- const pickedByType = isUserCommissioner && !isTeamOwner ? "commissioner" : "owner";
-
- // Create the draft pick
- const pick = await createDraftPick({
- seasonId,
- teamId,
- participantId,
- pickNumber: currentPickNumber,
- round,
- pickInRound,
- pickedByUserId: userId,
- pickedByType,
- timeUsed: 0,
- });
-
- // Remove from queue if it was queued
- const queue = await getTeamQueue(teamId);
- const queueItem = queue.find((item: any) => item.participantId === participantId);
- if (queueItem) {
- await removeFromQueue(queueItem.id);
- }
-
- // Update season to next pick
- const nextPickNumber = currentPickNumber + 1;
- await updateSeason(seasonId, { currentPickNumber: nextPickNumber });
-
- // Add increment time to current team's timer
- if (season.draftIncrementTime) {
- const timers = await getSeasonTimers(seasonId);
- const currentTimer = timers.find((t: any) => t.teamId === teamId);
- if (currentTimer) {
- const newTime = currentTimer.timeRemaining + season.draftIncrementTime;
- await updateTeamTimer(teamId, newTime);
- }
- }
-
- // Check if draft is complete
- const io = getSocketIO();
- if (nextPickNumber > totalPicks) {
- await updateSeasonStatus(seasonId, "active");
- io.to(`draft-${seasonId}`).emit("draft-completed", {
- seasonId,
- });
- }
-
- // Broadcast pick to all clients in the room
- io.to(`draft-${seasonId}`).emit("pick-made", {
- pick,
- nextPickNumber,
- isDraftComplete: nextPickNumber > totalPicks,
- });
-
- // Emit queue update to team owner
- const updatedQueue = await getTeamQueue(teamId);
- io.to(`draft-${seasonId}`).emit("queue-updated", {
- teamId,
- queue: updatedQueue,
- });
-
- return data({ success: true, pick });
- } catch (error) {
- console.error("Error making pick:", error);
- return data({
- error: error instanceof Error ? error.message : "Failed to make pick",
- }, { status: 500 });
- }
-}
diff --git a/app/routes/leagues/$leagueId.draft.tsx b/app/routes/leagues/$leagueId.draft.tsx
deleted file mode 100644
index 9860b28..0000000
--- a/app/routes/leagues/$leagueId.draft.tsx
+++ /dev/null
@@ -1,412 +0,0 @@
-import { data, redirect } from "react-router";
-import type { LoaderFunctionArgs } from "react-router";
-import { getAuth } from "@clerk/react-router/server";
-import { useEffect, useState } from "react";
-import {
- findLeagueById,
- findCurrentSeason,
- findTeamsBySeasonId,
- isCommissioner,
- findDraftSlotsBySeasonId,
- getDraftPicks,
- getSeasonTimers,
- findSeasonWithSportsSeasons,
- getTeamQueue,
- getTeamForPick,
-} from "~/models";
-import { DraftGrid } from "~/components/draft/DraftGrid";
-import { PlayerList } from "~/components/draft/PlayerList";
-import { DraftQueue } from "~/components/draft/DraftQueue";
-import { CommissionerControls } from "~/components/draft/CommissionerControls";
-import { useDraftSocket } from "~/hooks/useDraftSocket";
-
-export async function loader(args: LoaderFunctionArgs) {
- const { userId } = await getAuth(args);
- if (!userId) {
- throw redirect("/");
- }
-
- const { leagueId } = args.params;
- if (!leagueId) {
- throw new Response("League ID is required", { status: 400 });
- }
-
- // Get league and season
- const league = await findLeagueById(leagueId);
- if (!league) {
- throw new Response("League not found", { status: 404 });
- }
-
- const season = await findCurrentSeason(leagueId);
- if (!season) {
- throw new Response("No active season found", { status: 404 });
- }
-
- // Get season with sports and participants
- const seasonWithSports = await findSeasonWithSportsSeasons(season.id);
- const participants = seasonWithSports?.seasonSports?.flatMap(
- (ss: any) => {
- const sportName = ss.sportsSeason?.sport?.name || "Unknown";
- return (ss.sportsSeason?.participants || []).map((p: any) => ({
- ...p,
- sport: sportName,
- }));
- }
- ) || [];
-
- // Get teams and draft order
- const teams = await findTeamsBySeasonId(season.id);
- const draftSlots = await findDraftSlotsBySeasonId(season.id);
-
- // Sort teams by draft order
- const teamsWithOrder = teams.map((team) => {
- const slot = draftSlots.find((s: { teamId: string }) => s.teamId === team.id);
- return {
- ...team,
- draftOrder: slot?.draftOrder || 0,
- };
- }).sort((a, b) => a.draftOrder - b.draftOrder);
-
- // Check user permissions
- const isUserCommissioner = await isCommissioner(leagueId, userId);
- const userTeam = teams.find((team) => team.ownerId === userId);
-
- // Get draft data
- const draftPicks = await getDraftPicks(season.id);
- let timers = await getSeasonTimers(season.id);
-
- // If no timers exist, create default timers for display
- if (timers.length === 0) {
- timers = teams.map((team) => ({
- id: team.id,
- seasonId: season.id,
- teamId: team.id,
- timeRemaining: season.draftInitialTime,
- createdAt: new Date(),
- updatedAt: new Date(),
- }));
- }
-
- // Get user's queue if they have a team
- const queueItems = userTeam ? await getTeamQueue(userTeam.id) : [];
-
- // Calculate current pick info
- const totalPicks = season.draftRounds * teams.length;
- const currentPickNumber = season.currentPickNumber || 1;
- const isDraftComplete = currentPickNumber > totalPicks;
-
- return data({
- league,
- season,
- teams: teamsWithOrder,
- draftSlots,
- isCommissioner: isUserCommissioner,
- userTeam,
- draftPicks,
- participants,
- queueItems,
- timers,
- currentPickNumber,
- totalPicks,
- isDraftComplete,
- userId,
- });
-}
-
-export default function DraftRoom({ loaderData }: { loaderData: Awaited>["data"] }) {
- const {
- league,
- season,
- teams,
- draftPicks: initialDraftPicks,
- participants,
- queueItems: initialQueueItems,
- timers: initialTimers,
- isCommissioner,
- userTeam,
- currentPickNumber: initialPickNumber,
- totalPicks,
- isDraftComplete: initialIsDraftComplete,
- draftSlots,
- } = loaderData;
-
- // Local state for real-time updates
- const [draftPicks, setDraftPicks] = useState(initialDraftPicks);
- const [queueItems, setQueueItems] = useState(initialQueueItems);
- const [timers, setTimers] = useState(initialTimers);
- const [currentPickNumber, setCurrentPickNumber] = useState(initialPickNumber);
- const [isDraftComplete, setIsDraftComplete] = useState(initialIsDraftComplete);
- const [isDraftStarted, setIsDraftStarted] = useState(!!season.draftStartedAt);
- const [isDraftPaused, setIsDraftPaused] = useState(season.draftPaused || false);
-
- // Socket connection
- const { on, off } = useDraftSocket(season.id);
-
- // Calculate current team
- const draftOrder = draftSlots.map((slot: any) => ({
- teamId: slot.teamId,
- draftOrder: slot.draftOrder,
- }));
- const currentTeamId = getTeamForPick(currentPickNumber, draftOrder);
- const currentTeam = teams.find((t: any) => t.id === currentTeamId);
- const currentTeamName = currentTeam?.name || "Unknown";
-
- // Socket event handlers
- useEffect(() => {
- // Listen for pick made
- const handlePickMade = (data: any) => {
- setDraftPicks((prev: any) => [...prev, data.pick]);
- setCurrentPickNumber(data.nextPickNumber);
- setIsDraftComplete(data.isDraftComplete);
- };
-
- // Listen for draft started
- const handleDraftStarted = (data: any) => {
- setIsDraftStarted(true);
- setIsDraftPaused(false);
- setTimers(data.timers);
- };
-
- // Listen for draft paused
- const handleDraftPaused = (data: any) => {
- setIsDraftPaused(data.isPaused);
- };
-
- // Listen for draft completed
- const handleDraftCompleted = () => {
- setIsDraftComplete(true);
- };
-
- // Listen for queue updates
- const handleQueueUpdated = (data: any) => {
- if (userTeam && data.teamId === userTeam.id) {
- setQueueItems(data.queue);
- }
- };
-
- on("pick-made", handlePickMade);
- on("draft-started", handleDraftStarted);
- on("draft-paused", handleDraftPaused);
- on("draft-completed", handleDraftCompleted);
- on("queue-updated", handleQueueUpdated);
-
- return () => {
- off("pick-made", handlePickMade);
- off("draft-started", handleDraftStarted);
- off("draft-paused", handleDraftPaused);
- off("draft-completed", handleDraftCompleted);
- off("queue-updated", handleQueueUpdated);
- };
- }, [on, off, userTeam]);
-
- // Commissioner action handlers
- const handleStartDraft = async () => {
- try {
- const formData = new FormData();
- formData.append("action", "start-draft");
- formData.append("seasonId", season.id);
-
- const response = await fetch("/api/draft-commissioner", {
- method: "POST",
- body: formData,
- });
-
- if (!response.ok) {
- const error = await response.json();
- console.error("Failed to start draft:", error);
- }
- } catch (error) {
- console.error("Error starting draft:", error);
- }
- };
-
- const handlePauseDraft = async () => {
- try {
- const formData = new FormData();
- formData.append("action", "pause-draft");
- formData.append("seasonId", season.id);
- formData.append("pause", "true");
-
- const response = await fetch("/api/draft-commissioner", {
- method: "POST",
- body: formData,
- });
-
- if (!response.ok) {
- const error = await response.json();
- console.error("Failed to pause draft:", error);
- }
- } catch (error) {
- console.error("Error pausing draft:", error);
- }
- };
-
- const handleResumeDraft = async () => {
- try {
- const formData = new FormData();
- formData.append("action", "pause-draft");
- formData.append("seasonId", season.id);
- formData.append("pause", "false");
-
- const response = await fetch("/api/draft-commissioner", {
- method: "POST",
- body: formData,
- });
-
- if (!response.ok) {
- const error = await response.json();
- console.error("Failed to resume draft:", error);
- }
- } catch (error) {
- console.error("Error resuming draft:", error);
- }
- };
-
- const handleForcePick = async () => {
- if (!currentTeamId) return;
-
- try {
- const formData = new FormData();
- formData.append("action", "force-pick");
- formData.append("seasonId", season.id);
- formData.append("teamId", currentTeamId);
-
- const response = await fetch("/api/draft-commissioner", {
- method: "POST",
- body: formData,
- });
-
- if (!response.ok) {
- const error = await response.json();
- console.error("Failed to force pick:", error);
- }
- } catch (error) {
- console.error("Error forcing pick:", error);
- }
- };
-
- return (
-
- {/* Header */}
-
-
-
-
-
{league.name} Draft
-
- {season.year} Season • Pick {currentPickNumber} of {totalPicks}
-
-
-
- {isCommissioner && (
-
- Commissioner
-
- )}
- {userTeam && (
-
- {userTeam.name}
-
- )}
-
-
-
-
-
- {/* Main Content */}
-
- {isDraftComplete ? (
-
-
Draft Complete!
-
- All picks have been made. The season is ready to begin.
-
-
- ) : (
-
- {/* Draft Board - Full Width */}
-
-
Draft Board
-
-
-
- {/* 2x2 Grid Below */}
-
- {/* Available Players */}
-
-
- {/* Your Queue */}
-
-
- {/* Draft Timer */}
-
-
Draft Timer
-
- Timer will be implemented in Phase 8
-
-
-
- {/* Commissioner Controls */}
- {isCommissioner ? (
-
-
Commissioner Controls
-
-
- ) : (
-
-
Draft Info
-
-
- Current Pick:
- {currentPickNumber} of {totalPicks}
-
-
- Draft Rounds:
- {season.draftRounds}
-
-
- Current Team:
- {currentTeamName}
-
-
-
- )}
-
-
- )}
-
-
- );
-}
diff --git a/app/routes/leagues/$leagueId.tsx b/app/routes/leagues/$leagueId.tsx
index 6d3393a..aef86b6 100644
--- a/app/routes/leagues/$leagueId.tsx
+++ b/app/routes/leagues/$leagueId.tsx
@@ -372,15 +372,6 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
>
)}
- {season && (
-
-
-
-
-
- )}
diff --git a/package-lock.json b/package-lock.json
index c7f983d..8f81a3c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,8 +34,6 @@
"react-day-picker": "^9.11.1",
"react-dom": "^19.1.0",
"react-router": "^7.7.1",
- "socket.io": "^4.8.1",
- "socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"svix": "^1.77.0",
"tailwind-merge": "^3.3.1"
@@ -53,7 +51,7 @@
"@types/react-dom": "^19.1.2",
"dotenv-cli": "^8.0.0",
"tailwindcss": "^4.1.4",
- "tsx": "^4.20.6",
+ "tsx": "^4.19.2",
"tw-animate-css": "^1.4.0",
"typescript": "^5.8.3",
"vite": "^6.3.3",
@@ -2865,12 +2863,6 @@
"win32"
]
},
- "node_modules/@socket.io/component-emitter": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
- "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
- "license": "MIT"
- },
"node_modules/@stablelib/base64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
@@ -3186,15 +3178,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/cors": {
- "version": "2.8.19",
- "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
- "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -3255,6 +3238,7 @@
"version": "20.19.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.20.tgz",
"integrity": "sha512-2Q7WS25j4pS1cS8yw3d6buNCVJukOTeQ39bAnwR6sOJbaxvyCGebzTMypDFN82CxBLnl+lSWVdCCWbRY6y9yZQ==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -3426,15 +3410,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/base64id": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
- "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
- "license": "MIT",
- "engines": {
- "node": "^4.5.0 || >= 5.9"
- }
- },
"node_modules/baseline-browser-mapping": {
"version": "2.8.16",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
@@ -3760,19 +3735,6 @@
"node": ">=6.6.0"
}
},
- "node_modules/cors": {
- "version": "2.8.5",
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
- "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4",
- "vary": "^1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4121,125 +4083,6 @@
"node": ">= 0.8"
}
},
- "node_modules/engine.io": {
- "version": "6.6.4",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
- "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
- "license": "MIT",
- "dependencies": {
- "@types/cors": "^2.8.12",
- "@types/node": ">=10.0.0",
- "accepts": "~1.3.4",
- "base64id": "2.0.0",
- "cookie": "~0.7.2",
- "cors": "~2.8.5",
- "debug": "~4.3.1",
- "engine.io-parser": "~5.2.1",
- "ws": "~8.17.1"
- },
- "engines": {
- "node": ">=10.2.0"
- }
- },
- "node_modules/engine.io-client": {
- "version": "6.6.3",
- "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
- "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
- "license": "MIT",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1",
- "engine.io-parser": "~5.2.1",
- "ws": "~8.17.1",
- "xmlhttprequest-ssl": "~2.1.1"
- }
- },
- "node_modules/engine.io-client/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/engine.io-parser": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
- "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/engine.io/node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "license": "MIT",
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/engine.io/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/engine.io/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/engine.io/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/engine.io/node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/enhanced-resolve": {
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
@@ -5414,15 +5257,6 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -6231,173 +6065,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/socket.io": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
- "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
- "license": "MIT",
- "dependencies": {
- "accepts": "~1.3.4",
- "base64id": "~2.0.0",
- "cors": "~2.8.5",
- "debug": "~4.3.2",
- "engine.io": "~6.6.0",
- "socket.io-adapter": "~2.5.2",
- "socket.io-parser": "~4.2.4"
- },
- "engines": {
- "node": ">=10.2.0"
- }
- },
- "node_modules/socket.io-adapter": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
- "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
- "license": "MIT",
- "dependencies": {
- "debug": "~4.3.4",
- "ws": "~8.17.1"
- }
- },
- "node_modules/socket.io-adapter/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io-client": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
- "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
- "license": "MIT",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.2",
- "engine.io-client": "~6.6.1",
- "socket.io-parser": "~4.2.4"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/socket.io-client/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io-parser": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
- "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
- "license": "MIT",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/socket.io-parser/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io/node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "license": "MIT",
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/socket.io/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/socket.io/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/socket.io/node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
@@ -7232,6 +6899,7 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "devOptional": true,
"license": "MIT"
},
"node_modules/unpipe": {
@@ -8062,35 +7730,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
- "node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/xmlhttprequest-ssl": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
- "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index 38e9a3a..3de31db 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,9 @@
"build": "react-router build",
"db:generate": "dotenv -- drizzle-kit generate",
"db:migrate": "dotenv -- drizzle-kit migrate",
- "dev": "dotenv -- tsx server/index.ts",
- "start": "tsx server/index.ts",
- "start:production": "drizzle-kit migrate && rm -rf drizzle drizzle.config.ts database && tsx server/index.ts",
+ "dev": "dotenv -- node server.js",
+ "start": "node server.js",
+ "start:production": "drizzle-kit migrate && rm -rf drizzle drizzle.config.ts database && node server.js",
"typecheck": "react-router typegen && tsc -b"
},
"dependencies": {
@@ -40,8 +40,6 @@
"react-day-picker": "^9.11.1",
"react-dom": "^19.1.0",
"react-router": "^7.7.1",
- "socket.io": "^4.8.1",
- "socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"svix": "^1.77.0",
"tailwind-merge": "^3.3.1"
@@ -59,7 +57,7 @@
"@types/react-dom": "^19.1.2",
"dotenv-cli": "^8.0.0",
"tailwindcss": "^4.1.4",
- "tsx": "^4.20.6",
+ "tsx": "^4.19.2",
"tw-animate-css": "^1.4.0",
"typescript": "^5.8.3",
"vite": "^6.3.3",
diff --git a/server/index.ts b/server.js
similarity index 79%
rename from server/index.ts
rename to server.js
index 2d10f29..50fea2b 100644
--- a/server/index.ts
+++ b/server.js
@@ -1,6 +1,5 @@
import compression from "compression";
import express from "express";
-import { createServer } from "http";
import morgan from "morgan";
// Short-circuit the type-checking of the built output.
@@ -9,7 +8,6 @@ const DEVELOPMENT = process.env.NODE_ENV === "development";
const PORT = Number.parseInt(process.env.PORT || "3000");
const app = express();
-const httpServer = createServer(app);
app.use(compression());
app.disable("x-powered-by");
@@ -44,13 +42,6 @@ if (DEVELOPMENT) {
app.use(await import(BUILD_PATH).then((mod) => mod.app));
}
-// Initialize Socket.IO
-const socketModule = await import("./socket.js");
-const io = socketModule.setupSocketIO(httpServer);
-socketModule.setSocketIO(io);
-console.log("Socket.IO initialized");
-
-httpServer.listen(PORT, () => {
+app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
- console.log(`Socket.IO is ready for connections`);
});
diff --git a/server/app.ts b/server/app.ts
index bca8cca..9a60fc7 100644
--- a/server/app.ts
+++ b/server/app.ts
@@ -4,9 +4,9 @@ import express from "express";
import postgres from "postgres";
import { RouterContextProvider } from "react-router";
-import { DatabaseContext } from "../database/context.js";
-import * as schema from "../database/schema.js";
-import { expressValueContext } from "../app/contexts/express.js";
+import { DatabaseContext } from "~/database/context";
+import * as schema from "~/database/schema";
+import { expressValueContext } from "~/contexts/express";
export const app = express();
@@ -22,7 +22,7 @@ app.use(
getLoadContext() {
const context = new RouterContextProvider();
context.set(expressValueContext, "Hello from Express");
- return context as any;
+ return context;
},
}),
);
diff --git a/server/socket.ts b/server/socket.ts
deleted file mode 100644
index daa3cbb..0000000
--- a/server/socket.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Server as HTTPServer } from "http";
-import { Server, Socket } from "socket.io";
-
-export type SocketServer = Server;
-export type SocketClient = Socket;
-
-export function setupSocketIO(httpServer: HTTPServer): SocketServer {
- const io = new Server(httpServer, {
- cors: {
- origin: process.env.CLIENT_URL || "*",
- methods: ["GET", "POST"],
- },
- });
-
- io.on("connection", (socket: SocketClient) => {
- console.log(`Client connected: ${socket.id}`);
-
- // Join a draft room
- socket.on("join-draft", (seasonId: string) => {
- const roomName = `draft-${seasonId}`;
- socket.join(roomName);
- console.log(`Socket ${socket.id} joined draft room: ${roomName}`);
-
- // Notify others in the room
- socket.to(roomName).emit("user-joined", { socketId: socket.id });
- });
-
- // Leave a draft room
- socket.on("leave-draft", (seasonId: string) => {
- const roomName = `draft-${seasonId}`;
- socket.leave(roomName);
- console.log(`Socket ${socket.id} left draft room: ${roomName}`);
- });
-
- // Draft Actions - Phase 9
- // These events will be handled by API routes and then broadcast back via socket
- // The client will call API routes which will then use getSocketIO() to broadcast
- socket.on("make-pick", (_data) => {
- console.log("make-pick event received, should use API route instead");
- });
-
- socket.on("commissioner-start-draft", (_data) => {
- console.log("commissioner-start-draft event received, should use API route instead");
- });
-
- socket.on("commissioner-pause-draft", (_data) => {
- console.log("commissioner-pause-draft event received, should use API route instead");
- });
-
- socket.on("commissioner-force-pick", (_data) => {
- console.log("commissioner-force-pick event received, should use API route instead");
- });
-
- // Handle disconnection
- socket.on("disconnect", () => {
- console.log(`Client disconnected: ${socket.id}`);
- });
- });
-
- return io;
-}
-
-// Export a singleton instance that will be set by the server
-let ioInstance: SocketServer | null = null;
-
-export function setSocketIO(io: SocketServer) {
- ioInstance = io;
-}
-
-export function getSocketIO(): SocketServer {
- if (!ioInstance) {
- throw new Error("Socket.IO has not been initialized");
- }
- return ioInstance;
-}
diff --git a/server/types.d.ts b/server/types.d.ts
deleted file mode 100644
index 65fd064..0000000
--- a/server/types.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// Type declarations for server-side modules
-
-declare module "virtual:react-router/server-build" {
- import type { ServerBuild } from "@react-router/node";
- const serverBuild: ServerBuild;
- export = serverBuild;
-}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index a717121..12107b4 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -1,12 +1,6 @@
{
"extends": "./tsconfig.json",
- "include": [
- "server/**/*.ts",
- "vite.config.ts",
- "database/**/*.ts",
- "app/contexts/**/*.ts",
- "app/models/**/*.ts"
- ],
+ "include": ["server.js", "vite.config.ts"],
"compilerOptions": {
"composite": true,
"strict": true,
diff --git a/vite.config.ts b/vite.config.ts
index 8f02a76..36dea4b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -15,9 +15,4 @@ export default defineConfig(({ isSsrBuild }) => ({
server: {
allowedHosts: true,
},
- resolve: {
- alias: {
- "~/database": "/database",
- },
- },
}));