Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
import { useLoaderData, Link, useRevalidator } from "react-router";
|
2025-10-17 12:30:58 -07:00
|
|
|
import { useDraftSocket } from "~/hooks/useDraftSocket";
|
2026-03-21 13:41:39 -07:00
|
|
|
import { logger } from "~/lib/logger";
|
2026-04-23 22:09:25 -07:00
|
|
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
2025-10-17 12:45:30 -07:00
|
|
|
import { Button } from "~/components/ui/button";
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
import { auth } from "~/lib/auth.server";
|
2026-04-23 22:09:25 -07:00
|
|
|
import { useDraftRoomState, type QueueItem } from "~/hooks/useDraftRoomState";
|
|
|
|
|
import { useDraftAuthRecovery } from "~/hooks/useDraftAuthRecovery";
|
|
|
|
|
import { useDraftSocketEvents } from "~/hooks/useDraftSocketEvents";
|
|
|
|
|
import { RollbackConfirmDialog } from "~/components/draft/RollbackConfirmDialog";
|
|
|
|
|
import { TimeBankAdjustmentDialog } from "~/components/draft/TimeBankAdjustmentDialog";
|
|
|
|
|
import { CommissionerAutodraftDialog } from "~/components/draft/CommissionerAutodraftDialog";
|
|
|
|
|
import { CommissionerDraftControls } from "~/components/draft/CommissionerDraftControls";
|
2025-10-17 12:45:30 -07:00
|
|
|
import { getTeamQueue } from "~/models/draft-queue";
|
2026-04-23 22:09:25 -07:00
|
|
|
import { findSeasonWithTeamsAndLeague } from "~/models/season";
|
|
|
|
|
import { findDraftSlotsBySeasonId } from "~/models/draft-slot";
|
|
|
|
|
import { getDraftPicksForSeason } from "~/models/draft-pick";
|
|
|
|
|
import { getDraftParticipants } from "~/models/participant";
|
|
|
|
|
import { getSeasonTimers } from "~/models/draft-timer";
|
|
|
|
|
import { getSeasonAutodraftSettings } from "~/models/autodraft-settings";
|
|
|
|
|
import { hasCommissionerRecord } from "~/models/commissioner";
|
2026-04-23 14:12:15 -07:00
|
|
|
import logomarkUrl from "../../../public/logomark.svg?url";
|
2026-02-22 16:56:07 -08:00
|
|
|
import { buildOwnerMap } from "~/lib/owner-map";
|
2025-10-25 03:23:41 -07:00
|
|
|
import { DraftSidebar } from "~/components/DraftSidebar";
|
2026-03-02 16:46:35 -08:00
|
|
|
import { TeamRosterView } from "~/components/draft/TeamRosterView";
|
|
|
|
|
import { DraftSummaryView } from "~/components/draft/DraftSummaryView";
|
2025-10-25 03:23:41 -07:00
|
|
|
import { QueueSection } from "~/components/draft/QueueSection";
|
|
|
|
|
import { AvailableParticipantsSection } from "~/components/draft/AvailableParticipantsSection";
|
2025-10-25 18:25:26 -07:00
|
|
|
import { SidebarRecentPicks } from "~/components/draft/SidebarRecentPicks";
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
import { RecentPicksFeed } from "~/components/draft/RecentPicksFeed";
|
2025-10-25 03:23:41 -07:00
|
|
|
import { DraftGridSection } from "~/components/draft/DraftGridSection";
|
2025-10-26 21:01:12 -07:00
|
|
|
import { ConnectionOverlay } from "~/components/draft/ConnectionOverlay";
|
2026-03-01 19:33:03 -08:00
|
|
|
import { AuthRecoveryOverlay } from "~/components/draft/AuthRecoveryOverlay";
|
2026-02-27 23:46:09 -08:00
|
|
|
import { ParticipantSelectionDialog } from "~/components/draft/ParticipantSelectionDialog";
|
2025-10-26 21:09:30 -07:00
|
|
|
import { calculateDraftEligibility } from "~/lib/draft-eligibility";
|
2026-04-24 09:32:11 -07:00
|
|
|
import { getTeamForPick, getProjectedPicks } from "~/lib/draft-order";
|
2026-02-20 19:30:53 -08:00
|
|
|
import { useDraftNotifications } from "~/hooks/useDraftNotifications";
|
|
|
|
|
import { NotificationSettings } from "~/components/NotificationSettings";
|
2026-04-23 22:09:25 -07:00
|
|
|
import { AutodraftBadgeWithPopover } from "~/components/AutodraftSettings";
|
2025-10-25 21:02:16 -07:00
|
|
|
import { toast } from "sonner";
|
Add draft clock UI, Fischer increment timer logic, and security fixes (#19)
- Add prominent clock badge to tab bar (lights up on your turn) with
correct Fischer increment chess-clock model: bank starts at initialTime,
+= incrementTime after each pick, other teams' banks untouched
- Extract pure timer helpers to app/lib/draft-timer.ts and add 29 unit
tests covering formatClockTime, calculateTimeAfterPick, getTimerColorClass,
and full snake-draft lifecycle regression scenarios
- Fix make-pick.ts: add status !== 'draft' and draftPaused server guards
- Fix draft-utils.ts: replace (global as any).__socketIO with getSocketIO(),
fix timeUsed to store actual timeRemaining at pick moment (not always 120),
add null timer warning, move timer fetch before pick insert
- Fix draft.start.ts: batch timer inserts, guard against empty draftSlots
- Fix DraftGridSection: memoize currentTeamId, widen teamTimers type to
Record<string, number | undefined>
- Fix duplicate animate-pulse (getTimerColorClass already includes it)
- Clamp negative seconds in formatClockTime to guard against timer drift
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 16:51:12 -08:00
|
|
|
import { formatClockTime, getTimerColorClass } from "~/lib/draft-timer";
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
import { Users, LayoutGrid, ListChecks, Settings, ListOrdered } from "lucide-react";
|
Claude/fix pick timer ghll n (#29)
* Fix force-manual-pick resetting next team's timer to initial time
When a commissioner forced a manual pick, the next team's timer was
being reset to the initial time (2 minutes) instead of carrying
forward their existing time bank balance.
This aligns force-manual-pick with the behavior of regular user picks
and force-autopick: the picking team gets their increment added, and
the next team's timer is left untouched so their bank carries forward.
https://claude.ai/code/session_01X7gwWmafUSEvVHcV7Raz5p
* Add regression tests for draft.force-manual-pick timer behavior
18 tests across 5 describe blocks covering:
- Authorization (401/403)
- Input validation (missing fields, bad participant, ineligible sport)
- Successful pick (response shape, draft-complete detection, socket events)
- Timer behavior (increment added to picking team, new timer creation, additive not reset)
- Two regression tests confirming the next team's timer is never touched:
draftTimers.findFirst called exactly once, no timer-update emitted for
next team, db.update called exactly twice (not three times)
https://claude.ai/code/session_01X7gwWmafUSEvVHcV7Raz5p
* Add TypeScript types and improve draft validation (#28)
* Code review fixes: type safety, security hardening, and dead code removal
- Fix Socket.IO event types: draft-paused and draft-resumed were typed as
() => void but are emitted with { seasonId, paused } data payloads
- Fix draft.force-manual-pick: add missing season.status === "draft" guard
so commissioners cannot force picks outside an active draft; add duplicate
pick-number check so a slot cannot be assigned two picks (the previous
code only checked participant uniqueness, not slot uniqueness)
- Replace args: any with ActionFunctionArgs / Route.LoaderArgs across all
API routes and league loaders; replace (auth as any).userId casts with
proper const { userId } = await getAuth(args) destructuring
- Remove unused isSnakeDraft = true dead variable from draft.make-pick
- Replace autodraftSettings: any and draftSlots: any[] in draft-utils with
properly typed InferSelectModel / DraftSlot types
- Update force-manual-pick tests: sequence draftPicks.findFirst mock for
the two-call flow; add new tests for status-check and slot-uniqueness
https://claude.ai/code/session_01FKq2gPFYpgdfxr8cw4Z2AZ
* Fix RouterContextProvider type errors in action test files
Cast context argument to RouterContextProvider in test helpers so
ActionFunctionArgs strict typing is satisfied without weakening the
production action signatures back to any.
https://claude.ai/code/session_01FKq2gPFYpgdfxr8cw4Z2AZ
---------
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-22 19:29:29 -08:00
|
|
|
import type { Route } from "./+types/$leagueId.draft.$seasonId";
|
2025-10-17 12:30:58 -07:00
|
|
|
|
2026-03-10 12:10:52 -07:00
|
|
|
export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors {
|
|
|
|
|
return [{ title: `Draft — ${data?.season?.league?.name ?? "League"} - Brackt` }];
|
|
|
|
|
}
|
|
|
|
|
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
const MOBILE_TABS_BASE = [
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
{ id: "available" as const, label: "Participants", Icon: Users },
|
2026-02-22 22:36:12 -08:00
|
|
|
{ id: "board" as const, label: "Board", Icon: LayoutGrid },
|
2026-03-02 16:46:35 -08:00
|
|
|
{ id: "teams" as const, label: "Teams", Icon: ListChecks },
|
2026-02-22 22:36:12 -08:00
|
|
|
{ id: "controls" as const, label: "Controls", Icon: Settings },
|
|
|
|
|
];
|
|
|
|
|
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
const QUEUE_TAB = { id: "queue" as const, label: "Queue", Icon: ListOrdered };
|
|
|
|
|
|
Claude/fix pick timer ghll n (#29)
* Fix force-manual-pick resetting next team's timer to initial time
When a commissioner forced a manual pick, the next team's timer was
being reset to the initial time (2 minutes) instead of carrying
forward their existing time bank balance.
This aligns force-manual-pick with the behavior of regular user picks
and force-autopick: the picking team gets their increment added, and
the next team's timer is left untouched so their bank carries forward.
https://claude.ai/code/session_01X7gwWmafUSEvVHcV7Raz5p
* Add regression tests for draft.force-manual-pick timer behavior
18 tests across 5 describe blocks covering:
- Authorization (401/403)
- Input validation (missing fields, bad participant, ineligible sport)
- Successful pick (response shape, draft-complete detection, socket events)
- Timer behavior (increment added to picking team, new timer creation, additive not reset)
- Two regression tests confirming the next team's timer is never touched:
draftTimers.findFirst called exactly once, no timer-update emitted for
next team, db.update called exactly twice (not three times)
https://claude.ai/code/session_01X7gwWmafUSEvVHcV7Raz5p
* Add TypeScript types and improve draft validation (#28)
* Code review fixes: type safety, security hardening, and dead code removal
- Fix Socket.IO event types: draft-paused and draft-resumed were typed as
() => void but are emitted with { seasonId, paused } data payloads
- Fix draft.force-manual-pick: add missing season.status === "draft" guard
so commissioners cannot force picks outside an active draft; add duplicate
pick-number check so a slot cannot be assigned two picks (the previous
code only checked participant uniqueness, not slot uniqueness)
- Replace args: any with ActionFunctionArgs / Route.LoaderArgs across all
API routes and league loaders; replace (auth as any).userId casts with
proper const { userId } = await getAuth(args) destructuring
- Remove unused isSnakeDraft = true dead variable from draft.make-pick
- Replace autodraftSettings: any and draftSlots: any[] in draft-utils with
properly typed InferSelectModel / DraftSlot types
- Update force-manual-pick tests: sequence draftPicks.findFirst mock for
the two-call flow; add new tests for status-check and slot-uniqueness
https://claude.ai/code/session_01FKq2gPFYpgdfxr8cw4Z2AZ
* Fix RouterContextProvider type errors in action test files
Cast context argument to RouterContextProvider in test helpers so
ActionFunctionArgs strict typing is satisfied without weakening the
production action signatures back to any.
https://claude.ai/code/session_01FKq2gPFYpgdfxr8cw4Z2AZ
---------
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-22 19:29:29 -08:00
|
|
|
export async function loader(args: Route.LoaderArgs) {
|
2025-10-17 12:45:30 -07:00
|
|
|
const { params } = args;
|
|
|
|
|
const { seasonId, leagueId } = params;
|
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)
Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.
**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field
**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict
better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
|
|
|
const session = await auth.api.getSession({ headers: args.request.headers });
|
|
|
|
|
const userId = session?.user.id ?? null;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2025-10-17 12:30:58 -07:00
|
|
|
if (!seasonId) {
|
|
|
|
|
throw new Response("Season ID is required", { status: 400 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 22:09:25 -07:00
|
|
|
const season = await findSeasonWithTeamsAndLeague(seasonId);
|
2025-10-17 12:30:58 -07:00
|
|
|
|
|
|
|
|
if (!season) {
|
|
|
|
|
throw new Response("Season not found", { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:00:04 -08:00
|
|
|
const userTeam = userId
|
2026-03-21 09:44:05 -07:00
|
|
|
? season.teams.find((team) => team.ownerId === userId)
|
2026-02-23 12:00:04 -08:00
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const isCommissioner = userId
|
2026-04-23 22:09:25 -07:00
|
|
|
? await hasCommissionerRecord(leagueId, userId)
|
|
|
|
|
: false;
|
2025-10-17 12:45:30 -07:00
|
|
|
|
2026-02-23 12:00:04 -08:00
|
|
|
if (!season.league.isPublicDraftBoard) {
|
|
|
|
|
if (!userId) {
|
|
|
|
|
throw new Response("You must be logged in to view the draft room", {
|
|
|
|
|
status: 401,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!userTeam && !isCommissioner) {
|
|
|
|
|
throw new Response("You do not have access to this draft room", {
|
|
|
|
|
status: 403,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-10-17 12:45:30 -07:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 22:09:25 -07:00
|
|
|
const [draftSlots, draftPicks, availableParticipants, timers, autodraftSettings] =
|
|
|
|
|
await Promise.all([
|
|
|
|
|
findDraftSlotsBySeasonId(seasonId),
|
|
|
|
|
getDraftPicksForSeason(seasonId),
|
|
|
|
|
getDraftParticipants(seasonId),
|
|
|
|
|
getSeasonTimers(seasonId),
|
|
|
|
|
getSeasonAutodraftSettings(seasonId),
|
|
|
|
|
]);
|
2025-10-17 12:30:58 -07:00
|
|
|
|
2026-02-23 23:23:24 -08:00
|
|
|
const userQueue = userTeam
|
|
|
|
|
? await getTeamQueue(userTeam.id).catch((err) => {
|
2026-03-21 13:41:39 -07:00
|
|
|
logger.error("[DraftLoader] Failed to load queue, defaulting to empty:", err);
|
2026-02-23 23:23:24 -08:00
|
|
|
return [];
|
|
|
|
|
})
|
|
|
|
|
: [];
|
2025-10-17 12:45:30 -07:00
|
|
|
|
2025-10-21 23:22:17 -07:00
|
|
|
const userAutodraftSettings = userTeam
|
2026-02-20 21:10:26 -08:00
|
|
|
? (autodraftSettings.find((s) => s.teamId === userTeam.id) ?? null)
|
2025-10-21 23:22:17 -07:00
|
|
|
: null;
|
|
|
|
|
|
2026-02-22 16:56:07 -08:00
|
|
|
const ownerMap = await buildOwnerMap(draftSlots);
|
|
|
|
|
|
2025-10-17 12:30:58 -07:00
|
|
|
return {
|
|
|
|
|
season,
|
|
|
|
|
draftSlots,
|
|
|
|
|
draftPicks,
|
|
|
|
|
availableParticipants,
|
2025-10-17 12:45:30 -07:00
|
|
|
userTeam,
|
|
|
|
|
userQueue,
|
2025-10-18 23:13:04 -07:00
|
|
|
timers,
|
2025-10-21 23:22:17 -07:00
|
|
|
autodraftSettings,
|
|
|
|
|
userAutodraftSettings,
|
2025-10-17 12:45:30 -07:00
|
|
|
isCommissioner: !!isCommissioner,
|
|
|
|
|
currentUserId: userId,
|
2026-02-22 16:56:07 -08:00
|
|
|
ownerMap,
|
2025-10-17 12:30:58 -07:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function DraftRoom() {
|
2025-10-18 14:55:26 -07:00
|
|
|
const {
|
|
|
|
|
season,
|
|
|
|
|
draftSlots,
|
|
|
|
|
draftPicks,
|
|
|
|
|
availableParticipants,
|
|
|
|
|
userTeam,
|
|
|
|
|
userQueue,
|
2025-10-18 23:13:04 -07:00
|
|
|
timers,
|
2025-10-21 23:22:17 -07:00
|
|
|
autodraftSettings,
|
|
|
|
|
userAutodraftSettings,
|
2025-10-18 14:55:26 -07:00
|
|
|
isCommissioner,
|
2026-02-20 19:30:53 -08:00
|
|
|
currentUserId,
|
2026-02-22 16:56:07 -08:00
|
|
|
ownerMap,
|
2025-10-18 14:55:26 -07:00
|
|
|
} = useLoaderData<typeof loader>();
|
fix: fully sync local draft state after socket reconnection (#41)
On reconnect, revalidate() re-fetches fresh loader data but the local
useState copies (picks, currentPick, isPaused, isDraftComplete, queue)
never synced with the new values — leaving the UI stale until a manual
refresh.
- Watch revalidatorState (idle→loading→idle) to detect when a
revalidation completes and apply the fresh loader snapshot to all
affected local state
- Buffer pick-made socket events received during the revalidation window
(instead of discarding or double-applying them); merge into the DB
snapshot on completion, deduplicated by pick ID
- Keep setCurrentPick paired with setPicks in handlePickMade so the
"on the clock" indicator and the picks list never desync
- Sync queue state from fresh userQueue after revalidation so
participants drafted while the user was away disappear from their
queue even if the participant-removed-from-queues events were missed
Adds useDraftSocket reconnect test suite (9 tests) covering initial
connect, socket reconnect, visibility-change triggering, network
flapping, error exhaustion, and cleanup.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 22:50:46 -08:00
|
|
|
const { revalidate, state: revalidatorState } = useRevalidator();
|
2026-03-02 13:18:47 -08:00
|
|
|
const { isConnected, connectionError, isReconnecting, reconnectCount, socketVersion, on, off } = useDraftSocket(season.id, userTeam?.id);
|
2026-02-20 19:30:53 -08:00
|
|
|
const {
|
|
|
|
|
permissionState: notificationsPermission,
|
|
|
|
|
enabled: notificationsEnabled,
|
|
|
|
|
setEnabled: setNotificationsEnabled,
|
|
|
|
|
mode: notificationsMode,
|
|
|
|
|
setMode: setNotificationsMode,
|
|
|
|
|
sendNotification,
|
2026-02-23 12:00:04 -08:00
|
|
|
} = useDraftNotifications(season.id, currentUserId ?? "");
|
2026-02-20 19:30:53 -08:00
|
|
|
|
|
|
|
|
// Use refs so the socket effect doesn't re-register all handlers when the user
|
|
|
|
|
// changes notification settings (which would change the sendNotification reference).
|
|
|
|
|
const sendNotificationRef = useRef(sendNotification);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
sendNotificationRef.current = sendNotification;
|
|
|
|
|
}, [sendNotification]);
|
|
|
|
|
const notificationsModeRef = useRef(notificationsMode);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
notificationsModeRef.current = notificationsMode;
|
|
|
|
|
}, [notificationsMode]);
|
2026-02-24 12:24:04 -08:00
|
|
|
|
2026-04-23 22:09:25 -07:00
|
|
|
const {
|
|
|
|
|
picks, setPicks,
|
|
|
|
|
currentPick, setCurrentPick,
|
|
|
|
|
searchQuery, setSearchQuery,
|
|
|
|
|
hideDrafted, setHideDrafted,
|
|
|
|
|
hideIneligible, setHideIneligible,
|
|
|
|
|
hideCompletedSports, setHideCompletedSports,
|
|
|
|
|
sportFilters, setSportFilters,
|
|
|
|
|
queue, setQueue,
|
|
|
|
|
isPaused, setIsPaused,
|
|
|
|
|
isDraftComplete, setIsDraftComplete,
|
|
|
|
|
sidebarCollapsed, setSidebarCollapsed,
|
|
|
|
|
activeTab, setActiveTab,
|
|
|
|
|
mobileTab, setMobileTab,
|
|
|
|
|
teamsSubTab, setTeamsSubTab,
|
|
|
|
|
boardSubTab, setBoardSubTab,
|
|
|
|
|
isMobile,
|
|
|
|
|
autodraftStatus, setAutodraftStatus,
|
|
|
|
|
connectedTeams, setConnectedTeams,
|
|
|
|
|
teamTimers, setTeamTimers,
|
|
|
|
|
forcePickDialogOpen, setForcePickDialogOpen,
|
|
|
|
|
selectedPickSlot, setSelectedPickSlot,
|
|
|
|
|
replacePickDialogOpen, setReplacePickDialogOpen,
|
|
|
|
|
replacePickSlot, setReplacePickSlot,
|
|
|
|
|
rollbackConfirmOpen, setRollbackConfirmOpen,
|
|
|
|
|
rollbackPickNumber, setRollbackPickNumber,
|
|
|
|
|
isRollingBack, setIsRollingBack,
|
|
|
|
|
timeBankDialogOpen, setTimeBankDialogOpen,
|
|
|
|
|
timeBankTeamId, setTimeBankTeamId,
|
|
|
|
|
commissionerAutodraftDialogOpen, setCommissionerAutodraftDialogOpen,
|
|
|
|
|
commissionerAutodraftTeamId, setCommissionerAutodraftTeamId,
|
|
|
|
|
timeBankAmount, setTimeBankAmount,
|
|
|
|
|
timeBankUnit, setTimeBankUnit,
|
|
|
|
|
timeBankDirection, setTimeBankDirection,
|
|
|
|
|
isAdjustingTimeBank, setIsAdjustingTimeBank,
|
|
|
|
|
} = useDraftRoomState({
|
|
|
|
|
draftPicks,
|
|
|
|
|
season,
|
|
|
|
|
userTeam,
|
|
|
|
|
isCommissioner,
|
|
|
|
|
autodraftSettings,
|
|
|
|
|
timers,
|
|
|
|
|
draftSlots,
|
|
|
|
|
userQueue,
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
});
|
|
|
|
|
|
2026-04-23 22:09:25 -07:00
|
|
|
const {
|
|
|
|
|
authDegraded,
|
|
|
|
|
authFetch,
|
|
|
|
|
userAutodraft,
|
|
|
|
|
setUserAutodraft,
|
|
|
|
|
userAutodraftRef,
|
|
|
|
|
isRevalidatingRef,
|
|
|
|
|
pendingPicksDuringRevalidationRef,
|
|
|
|
|
pendingQueueMutationsRef,
|
|
|
|
|
} = useDraftAuthRecovery({
|
|
|
|
|
reconnectCount,
|
|
|
|
|
revalidate,
|
|
|
|
|
revalidatorState,
|
|
|
|
|
currentUserId,
|
|
|
|
|
userAutodraftSettings,
|
|
|
|
|
draftPicks,
|
|
|
|
|
season,
|
|
|
|
|
userQueue,
|
|
|
|
|
timers,
|
|
|
|
|
autodraftSettings,
|
|
|
|
|
setPicks,
|
|
|
|
|
setCurrentPick,
|
|
|
|
|
setIsPaused,
|
|
|
|
|
setIsDraftComplete,
|
|
|
|
|
setQueue,
|
|
|
|
|
setTeamTimers,
|
|
|
|
|
setAutodraftStatus,
|
2025-10-21 23:22:17 -07:00
|
|
|
});
|
|
|
|
|
|
2026-02-22 16:16:51 -08:00
|
|
|
|
2026-02-20 21:10:26 -08:00
|
|
|
// Shared transforms for eligibility calculations
|
|
|
|
|
const transformedPicks = useMemo(
|
|
|
|
|
() =>
|
2026-03-21 09:44:05 -07:00
|
|
|
picks.map((p) => ({
|
2026-02-20 21:10:26 -08:00
|
|
|
teamId: p.team.id,
|
|
|
|
|
participant: {
|
|
|
|
|
id: p.participant.id,
|
|
|
|
|
sport: { id: p.sport.id, name: p.sport.name },
|
2025-10-24 21:12:07 -07:00
|
|
|
},
|
2026-02-20 21:10:26 -08:00
|
|
|
})),
|
|
|
|
|
[picks]
|
|
|
|
|
);
|
2025-10-24 21:12:07 -07:00
|
|
|
|
2026-02-20 21:10:26 -08:00
|
|
|
const transformedParticipants = useMemo(
|
|
|
|
|
() =>
|
2026-03-21 09:44:05 -07:00
|
|
|
availableParticipants.map((p) => ({
|
2026-02-20 21:10:26 -08:00
|
|
|
id: p.id,
|
|
|
|
|
sport: { id: p.sport.id, name: p.sport.name },
|
|
|
|
|
})),
|
|
|
|
|
[availableParticipants]
|
|
|
|
|
);
|
2025-10-24 21:12:07 -07:00
|
|
|
|
2026-02-20 21:10:26 -08:00
|
|
|
const seasonSportsData = useMemo(() => {
|
|
|
|
|
const sportsMap = new Map<string, { id: string; name: string }>();
|
2026-02-20 21:26:27 -08:00
|
|
|
availableParticipants.forEach((p) => {
|
2025-10-24 21:12:07 -07:00
|
|
|
if (!sportsMap.has(p.sport.id)) {
|
|
|
|
|
sportsMap.set(p.sport.id, { id: p.sport.id, name: p.sport.name });
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-03-21 09:44:05 -07:00
|
|
|
return Array.from(sportsMap.values()).toSorted((a, b) =>
|
2026-02-20 21:26:27 -08:00
|
|
|
a.name.localeCompare(b.name)
|
|
|
|
|
);
|
2026-02-20 21:10:26 -08:00
|
|
|
}, [availableParticipants]);
|
2025-10-24 21:12:07 -07:00
|
|
|
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
const participantRanks = useMemo(() => {
|
|
|
|
|
const ranks = new Map<string, { overallRank: number; sportRank: number }>();
|
|
|
|
|
const sportCounters = new Map<string, number>();
|
|
|
|
|
availableParticipants.forEach((p, i) => {
|
|
|
|
|
const sportIdx = (sportCounters.get(p.sport.id) ?? 0) + 1;
|
|
|
|
|
sportCounters.set(p.sport.id, sportIdx);
|
|
|
|
|
ranks.set(p.id, { overallRank: i + 1, sportRank: sportIdx });
|
|
|
|
|
});
|
|
|
|
|
return ranks;
|
|
|
|
|
}, [availableParticipants]);
|
|
|
|
|
|
2026-02-20 21:10:26 -08:00
|
|
|
// Calculate draft eligibility for current user's team
|
|
|
|
|
const eligibility = useMemo(() => {
|
|
|
|
|
if (!userTeam) return null;
|
|
|
|
|
const userTeamPicks = transformedPicks.filter(
|
2026-03-21 09:44:05 -07:00
|
|
|
(p) => p.teamId === userTeam.id
|
2026-02-20 21:10:26 -08:00
|
|
|
);
|
2025-10-24 21:12:07 -07:00
|
|
|
return calculateDraftEligibility(
|
|
|
|
|
userTeam.id,
|
|
|
|
|
userTeamPicks,
|
|
|
|
|
transformedPicks,
|
|
|
|
|
transformedParticipants,
|
|
|
|
|
seasonSportsData,
|
|
|
|
|
season.draftRounds,
|
|
|
|
|
season.teams
|
|
|
|
|
);
|
2026-02-20 21:10:26 -08:00
|
|
|
}, [userTeam, transformedPicks, transformedParticipants, seasonSportsData, season]);
|
2025-10-24 21:12:07 -07:00
|
|
|
|
|
|
|
|
// Calculate eligibility for force manual pick dialog (selected team)
|
|
|
|
|
const forcePickEligibility = useMemo(() => {
|
|
|
|
|
if (!selectedPickSlot) return null;
|
|
|
|
|
const selectedTeamPicks = transformedPicks.filter(
|
2026-03-21 09:44:05 -07:00
|
|
|
(p) => p.teamId === selectedPickSlot.teamId
|
2025-10-24 21:12:07 -07:00
|
|
|
);
|
|
|
|
|
return calculateDraftEligibility(
|
|
|
|
|
selectedPickSlot.teamId,
|
|
|
|
|
selectedTeamPicks,
|
|
|
|
|
transformedPicks,
|
|
|
|
|
transformedParticipants,
|
|
|
|
|
seasonSportsData,
|
|
|
|
|
season.draftRounds,
|
|
|
|
|
season.teams
|
|
|
|
|
);
|
2026-02-20 21:10:26 -08:00
|
|
|
}, [selectedPickSlot, transformedPicks, transformedParticipants, seasonSportsData, season]);
|
2025-10-24 21:12:07 -07:00
|
|
|
|
2026-02-20 22:47:29 -08:00
|
|
|
// Calculate eligibility for replace pick dialog — exclude the old pick so its slot is free
|
|
|
|
|
const replacePickEligibility = useMemo(() => {
|
|
|
|
|
if (!replacePickSlot) return null;
|
|
|
|
|
const allPicksExcluding = transformedPicks.filter(
|
2026-03-21 09:44:05 -07:00
|
|
|
(p) => p.participant.id !== replacePickSlot.oldParticipantId
|
2026-02-20 22:47:29 -08:00
|
|
|
);
|
|
|
|
|
const selectedTeamPicks = allPicksExcluding.filter(
|
2026-03-21 09:44:05 -07:00
|
|
|
(p) => p.teamId === replacePickSlot.teamId
|
2026-02-20 22:47:29 -08:00
|
|
|
);
|
|
|
|
|
return calculateDraftEligibility(
|
|
|
|
|
replacePickSlot.teamId,
|
|
|
|
|
selectedTeamPicks,
|
|
|
|
|
allPicksExcluding,
|
|
|
|
|
transformedParticipants,
|
|
|
|
|
seasonSportsData,
|
|
|
|
|
season.draftRounds,
|
|
|
|
|
season.teams
|
|
|
|
|
);
|
|
|
|
|
}, [replacePickSlot, transformedPicks, transformedParticipants, seasonSportsData, season]);
|
|
|
|
|
|
2026-04-23 22:09:25 -07:00
|
|
|
useDraftSocketEvents({
|
|
|
|
|
on,
|
|
|
|
|
off,
|
|
|
|
|
socketVersion,
|
|
|
|
|
userTeam,
|
|
|
|
|
draftSlots,
|
|
|
|
|
leagueName: season.league.name,
|
|
|
|
|
isRevalidatingRef,
|
|
|
|
|
pendingPicksDuringRevalidationRef,
|
|
|
|
|
pendingQueueMutationsRef,
|
|
|
|
|
userAutodraftRef,
|
|
|
|
|
sendNotificationRef,
|
|
|
|
|
notificationsModeRef,
|
|
|
|
|
setPicks,
|
|
|
|
|
setCurrentPick,
|
|
|
|
|
setIsPaused,
|
|
|
|
|
setIsDraftComplete,
|
|
|
|
|
setAutodraftStatus,
|
|
|
|
|
setUserAutodraft,
|
|
|
|
|
setConnectedTeams,
|
|
|
|
|
setQueue,
|
|
|
|
|
setTeamTimers,
|
|
|
|
|
});
|
2025-10-17 12:30:58 -07:00
|
|
|
|
2025-10-25 03:23:41 -07:00
|
|
|
// Persist sidebar collapsed state
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
|
localStorage.setItem("draftSidebarCollapsed", JSON.stringify(sidebarCollapsed));
|
|
|
|
|
}
|
|
|
|
|
}, [sidebarCollapsed]);
|
|
|
|
|
|
2025-10-17 17:42:40 -07:00
|
|
|
// Queue handlers
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleAddToQueue = useCallback(async (participantId: string) => {
|
2025-10-17 17:42:40 -07:00
|
|
|
if (!userTeam) return;
|
|
|
|
|
|
2025-10-25 21:02:16 -07:00
|
|
|
// Check if participant is already in queue (client-side)
|
2026-02-24 11:31:25 -08:00
|
|
|
const alreadyInQueue = queue.some((item) => item.participantId === participantId);
|
2025-10-25 21:02:16 -07:00
|
|
|
if (alreadyInQueue) {
|
|
|
|
|
toast.error("This participant is already in your queue");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optimistic update - create a temporary queue item
|
2026-02-24 11:31:25 -08:00
|
|
|
const now = new Date();
|
|
|
|
|
const tempQueueItem: QueueItem = {
|
|
|
|
|
id: `temp-${Date.now()}`,
|
2025-10-25 21:02:16 -07:00
|
|
|
participantId,
|
|
|
|
|
queuePosition: queue.length + 1,
|
2026-02-24 11:31:25 -08:00
|
|
|
seasonId: season.id,
|
|
|
|
|
teamId: userTeam.id,
|
|
|
|
|
createdAt: now,
|
|
|
|
|
updatedAt: now,
|
2025-10-25 21:02:16 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Immediately update the UI
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue((prev) => [...prev, tempQueueItem]);
|
2025-10-25 21:02:16 -07:00
|
|
|
|
2025-10-17 17:42:40 -07:00
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("teamId", userTeam.id);
|
|
|
|
|
formData.append("participantId", participantId);
|
|
|
|
|
|
2026-03-04 21:39:54 -08:00
|
|
|
pendingQueueMutationsRef.current++;
|
2025-10-25 21:02:16 -07:00
|
|
|
try {
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/queue/add", {
|
2025-10-25 21:02:16 -07:00
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response) { setQueue((prev) => prev.filter((item) => item.id !== tempQueueItem.id)); return; }
|
2025-10-17 17:42:40 -07:00
|
|
|
|
2025-10-25 21:02:16 -07:00
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
// Replace temp item with real item from server
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue((prev) =>
|
|
|
|
|
prev.map((item) =>
|
2025-10-25 21:02:16 -07:00
|
|
|
item.id === tempQueueItem.id ? data.queueItem : item
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// Revert the optimistic update on error
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue((prev) => prev.filter((item) => item.id !== tempQueueItem.id));
|
2025-10-25 21:02:16 -07:00
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to add to queue");
|
|
|
|
|
}
|
2026-03-21 09:44:05 -07:00
|
|
|
} catch {
|
2025-10-25 21:02:16 -07:00
|
|
|
// Revert the optimistic update on network error
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue((prev) => prev.filter((item) => item.id !== tempQueueItem.id));
|
2025-10-25 21:02:16 -07:00
|
|
|
toast.error("Network error - failed to add to queue");
|
2026-03-04 21:39:54 -08:00
|
|
|
} finally {
|
|
|
|
|
pendingQueueMutationsRef.current--;
|
2025-10-17 17:42:40 -07:00
|
|
|
}
|
2026-03-02 13:18:47 -08:00
|
|
|
// queue is read directly (not via functional updater) for the duplicate check and
|
|
|
|
|
// optimistic position, so it must be a dep. This is fine: AvailableParticipantsSection
|
|
|
|
|
// already re-renders when queue changes (it receives queue as a prop).
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [userTeam, queue, season.id, authFetch, setQueue, pendingQueueMutationsRef]);
|
2025-10-17 17:42:40 -07:00
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleRemoveFromQueue = useCallback(async (queueId: string) => {
|
2025-10-17 17:42:40 -07:00
|
|
|
if (!userTeam) return;
|
|
|
|
|
|
2026-02-24 11:31:25 -08:00
|
|
|
// Optimistically remove the item immediately
|
|
|
|
|
let previousQueue: QueueItem[] = [];
|
|
|
|
|
setQueue((prev) => {
|
|
|
|
|
previousQueue = prev;
|
|
|
|
|
return prev.filter((item) => item.id !== queueId);
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-17 17:42:40 -07:00
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("queueId", queueId);
|
|
|
|
|
formData.append("teamId", userTeam.id);
|
|
|
|
|
|
2026-03-04 21:39:54 -08:00
|
|
|
pendingQueueMutationsRef.current++;
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
try {
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/queue/remove", {
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response) { setQueue(previousQueue); return; }
|
2025-10-17 17:42:40 -07:00
|
|
|
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
setQueue(data.queue);
|
|
|
|
|
} else {
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue(previousQueue);
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to remove from queue");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue(previousQueue);
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
toast.error("Network error - failed to remove from queue");
|
2026-03-04 21:39:54 -08:00
|
|
|
} finally {
|
|
|
|
|
pendingQueueMutationsRef.current--;
|
2025-10-17 17:42:40 -07:00
|
|
|
}
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [userTeam, authFetch, setQueue, pendingQueueMutationsRef]);
|
2025-10-17 17:42:40 -07:00
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleReorderQueue = useCallback(async (participantIds: string[]) => {
|
2025-10-17 17:42:40 -07:00
|
|
|
if (!userTeam) return;
|
|
|
|
|
|
2026-02-24 11:31:25 -08:00
|
|
|
// Optimistically reorder the queue immediately
|
|
|
|
|
let previousQueue: QueueItem[] = [];
|
|
|
|
|
setQueue((prev) => {
|
|
|
|
|
previousQueue = prev;
|
|
|
|
|
return participantIds
|
|
|
|
|
.map((pid) => prev.find((item) => item.participantId === pid))
|
|
|
|
|
.filter((item): item is QueueItem => item !== undefined);
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-17 17:42:40 -07:00
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("teamId", userTeam.id);
|
2025-10-25 21:02:16 -07:00
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("participantIds", JSON.stringify(participantIds));
|
2025-10-17 17:42:40 -07:00
|
|
|
|
2026-03-04 21:39:54 -08:00
|
|
|
pendingQueueMutationsRef.current++;
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
try {
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/queue/reorder", {
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response) { setQueue(previousQueue); return; }
|
2025-10-17 17:42:40 -07:00
|
|
|
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
setQueue(data.queue);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue(previousQueue);
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
toast.error(error.error || "Failed to reorder queue");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
2026-02-24 11:31:25 -08:00
|
|
|
setQueue(previousQueue);
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
toast.error("Network error - failed to reorder queue");
|
2026-03-04 21:39:54 -08:00
|
|
|
} finally {
|
|
|
|
|
pendingQueueMutationsRef.current--;
|
2025-10-17 17:42:40 -07:00
|
|
|
}
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [userTeam, season.id, authFetch, setQueue, pendingQueueMutationsRef]);
|
2025-10-17 17:42:40 -07:00
|
|
|
|
2025-10-18 14:55:26 -07:00
|
|
|
const handleStartDraft = async () => {
|
2026-03-01 19:33:03 -08:00
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/start", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
if (!response) return;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
revalidate();
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to start draft");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to start draft");
|
2025-10-18 14:55:26 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-18 23:19:41 -07:00
|
|
|
const handlePauseDraft = async () => {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
try {
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/pause", {
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response) return;
|
2025-10-18 23:19:41 -07:00
|
|
|
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
setIsPaused(true);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to pause draft");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to pause draft");
|
2025-10-18 23:19:41 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleResumeDraft = async () => {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
try {
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/resume", {
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response) return;
|
2025-10-18 23:19:41 -07:00
|
|
|
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
setIsPaused(false);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to resume draft");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to resume draft");
|
2025-10-18 23:19:41 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleMakePick = useCallback(async (participantId: string) => {
|
2026-03-01 19:33:03 -08:00
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("participantId", participantId);
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/make-pick", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
if (!response) return;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response.ok) {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to make pick");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to make pick");
|
2025-10-18 14:55:26 -07:00
|
|
|
}
|
2026-03-02 13:18:47 -08:00
|
|
|
}, [season.id, authFetch]);
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleForceAutopick = useCallback(async (pickNumber: number, teamId: string) => {
|
2026-03-01 19:33:03 -08:00
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("teamId", teamId);
|
|
|
|
|
formData.append("pickNumber", pickNumber.toString());
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/force-autopick", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
if (!response) return;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response.ok) {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to force autopick");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to force autopick");
|
2025-10-18 14:55:26 -07:00
|
|
|
}
|
2026-03-02 13:18:47 -08:00
|
|
|
}, [season.id, authFetch]);
|
2025-10-18 14:55:26 -07:00
|
|
|
|
|
|
|
|
const handleForceManualPick = async (participantId: string) => {
|
|
|
|
|
if (!selectedPickSlot) return;
|
|
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("teamId", selectedPickSlot.teamId);
|
|
|
|
|
formData.append("participantId", participantId);
|
|
|
|
|
formData.append("pickNumber", selectedPickSlot.pickNumber.toString());
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/force-manual-pick", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
if (!response) return;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
setForcePickDialogOpen(false);
|
|
|
|
|
setSelectedPickSlot(null);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to force manual pick");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to force manual pick");
|
2025-10-18 14:55:26 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleReplacePickOpen = useCallback((pickNumber: number, teamId: string) => {
|
2026-03-21 09:44:05 -07:00
|
|
|
const pick = picks.find((p) => p.pickNumber === pickNumber);
|
2026-02-20 22:47:29 -08:00
|
|
|
if (!pick) {
|
|
|
|
|
toast.error("Pick not found — try refreshing the page");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setReplacePickSlot({ pickNumber, teamId, oldParticipantId: pick.participant.id });
|
|
|
|
|
setReplacePickDialogOpen(true);
|
2026-03-02 13:18:47 -08:00
|
|
|
// picks is read directly to find the pick being replaced.
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [picks, setReplacePickSlot, setReplacePickDialogOpen]);
|
2026-02-20 22:47:29 -08:00
|
|
|
|
|
|
|
|
const handleReplacePick = async (participantId: string) => {
|
|
|
|
|
if (!replacePickSlot) return;
|
|
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("teamId", replacePickSlot.teamId);
|
|
|
|
|
formData.append("participantId", participantId);
|
|
|
|
|
formData.append("pickNumber", replacePickSlot.pickNumber.toString());
|
2026-02-20 22:47:29 -08:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/replace-pick", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
if (!response) return;
|
2026-02-20 22:47:29 -08:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
if (response.ok) {
|
|
|
|
|
setReplacePickDialogOpen(false);
|
|
|
|
|
setReplacePickSlot(null);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to replace pick");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error - failed to replace pick");
|
2026-02-20 22:47:29 -08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleRollbackToPick = useCallback((pickNumber: number) => {
|
2026-02-20 22:47:29 -08:00
|
|
|
setRollbackPickNumber(pickNumber);
|
|
|
|
|
setRollbackConfirmOpen(true);
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [setRollbackPickNumber, setRollbackConfirmOpen]);
|
2026-02-20 22:47:29 -08:00
|
|
|
|
|
|
|
|
const handleConfirmRollback = async () => {
|
|
|
|
|
if (!rollbackPickNumber || isRollingBack) return;
|
|
|
|
|
|
|
|
|
|
setIsRollingBack(true);
|
2026-03-01 19:33:03 -08:00
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("pickNumber", rollbackPickNumber.toString());
|
2026-02-20 22:47:29 -08:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/rollback", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-02-20 22:47:29 -08:00
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
setIsRollingBack(false);
|
|
|
|
|
if (!response) return;
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
setRollbackConfirmOpen(false);
|
|
|
|
|
setRollbackPickNumber(null);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to roll back draft");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
setIsRollingBack(false);
|
|
|
|
|
toast.error("Network error - failed to roll back draft");
|
2026-02-20 22:47:29 -08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleAdjustTimeBankOpen = useCallback((teamId: string) => {
|
2026-02-22 16:16:51 -08:00
|
|
|
setTimeBankTeamId(teamId);
|
|
|
|
|
setTimeBankAmount("1");
|
|
|
|
|
setTimeBankUnit("minutes");
|
|
|
|
|
setTimeBankDirection("add");
|
|
|
|
|
setTimeBankDialogOpen(true);
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [setTimeBankTeamId, setTimeBankAmount, setTimeBankUnit, setTimeBankDirection, setTimeBankDialogOpen]);
|
2026-02-22 16:16:51 -08:00
|
|
|
|
2026-03-03 20:14:38 -08:00
|
|
|
const handleSetAutodraftOpen = useCallback((teamId: string) => {
|
|
|
|
|
setCommissionerAutodraftTeamId(teamId);
|
|
|
|
|
setCommissionerAutodraftDialogOpen(true);
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [setCommissionerAutodraftTeamId, setCommissionerAutodraftDialogOpen]);
|
2026-03-03 20:14:38 -08:00
|
|
|
|
|
|
|
|
const handleCommissionerAutodraftUpdate = useCallback(() => {
|
|
|
|
|
setCommissionerAutodraftDialogOpen(false);
|
|
|
|
|
setCommissionerAutodraftTeamId(null);
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [setCommissionerAutodraftDialogOpen, setCommissionerAutodraftTeamId]);
|
2026-03-03 20:14:38 -08:00
|
|
|
|
2026-02-22 16:16:51 -08:00
|
|
|
const handleConfirmAdjustTimeBank = async () => {
|
|
|
|
|
if (!timeBankTeamId || isAdjustingTimeBank) return;
|
|
|
|
|
|
|
|
|
|
const amount = parseFloat(timeBankAmount);
|
|
|
|
|
if (isNaN(amount) || amount <= 0) {
|
|
|
|
|
toast.error("Please enter a valid positive amount");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const unitMultipliers = { seconds: 1, minutes: 60, hours: 3600 };
|
|
|
|
|
const totalSeconds = Math.round(amount * unitMultipliers[timeBankUnit]);
|
|
|
|
|
|
|
|
|
|
if (totalSeconds === 0) {
|
|
|
|
|
toast.error("Adjustment rounds to 0 seconds — please enter a larger value");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const adjustment = timeBankDirection === "add" ? totalSeconds : -totalSeconds;
|
|
|
|
|
|
|
|
|
|
setIsAdjustingTimeBank(true);
|
|
|
|
|
try {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("seasonId", season.id);
|
|
|
|
|
formData.append("teamId", timeBankTeamId);
|
|
|
|
|
formData.append("adjustment", adjustment.toString());
|
|
|
|
|
|
2026-03-01 19:33:03 -08:00
|
|
|
const response = await authFetch("/api/draft/adjust-time-bank", {
|
2026-02-22 16:16:51 -08:00
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
2026-03-01 19:33:03 -08:00
|
|
|
if (!response) return;
|
2026-02-22 16:16:51 -08:00
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
setTeamTimers((prev) => ({ ...prev, [timeBankTeamId]: data.timeRemaining }));
|
|
|
|
|
const teamName = draftSlots.find((s) => s.team.id === timeBankTeamId)?.team.name;
|
|
|
|
|
toast.success(`Time bank adjusted for ${teamName}`);
|
|
|
|
|
setTimeBankDialogOpen(false);
|
|
|
|
|
setTimeBankTeamId(null);
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
toast.error(error.error || "Failed to adjust time bank");
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Network error — failed to adjust time bank");
|
|
|
|
|
} finally {
|
|
|
|
|
setIsAdjustingTimeBank(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleForceManualPickOpen = useCallback((pickNumber: number, teamId: string) => {
|
2026-02-22 22:36:12 -08:00
|
|
|
setSelectedPickSlot({ pickNumber, teamId });
|
|
|
|
|
setForcePickDialogOpen(true);
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [setSelectedPickSlot, setForcePickDialogOpen]);
|
2026-02-22 22:36:12 -08:00
|
|
|
|
2025-10-17 12:51:47 -07:00
|
|
|
// Calculate current round
|
2026-02-20 11:16:34 -08:00
|
|
|
const currentRound = draftSlots.length > 0 ? Math.ceil(currentPick / draftSlots.length) : 1;
|
2025-10-17 12:30:58 -07:00
|
|
|
|
2025-10-18 14:55:26 -07:00
|
|
|
// Determine whose turn it is
|
2026-02-27 23:21:08 -08:00
|
|
|
const currentDraftSlot = useMemo(
|
|
|
|
|
() => getTeamForPick(currentPick, draftSlots),
|
|
|
|
|
[currentPick, draftSlots]
|
|
|
|
|
);
|
2025-10-21 23:22:17 -07:00
|
|
|
const isMyTurn = !!(
|
|
|
|
|
userTeam && currentDraftSlot && currentDraftSlot.team.id === userTeam.id
|
|
|
|
|
);
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
const canPick = isMyTurn && season.status === "draft" && !isPaused; // Only team owner on their turn when draft is active
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-02-27 23:21:08 -08:00
|
|
|
const currentDraftSlotOwnerName = currentDraftSlot ? ownerMap[currentDraftSlot.team.id] : undefined;
|
Add draft clock UI, Fischer increment timer logic, and security fixes (#19)
- Add prominent clock badge to tab bar (lights up on your turn) with
correct Fischer increment chess-clock model: bank starts at initialTime,
+= incrementTime after each pick, other teams' banks untouched
- Extract pure timer helpers to app/lib/draft-timer.ts and add 29 unit
tests covering formatClockTime, calculateTimeAfterPick, getTimerColorClass,
and full snake-draft lifecycle regression scenarios
- Fix make-pick.ts: add status !== 'draft' and draftPaused server guards
- Fix draft-utils.ts: replace (global as any).__socketIO with getSocketIO(),
fix timeUsed to store actual timeRemaining at pick moment (not always 120),
add null timer warning, move timer fetch before pick insert
- Fix draft.start.ts: batch timer inserts, guard against empty draftSlots
- Fix DraftGridSection: memoize currentTeamId, widen teamTimers type to
Record<string, number | undefined>
- Fix duplicate animate-pulse (getTimerColorClass already includes it)
- Clamp negative seconds in formatClockTime to guard against timer drift
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 16:51:12 -08:00
|
|
|
const currentClockTime = currentDraftSlot ? teamTimers[currentDraftSlot.team.id] : undefined;
|
|
|
|
|
const currentClockColor = getTimerColorClass(currentClockTime);
|
|
|
|
|
|
2025-10-17 12:51:47 -07:00
|
|
|
// Generate snake draft grid structure
|
2026-02-20 21:10:26 -08:00
|
|
|
const draftGrid = useMemo(() => {
|
2025-10-17 12:51:47 -07:00
|
|
|
const teamCount = draftSlots.length;
|
|
|
|
|
const rounds = season.draftRounds;
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
const pickMap = new Map(picks.map((p) => [p.pickNumber, p]));
|
2025-10-18 14:55:26 -07:00
|
|
|
const grid: Array<
|
|
|
|
|
Array<{
|
|
|
|
|
pickNumber: number;
|
|
|
|
|
round: number;
|
|
|
|
|
pickInRound: number;
|
|
|
|
|
teamId: string;
|
2026-03-21 09:44:05 -07:00
|
|
|
pick?: (typeof draftPicks)[number];
|
2025-10-18 14:55:26 -07:00
|
|
|
}>
|
|
|
|
|
> = [];
|
2025-10-17 12:51:47 -07:00
|
|
|
|
|
|
|
|
for (let round = 1; round <= rounds; round++) {
|
|
|
|
|
const roundPicks = [];
|
|
|
|
|
const isOddRound = round % 2 === 1;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2025-10-17 12:51:47 -07:00
|
|
|
for (let i = 0; i < teamCount; i++) {
|
|
|
|
|
const pickInRound = i + 1;
|
|
|
|
|
const teamIndex = isOddRound ? i : teamCount - 1 - i;
|
|
|
|
|
const pickNumber = (round - 1) * teamCount + pickInRound;
|
|
|
|
|
const teamId = draftSlots[teamIndex]?.team.id;
|
2025-10-18 14:55:26 -07:00
|
|
|
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
const pick = pickMap.get(pickNumber);
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-02-20 21:10:26 -08:00
|
|
|
roundPicks.push({ pickNumber, round, pickInRound, teamId, pick });
|
2025-10-17 12:51:47 -07:00
|
|
|
}
|
|
|
|
|
grid.push(roundPicks);
|
|
|
|
|
}
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2025-10-17 12:51:47 -07:00
|
|
|
return grid;
|
2026-02-20 21:10:26 -08:00
|
|
|
}, [picks, draftSlots, season.draftRounds]);
|
2025-10-17 12:51:47 -07:00
|
|
|
|
2026-04-24 09:32:11 -07:00
|
|
|
const projectedPicks = useMemo(() => {
|
|
|
|
|
if (!userTeam || season.status !== "draft" || isDraftComplete) return [];
|
|
|
|
|
return getProjectedPicks({
|
|
|
|
|
draftSlots,
|
|
|
|
|
userTeamId: userTeam.id,
|
|
|
|
|
currentPick,
|
|
|
|
|
totalRounds: season.draftRounds,
|
|
|
|
|
existingPicks: picks.map((p) => ({
|
|
|
|
|
pickNumber: p.pickNumber,
|
|
|
|
|
teamId: p.team.id,
|
|
|
|
|
})),
|
|
|
|
|
});
|
|
|
|
|
}, [userTeam, season.status, isDraftComplete, draftSlots, currentPick, season.draftRounds, picks]);
|
|
|
|
|
|
2025-10-17 16:58:35 -07:00
|
|
|
// Get drafted participant IDs for filtering
|
2026-02-20 21:10:26 -08:00
|
|
|
const draftedParticipantIds = useMemo(
|
2026-03-21 09:44:05 -07:00
|
|
|
() => new Set(picks.map((p) => p.participant.id)),
|
2026-02-20 21:10:26 -08:00
|
|
|
[picks]
|
2025-10-18 14:55:26 -07:00
|
|
|
);
|
2025-10-17 16:58:35 -07:00
|
|
|
|
2026-02-28 23:16:06 -08:00
|
|
|
// Sport IDs where the current user's team has already made at least one pick
|
|
|
|
|
const userDraftedSportIds = useMemo(() => {
|
|
|
|
|
if (!userTeam) return new Set<string>();
|
|
|
|
|
return new Set(
|
|
|
|
|
picks
|
2026-03-21 09:44:05 -07:00
|
|
|
.filter((p) => p.team.id === userTeam.id)
|
|
|
|
|
.map((p) => p.sport.id)
|
2026-02-28 23:16:06 -08:00
|
|
|
);
|
|
|
|
|
}, [picks, userTeam]);
|
|
|
|
|
|
2026-03-02 10:13:51 -08:00
|
|
|
// Sport names where the current user's team has already made at least one pick
|
|
|
|
|
const userDraftedSportNames = useMemo(() => {
|
|
|
|
|
if (!userTeam) return new Set<string>();
|
|
|
|
|
return new Set(
|
|
|
|
|
picks
|
2026-03-21 09:44:05 -07:00
|
|
|
.filter((p) => p.team.id === userTeam.id)
|
|
|
|
|
.map((p) => p.sport.name)
|
2026-03-02 10:13:51 -08:00
|
|
|
);
|
|
|
|
|
}, [picks, userTeam]);
|
|
|
|
|
|
2025-10-17 16:58:35 -07:00
|
|
|
// Get unique sports for filter dropdown
|
2026-02-20 21:10:26 -08:00
|
|
|
const uniqueSports = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
Array.from(
|
2026-03-21 09:44:05 -07:00
|
|
|
new Set(availableParticipants.map((p) => p.sport.name))
|
2026-02-20 21:10:26 -08:00
|
|
|
).sort(),
|
|
|
|
|
[availableParticipants]
|
|
|
|
|
);
|
|
|
|
|
|
2025-10-26 21:09:30 -07:00
|
|
|
// Filter participants based on search, sport, drafted status, and eligibility
|
2026-03-02 00:29:54 -08:00
|
|
|
const filteredParticipants = useMemo(() => {
|
|
|
|
|
const sportFilterSet = new Set(sportFilters);
|
2026-03-21 09:44:05 -07:00
|
|
|
return availableParticipants.filter((participant) => {
|
2026-03-02 00:29:54 -08:00
|
|
|
// Drafted filter - hide drafted participants by default
|
|
|
|
|
if (hideDrafted && draftedParticipantIds.has(participant.id)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Eligibility filter - hide ineligible participants by default (if user has a team)
|
|
|
|
|
if (hideIneligible && eligibility) {
|
|
|
|
|
const isEligible = eligibility.eligibleSportIds.has(participant.sport.id);
|
|
|
|
|
if (!isEligible) {
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-02 00:29:54 -08:00
|
|
|
}
|
|
|
|
|
// Hide completed sports filter - hide participants from sports already drafted by user's team
|
|
|
|
|
if (hideCompletedSports && userDraftedSportIds.has(participant.sport.id)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Search filter
|
|
|
|
|
if (
|
|
|
|
|
searchQuery &&
|
|
|
|
|
!participant.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Sport filter
|
|
|
|
|
if (sportFilterSet.size > 0 && !sportFilterSet.has(participant.sport.name)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}, [availableParticipants, hideDrafted, hideIneligible, hideCompletedSports, eligibility, draftedParticipantIds, userDraftedSportIds, searchQuery, sportFilters]);
|
2025-10-17 16:58:35 -07:00
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
// Shared component props — defined once to avoid duplication between desktop and mobile layouts
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleHideCompletedSportsChange = useCallback((hide: boolean) => {
|
|
|
|
|
setHideCompletedSports(hide);
|
|
|
|
|
if (hide) {
|
|
|
|
|
setSportFilters((prev) => prev.filter((s) => !userDraftedSportNames.has(s)));
|
|
|
|
|
}
|
2026-04-23 22:09:25 -07:00
|
|
|
}, [userDraftedSportNames, setHideCompletedSports, setSportFilters]);
|
2026-03-02 13:18:47 -08:00
|
|
|
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
const miniDraftGrid = useMemo(() => ({
|
|
|
|
|
draftSlots,
|
|
|
|
|
draftGrid,
|
|
|
|
|
currentPick,
|
|
|
|
|
currentRound,
|
|
|
|
|
ownerMap,
|
|
|
|
|
teamTimers,
|
|
|
|
|
autodraftStatus,
|
|
|
|
|
seasonStatus: season.status,
|
|
|
|
|
draftPaused: isPaused,
|
|
|
|
|
onForceAutopick: isCommissioner ? handleForceAutopick : undefined,
|
|
|
|
|
onForceManualPickOpen: isCommissioner ? handleForceManualPickOpen : undefined,
|
|
|
|
|
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
|
|
|
|
|
onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined,
|
|
|
|
|
}), [draftSlots, draftGrid, currentPick, currentRound, ownerMap, teamTimers, autodraftStatus, season.status, isPaused, isCommissioner, handleForceAutopick, handleForceManualPickOpen, handleReplacePickOpen, handleRollbackToPick, isDraftComplete]);
|
|
|
|
|
|
|
|
|
|
const availableParticipantsSectionProps = useMemo(() => ({
|
2026-02-22 22:36:12 -08:00
|
|
|
participants: filteredParticipants,
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
participantRanks,
|
|
|
|
|
miniDraftGrid,
|
2026-02-22 22:36:12 -08:00
|
|
|
searchQuery,
|
2026-03-02 00:29:54 -08:00
|
|
|
sportFilters,
|
2026-02-22 22:36:12 -08:00
|
|
|
hideDrafted,
|
|
|
|
|
hideIneligible,
|
|
|
|
|
uniqueSports,
|
|
|
|
|
draftedParticipantIds,
|
|
|
|
|
queue,
|
|
|
|
|
eligibility,
|
|
|
|
|
canPick,
|
|
|
|
|
hasTeam: !!userTeam,
|
|
|
|
|
onSearchChange: setSearchQuery,
|
2026-03-02 00:29:54 -08:00
|
|
|
onSportFiltersChange: setSportFilters,
|
2026-02-22 22:36:12 -08:00
|
|
|
onHideDraftedChange: setHideDrafted,
|
|
|
|
|
onHideIneligibleChange: setHideIneligible,
|
2026-02-28 23:16:06 -08:00
|
|
|
hideCompletedSports,
|
2026-03-02 10:13:51 -08:00
|
|
|
userDraftedSportNames,
|
2026-03-02 13:18:47 -08:00
|
|
|
onHideCompletedSportsChange: handleHideCompletedSportsChange,
|
2026-02-22 22:36:12 -08:00
|
|
|
onMakePick: handleMakePick,
|
|
|
|
|
onAddToQueue: handleAddToQueue,
|
|
|
|
|
onRemoveFromQueue: handleRemoveFromQueue,
|
2026-04-24 09:32:11 -07:00
|
|
|
projectedPicks,
|
|
|
|
|
}), [filteredParticipants, participantRanks, miniDraftGrid, searchQuery, sportFilters, hideDrafted, hideIneligible, uniqueSports, draftedParticipantIds, queue, eligibility, canPick, userTeam, hideCompletedSports, userDraftedSportNames, handleHideCompletedSportsChange, handleMakePick, handleAddToQueue, handleRemoveFromQueue, setSearchQuery, setSportFilters, setHideDrafted, setHideIneligible, projectedPicks]);
|
2026-02-22 22:36:12 -08:00
|
|
|
|
|
|
|
|
const draftGridSectionProps = {
|
|
|
|
|
draftSlots,
|
|
|
|
|
draftGrid,
|
|
|
|
|
currentPick,
|
|
|
|
|
teamTimers,
|
|
|
|
|
autodraftStatus,
|
|
|
|
|
connectedTeams,
|
|
|
|
|
isCommissioner,
|
|
|
|
|
ownerMap,
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
seasonStatus: season.status,
|
|
|
|
|
draftPaused: isPaused,
|
2026-02-22 22:36:12 -08:00
|
|
|
onAdjustTimeBankOpen: isCommissioner ? handleAdjustTimeBankOpen : undefined,
|
2026-03-03 20:14:38 -08:00
|
|
|
onSetAutodraftOpen: isCommissioner ? handleSetAutodraftOpen : undefined,
|
2026-02-22 22:36:12 -08:00
|
|
|
onForceAutopick: handleForceAutopick,
|
|
|
|
|
onForceManualPickOpen: handleForceManualPickOpen,
|
|
|
|
|
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
|
|
|
|
|
onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined,
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 16:46:35 -08:00
|
|
|
const summaryViewProps = {
|
|
|
|
|
draftSlots,
|
|
|
|
|
picks,
|
|
|
|
|
sports: seasonSportsData,
|
|
|
|
|
ownerMap,
|
2026-03-02 22:19:19 -08:00
|
|
|
totalRounds: season.draftRounds,
|
2026-03-02 16:46:35 -08:00
|
|
|
};
|
2026-03-02 13:18:47 -08:00
|
|
|
|
2026-03-02 16:46:35 -08:00
|
|
|
const rosterViewProps = {
|
2026-02-22 22:36:12 -08:00
|
|
|
draftSlots,
|
|
|
|
|
picks,
|
|
|
|
|
sports: seasonSportsData,
|
|
|
|
|
ownerMap,
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
totalRounds: season.draftRounds,
|
2026-02-22 22:36:12 -08:00
|
|
|
};
|
|
|
|
|
|
2026-03-02 13:18:47 -08:00
|
|
|
const handleAutodraftUpdate = useCallback(
|
|
|
|
|
(isEnabled: boolean, mode: "next_pick" | "while_on", queueOnly: boolean) => {
|
|
|
|
|
setUserAutodraft({ isEnabled, mode, queueOnly });
|
|
|
|
|
},
|
2026-04-23 22:09:25 -07:00
|
|
|
[setUserAutodraft]
|
2026-03-02 13:18:47 -08:00
|
|
|
);
|
|
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
const queueSectionProps = userTeam
|
|
|
|
|
? {
|
|
|
|
|
queue,
|
|
|
|
|
availableParticipants,
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
canPick,
|
2026-02-22 22:36:12 -08:00
|
|
|
onRemoveFromQueue: handleRemoveFromQueue,
|
|
|
|
|
onReorder: handleReorderQueue,
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
onMakePick: handleMakePick,
|
2026-02-22 22:36:12 -08:00
|
|
|
}
|
|
|
|
|
: null;
|
|
|
|
|
|
2026-02-27 23:12:21 -08:00
|
|
|
const mobileTabs = userTeam
|
|
|
|
|
? [MOBILE_TABS_BASE[0], QUEUE_TAB, ...MOBILE_TABS_BASE.slice(1)]
|
|
|
|
|
: MOBILE_TABS_BASE;
|
|
|
|
|
const mobileColCount = mobileTabs.length;
|
|
|
|
|
|
2025-10-17 12:30:58 -07:00
|
|
|
return (
|
2026-02-23 08:22:00 -08:00
|
|
|
<div className="h-dvh bg-background flex flex-col overflow-hidden">
|
2025-10-18 23:33:13 -07:00
|
|
|
{/* Draft Completion Banner */}
|
|
|
|
|
{isDraftComplete && (
|
2026-02-20 19:26:11 -08:00
|
|
|
<div className="bg-emerald-500/20 border-b border-emerald-500/30 text-emerald-400 px-4 py-3 text-center font-semibold flex-shrink-0">
|
2025-10-18 23:33:13 -07:00
|
|
|
🎉 Draft Complete! The season is now active.
|
2025-10-20 15:03:11 -07:00
|
|
|
{season.league.isPublicDraftBoard && (
|
|
|
|
|
<>
|
|
|
|
|
{" "}
|
|
|
|
|
<a
|
|
|
|
|
href={`/leagues/${season.leagueId}/draft-board/${season.id}`}
|
2026-02-20 19:26:11 -08:00
|
|
|
className="underline hover:text-emerald-300"
|
2025-10-20 15:03:11 -07:00
|
|
|
>
|
|
|
|
|
View Draft Board
|
|
|
|
|
</a>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2025-10-18 23:33:13 -07:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-10-17 12:45:30 -07:00
|
|
|
{/* Standalone Header - No Main Nav */}
|
2025-10-25 03:23:41 -07:00
|
|
|
<div className="border-b bg-card flex-shrink-0">
|
2025-10-17 12:51:47 -07:00
|
|
|
<div className="w-full px-4 py-4">
|
2025-10-17 12:45:30 -07:00
|
|
|
<div className="flex items-center justify-between">
|
2025-10-25 03:23:41 -07:00
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
{/* Mobile menu button - only show if user has team and sidebar is collapsed */}
|
|
|
|
|
{userTeam && sidebarCollapsed && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => setSidebarCollapsed(false)}
|
|
|
|
|
className="lg:hidden"
|
|
|
|
|
aria-label="Open sidebar"
|
|
|
|
|
>
|
|
|
|
|
<svg
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
width="24"
|
|
|
|
|
height="24"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
>
|
2026-03-21 09:44:05 -07:00
|
|
|
<line x1="3" y1="12" x2="21" y2="12" />
|
|
|
|
|
<line x1="3" y1="6" x2="21" y2="6" />
|
|
|
|
|
<line x1="3" y1="18" x2="21" y2="18" />
|
2025-10-25 03:23:41 -07:00
|
|
|
</svg>
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
<Link to="/">
|
2026-04-23 13:52:34 -07:00
|
|
|
<img src={logomarkUrl} alt="Brackt" className="h-8" />
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
</Link>
|
|
|
|
|
<h1 className="text-lg md:text-xl font-bold">
|
|
|
|
|
Draft Room
|
|
|
|
|
</h1>
|
2025-10-17 12:45:30 -07:00
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-3">
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
<div className="hidden md:flex items-center gap-2 relative">
|
|
|
|
|
<span className="text-xs text-muted-foreground">Pick Notifications</span>
|
|
|
|
|
<NotificationSettings
|
|
|
|
|
enabled={notificationsEnabled}
|
|
|
|
|
onEnabledChange={setNotificationsEnabled}
|
|
|
|
|
mode={notificationsMode}
|
|
|
|
|
onModeChange={setNotificationsMode}
|
|
|
|
|
permissionState={notificationsPermission}
|
|
|
|
|
switchOnly
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-02-22 22:36:12 -08:00
|
|
|
{/* Commissioner Controls - hidden on mobile (shown in Controls tab) */}
|
2026-04-23 22:09:25 -07:00
|
|
|
{isCommissioner && (
|
2026-02-22 22:36:12 -08:00
|
|
|
<div className="hidden md:flex">
|
2026-04-23 22:09:25 -07:00
|
|
|
<CommissionerDraftControls
|
|
|
|
|
seasonStatus={season.status}
|
|
|
|
|
isDraftComplete={isDraftComplete}
|
|
|
|
|
isPaused={isPaused}
|
|
|
|
|
onStart={handleStartDraft}
|
|
|
|
|
onPause={handlePauseDraft}
|
|
|
|
|
onResume={handleResumeDraft}
|
|
|
|
|
/>
|
2026-02-22 22:36:12 -08:00
|
|
|
</div>
|
2025-10-18 23:19:41 -07:00
|
|
|
)}
|
2025-10-18 14:55:26 -07:00
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
<Button variant="outline" asChild className="hidden md:flex">
|
2025-10-18 14:55:26 -07:00
|
|
|
<Link to={`/leagues/${season.leagueId}`}>Exit Draft Room</Link>
|
2025-10-17 12:45:30 -07:00
|
|
|
</Button>
|
|
|
|
|
</div>
|
2025-10-17 12:30:58 -07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
{/* Mobile On Clock bar */}
|
|
|
|
|
{season.status === "draft" && !isDraftComplete && currentDraftSlot && (
|
|
|
|
|
<div className={`md:hidden flex-shrink-0 flex items-center justify-between px-4 py-2 border-b ${
|
|
|
|
|
isMyTurn
|
|
|
|
|
? "bg-electric/25 border-electric"
|
|
|
|
|
: "bg-muted/30"
|
|
|
|
|
}`}>
|
|
|
|
|
<div>
|
|
|
|
|
<div className={`text-xs font-bold uppercase tracking-wider ${isMyTurn ? "text-electric" : "text-muted-foreground"}`}>
|
|
|
|
|
{isMyTurn ? "Your Turn!" : "On the Clock"}
|
|
|
|
|
</div>
|
2026-02-27 23:21:08 -08:00
|
|
|
<div className="text-sm font-bold">
|
|
|
|
|
{currentDraftSlotOwnerName ?? currentDraftSlot.team.name}
|
|
|
|
|
</div>
|
|
|
|
|
{currentDraftSlotOwnerName && (
|
|
|
|
|
<div className="text-xs text-muted-foreground">{currentDraftSlot.team.name}</div>
|
|
|
|
|
)}
|
2026-02-22 22:36:12 -08:00
|
|
|
</div>
|
|
|
|
|
{isPaused ? (
|
|
|
|
|
<span className="text-sm font-bold text-amber-accent">Paused</span>
|
|
|
|
|
) : (
|
|
|
|
|
<span className={`text-xl font-mono font-bold tabular-nums ${currentClockColor}`}>
|
|
|
|
|
{formatClockTime(currentClockTime)}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-10-25 03:23:41 -07:00
|
|
|
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
{/* Desktop Tab Navigation Row */}
|
|
|
|
|
<div className={`hidden md:flex flex-shrink-0 items-center justify-between px-4 py-2 border-b transition-all duration-300 ${
|
|
|
|
|
isMyTurn && season.status === "draft" && !isDraftComplete
|
|
|
|
|
? "bg-electric/25 border-electric shadow-[0_0_24px_0_rgb(0_200_255_/_0.18)]"
|
|
|
|
|
: "bg-card"
|
|
|
|
|
}`}>
|
|
|
|
|
<Tabs
|
|
|
|
|
value={activeTab}
|
|
|
|
|
onValueChange={(value) =>
|
|
|
|
|
setActiveTab(value as "participants" | "board" | "rosters" | "summary")
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<TabsList>
|
|
|
|
|
<TabsTrigger value="participants">Participants</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="board">Draft Board</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="rosters">Rosters</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="summary">Summary</TabsTrigger>
|
|
|
|
|
</TabsList>
|
|
|
|
|
</Tabs>
|
|
|
|
|
{userTeam && (
|
|
|
|
|
<AutodraftBadgeWithPopover
|
|
|
|
|
seasonId={season.id}
|
|
|
|
|
teamId={userTeam.id}
|
|
|
|
|
isEnabled={userAutodraft.isEnabled}
|
|
|
|
|
mode={userAutodraft.mode}
|
|
|
|
|
queueOnly={userAutodraft.queueOnly}
|
|
|
|
|
isMyTurn={isMyTurn}
|
|
|
|
|
onUpdate={handleAutodraftUpdate}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
{/* Main Content — single layout tree based on isMobile to avoid duplicate component instances */}
|
|
|
|
|
<div className="flex-1 overflow-hidden">
|
|
|
|
|
{isMobile ? (
|
|
|
|
|
<div className="flex h-full overflow-hidden flex-col">
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
{mobileTab === "available" && (
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
<div className="flex flex-col h-full overflow-hidden">
|
|
|
|
|
<div className="flex-shrink-0">
|
|
|
|
|
<RecentPicksFeed picks={picks} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
|
|
|
<AvailableParticipantsSection {...availableParticipantsSectionProps} miniDraftGrid={undefined} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-22 22:36:12 -08:00
|
|
|
)}
|
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint
Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field
Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs
Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock
Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons
Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states
https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB
* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests
- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
(line 488) — was dead code since the column is NOT NULL, but semantically wrong
and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
constraint: empty queue, all items drafted, partial queue skip, and EV fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add missing queueOnly prop to AutodraftSettings test fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: rewrite AutodraftSettings tests for three-state button group UI
The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00
|
|
|
{mobileTab === "queue" && (
|
|
|
|
|
<div className="h-full overflow-y-auto">
|
|
|
|
|
{queueSectionProps ? (
|
|
|
|
|
<QueueSection {...queueSectionProps} />
|
|
|
|
|
) : (
|
|
|
|
|
<p className="text-muted-foreground text-sm text-center py-8 px-4">
|
|
|
|
|
Join a team to manage your queue
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-02-22 22:36:12 -08:00
|
|
|
{mobileTab === "board" && (
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
<div className="flex flex-col h-full overflow-hidden">
|
|
|
|
|
<div className="flex-shrink-0 flex gap-1 p-2 border-b">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setBoardSubTab("board")}
|
|
|
|
|
className={`flex-1 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
|
|
|
boardSubTab === "board"
|
|
|
|
|
? "bg-accent text-accent-foreground"
|
|
|
|
|
: "text-muted-foreground hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
Board
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setBoardSubTab("pick-list")}
|
|
|
|
|
className={`flex-1 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
|
|
|
boardSubTab === "pick-list"
|
|
|
|
|
? "bg-accent text-accent-foreground"
|
|
|
|
|
: "text-muted-foreground hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
Pick List
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 overflow-hidden">
|
|
|
|
|
{boardSubTab !== "pick-list" ? (
|
|
|
|
|
<DraftGridSection {...draftGridSectionProps} />
|
|
|
|
|
) : (
|
|
|
|
|
<div className="h-full overflow-y-auto p-4">
|
|
|
|
|
<SidebarRecentPicks picks={picks} />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-22 22:36:12 -08:00
|
|
|
)}
|
2026-03-02 16:46:35 -08:00
|
|
|
{mobileTab === "teams" && (
|
|
|
|
|
<div className="flex flex-col h-full overflow-hidden">
|
|
|
|
|
<div className="flex-shrink-0 flex gap-1 p-2 border-b">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setTeamsSubTab("rosters")}
|
|
|
|
|
className={`flex-1 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
|
|
|
teamsSubTab === "rosters"
|
|
|
|
|
? "bg-accent text-accent-foreground"
|
|
|
|
|
: "text-muted-foreground hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
Roster
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setTeamsSubTab("summary")}
|
|
|
|
|
className={`flex-1 py-1.5 text-sm font-medium rounded transition-colors ${
|
|
|
|
|
teamsSubTab === "summary"
|
|
|
|
|
? "bg-accent text-accent-foreground"
|
|
|
|
|
: "text-muted-foreground hover:text-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
Summary
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 overflow-hidden">
|
|
|
|
|
{teamsSubTab === "rosters" ? (
|
|
|
|
|
<TeamRosterView {...rosterViewProps} />
|
|
|
|
|
) : (
|
|
|
|
|
<DraftSummaryView {...summaryViewProps} />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-22 22:36:12 -08:00
|
|
|
)}
|
|
|
|
|
{mobileTab === "controls" && (
|
|
|
|
|
<div className="h-full overflow-y-auto p-4 space-y-6">
|
|
|
|
|
{isCommissioner && season.status === "pre_draft" && (
|
|
|
|
|
<Button className="w-full min-h-[48px]" onClick={handleStartDraft}>
|
|
|
|
|
Start Draft
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
{isCommissioner && season.status === "draft" && !isDraftComplete && (
|
|
|
|
|
isPaused ? (
|
|
|
|
|
<Button className="w-full min-h-[48px]" onClick={handleResumeDraft}>
|
|
|
|
|
Resume Draft
|
|
|
|
|
</Button>
|
|
|
|
|
) : (
|
|
|
|
|
<Button className="w-full min-h-[48px]" variant="outline" onClick={handlePauseDraft}>
|
|
|
|
|
Pause Draft
|
|
|
|
|
</Button>
|
|
|
|
|
)
|
Add draft clock UI, Fischer increment timer logic, and security fixes (#19)
- Add prominent clock badge to tab bar (lights up on your turn) with
correct Fischer increment chess-clock model: bank starts at initialTime,
+= incrementTime after each pick, other teams' banks untouched
- Extract pure timer helpers to app/lib/draft-timer.ts and add 29 unit
tests covering formatClockTime, calculateTimeAfterPick, getTimerColorClass,
and full snake-draft lifecycle regression scenarios
- Fix make-pick.ts: add status !== 'draft' and draftPaused server guards
- Fix draft-utils.ts: replace (global as any).__socketIO with getSocketIO(),
fix timeUsed to store actual timeRemaining at pick moment (not always 120),
add null timer warning, move timer fetch before pick insert
- Fix draft.start.ts: batch timer inserts, guard against empty draftSlots
- Fix DraftGridSection: memoize currentTeamId, widen teamTimers type to
Record<string, number | undefined>
- Fix duplicate animate-pulse (getTimerColorClass already includes it)
- Clamp negative seconds in formatClockTime to guard against timer drift
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 16:51:12 -08:00
|
|
|
)}
|
2025-10-25 03:23:41 -07:00
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
<Button variant="outline" asChild className="w-full min-h-[48px]">
|
|
|
|
|
<Link to={`/leagues/${season.leagueId}`}>Exit Draft Room</Link>
|
|
|
|
|
</Button>
|
2025-10-25 18:25:26 -07:00
|
|
|
</div>
|
2026-02-22 22:36:12 -08:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex h-full overflow-hidden">
|
|
|
|
|
{userTeam && queueSectionProps && (
|
|
|
|
|
<DraftSidebar
|
|
|
|
|
collapsed={sidebarCollapsed}
|
|
|
|
|
onCollapsedChange={setSidebarCollapsed}
|
|
|
|
|
queueSection={<QueueSection {...queueSectionProps} />}
|
|
|
|
|
recentPicksSection={<SidebarRecentPicks picks={picks} />}
|
2025-10-25 03:23:41 -07:00
|
|
|
/>
|
2026-02-22 22:36:12 -08:00
|
|
|
)}
|
|
|
|
|
|
New design (#309)
* Redesign home page with new layout and component system
- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack
- LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar
- MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader
- UpcomingEventsCard: vertical timeline with grouped multi-league events
- Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants
- Button default variant updated to green→cyan gradient
- Navbar: plain nav links with gradient hover, support/admin icon buttons
- Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements
- Storybook stories for all new components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Responsive league row layout and mobile polish
- League rows stack avatar+name on top, stats full-width below on mobile
- Stats spread to right side on sm+ screens with border separator on mobile
- Tighter padding on mobile (px-3/py-3), full padding on sm+
- Card headers and content use px-3 sm:px-6 to reduce mobile gutters
- Two-column home layout deferred to lg breakpoint (tablet gets stacked)
- Active leagues sorted by completion percentage descending
- Default rank 1 / 0 points for active leagues with no scoring events yet
- Fix ordinal bug for 11th/12th/13th; add aria-labels to rank change indicators
- Remove dead StatDivider className prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Improve claude file.
* Add StandingsPreview card component with podium row styling
- New StandingsPreview component with gold/silver/bronze row tints for
top 3, team avatar, and LeagueRow-style stat columns (Ranking + Points)
with rank and 7-day point change indicators
- Fix GradientIcon in Storybook by adding BracktGradients decorator to
preview.tsx (renamed from .ts to support JSX)
- Fix degenerate SVG gradient on horizontal strokes by switching
BracktGradients to gradientUnits="userSpaceOnUse" with Lucide-space
coordinates (0→24)
- Revert erroneous fill: url(#gradient) from GradientIcon; stroke-only
fix was sufficient once gradientUnits was corrected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update components on league homepage.
* Finish up league page styling.
* Work on standings page.
* Add story for RecentScoresCard
* Update Point Progression Chart.
* Sort point progression legend by ranking and add team links to standings rows
* Fix standings discrepancy on change.
* Create draft cell component.
* Update draft board page
* Draft room improvements.
* Update some draft room styling.
* Fix context menu missing.
* Move tab navigation and autodraft to header row, narrow sidebar
* Virtualize available participants list, memoize draft room props
Adds @tanstack/react-virtual to replace separate mobile/desktop lists
with a single unified virtual scroll loop. Also memoizes miniDraftGrid
and availableParticipantsSectionProps, and switches pick lookup from
Array.find to a Map for O(1) access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update draft room UI.
* More draft room fixes.
* Draft room tweaks.
* Fix Rosters page.
* Queue Section fixes.
* Mobile Draft fixes.
* Fix draft board page.
* Create bracket look.
* Bracket work.
* Finish bracket page.
* Homepage initial styling
* homepage copy
* Add privacy policy. Fixes #88.
* how to play copy
* rules copy
* Fix brackets on homepage.
* Add footer to website.
* Glow on dots.
* Landing page copy.
* Fix sidebar.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:14:55 -07:00
|
|
|
<div className="flex-1 overflow-hidden relative">
|
|
|
|
|
<div className={`absolute inset-0 ${activeTab === "participants" ? "" : "hidden"}`}>
|
|
|
|
|
<AvailableParticipantsSection {...availableParticipantsSectionProps} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className={`absolute inset-0 ${activeTab === "board" ? "" : "hidden"}`}>
|
|
|
|
|
<DraftGridSection {...draftGridSectionProps} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className={`absolute inset-0 ${activeTab === "rosters" ? "" : "hidden"}`}>
|
|
|
|
|
<TeamRosterView {...rosterViewProps} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className={`absolute inset-0 ${activeTab === "summary" ? "" : "hidden"}`}>
|
|
|
|
|
<DraftSummaryView {...summaryViewProps} />
|
|
|
|
|
</div>
|
2026-02-22 22:36:12 -08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-10-18 14:55:26 -07:00
|
|
|
</div>
|
2025-10-17 12:30:58 -07:00
|
|
|
|
2026-02-22 22:36:12 -08:00
|
|
|
{/* Mobile Bottom Nav */}
|
2026-02-27 23:12:21 -08:00
|
|
|
<nav className={`md:hidden flex-shrink-0 border-t bg-card grid`} style={{ gridTemplateColumns: `repeat(${mobileColCount}, 1fr)` }}>
|
|
|
|
|
{mobileTabs.map((tab) => {
|
|
|
|
|
const showTurnIndicator =
|
|
|
|
|
(tab.id === "available" || tab.id === "queue") &&
|
|
|
|
|
isMyTurn &&
|
|
|
|
|
season.status === "draft" &&
|
|
|
|
|
!isDraftComplete;
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={tab.id}
|
|
|
|
|
onClick={() => setMobileTab(tab.id as typeof mobileTab)}
|
|
|
|
|
className={`relative flex flex-col items-center justify-center py-2 gap-0.5 min-h-[56px] transition-colors ${
|
|
|
|
|
mobileTab === tab.id ? "text-electric" : "text-muted-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<tab.Icon className="h-5 w-5" />
|
|
|
|
|
{showTurnIndicator && (
|
|
|
|
|
<span className="absolute -top-1 -right-1 w-2 h-2 rounded-full bg-electric" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-[10px] font-medium">{tab.label}</span>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</nav>
|
2026-02-22 22:36:12 -08:00
|
|
|
|
2026-02-27 23:46:09 -08:00
|
|
|
<ParticipantSelectionDialog
|
Improve draft room UX with better error handling and UI refinements (#17)
* Remove pause/resume controls when draft is complete, rename Live to Connected
- Hide Pause/Resume Draft buttons when isDraftComplete is true
- Change 'Live' status indicator to 'Connected' in both draft room and draft board views
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
* Fix code review issues in draft room: security, bugs, and quality
Security:
- Fix inconsistent commissioner check: draft.start, force-autopick, force-manual-pick,
and make-pick all now query the commissioners table instead of league.createdBy,
so co-commissioners have consistent access to all draft controls
Bugs:
- canPick now includes !isPaused so the UI correctly blocks picks during a pause
- isDraftComplete initial state now covers 'completed' season status, not just 'active'
- Guard JSON.parse in queue.reorder.ts with try/catch to return 400 instead of 500
Code quality:
- Add error handling (try/catch + toast) to handlePauseDraft, handleResumeDraft,
handleRemoveFromQueue, and handleReorderQueue
- Replace alert() with toast.error() in handleMakePick, handleForceAutopick,
handleForceManualPick for consistent UX
- Memoize filteredParticipants with useMemo to avoid recomputing on every render
- Replace custom force-pick dialog div with ShadCN Dialog component for proper
keyboard support (Escape to close, focus trap, accessible markup); add dialog.tsx
- Remove console.log debug statements from socket event handlers and API routes
- Replace (global as any).__socketIO with getSocketIO() across all API routes
- Replace window.location.reload() in handleStartDraft with useRevalidator
https://claude.ai/code/session_01AUaKzx465NrY29Qv6MVwjC
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 21:50:27 -08:00
|
|
|
open={forcePickDialogOpen && !!selectedPickSlot}
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
setForcePickDialogOpen(open);
|
|
|
|
|
if (!open) setSelectedPickSlot(null);
|
|
|
|
|
}}
|
2026-02-27 23:46:09 -08:00
|
|
|
title="Force Manual Pick"
|
|
|
|
|
description={`Select a participant to draft for Pick #${selectedPickSlot?.pickNumber}`}
|
|
|
|
|
participants={availableParticipants}
|
|
|
|
|
draftedParticipantIds={draftedParticipantIds}
|
|
|
|
|
eligibility={forcePickEligibility}
|
|
|
|
|
uniqueSports={uniqueSports}
|
|
|
|
|
onSelect={handleForceManualPick}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<ParticipantSelectionDialog
|
2026-02-20 22:47:29 -08:00
|
|
|
open={replacePickDialogOpen && !!replacePickSlot}
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
setReplacePickDialogOpen(open);
|
|
|
|
|
if (!open) setReplacePickSlot(null);
|
|
|
|
|
}}
|
2026-02-27 23:46:09 -08:00
|
|
|
title="Replace Pick"
|
|
|
|
|
description={`Select a participant to replace the current pick at slot #${replacePickSlot?.pickNumber}`}
|
|
|
|
|
participants={availableParticipants}
|
|
|
|
|
draftedParticipantIds={draftedParticipantIds}
|
|
|
|
|
allowParticipantId={replacePickSlot?.oldParticipantId}
|
|
|
|
|
currentParticipantId={replacePickSlot?.oldParticipantId}
|
|
|
|
|
eligibility={replacePickEligibility}
|
|
|
|
|
uniqueSports={uniqueSports}
|
|
|
|
|
onSelect={handleReplacePick}
|
|
|
|
|
/>
|
2026-02-20 22:47:29 -08:00
|
|
|
|
2026-04-23 22:09:25 -07:00
|
|
|
|
2026-02-20 22:47:29 -08:00
|
|
|
{/* Rollback Confirmation Dialog */}
|
2026-04-23 22:09:25 -07:00
|
|
|
<RollbackConfirmDialog
|
2026-02-20 22:47:29 -08:00
|
|
|
open={rollbackConfirmOpen}
|
2026-04-23 22:09:25 -07:00
|
|
|
onOpenChange={(open) => { setRollbackConfirmOpen(open); if (!open) setRollbackPickNumber(null); }}
|
|
|
|
|
pickNumber={rollbackPickNumber}
|
|
|
|
|
picksCount={rollbackPickNumber !== null ? picks.filter((p) => p.pickNumber >= rollbackPickNumber).length : 0}
|
|
|
|
|
isRollingBack={isRollingBack}
|
|
|
|
|
onConfirm={handleConfirmRollback}
|
|
|
|
|
/>
|
2026-02-20 22:47:29 -08:00
|
|
|
|
2026-02-22 16:16:51 -08:00
|
|
|
{/* Time Bank Adjustment Dialog */}
|
2026-04-23 22:09:25 -07:00
|
|
|
<TimeBankAdjustmentDialog
|
2026-02-22 16:16:51 -08:00
|
|
|
open={timeBankDialogOpen}
|
2026-04-23 22:09:25 -07:00
|
|
|
onOpenChange={(open) => { setTimeBankDialogOpen(open); if (!open) setTimeBankTeamId(null); }}
|
|
|
|
|
teamName={draftSlots.find((s) => s.team.id === timeBankTeamId)?.team.name}
|
|
|
|
|
amount={timeBankAmount}
|
|
|
|
|
onAmountChange={setTimeBankAmount}
|
|
|
|
|
unit={timeBankUnit}
|
|
|
|
|
onUnitChange={setTimeBankUnit}
|
|
|
|
|
direction={timeBankDirection}
|
|
|
|
|
onDirectionChange={setTimeBankDirection}
|
|
|
|
|
isAdjusting={isAdjustingTimeBank}
|
|
|
|
|
onConfirm={handleConfirmAdjustTimeBank}
|
|
|
|
|
/>
|
2026-02-22 16:16:51 -08:00
|
|
|
|
2026-03-03 20:14:38 -08:00
|
|
|
{/* Commissioner Autodraft Override Dialog */}
|
2026-04-23 22:09:25 -07:00
|
|
|
<CommissionerAutodraftDialog
|
2026-03-03 20:14:38 -08:00
|
|
|
open={commissionerAutodraftDialogOpen}
|
2026-04-23 22:09:25 -07:00
|
|
|
onOpenChange={(open) => { setCommissionerAutodraftDialogOpen(open); if (!open) setCommissionerAutodraftTeamId(null); }}
|
|
|
|
|
teamId={commissionerAutodraftTeamId}
|
|
|
|
|
teamName={draftSlots.find((s) => s.team.id === commissionerAutodraftTeamId)?.team.name}
|
|
|
|
|
seasonId={season.id}
|
|
|
|
|
isEnabled={autodraftStatus[commissionerAutodraftTeamId ?? ""]?.isEnabled ?? false}
|
|
|
|
|
mode={autodraftStatus[commissionerAutodraftTeamId ?? ""]?.mode ?? "next_pick"}
|
|
|
|
|
queueOnly={autodraftStatus[commissionerAutodraftTeamId ?? ""]?.queueOnly ?? false}
|
|
|
|
|
onUpdate={handleCommissionerAutodraftUpdate}
|
|
|
|
|
/>
|
2026-03-03 20:14:38 -08:00
|
|
|
|
2025-10-26 21:01:12 -07:00
|
|
|
{/* Connection Overlay - blocks interaction until socket connects */}
|
|
|
|
|
<ConnectionOverlay
|
|
|
|
|
isConnected={isConnected}
|
|
|
|
|
isReconnecting={isReconnecting}
|
|
|
|
|
connectionError={connectionError}
|
|
|
|
|
/>
|
2026-03-01 19:33:03 -08:00
|
|
|
|
|
|
|
|
{/* Auth Recovery Overlay - blocks interaction when session expires */}
|
|
|
|
|
<AuthRecoveryOverlay isAuthDegraded={authDegraded} />
|
2025-10-17 12:30:58 -07:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|