874 lines
26 KiB
Markdown
874 lines
26 KiB
Markdown
# Draft Room Implementation Plan
|
|
|
|
## ✅ IMPLEMENTATION STATUS: 90% COMPLETE
|
|
|
|
**Last Updated:** October 18, 2025
|
|
|
|
### Quick Summary
|
|
- ✅ **Phases 1-7**: COMPLETE (Database, Settings, Socket.IO, Draft Room, Grid, Player List, Queue)
|
|
- 🟡 **Phase 8**: PARTIAL (Timer display done, server-side countdown NOT implemented)
|
|
- ✅ **Phase 9**: COMPLETE (Draft actions, commissioner controls, force pick)
|
|
- 🟡 **Phase 10**: PARTIAL (Pick updates work, timer sync not implemented)
|
|
- ❌ **Phase 11**: NOT STARTED (Mobile polish)
|
|
|
|
### What Works Now
|
|
- Full draft room with snake draft grid
|
|
- Real-time pick updates via Socket.IO
|
|
- Team queues with add/remove/clear
|
|
- Commissioner force pick (auto + manual)
|
|
- Search, filter, and hide drafted toggle
|
|
- Start draft functionality
|
|
- Timer display (client-side only)
|
|
|
|
### What's Missing
|
|
- Server-side timer countdown
|
|
- Auto-pick when timer expires
|
|
- Pause/Resume draft
|
|
- Timer persistence across server restarts
|
|
- Mobile responsive tweaks
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Build a real-time draft room using Socket.IO for live updates, with snake draft grid, player selection, per-team queue, and chess clock timers.
|
|
|
|
---
|
|
|
|
## Phase 1: Database Schema & Models ✅ COMPLETE
|
|
|
|
### 1.1 New Tables ✅ COMPLETE
|
|
|
|
**`draft_picks`**
|
|
|
|
```sql
|
|
- id (uuid, primary key)
|
|
- season_id (uuid, references seasons)
|
|
- team_id (uuid, references teams)
|
|
- participant_id (uuid, references participants)
|
|
- pick_number (integer) - overall pick number
|
|
- round (integer)
|
|
- pick_in_round (integer)
|
|
- picked_by_user_id (varchar) - Clerk user ID
|
|
- picked_by_type (enum: 'owner', 'commissioner', 'auto') - who made the pick
|
|
- time_used (integer) - seconds used for this pick
|
|
- created_at (timestamp)
|
|
```
|
|
|
|
**`draft_queue`**
|
|
|
|
```sql
|
|
- id (uuid, primary key)
|
|
- season_id (uuid, references seasons)
|
|
- team_id (uuid, references teams)
|
|
- participant_id (uuid, references participants)
|
|
- queue_position (integer)
|
|
- created_at (timestamp)
|
|
- updated_at (timestamp)
|
|
```
|
|
|
|
**`draft_timers`**
|
|
|
|
```sql
|
|
- id (uuid, primary key)
|
|
- season_id (uuid, references seasons)
|
|
- team_id (uuid, references teams)
|
|
- time_remaining (integer) - seconds remaining
|
|
- updated_at (timestamp)
|
|
```
|
|
|
|
### 1.2 Schema Updates ✅ COMPLETE
|
|
|
|
**Update `seasons` table:**
|
|
|
|
```sql
|
|
- draft_initial_time (integer) - seconds, default 120 (2 minutes)
|
|
- draft_increment_time (integer) - seconds, default 30
|
|
- current_pick_number (integer) - track current pick
|
|
- draft_started_at (timestamp)
|
|
- draft_paused (boolean, default false)
|
|
```
|
|
|
|
### 1.3 Model Functions 🟡 PARTIAL
|
|
|
|
✅ **Implemented:**
|
|
- `getTeamQueue(teamId)` - in `app/models/draft-queue.ts`
|
|
- `addToQueue()`, `removeFromQueue()`, `clearTeamQueue()`, `reorderQueueWithSeason()` - in `app/models/draft-queue.ts`
|
|
- Draft pick creation - in API routes
|
|
|
|
❌ **Not Implemented:**
|
|
- `updateTeamTimer(teamId, timeRemaining)` - not needed yet (no server-side countdown)
|
|
- `getTeamTimers(seasonId)` - not needed yet
|
|
- Standalone model functions (using inline queries in API routes instead)
|
|
|
|
---
|
|
|
|
## Phase 2: League Settings Updates ✅ COMPLETE
|
|
|
|
### 2.1 Add Timer Configuration to Settings Page ✅ COMPLETE
|
|
|
|
- Add fields to "Draft Rounds" card:
|
|
- Initial Time (seconds) - default 120
|
|
- Increment Time (seconds) - default 30
|
|
- Update action handler to save these values
|
|
- Show in league info panel
|
|
|
|
---
|
|
|
|
## Phase 3: Socket.IO Setup ✅ COMPLETE
|
|
|
|
### Overview ✅ COMPLETE
|
|
|
|
Socket.IO requires access to the HTTP server instance (not just the Express app). The key pattern is:
|
|
```javascript
|
|
const server = http.createServer(app);
|
|
const io = socketIo(server);
|
|
server.listen(port); // NOT app.listen()
|
|
```
|
|
|
|
### Architecture Decision
|
|
|
|
**Option A: Modify server.js (Recommended)**
|
|
- Change `app.listen()` to `http.createServer(app)` + `server.listen()`
|
|
- Initialize Socket.IO with the HTTP server
|
|
- Keep everything in one process
|
|
- ✅ Simple, standard pattern
|
|
- ✅ Socket.IO and Express share same port
|
|
- ✅ Works with React Router v7 SSR
|
|
- ❌ Requires modifying server.js
|
|
|
|
**Option B: Separate Socket Server**
|
|
- Run Socket.IO on different port (e.g., 3001)
|
|
- Keep server.js untouched
|
|
- ✅ No changes to existing server
|
|
- ❌ CORS complexity
|
|
- ❌ Two processes to manage
|
|
- ❌ Different ports for HTTP and WebSocket
|
|
|
|
**Decision: Use Option A** - Industry standard, simpler deployment, better DX
|
|
|
|
### 3.1 Server Setup
|
|
|
|
#### Step 1: Modify server.js to use HTTP server
|
|
|
|
**File: `server.js`**
|
|
|
|
Change from:
|
|
```javascript
|
|
app.listen(PORT, () => {
|
|
console.log(`Server is running on http://localhost:${PORT}`);
|
|
});
|
|
```
|
|
|
|
To:
|
|
```javascript
|
|
import { createServer } from "http";
|
|
|
|
const httpServer = createServer(app);
|
|
|
|
// Import and initialize Socket.IO
|
|
const { initializeSocketIO } = await import("./server/socket.js");
|
|
initializeSocketIO(httpServer);
|
|
|
|
httpServer.listen(PORT, () => {
|
|
console.log(`Server is running on http://localhost:${PORT}`);
|
|
});
|
|
```
|
|
|
|
#### Step 2: Create Socket.IO module
|
|
|
|
**File: `server/socket.js`** (use .js not .ts to avoid bundling issues)
|
|
|
|
```javascript
|
|
import { Server as SocketIOServer } from "socket.io";
|
|
|
|
let io;
|
|
|
|
export function initializeSocketIO(httpServer) {
|
|
if (io) {
|
|
console.log("Socket.IO already initialized");
|
|
return io;
|
|
}
|
|
|
|
io = new SocketIOServer(httpServer, {
|
|
cors: {
|
|
origin: process.env.NODE_ENV === "production"
|
|
? process.env.APP_URL
|
|
: `http://localhost:${process.env.PORT || 3000}`,
|
|
credentials: true,
|
|
},
|
|
});
|
|
|
|
io.on("connection", (socket) => {
|
|
console.log("Client connected:", socket.id);
|
|
|
|
// Join draft room
|
|
socket.on("join-draft", (seasonId) => {
|
|
socket.join(`draft-${seasonId}`);
|
|
console.log(`Socket ${socket.id} joined draft-${seasonId}`);
|
|
});
|
|
|
|
// Leave draft room
|
|
socket.on("leave-draft", (seasonId) => {
|
|
socket.leave(`draft-${seasonId}`);
|
|
console.log(`Socket ${socket.id} left draft-${seasonId}`);
|
|
});
|
|
|
|
socket.on("disconnect", () => {
|
|
console.log("Client disconnected:", socket.id);
|
|
});
|
|
});
|
|
|
|
// Store globally for access in route handlers
|
|
global.__socketIO = io;
|
|
|
|
console.log("Socket.IO initialized");
|
|
return io;
|
|
}
|
|
|
|
export function getSocketIO() {
|
|
if (!io && !global.__socketIO) {
|
|
throw new Error("Socket.IO not initialized");
|
|
}
|
|
return io || global.__socketIO;
|
|
}
|
|
```
|
|
|
|
#### Step 3: Add global type definition
|
|
|
|
**File: `server/types.d.ts`**
|
|
|
|
```typescript
|
|
import type { Server as SocketIOServer } from "socket.io";
|
|
|
|
declare global {
|
|
var __socketIO: SocketIOServer | undefined;
|
|
}
|
|
|
|
export {};
|
|
```
|
|
|
|
#### Step 4: Update tsconfig.node.json
|
|
|
|
Add server directory to includes:
|
|
```json
|
|
{
|
|
"include": [
|
|
"server.js",
|
|
"server/**/*.js",
|
|
"server/**/*.ts",
|
|
"vite.config.ts"
|
|
]
|
|
}
|
|
```
|
|
|
|
### 3.2 Socket Events (Server)
|
|
|
|
**Listen for:**
|
|
|
|
- `join-draft` - user joins draft room
|
|
- `leave-draft` - user leaves draft room
|
|
- `make-pick` - user makes a pick (handled via HTTP POST, not socket)
|
|
- `add-to-queue` - add player to queue (handled via HTTP POST)
|
|
- `remove-from-queue` - remove from queue (handled via HTTP POST)
|
|
- `reorder-queue` - reorder queue (handled via HTTP POST)
|
|
|
|
**Note:** Write operations (picks, queue changes) should use HTTP POST requests for reliability and error handling. Socket.IO is for broadcasting updates to all clients.
|
|
|
|
**Emit (from server to clients):**
|
|
|
|
- `pick-made` - broadcast new pick to room
|
|
- `timer-update` - broadcast timer updates (every second)
|
|
- `current-pick-changed` - notify whose turn it is
|
|
- `draft-started` - draft has begun
|
|
- `draft-paused` - draft paused/resumed
|
|
- `draft-completed` - all picks made
|
|
- `queue-updated` - queue changed (only to team owner)
|
|
|
|
### 3.3 Client Hook
|
|
|
|
**File: `app/hooks/useDraftSocket.ts`**
|
|
|
|
```typescript
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { io, Socket } from "socket.io-client";
|
|
|
|
export function useDraftSocket(seasonId: string) {
|
|
const socketRef = useRef<Socket | null>(null);
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Connect to Socket.IO server
|
|
const socket = io({
|
|
path: "/socket.io",
|
|
transports: ["websocket", "polling"],
|
|
});
|
|
|
|
socketRef.current = socket;
|
|
|
|
socket.on("connect", () => {
|
|
console.log("Connected to Socket.IO");
|
|
setIsConnected(true);
|
|
// Join the draft room
|
|
socket.emit("join-draft", seasonId);
|
|
});
|
|
|
|
socket.on("disconnect", () => {
|
|
console.log("Disconnected from Socket.IO");
|
|
setIsConnected(false);
|
|
});
|
|
|
|
// Cleanup on unmount
|
|
return () => {
|
|
if (socket) {
|
|
socket.emit("leave-draft", seasonId);
|
|
socket.disconnect();
|
|
}
|
|
};
|
|
}, [seasonId]);
|
|
|
|
// Helper to subscribe to events
|
|
const on = (event: string, callback: (...args: any[]) => void) => {
|
|
socketRef.current?.on(event, callback);
|
|
};
|
|
|
|
// Helper to unsubscribe from events
|
|
const off = (event: string, callback?: (...args: any[]) => void) => {
|
|
socketRef.current?.off(event, callback);
|
|
};
|
|
|
|
return { socket: socketRef.current, isConnected, on, off };
|
|
}
|
|
```
|
|
|
|
### 3.4 Using Socket in Route Handlers
|
|
|
|
When a pick is made via HTTP POST, broadcast to all clients:
|
|
|
|
**Example in `app/routes/api/draft-pick.ts`:**
|
|
|
|
```typescript
|
|
import { getSocketIO } from "~/server/socket.js";
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
// ... validate and create pick ...
|
|
|
|
const pick = await createDraftPick({...});
|
|
|
|
// Broadcast to all clients in the draft room
|
|
const io = getSocketIO();
|
|
io.to(`draft-${seasonId}`).emit("pick-made", {
|
|
pick,
|
|
nextPickNumber,
|
|
isDraftComplete,
|
|
});
|
|
|
|
return json({ success: true, pick });
|
|
}
|
|
```
|
|
|
|
### 3.5 Testing Socket.IO
|
|
|
|
1. Start dev server: `npm run dev`
|
|
2. Check console for "Socket.IO initialized"
|
|
3. Open browser console on draft page
|
|
4. Should see "Connected to Socket.IO"
|
|
5. Check server logs for "Client connected: [socket-id]"
|
|
|
|
### 3.6 Gotchas & Important Notes
|
|
|
|
#### ⚠️ Gotcha #1: Import path in route handlers
|
|
```typescript
|
|
// ❌ WRONG - This won't work in React Router v7
|
|
import { getSocketIO } from "~/server/socket.js";
|
|
|
|
// ✅ CORRECT - Use relative path or configure alias
|
|
import { getSocketIO } from "../../../server/socket.js";
|
|
```
|
|
**Why:** React Router v7 runs server code, but `~` alias typically only works for app code. You may need to configure the import path or use relative imports.
|
|
|
|
#### ⚠️ Gotcha #2: Client hook creates new connection on every render
|
|
The current `useDraftSocket` hook will reconnect if `seasonId` changes. This is correct, but be aware:
|
|
- Don't call this hook multiple times in the same component tree
|
|
- Don't pass a changing `seasonId` unless you want to switch rooms
|
|
- Consider memoizing the `seasonId` prop
|
|
|
|
#### ⚠️ Gotcha #3: Event listener cleanup
|
|
```typescript
|
|
// ❌ WRONG - Listeners not cleaned up
|
|
useEffect(() => {
|
|
socket.on("pick-made", handlePickMade);
|
|
}, []);
|
|
|
|
// ✅ CORRECT - Clean up listeners
|
|
useEffect(() => {
|
|
socket.on("pick-made", handlePickMade);
|
|
return () => {
|
|
socket.off("pick-made", handlePickMade);
|
|
};
|
|
}, [handlePickMade]);
|
|
```
|
|
|
|
#### ⚠️ Gotcha #4: Socket.IO path in production
|
|
If you deploy behind a reverse proxy (nginx, etc.), you may need to configure the Socket.IO path:
|
|
```javascript
|
|
// Client
|
|
const socket = io({
|
|
path: "/socket.io", // Make sure this matches your proxy config
|
|
});
|
|
|
|
// Server (if needed)
|
|
const io = new SocketIOServer(httpServer, {
|
|
path: "/socket.io",
|
|
});
|
|
```
|
|
|
|
#### ⚠️ Gotcha #5: CORS might not be needed
|
|
Since Socket.IO runs on the same server/port as your app, CORS might be unnecessary for same-origin requests. You can simplify:
|
|
```javascript
|
|
// Simpler config if same-origin
|
|
const io = new SocketIOServer(httpServer);
|
|
```
|
|
|
|
#### ⚠️ Gotcha #6: Global storage in development
|
|
The global `__socketIO` pattern works, but in development with HMR, you might get multiple instances. The `if (io)` check helps, but be aware of potential stale connections during hot reloads.
|
|
|
|
#### ⚠️ Gotcha #7: TypeScript import in route handlers
|
|
```typescript
|
|
// This might cause issues because socket.js is JavaScript
|
|
import { getSocketIO } from "~/server/socket.js";
|
|
|
|
// Consider making it TypeScript with proper types
|
|
// Or use @ts-ignore if necessary
|
|
// @ts-ignore
|
|
import { getSocketIO } from "~/server/socket.js";
|
|
```
|
|
|
|
### 3.7 Production Considerations
|
|
|
|
- Socket.IO will automatically upgrade from polling to WebSocket
|
|
- Ensure load balancer supports WebSocket (sticky sessions required!)
|
|
- Consider Redis adapter for multi-server deployments (future)
|
|
- Monitor connection counts and memory usage
|
|
- Set up proper error handling for disconnections
|
|
- Consider implementing reconnection logic with exponential backoff
|
|
|
|
---
|
|
|
|
## Phase 4: Draft Room Route & Layout ✅ COMPLETE
|
|
|
|
### 4.1 Route Setup ✅ COMPLETE
|
|
|
|
- Create `/app/routes/leagues/$leagueId.draft.tsx`
|
|
- Loader:
|
|
- Verify user access (team owner or commissioner)
|
|
- Load season, teams, draft slots, participants
|
|
- Load existing draft picks
|
|
- Load user's team queue
|
|
- Load timer states
|
|
- No action needed (all via Socket.IO)
|
|
|
|
### 4.2 Layout Structure
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Draft Room Header │
|
|
│ League Name | Status | Commissioner Controls │
|
|
└─────────────────────────────────────────────────┘
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Draft Grid (horizontally scrollable) │
|
|
│ Team 1 Team 2 Team 3 ... │
|
|
│ 5:23 4:12 3:45 ... (timers) │
|
|
│ ┌────┐ ┌────┐ ┌────┐ │
|
|
│ │1.01│ │1.02│ │1.03│ ... │
|
|
│ └────┘ └────┘ └────┘ │
|
|
│ ┌────┐ ┌────┐ ┌────┐ │
|
|
│ │2.03│ │2.02│ │2.01│ ... (snake) │
|
|
│ └────┘ └────┘ └────┘ │
|
|
└─────────────────────────────────────────────────┘
|
|
┌──────────────────────┬──────────────────────────┐
|
|
│ Available Players │ My Queue │
|
|
│ [Search: ____] │ 1. Player A │
|
|
│ [x] Hide Drafted │ 2. Player B │
|
|
│ │ 3. Player C │
|
|
│ □ Player 1 - NFL │ [Clear Queue] │
|
|
│ EV: 125 │ │
|
|
│ □ Player 2 - NBA │ │
|
|
│ EV: 118 │ │
|
|
│ ⊠ Player 3 - MLB │ │
|
|
│ (DRAFTED) │ │
|
|
└──────────────────────┴──────────────────────────┘
|
|
```
|
|
|
|
### 4.3 No Navigation Bar
|
|
|
|
- Create standalone layout without main nav
|
|
- Add "Exit Draft Room" button to return to league page
|
|
|
|
---
|
|
|
|
## Phase 5: Draft Grid Component ✅ COMPLETE
|
|
|
|
### 5.1 Calculate Snake Order ✅ COMPLETE
|
|
|
|
- Use existing draft slots for team order
|
|
- Generate pick order:
|
|
- Round 1: slots in order (1→N)
|
|
- Round 2: slots reversed (N→1)
|
|
- Round 3: slots in order (1→N)
|
|
- etc.
|
|
|
|
### 5.2 Grid Display
|
|
|
|
- Show team names as column headers
|
|
- Show timer under each team name
|
|
- Render grid cells with pick numbers (1.01, 1.02, etc.)
|
|
- Highlight current pick (border, background color)
|
|
- Show drafted participant in completed picks
|
|
- Responsive: horizontal scroll on smaller screens
|
|
|
|
### 5.3 Pick Cell Component
|
|
|
|
```tsx
|
|
<PickCell
|
|
pickNumber="1.01"
|
|
round={1}
|
|
pickInRound={1}
|
|
teamId={teamId}
|
|
participant={draftedParticipant}
|
|
isCurrent={isCurrentPick}
|
|
isUserTeam={isUserTeam}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 6: Player List Component ✅ COMPLETE
|
|
|
|
### 6.1 Player List Features ✅ COMPLETE
|
|
|
|
- Display all participants from season sports
|
|
- Show: "Participant Name - Sport"
|
|
- Show expected value
|
|
- Sort by: EV (desc), then alphabetical
|
|
- Search/filter by name
|
|
- Toggle to hide/show drafted players
|
|
- Click to add to queue (if not drafted)
|
|
- Visual indicator for drafted players
|
|
|
|
### 6.2 Player Item Component
|
|
|
|
```tsx
|
|
<PlayerItem
|
|
participant={participant}
|
|
isDrafted={isDrafted}
|
|
onAddToQueue={handleAddToQueue}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 7: Queue Component ✅ COMPLETE
|
|
|
|
### 7.1 Queue Features 🟡 MOSTLY COMPLETE
|
|
|
|
✅ **Implemented:**
|
|
- Show only current user's team queue
|
|
- Display in order (1, 2, 3, ...)
|
|
- Remove button for each item
|
|
- "Clear Queue" button
|
|
- Queue icon buttons next to Pick button
|
|
|
|
❌ **Not Implemented:**
|
|
- Drag & drop to reorder (API route exists, UI not implemented)
|
|
- Real-time Socket.IO sync (using HTTP requests + page state instead)
|
|
|
|
### 7.2 Queue Actions ✅ COMPLETE
|
|
|
|
✅ **Implemented via API routes:**
|
|
- `/api/queue/add` - Add player to queue
|
|
- `/api/queue/remove` - Remove from queue
|
|
- `/api/queue/clear` - Clear entire queue
|
|
- `/api/queue/reorder` - Reorder queue (API ready, UI not implemented)
|
|
|
|
---
|
|
|
|
## Phase 8: Timer System 🟡 PARTIAL (50% Complete)
|
|
|
|
### 8.1 Timer Display ✅ COMPLETE
|
|
|
|
- Show under each team name in grid
|
|
- Format:
|
|
- "M:SS" for times under 1 hour (e.g., "5:23")
|
|
- "H:MM:SS" for times 1 hour or more (e.g., "2:15:30")
|
|
- Color coding:
|
|
- Green: > 60s
|
|
- Yellow: 30-60s
|
|
- Red: < 30s
|
|
- Flashing red: < 10s
|
|
|
|
### 8.2 Timer Logic (Server-Side) ❌ NOT IMPLEMENTED
|
|
|
|
**What's Missing:**
|
|
- Server-side countdown interval
|
|
- Emit `timer-update` every second
|
|
- Auto-pick when timer reaches 0
|
|
- Timer increment after pick
|
|
- Timer persistence
|
|
|
|
**Note:** Timer display shows `--:--` because no server-side countdown is running. This is the main missing piece for a fully functional draft.
|
|
|
|
### 8.3 Auto-Pick Function 🟡 PARTIAL
|
|
|
|
✅ **Implemented:**
|
|
- `/api/draft/force-autopick` - Commissioner can force auto-pick
|
|
- Checks queue first, then picks highest EV
|
|
- Creates pick with `picked_by_type='auto'`
|
|
|
|
❌ **Not Implemented:**
|
|
- Automatic trigger when timer reaches 0
|
|
- Server-side timer countdown to trigger it
|
|
|
|
---
|
|
|
|
## Phase 9: Draft Actions & Permissions ✅ COMPLETE
|
|
|
|
### 9.1 Making a Pick ✅ COMPLETE
|
|
|
|
- Only current team owner or commissioner can pick
|
|
- Click participant in player list (if current turn)
|
|
- Validate: participant not already drafted
|
|
- Create draft pick record
|
|
- Emit `pick-made` to room
|
|
- Update current pick
|
|
- Update timers
|
|
|
|
### 9.2 Commissioner Controls 🟡 MOSTLY COMPLETE
|
|
|
|
✅ **Implemented:**
|
|
- Start Draft button (shows when status is "pre_draft")
|
|
- Force Auto Pick (right-click context menu on current pick)
|
|
- Force Manual Pick (right-click context menu, opens participant dialog)
|
|
- `/api/draft/start` - Starts draft, initializes timers
|
|
- `/api/draft/make-pick` - Regular pick by team owner
|
|
- `/api/draft/force-autopick` - Commissioner force auto-pick
|
|
- `/api/draft/force-manual-pick` - Commissioner manual pick for any team
|
|
|
|
❌ **Not Implemented:**
|
|
- Pause/Resume button
|
|
- Pause/Resume API routes
|
|
|
|
### 9.3 Draft Start Logic ✅ COMPLETE
|
|
|
|
✅ **Implemented:**
|
|
- Manual start by commissioner via "Start Draft" button
|
|
- Changes season status to "draft"
|
|
- Initializes timers for all teams
|
|
- Emits `draft-started` socket event
|
|
|
|
❌ **Not Implemented:**
|
|
- Auto-start when `draftDateTime` is reached (would need background job/cron)
|
|
|
|
### 9.4 Draft Completion ✅ COMPLETE
|
|
|
|
✅ **Implemented:**
|
|
- Detects when all picks made
|
|
- Changes season status to "active"
|
|
- Emits `draft-completed` socket event
|
|
|
|
---
|
|
|
|
## Phase 10: Real-Time Updates 🟡 PARTIAL (60% Complete)
|
|
|
|
### 10.1 Client State Management ✅ COMPLETE
|
|
|
|
- Use React state for:
|
|
- Draft picks array
|
|
- Current pick number
|
|
- Team timers
|
|
- User's queue
|
|
- Available participants
|
|
- Update state when Socket.IO events received
|
|
|
|
### 10.2 Optimistic Updates 🟡 PARTIAL
|
|
|
|
✅ **Implemented:**
|
|
- Pick updates broadcast via Socket.IO `pick-made` event
|
|
- All clients receive and display picks in real-time
|
|
- Queue operations update local state immediately
|
|
|
|
❌ **Not Implemented:**
|
|
- Timer updates via Socket.IO (no server-side countdown)
|
|
- Revert on server rejection (currently just shows alert)
|
|
|
|
**Note:** Real-time updates work for picks but not for timers since server-side countdown isn't implemented.
|
|
|
|
---
|
|
|
|
## Phase 11: Mobile Responsiveness ❌ NOT STARTED
|
|
|
|
### 11.1 Mobile Layout Adjustments ❌ NOT STARTED
|
|
|
|
**What's Needed:**
|
|
- Draft grid: horizontal scroll (partially works)
|
|
- Stack player list and queue vertically on mobile
|
|
- Collapsible sections for player list/queue
|
|
- Larger touch targets for buttons
|
|
- Simplified timer display
|
|
- Test on actual mobile devices
|
|
- Responsive breakpoints optimization
|
|
|
|
**Current State:** Desktop layout works, mobile usable but not optimized.
|
|
|
|
---
|
|
|
|
## Implementation Order
|
|
|
|
1. **Phase 1**: Database schema & migrations
|
|
2. **Phase 2**: League settings timer config
|
|
3. **Phase 3**: Socket.IO setup (server + client hook)
|
|
4. **Phase 4**: Draft room route & basic layout
|
|
5. **Phase 5**: Draft grid component (static first)
|
|
6. **Phase 6**: Player list component
|
|
7. **Phase 7**: Queue component
|
|
8. **Phase 8**: Timer system (server-side logic)
|
|
9. **Phase 9**: Draft actions & permissions
|
|
10. **Phase 10**: Wire up real-time updates
|
|
11. **Phase 11**: Mobile polish
|
|
|
|
---
|
|
|
|
## ✅ IMPLEMENTATION COMPLETION SUMMARY
|
|
|
|
### Files Created/Modified
|
|
|
|
**API Routes:**
|
|
- ✅ `/app/routes/api/draft.make-pick.ts` - Team owner makes pick
|
|
- ✅ `/app/routes/api/draft.start.ts` - Commissioner starts draft
|
|
- ✅ `/app/routes/api/draft.force-autopick.ts` - Commissioner force auto-pick
|
|
- ✅ `/app/routes/api/draft.force-manual-pick.ts` - Commissioner manual pick
|
|
- ✅ `/app/routes/api/queue.add.ts` - Add to queue
|
|
- ✅ `/app/routes/api/queue.remove.ts` - Remove from queue
|
|
- ✅ `/app/routes/api/queue.clear.ts` - Clear queue
|
|
- ✅ `/app/routes/api/queue.reorder.ts` - Reorder queue (API ready, UI not implemented)
|
|
|
|
**Models:**
|
|
- ✅ `/app/models/draft-queue.ts` - Queue management functions
|
|
|
|
**Routes:**
|
|
- ✅ `/app/routes/leagues/$leagueId.draft.$seasonId.tsx` - Main draft room
|
|
|
|
**Hooks:**
|
|
- ✅ `/app/hooks/useDraftSocket.ts` - Socket.IO client hook
|
|
|
|
**Server:**
|
|
- ✅ `/server/socket.js` - Socket.IO server setup
|
|
- ✅ `server.js` - Modified to use HTTP server for Socket.IO
|
|
|
|
**Components:**
|
|
- ✅ `/app/components/ui/context-menu.tsx` - Added for commissioner controls
|
|
|
|
**Database:**
|
|
- ✅ Schema updated with draft tables and timer fields
|
|
- ✅ `draftQueueRelations` added to schema
|
|
|
|
### What Works Right Now
|
|
|
|
1. **Full Draft Flow:**
|
|
- Commissioner can start draft
|
|
- Team owners can make picks on their turn
|
|
- Commissioner can force picks (auto or manual)
|
|
- Real-time pick updates via Socket.IO
|
|
- Draft completion detection
|
|
|
|
2. **Queue Management:**
|
|
- Add/remove/clear queue
|
|
- Queue persists in database
|
|
- Queue icon buttons next to Pick button
|
|
- Queue used by force autopick
|
|
|
|
3. **UI Features:**
|
|
- Snake draft grid with pick numbers
|
|
- Search participants by name
|
|
- Filter by sport
|
|
- Hide/show drafted toggle
|
|
- Context menu for commissioner force pick
|
|
- Responsive grid (horizontal scroll)
|
|
|
|
4. **Real-Time:**
|
|
- Socket.IO connection indicator
|
|
- Pick updates broadcast to all users
|
|
- Room-based isolation (multiple drafts can run simultaneously)
|
|
|
|
### What's Missing (10%)
|
|
|
|
1. **Server-Side Timer Countdown** (Main Missing Piece):
|
|
- No interval running on server
|
|
- No `timer-update` socket events
|
|
- Timer display shows `--:--`
|
|
- No automatic trigger for auto-pick
|
|
|
|
2. **Pause/Resume:**
|
|
- No pause/resume buttons
|
|
- No API routes for pause/resume
|
|
|
|
3. **Minor Features:**
|
|
- Drag & drop queue reorder UI
|
|
- Mobile optimization
|
|
- Auto-start at `draftDateTime`
|
|
|
|
### To Complete the Draft Room (Remaining 10%)
|
|
|
|
**Priority 1: Server-Side Timer (Critical)**
|
|
1. Add interval in `server/socket.js` to countdown every second
|
|
2. Emit `timer-update` to draft room
|
|
3. Trigger auto-pick when timer reaches 0
|
|
4. Add timer increment after pick
|
|
|
|
**Priority 2: Pause/Resume (Nice to Have)**
|
|
1. Add pause/resume buttons for commissioner
|
|
2. Create `/api/draft/pause` and `/api/draft/resume` routes
|
|
3. Update timer logic to respect pause state
|
|
|
|
**Priority 3: Polish (Optional)**
|
|
1. Drag & drop queue reorder UI
|
|
2. Mobile responsive improvements
|
|
3. Auto-start background job
|
|
|
|
---
|
|
|
|
## Open Questions / Future Considerations
|
|
|
|
- **Background job for auto-start**: How to trigger draft start at `draftDateTime`? Cron job? Polling?
|
|
- **Timer persistence**: What if server restarts during draft? Store timer state in DB and resume?
|
|
- **Undo picks**: Not in initial scope, but may want later
|
|
- **Draft history/log**: Separate view to see all picks in order?
|
|
- **Notifications**: Alert users when it's their turn? (email, push, etc.)
|
|
- **Multi-server**: If scaling to multiple servers, need Redis adapter for Socket.IO
|
|
|
|
---
|
|
|
|
## Requirements Summary
|
|
|
|
### User Requirements
|
|
|
|
- Route: `/leagues/$leagueId/draft`
|
|
- Queue: Per-team, private (stored in database)
|
|
- Timer: Chess clock (initial time + increment per pick)
|
|
- Real-time: WebSocket (Socket.IO)
|
|
- Mobile: Should work on mobile
|
|
- Commissioner: Can force picks
|
|
- Participant sorting: By EV (desc), then alphabetical
|
|
- Search: Filter participants by name
|
|
- Toggle: Show/hide drafted participants
|
|
- Teams: 6-16 teams typical (12 average)
|
|
- Timer config: Configurable per league/season
|
|
- Auto-pick: From queue, or top EV if queue empty
|
|
- Draft state: "drafting" status
|
|
- Start: Auto-start at draft date/time, or manual by commissioner
|
|
- Pause/Resume: Commissioner can pause/resume
|
|
- Pick log: Track who made each pick (owner/commissioner/auto)
|
|
- Grid: Horizontally scrollable
|
|
- Player display: "Player Name - Sport"
|
|
- Pre-draft: Room viewable before draft starts
|
|
- Post-draft: Change status to "active" when complete
|