424 lines
12 KiB
Markdown
424 lines
12 KiB
Markdown
|
|
# Draft Room Implementation Plan
|
|||
|
|
|
|||
|
|
## 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
|
|||
|
|
|
|||
|
|
### 1.1 New Tables
|
|||
|
|
|
|||
|
|
**`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
|
|||
|
|
|
|||
|
|
**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
|
|||
|
|
|
|||
|
|
- `createDraftPick()`
|
|||
|
|
- `getDraftPicks(seasonId)`
|
|||
|
|
- `getCurrentPick(seasonId)`
|
|||
|
|
- `addToQueue(teamId, participantId)`
|
|||
|
|
- `removeFromQueue(queueId)`
|
|||
|
|
- `reorderQueue(teamId, participantIds[])`
|
|||
|
|
- `getTeamQueue(teamId)`
|
|||
|
|
- `updateTeamTimer(teamId, timeRemaining)`
|
|||
|
|
- `getTeamTimers(seasonId)`
|
|||
|
|
- `initializeDraftTimers(seasonId)` - set initial time for all teams
|
|||
|
|
- `autoPickForTeam(teamId)` - pick from queue or top EV
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 2: League Settings Updates
|
|||
|
|
|
|||
|
|
### 2.1 Add Timer Configuration to Settings Page
|
|||
|
|
|
|||
|
|
- 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
|
|||
|
|
|
|||
|
|
### 3.1 Server Setup
|
|||
|
|
|
|||
|
|
- Install `socket.io`
|
|||
|
|
- Create `server/socket.ts`
|
|||
|
|
- Initialize Socket.IO server alongside Remix
|
|||
|
|
- Set up draft room namespaces: `draft-${seasonId}`
|
|||
|
|
|
|||
|
|
### 3.2 Socket Events (Server)
|
|||
|
|
|
|||
|
|
**Listen for:**
|
|||
|
|
|
|||
|
|
- `join-draft` - user joins draft room
|
|||
|
|
- `make-pick` - user makes a pick
|
|||
|
|
- `add-to-queue` - add player to queue
|
|||
|
|
- `remove-from-queue` - remove from queue
|
|||
|
|
- `reorder-queue` - reorder queue
|
|||
|
|
- `commissioner-force-pick` - commissioner forces pick
|
|||
|
|
- `commissioner-start-draft` - start draft
|
|||
|
|
- `commissioner-pause-draft` - pause/resume draft
|
|||
|
|
|
|||
|
|
**Emit:**
|
|||
|
|
|
|||
|
|
- `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
|
|||
|
|
|
|||
|
|
- Create `hooks/useDraftSocket.ts`
|
|||
|
|
- Handle connection/disconnection
|
|||
|
|
- Provide methods to emit events
|
|||
|
|
- Provide listeners for events
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 4: Draft Room Route & Layout
|
|||
|
|
|
|||
|
|
### 4.1 Route Setup
|
|||
|
|
|
|||
|
|
- 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
|
|||
|
|
|
|||
|
|
### 5.1 Calculate Snake Order
|
|||
|
|
|
|||
|
|
- 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
|
|||
|
|
|
|||
|
|
### 6.1 Player List Features
|
|||
|
|
|
|||
|
|
- 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
|
|||
|
|
|
|||
|
|
### 7.1 Queue Features
|
|||
|
|
|
|||
|
|
- Show only current user's team queue
|
|||
|
|
- Display in order (1, 2, 3, ...)
|
|||
|
|
- Drag & drop to reorder
|
|||
|
|
- Remove button for each item
|
|||
|
|
- "Clear Queue" button
|
|||
|
|
- Real-time sync via Socket.IO
|
|||
|
|
|
|||
|
|
### 7.2 Queue Actions
|
|||
|
|
|
|||
|
|
- Add player → emit `add-to-queue` → update DB → emit `queue-updated` to team
|
|||
|
|
- Remove → emit `remove-from-queue` → update DB → emit `queue-updated`
|
|||
|
|
- Reorder → emit `reorder-queue` → update DB → emit `queue-updated`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 8: Timer System
|
|||
|
|
|
|||
|
|
### 8.1 Timer Display
|
|||
|
|
|
|||
|
|
- 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)
|
|||
|
|
|
|||
|
|
- When draft starts: initialize all timers with `draft_initial_time`
|
|||
|
|
- During a team's turn:
|
|||
|
|
- Their timer counts down every second
|
|||
|
|
- Other teams' timers remain paused
|
|||
|
|
- When pick is made:
|
|||
|
|
- Stop countdown for current team
|
|||
|
|
- Add `draft_increment_time` to current team's timer
|
|||
|
|
- Move to next pick
|
|||
|
|
- Start next team's timer countdown
|
|||
|
|
- Emit `timer-update` every second to all clients
|
|||
|
|
- When timer reaches 0: trigger `autoPickForTeam()`
|
|||
|
|
|
|||
|
|
### 8.3 Auto-Pick Function
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
async function autoPickForTeam(teamId: string, seasonId: string) {
|
|||
|
|
// 1. Check queue - pick first item
|
|||
|
|
// 2. If queue empty, pick highest EV participant not drafted
|
|||
|
|
// 3. Create draft pick with picked_by_type='auto'
|
|||
|
|
// 4. Emit pick-made event
|
|||
|
|
// 5. Move to next pick
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 9: Draft Actions & Permissions
|
|||
|
|
|
|||
|
|
### 9.1 Making a Pick
|
|||
|
|
|
|||
|
|
- 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
|
|||
|
|
|
|||
|
|
- Start Draft button (if not started)
|
|||
|
|
- Pause/Resume button
|
|||
|
|
- Force Pick button (for current team)
|
|||
|
|
- Show in header, only visible to commissioners
|
|||
|
|
|
|||
|
|
### 9.3 Draft Start Logic
|
|||
|
|
|
|||
|
|
- Can be started manually by commissioner
|
|||
|
|
- Or auto-start when `draftDateTime` is reached (background job?)
|
|||
|
|
- Change season status to "drafting"
|
|||
|
|
- Initialize timers
|
|||
|
|
- Emit `draft-started`
|
|||
|
|
|
|||
|
|
### 9.4 Draft Completion
|
|||
|
|
|
|||
|
|
- When all picks made (picks = teams × rounds):
|
|||
|
|
- Change season status to "active"
|
|||
|
|
- Emit `draft-completed`
|
|||
|
|
- Stop timers
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 10: Real-Time Updates
|
|||
|
|
|
|||
|
|
### 10.1 Client State Management
|
|||
|
|
|
|||
|
|
- 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
|
|||
|
|
|
|||
|
|
- When user makes pick: update UI immediately
|
|||
|
|
- If server rejects: revert and show error
|
|||
|
|
- For queue operations: update immediately
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 11: Mobile Responsiveness
|
|||
|
|
|
|||
|
|
### 11.1 Mobile Layout Adjustments
|
|||
|
|
|
|||
|
|
- Draft grid: horizontal scroll
|
|||
|
|
- Stack player list and queue vertically
|
|||
|
|
- Collapsible sections for player list/queue
|
|||
|
|
- Larger touch targets for buttons
|
|||
|
|
- Simplified timer display
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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?
|
|||
|
|
- **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.)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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
|