brackt/app/routes/leagues/$leagueId.draft.$seasonId.tsx

2132 lines
80 KiB
TypeScript
Raw Normal View History

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";
fix: eliminate queue tab flash on mobile by using useSyncExternalStore (#46) * fix: eliminate queue tab flash on mobile by using useSyncExternalStore useMediaQuery initialized to false and updated in useEffect, causing a paint where isMobile was false on mobile devices. This rendered the desktop layout first, hiding the queue tab until the effect fired. useSyncExternalStore reads matchMedia synchronously on the client so the correct layout is used on the first render with no extra paint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revalidate draft room when Clerk JWT expires at load time Clerk's short-lived JWTs (~1 min) can expire between navigations. Server-side clerkMiddleware can't refresh them for client-side loader fetches, so getAuth() returns userId=null — causing userTeam=undefined, the queue button to disappear, and the tab to default to "board". Added a useEffect that detects the mismatch: if the Clerk client SDK has a valid userId but the loader ran without one, trigger revalidate() so the loader re-runs with a fresh token and returns correct userTeam/userQueue data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: prevent infinite revalidation loop in auth guard If the server consistently fails to populate currentUserId after JWT refresh (e.g. misconfigured CLERK_SECRET_KEY), the previous effect would loop indefinitely. A one-shot ref ensures we attempt auth recovery at most once per mount. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:54:14 -08:00
import { useAuth } from "@clerk/react-router";
import { eq, and, asc, desc, inArray } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { useDraftSocket } from "~/hooks/useDraftSocket";
import { logger } from "~/lib/logger";
import { useCallback, useEffect, useState, useMemo, useRef } from "react";
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";
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 { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "~/components/ui/dialog";
import { getAuth } from "@clerk/react-router/server";
import { getTeamQueue } from "~/models/draft-queue";
2026-04-23 14:12:15 -07:00
import logomarkUrl from "../../../public/logomark.svg?url";
import { buildOwnerMap } from "~/lib/owner-map";
import { DraftSidebar } from "~/components/DraftSidebar";
import { TeamRosterView } from "~/components/draft/TeamRosterView";
import { DraftSummaryView } from "~/components/draft/DraftSummaryView";
import { QueueSection } from "~/components/draft/QueueSection";
import { AvailableParticipantsSection } from "~/components/draft/AvailableParticipantsSection";
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";
import { DraftGridSection } from "~/components/draft/DraftGridSection";
import { ConnectionOverlay } from "~/components/draft/ConnectionOverlay";
import { AuthRecoveryOverlay } from "~/components/draft/AuthRecoveryOverlay";
import { ParticipantSelectionDialog } from "~/components/draft/ParticipantSelectionDialog";
import { calculateDraftEligibility } from "~/lib/draft-eligibility";
Add browser push notifications toggle to draft page (#11) * Add push notifications implementation plan Documents the approach for adding a browser Notification API toggle to the draft page sidebar, including files to create/modify and edge cases to handle. https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Add browser push notifications toggle to draft page Adds a Notifications toggle in the draft sidebar (below Autodraft) that uses the Browser Notification API to alert users when a pick is made or when it's their turn, while the tab is not focused. - New useDraftNotifications hook for state, permission, and localStorage - New NotificationSettings component with Switch toggle - Fires "It's your turn to pick!" when the next pick is the user's - Fires "{Team} picked {Player}" for all other picks - Gracefully hides toggle when Notification API is unavailable https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix all code review issues with push notifications - Extract snake draft order calculation to shared getTeamForPick() helper in lib/draft-order.ts, removing the duplicated logic - Fix notification firing on user's own picks (guard with team id check) - Fix notification firing after draft is complete (early return) - Use a ref for sendNotification to prevent full socket handler re-registration every time the toggle is changed - Add permission drift listener via Permissions API so the UI reacts if the user revokes notification permission in browser settings - Add SSR guard for document.hidden in sendNotification - Remove redundant isSupported prop from NotificationSettings; derive unsupported state from permissionState === "unsupported" - Move NotificationSettings out of QueueSection prop-drilling path; render it via a new settingsSection slot on DraftSidebar instead https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix second round of notification code review issues - Add My Turn Only / All Picks mode granularity so users can choose to only be notified when it's their pick, or for every pick - Remove double top-border by stripping leftover border-t/pt-4/mt-4 wrapper from NotificationSettings (now provided by DraftSidebar) - Remove unused isSupported from hook return value - Add n.onclick = () => window.focus() so clicking a notification brings the draft tab back into focus - Scope localStorage keys to userId to avoid shared-browser conflicts (keys now: draftNotifications-{userId}-{seasonId}) - Add notificationsModeRef alongside sendNotificationRef so mode changes don't trigger full socket handler re-registration https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 19:30:53 -08:00
import { getTeamForPick } from "~/lib/draft-order";
import { useDraftNotifications } from "~/hooks/useDraftNotifications";
import { useMediaQuery } from "~/hooks/useMediaQuery";
Add browser push notifications toggle to draft page (#11) * Add push notifications implementation plan Documents the approach for adding a browser Notification API toggle to the draft page sidebar, including files to create/modify and edge cases to handle. https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Add browser push notifications toggle to draft page Adds a Notifications toggle in the draft sidebar (below Autodraft) that uses the Browser Notification API to alert users when a pick is made or when it's their turn, while the tab is not focused. - New useDraftNotifications hook for state, permission, and localStorage - New NotificationSettings component with Switch toggle - Fires "It's your turn to pick!" when the next pick is the user's - Fires "{Team} picked {Player}" for all other picks - Gracefully hides toggle when Notification API is unavailable https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix all code review issues with push notifications - Extract snake draft order calculation to shared getTeamForPick() helper in lib/draft-order.ts, removing the duplicated logic - Fix notification firing on user's own picks (guard with team id check) - Fix notification firing after draft is complete (early return) - Use a ref for sendNotification to prevent full socket handler re-registration every time the toggle is changed - Add permission drift listener via Permissions API so the UI reacts if the user revokes notification permission in browser settings - Add SSR guard for document.hidden in sendNotification - Remove redundant isSupported prop from NotificationSettings; derive unsupported state from permissionState === "unsupported" - Move NotificationSettings out of QueueSection prop-drilling path; render it via a new settingsSection slot on DraftSidebar instead https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix second round of notification code review issues - Add My Turn Only / All Picks mode granularity so users can choose to only be notified when it's their pick, or for every pick - Remove double top-border by stripping leftover border-t/pt-4/mt-4 wrapper from NotificationSettings (now provided by DraftSidebar) - Remove unused isSupported from hook return value - Add n.onclick = () => window.focus() so clicking a notification brings the draft tab back into focus - Scope localStorage keys to userId to avoid shared-browser conflicts (keys now: draftNotifications-{userId}-{seasonId}) - Add notificationsModeRef alongside sendNotificationRef so mode changes don't trigger full socket handler re-registration https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 19:30:53 -08:00
import { NotificationSettings } from "~/components/NotificationSettings";
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 { AutodraftSettings, getAutodraftLabel, AutodraftBadgeWithPopover } from "~/components/AutodraftSettings";
import { toast } from "sonner";
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";
export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors {
return [{ title: `Draft — ${data?.season?.league?.name ?? "League"} - Brackt` }];
}
type QueueItem = typeof schema.draftQueue.$inferSelect;
type AutodraftStatusEntry = {
isEnabled: boolean;
mode: "next_pick" | "while_on";
queueOnly: boolean;
};
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 },
{ id: "board" as const, label: "Board", Icon: LayoutGrid },
{ id: "teams" as const, label: "Teams", Icon: ListChecks },
{ 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) {
const { params } = args;
const { seasonId, leagueId } = params;
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
const { userId } = await getAuth(args);
if (!seasonId) {
throw new Response("Season ID is required", { status: 400 });
}
const db = database();
// Get season details
const season = await db.query.seasons.findFirst({
where: eq(schema.seasons.id, seasonId),
with: {
league: true,
teams: true,
},
});
if (!season) {
throw new Response("Season not found", { status: 404 });
}
// Resolve user's team and commissioner status (null userId means neither)
const userTeam = userId
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
? season.teams.find((team) => team.ownerId === userId)
: undefined;
const isCommissioner = userId
? await db.query.commissioners.findFirst({
where: and(
eq(schema.commissioners.leagueId, leagueId),
eq(schema.commissioners.userId, userId)
),
})
: null;
// Access control: if draft board is not public, require authentication and membership
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,
});
}
}
// Get draft slots (draft order) - using select instead of query builder
const draftSlots = await db
.select({
id: schema.draftSlots.id,
draftOrder: schema.draftSlots.draftOrder,
team: schema.teams,
})
.from(schema.draftSlots)
.innerJoin(schema.teams, eq(schema.draftSlots.teamId, schema.teams.id))
.where(eq(schema.draftSlots.seasonId, seasonId))
.orderBy(asc(schema.draftSlots.draftOrder));
// Get all draft picks - using select instead of query builder
const draftPicks = await db
.select({
id: schema.draftPicks.id,
pickNumber: schema.draftPicks.pickNumber,
round: schema.draftPicks.round,
pickInRound: schema.draftPicks.pickInRound,
timeUsed: schema.draftPicks.timeUsed,
team: schema.teams,
participant: schema.participants,
sport: schema.sports,
})
.from(schema.draftPicks)
.innerJoin(schema.teams, eq(schema.draftPicks.teamId, schema.teams.id))
.innerJoin(
schema.participants,
eq(schema.draftPicks.participantId, schema.participants.id)
)
.innerJoin(
schema.sportsSeasons,
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)
)
.innerJoin(
schema.sports,
eq(schema.sportsSeasons.sportId, schema.sports.id)
)
.where(eq(schema.draftPicks.seasonId, seasonId))
.orderBy(asc(schema.draftPicks.pickNumber));
// Get sports seasons for this season
const seasonSports = await db.query.seasonSports.findMany({
where: eq(schema.seasonSports.seasonId, seasonId),
});
const sportsSeasonIds = seasonSports.map((ss) => ss.sportsSeasonId);
// Get ALL participants (both drafted and undrafted) - sorted by EV desc, then name
// Filtering will be done on the client side based on the "Show Drafted" toggle
const availableParticipants = sportsSeasonIds.length > 0
? await db
.select({
id: schema.participants.id,
name: schema.participants.name,
vorpValue: schema.participants.vorpValue,
sport: schema.sports,
})
.from(schema.participants)
.innerJoin(
schema.sportsSeasons,
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)
)
.innerJoin(
schema.sports,
eq(schema.sportsSeasons.sportId, schema.sports.id)
)
.where(
sportsSeasonIds.length === 1
? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])
: inArray(schema.participants.sportsSeasonId, sportsSeasonIds)
)
.orderBy(
desc(schema.participants.vorpValue),
asc(schema.participants.name)
)
: [];
// Load user's team queue if they have a team
fix: harden draft timer system with race-condition safety and DRY refactor (#34) - Fix settings action silently resetting timer values when draft speed select is disabled (add null guard before overwriting draftInitialTime/ draftIncrementTime) - Make timer decrement and pick increment atomic using SQL expressions to prevent read-modify-write races between the timer loop and HTTP handlers - Add UNIQUE INDEX on (season_id, pick_number) in draft_picks to prevent duplicate picks from concurrent requests (TOCTOU guard) - Add socket join-draft team ownership validation via DB query - Add iteration cap to autodraft chain while loop (max = totalTeams) - Add draftPaused re-check before firing autodraft chain in make-pick and force-manual-pick - Consolidate all snake draft calculations into calculatePickInfo (DRY); remove duplicated logic from timer.ts, make-pick, force-manual-pick, executeAutoPick, and checkAndTriggerNextAutodraft - Fix calculatePickInfo to return snake-adjusted pickInRound matching draftOrder values instead of raw pre-snake value - Fix timer-update socket events to emit nextPickNumber after a pick instead of the already-completed currentPickNumber - Fix force-manual-pick to validate submitted teamId matches the team whose turn it actually is at the given pick number - Replace inline timer init in draft.start with deleteSeasonTimers + initializeDraftTimers model functions (dead code fix) - Fix draft loader to not crash when getTeamQueue fails (returns []) - Fix misleading 403 message for commissioner picks - Remove dead hidden inputs from league settings form - Document connectedTeams single-instance limitation in socket.ts Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 23:23:24 -08:00
const userQueue = userTeam
? await getTeamQueue(userTeam.id).catch((err) => {
logger.error("[DraftLoader] Failed to load queue, defaulting to empty:", err);
fix: harden draft timer system with race-condition safety and DRY refactor (#34) - Fix settings action silently resetting timer values when draft speed select is disabled (add null guard before overwriting draftInitialTime/ draftIncrementTime) - Make timer decrement and pick increment atomic using SQL expressions to prevent read-modify-write races between the timer loop and HTTP handlers - Add UNIQUE INDEX on (season_id, pick_number) in draft_picks to prevent duplicate picks from concurrent requests (TOCTOU guard) - Add socket join-draft team ownership validation via DB query - Add iteration cap to autodraft chain while loop (max = totalTeams) - Add draftPaused re-check before firing autodraft chain in make-pick and force-manual-pick - Consolidate all snake draft calculations into calculatePickInfo (DRY); remove duplicated logic from timer.ts, make-pick, force-manual-pick, executeAutoPick, and checkAndTriggerNextAutodraft - Fix calculatePickInfo to return snake-adjusted pickInRound matching draftOrder values instead of raw pre-snake value - Fix timer-update socket events to emit nextPickNumber after a pick instead of the already-completed currentPickNumber - Fix force-manual-pick to validate submitted teamId matches the team whose turn it actually is at the given pick number - Replace inline timer init in draft.start with deleteSeasonTimers + initializeDraftTimers model functions (dead code fix) - Fix draft loader to not crash when getTeamQueue fails (returns []) - Fix misleading 403 message for commissioner picks - Remove dead hidden inputs from league settings form - Document connectedTeams single-instance limitation in socket.ts Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 23:23:24 -08:00
return [];
})
: [];
// Load draft timers for all teams
const timers = await db.query.draftTimers.findMany({
where: eq(schema.draftTimers.seasonId, seasonId),
});
// Load autodraft settings for all teams
const autodraftSettings = await db.query.autodraftSettings.findMany({
where: eq(schema.autodraftSettings.seasonId, seasonId),
});
// Derive user's autodraft settings from the already-loaded list
const userAutodraftSettings = userTeam
? (autodraftSettings.find((s) => s.teamId === userTeam.id) ?? null)
: null;
const ownerMap = await buildOwnerMap(draftSlots);
return {
season,
draftSlots,
draftPicks,
availableParticipants,
userTeam,
userQueue,
timers,
autodraftSettings,
userAutodraftSettings,
isCommissioner: !!isCommissioner,
currentUserId: userId,
ownerMap,
};
}
export default function DraftRoom() {
const {
season,
draftSlots,
draftPicks,
availableParticipants,
userTeam,
userQueue,
timers,
autodraftSettings,
userAutodraftSettings,
isCommissioner,
Add browser push notifications toggle to draft page (#11) * Add push notifications implementation plan Documents the approach for adding a browser Notification API toggle to the draft page sidebar, including files to create/modify and edge cases to handle. https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Add browser push notifications toggle to draft page Adds a Notifications toggle in the draft sidebar (below Autodraft) that uses the Browser Notification API to alert users when a pick is made or when it's their turn, while the tab is not focused. - New useDraftNotifications hook for state, permission, and localStorage - New NotificationSettings component with Switch toggle - Fires "It's your turn to pick!" when the next pick is the user's - Fires "{Team} picked {Player}" for all other picks - Gracefully hides toggle when Notification API is unavailable https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix all code review issues with push notifications - Extract snake draft order calculation to shared getTeamForPick() helper in lib/draft-order.ts, removing the duplicated logic - Fix notification firing on user's own picks (guard with team id check) - Fix notification firing after draft is complete (early return) - Use a ref for sendNotification to prevent full socket handler re-registration every time the toggle is changed - Add permission drift listener via Permissions API so the UI reacts if the user revokes notification permission in browser settings - Add SSR guard for document.hidden in sendNotification - Remove redundant isSupported prop from NotificationSettings; derive unsupported state from permissionState === "unsupported" - Move NotificationSettings out of QueueSection prop-drilling path; render it via a new settingsSection slot on DraftSidebar instead https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix second round of notification code review issues - Add My Turn Only / All Picks mode granularity so users can choose to only be notified when it's their pick, or for every pick - Remove double top-border by stripping leftover border-t/pt-4/mt-4 wrapper from NotificationSettings (now provided by DraftSidebar) - Remove unused isSupported from hook return value - Add n.onclick = () => window.focus() so clicking a notification brings the draft tab back into focus - Scope localStorage keys to userId to avoid shared-browser conflicts (keys now: draftNotifications-{userId}-{seasonId}) - Add notificationsModeRef alongside sendNotificationRef so mode changes don't trigger full socket handler re-registration https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 19:30:53 -08:00
currentUserId,
ownerMap,
} = useLoaderData<typeof loader>();
const { revalidate, state: revalidatorState } = useRevalidator();
const { userId: clerkUserId, getToken } = useAuth();
const { isConnected, connectionError, isReconnecting, reconnectCount, socketVersion, on, off } = useDraftSocket(season.id, userTeam?.id);
Add browser push notifications toggle to draft page (#11) * Add push notifications implementation plan Documents the approach for adding a browser Notification API toggle to the draft page sidebar, including files to create/modify and edge cases to handle. https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Add browser push notifications toggle to draft page Adds a Notifications toggle in the draft sidebar (below Autodraft) that uses the Browser Notification API to alert users when a pick is made or when it's their turn, while the tab is not focused. - New useDraftNotifications hook for state, permission, and localStorage - New NotificationSettings component with Switch toggle - Fires "It's your turn to pick!" when the next pick is the user's - Fires "{Team} picked {Player}" for all other picks - Gracefully hides toggle when Notification API is unavailable https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix all code review issues with push notifications - Extract snake draft order calculation to shared getTeamForPick() helper in lib/draft-order.ts, removing the duplicated logic - Fix notification firing on user's own picks (guard with team id check) - Fix notification firing after draft is complete (early return) - Use a ref for sendNotification to prevent full socket handler re-registration every time the toggle is changed - Add permission drift listener via Permissions API so the UI reacts if the user revokes notification permission in browser settings - Add SSR guard for document.hidden in sendNotification - Remove redundant isSupported prop from NotificationSettings; derive unsupported state from permissionState === "unsupported" - Move NotificationSettings out of QueueSection prop-drilling path; render it via a new settingsSection slot on DraftSidebar instead https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix second round of notification code review issues - Add My Turn Only / All Picks mode granularity so users can choose to only be notified when it's their pick, or for every pick - Remove double top-border by stripping leftover border-t/pt-4/mt-4 wrapper from NotificationSettings (now provided by DraftSidebar) - Remove unused isSupported from hook return value - Add n.onclick = () => window.focus() so clicking a notification brings the draft tab back into focus - Scope localStorage keys to userId to avoid shared-browser conflicts (keys now: draftNotifications-{userId}-{seasonId}) - Add notificationsModeRef alongside sendNotificationRef so mode changes don't trigger full socket handler re-registration https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 19:30:53 -08:00
const {
permissionState: notificationsPermission,
enabled: notificationsEnabled,
setEnabled: setNotificationsEnabled,
mode: notificationsMode,
setMode: setNotificationsMode,
sendNotification,
} = useDraftNotifications(season.id, currentUserId ?? "");
Add browser push notifications toggle to draft page (#11) * Add push notifications implementation plan Documents the approach for adding a browser Notification API toggle to the draft page sidebar, including files to create/modify and edge cases to handle. https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Add browser push notifications toggle to draft page Adds a Notifications toggle in the draft sidebar (below Autodraft) that uses the Browser Notification API to alert users when a pick is made or when it's their turn, while the tab is not focused. - New useDraftNotifications hook for state, permission, and localStorage - New NotificationSettings component with Switch toggle - Fires "It's your turn to pick!" when the next pick is the user's - Fires "{Team} picked {Player}" for all other picks - Gracefully hides toggle when Notification API is unavailable https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix all code review issues with push notifications - Extract snake draft order calculation to shared getTeamForPick() helper in lib/draft-order.ts, removing the duplicated logic - Fix notification firing on user's own picks (guard with team id check) - Fix notification firing after draft is complete (early return) - Use a ref for sendNotification to prevent full socket handler re-registration every time the toggle is changed - Add permission drift listener via Permissions API so the UI reacts if the user revokes notification permission in browser settings - Add SSR guard for document.hidden in sendNotification - Remove redundant isSupported prop from NotificationSettings; derive unsupported state from permissionState === "unsupported" - Move NotificationSettings out of QueueSection prop-drilling path; render it via a new settingsSection slot on DraftSidebar instead https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix second round of notification code review issues - Add My Turn Only / All Picks mode granularity so users can choose to only be notified when it's their pick, or for every pick - Remove double top-border by stripping leftover border-t/pt-4/mt-4 wrapper from NotificationSettings (now provided by DraftSidebar) - Remove unused isSupported from hook return value - Add n.onclick = () => window.focus() so clicking a notification brings the draft tab back into focus - Scope localStorage keys to userId to avoid shared-browser conflicts (keys now: draftNotifications-{userId}-{seasonId}) - Add notificationsModeRef alongside sendNotificationRef so mode changes don't trigger full socket handler re-registration https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 --------- Co-authored-by: Claude <noreply@anthropic.com>
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]);
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
// Track current autodraft state for detecting server-initiated auto-disable
const [userAutodraft, setUserAutodraft] = useState({
isEnabled: userAutodraftSettings?.isEnabled || false,
mode: (userAutodraftSettings?.mode || "next_pick") as "next_pick" | "while_on",
queueOnly: userAutodraftSettings?.queueOnly || false,
});
const userAutodraftRef = useRef(userAutodraft);
useEffect(() => {
userAutodraftRef.current = userAutodraft;
}, [userAutodraft]);
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// Re-fetch loader data after each reconnect so stale picks/timers are refreshed.
// Schedule a delayed retry so that if the first attempt fails (common on mobile
// where the network may not be fully restored yet), we get another chance.
const revalidationRetryRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
if (reconnectCount > 0) {
revalidate();
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// Clear any pending retry from a previous reconnect
if (revalidationRetryRef.current) {
clearTimeout(revalidationRetryRef.current);
}
revalidationRetryRef.current = setTimeout(() => {
revalidate();
revalidationRetryRef.current = null;
}, 3000);
}
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
return () => {
if (revalidationRetryRef.current) {
clearTimeout(revalidationRetryRef.current);
}
};
}, [reconnectCount, revalidate]);
// Guard against Clerk JWT expiry race: if the loader ran without a userId
// (expired short-lived JWT) but the Clerk client SDK has a valid session,
// revalidate so the loader re-runs with a fresh token and returns correct
// userTeam/userQueue data. This can happen repeatedly on mobile when the
// user backgrounds and foregrounds the app, so we don't limit it to a
// single attempt. Instead we debounce to avoid rapid-fire revalidations.
//
// Cap at 3 attempts to avoid an infinite loop if the server consistently
// fails to resolve a userId (e.g. clock skew, misconfigured Clerk keys).
// After 3 failures we fall through to the authDegraded overlay.
const authRecoveryTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const authRecoveryAttemptsRef = useRef(0);
const MAX_AUTH_RECOVERY_ATTEMPTS = 3;
fix: eliminate queue tab flash on mobile by using useSyncExternalStore (#46) * fix: eliminate queue tab flash on mobile by using useSyncExternalStore useMediaQuery initialized to false and updated in useEffect, causing a paint where isMobile was false on mobile devices. This rendered the desktop layout first, hiding the queue tab until the effect fired. useSyncExternalStore reads matchMedia synchronously on the client so the correct layout is used on the first render with no extra paint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revalidate draft room when Clerk JWT expires at load time Clerk's short-lived JWTs (~1 min) can expire between navigations. Server-side clerkMiddleware can't refresh them for client-side loader fetches, so getAuth() returns userId=null — causing userTeam=undefined, the queue button to disappear, and the tab to default to "board". Added a useEffect that detects the mismatch: if the Clerk client SDK has a valid userId but the loader ran without one, trigger revalidate() so the loader re-runs with a fresh token and returns correct userTeam/userQueue data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: prevent infinite revalidation loop in auth guard If the server consistently fails to populate currentUserId after JWT refresh (e.g. misconfigured CLERK_SECRET_KEY), the previous effect would loop indefinitely. A one-shot ref ensures we attempt auth recovery at most once per mount. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:54:14 -08:00
useEffect(() => {
if (clerkUserId && !currentUserId) {
if (authRecoveryAttemptsRef.current >= MAX_AUTH_RECOVERY_ATTEMPTS) {
setAuthDegraded(true);
return;
}
// Small debounce so we don't fire multiple times in quick succession
// (e.g. visibility change + socket reconnect both updating deps).
clearTimeout(authRecoveryTimerRef.current);
authRecoveryTimerRef.current = setTimeout(() => {
authRecoveryAttemptsRef.current += 1;
revalidate();
}, 100);
} else if (currentUserId) {
// Auth recovered — reset the counter for the next background cycle.
authRecoveryAttemptsRef.current = 0;
fix: eliminate queue tab flash on mobile by using useSyncExternalStore (#46) * fix: eliminate queue tab flash on mobile by using useSyncExternalStore useMediaQuery initialized to false and updated in useEffect, causing a paint where isMobile was false on mobile devices. This rendered the desktop layout first, hiding the queue tab until the effect fired. useSyncExternalStore reads matchMedia synchronously on the client so the correct layout is used on the first render with no extra paint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revalidate draft room when Clerk JWT expires at load time Clerk's short-lived JWTs (~1 min) can expire between navigations. Server-side clerkMiddleware can't refresh them for client-side loader fetches, so getAuth() returns userId=null — causing userTeam=undefined, the queue button to disappear, and the tab to default to "board". Added a useEffect that detects the mismatch: if the Clerk client SDK has a valid userId but the loader ran without one, trigger revalidate() so the loader re-runs with a fresh token and returns correct userTeam/userQueue data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: prevent infinite revalidation loop in auth guard If the server consistently fails to populate currentUserId after JWT refresh (e.g. misconfigured CLERK_SECRET_KEY), the previous effect would loop indefinitely. A one-shot ref ensures we attempt auth recovery at most once per mount. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:54:14 -08:00
}
return () => clearTimeout(authRecoveryTimerRef.current);
fix: eliminate queue tab flash on mobile by using useSyncExternalStore (#46) * fix: eliminate queue tab flash on mobile by using useSyncExternalStore useMediaQuery initialized to false and updated in useEffect, causing a paint where isMobile was false on mobile devices. This rendered the desktop layout first, hiding the queue tab until the effect fired. useSyncExternalStore reads matchMedia synchronously on the client so the correct layout is used on the first render with no extra paint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revalidate draft room when Clerk JWT expires at load time Clerk's short-lived JWTs (~1 min) can expire between navigations. Server-side clerkMiddleware can't refresh them for client-side loader fetches, so getAuth() returns userId=null — causing userTeam=undefined, the queue button to disappear, and the tab to default to "board". Added a useEffect that detects the mismatch: if the Clerk client SDK has a valid userId but the loader ran without one, trigger revalidate() so the loader re-runs with a fresh token and returns correct userTeam/userQueue data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: prevent infinite revalidation loop in auth guard If the server consistently fails to populate currentUserId after JWT refresh (e.g. misconfigured CLERK_SECRET_KEY), the previous effect would loop indefinitely. A one-shot ref ensures we attempt auth recovery at most once per mount. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:54:14 -08:00
}, [clerkUserId, currentUserId, revalidate]);
// Auth session protection: proactively refresh the Clerk JWT to prevent
// silent logout during long-running draft sessions.
const [authDegraded, setAuthDegraded] = useState(false);
// Fetch wrapper that detects 401 responses and shows the auth recovery
// overlay instead of letting each handler deal with it individually.
// Returns null on 401 so callers can bail with `if (!response) return;`.
const authFetch = useCallback(async (url: string, init?: RequestInit): Promise<Response | null> => {
const response = await fetch(url, init);
if (response.status === 401) {
setAuthDegraded(true);
return null;
}
return response;
}, []);
// Refresh the Clerk JWT when the tab becomes visible again. Browsers
// throttle/suspend intervals in background tabs, so the SDK's automatic
// 60-second refresh may not have run. Getting a fresh token here prevents
// the next user action from hitting a 401. After a successful refresh we
// also revalidate the loader so it re-runs with the fresh token — without
// this, userTeam/userQueue stay stale from the previous (possibly
// unauthenticated) loader run.
useEffect(() => {
// Nothing to do once auth is fully degraded — avoid re-registering a
// listener that would immediately exit on every future visibility change.
if (authDegraded) return;
let aborted = false;
let inFlight = false;
const handleVisibilityChange = async () => {
// Ignore hide events, and skip if another invocation is already running
// (rapid tab switching) or this effect has been cleaned up.
if (document.visibilityState !== "visible" || !clerkUserId || inFlight) return;
inFlight = true;
try {
const token = await getToken({ skipCache: true });
if (aborted) return;
if (token !== null) {
// Token refreshed successfully. Only revalidate if the loader is
// missing auth (currentUserId is null) — if the loader already has
// a valid user there's no stale state to fix and the extra round
// trip just adds unnecessary server load.
if (!currentUserId) {
revalidate();
}
} else {
// Retry after a longer delay — mobile networks may take a few
// seconds to fully wake up after background suspension.
await new Promise((r) => setTimeout(r, 2500));
if (aborted) return;
const retryToken = await getToken({ skipCache: true });
if (aborted) return;
if (retryToken !== null) {
revalidate();
} else {
setAuthDegraded(true);
}
}
} catch {
try {
await new Promise((r) => setTimeout(r, 2500));
if (aborted) return;
const retryToken = await getToken({ skipCache: true });
if (aborted) return;
if (retryToken !== null) {
revalidate();
} else {
setAuthDegraded(true);
}
} catch {
if (!aborted) setAuthDegraded(true);
}
} finally {
inFlight = false;
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
aborted = true;
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [clerkUserId, currentUserId, getToken, authDegraded, revalidate]);
// Track revalidation lifecycle to sync local state from fresh loader data.
//
// The problem: `picks` and `currentPick` are local state initialized once
// from useLoaderData(). When revalidate() runs after a reconnect, React
// Router re-fetches `draftPicks`/`season` — but those updates never flow
// back into the local useState copies automatically.
//
// Race condition: a `pick-made` socket event that arrives *during* the
// revalidation window would normally be applied to the stale prev state and
// then overwritten when we sync from the DB snapshot. We prevent that by
// buffering live picks while revalidation is in flight and merging them
// (deduplicated by ID) after the DB snapshot lands.
const revalidatorStateRef = useRef(revalidatorState);
const isRevalidatingRef = useRef(false);
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const pendingPicksDuringRevalidationRef = useRef<(typeof draftPicks)[number][]>([]);
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// Track the draftPicks reference when revalidation starts. If it hasn't
// changed by the time revalidation completes, the loader didn't return new
// data (e.g. network failure) and we should NOT overwrite state that may
// have been freshly set by draft-state-sync.
const draftPicksAtRevalidationStartRef = useRef(draftPicks);
// Same pattern for userQueue: only sync from loader if the reference changed,
// meaning the fetch actually returned fresh data. Prevents overwriting
// socket-applied queue changes (queue-updated, participant-removed-from-queues)
// with stale loader data when a revalidation fetch fails.
const userQueueAtRevalidationStartRef = useRef(userQueue);
// Tracks how many queue mutations are in-flight from this window. While > 0,
// incoming queue-updated socket events are ignored so that the server echo
// doesn't clobber a more-recent optimistic update (e.g. rapid drag-reorders).
// The initiating window uses the HTTP response as the authoritative state;
// other windows use the socket event.
const pendingQueueMutationsRef = useRef(0);
useEffect(() => {
const prev = revalidatorStateRef.current;
revalidatorStateRef.current = revalidatorState;
if (prev !== "loading" && revalidatorState === "loading") {
// Revalidation just started — buffer any live picks to avoid loss
isRevalidatingRef.current = true;
pendingPicksDuringRevalidationRef.current = [];
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
draftPicksAtRevalidationStartRef.current = draftPicks;
userQueueAtRevalidationStartRef.current = userQueue;
} else if (prev === "loading" && revalidatorState === "idle") {
// Revalidation just completed — merge DB snapshot with buffered picks
isRevalidatingRef.current = false;
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// If draftPicks is the same reference as when revalidation started, the
// loader didn't actually update (likely a failed fetch). Skip the sync
// to avoid overwriting fresh draft-state-sync data with stale state.
if (draftPicks === draftPicksAtRevalidationStartRef.current) {
pendingPicksDuringRevalidationRef.current = [];
return;
}
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const dbPickIds = new Set(draftPicks.map((p) => p.id));
const missedPicks = pendingPicksDuringRevalidationRef.current.filter(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
(p) => !dbPickIds.has(p.id)
);
pendingPicksDuringRevalidationRef.current = [];
setPicks([...draftPicks, ...missedPicks]);
setCurrentPick(season.currentPickNumber || 1);
setIsPaused(season.draftPaused || false);
setIsDraftComplete(
season.status === "active" || season.status === "completed"
);
// Sync the queue if the loader returned fresh data for it. Using reference
// equality (same pattern as draftPicks above): drizzle always returns a new
// array on a successful fetch, so a changed reference means the fetch
// succeeded and we can trust the value. A same reference means the fetch
// failed and we must NOT overwrite socket-applied changes (queue-updated,
// participant-removed-from-queues) with stale data.
//
// Guard: skip if the loader ran unauthenticated (currentUserId null) to avoid
// overwriting with an empty userQueue returned for an expired JWT session.
if (currentUserId && userQueue !== userQueueAtRevalidationStartRef.current) {
setQueue(userQueue);
}
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// Sync timers — the server-side timer state may have changed while
// we were disconnected (time bank adjustments, new timers, etc.)
if (timers.length > 0) {
Fix oxlint warnings: no-shadow, consistent-function-scoping, no-non-null-assertion, and others (#196) * Fix no-shadow and consistent-function-scoping lint violations Resolves all 11 no-shadow and 16 consistent-function-scoping oxlint warnings and promotes both rules to errors in .oxlintrc.json. no-shadow: renamed Drizzle callback params (sports→s, matches→m, seasons→s) to avoid shadowing outer imports; removed shadowed destructures (eq, inArray) from where callbacks; renamed inner template→bracketTemplate, prev→currentTimers, season→ss, name→teamName (with name: teamName fix to preserve semantics). consistent-function-scoping: moved formatDate, getRankBadge, getMovementIndicator, getPositionBadge, getStatusBadge, toDateStr, elo (×2), weightedPick, sortByMatchNumber (×2) to module scope; moved formatTime (×2), isValidLeagueName, getDraftTimes, makeSeasonQueues to file scope in test files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-non-null-assertion lint violations and promote to error Eliminates all 208 no-non-null-assertion warnings across 38 files. Promotes typescript/no-non-null-assertion from warn to error in .oxlintrc.json. Fix patterns applied: - Map.get(key)! after .has() check → extract with get() + null guard - Map.get(key)! on pre-populated count maps → ?? 0 default - .set(id, map.get(id)! + 1) increment → ?? 0 before adding - participant1Id!/participant2Id! on DB matches → ?? "" fallback - array.find()! in tests → guard + throw or expect().toBeDefined() - bracketTemplateCache.get(id)! → null guard extract - Various nullable field accesses → optional chain or ?? default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix prefer-add-event-listener, no-unassigned-import, require-module-specifiers Resolves all 9 remaining non-console lint warnings and promotes all three rules to errors in .oxlintrc.json. - prefer-add-event-listener: converted onchange/onclick/onload assignments to addEventListener in useDraftNotifications.ts and admin.data-sync.tsx; stored changeHandler ref for proper cleanup with removeEventListener - no-unassigned-import: configured rule with allow list for legitimate side-effect imports (*.css, @testing-library/jest-dom, @testing-library/cypress/add-commands) - require-module-specifiers: removed redundant `export {}` from cypress/support/e2e.ts (file already has an import) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TypeScript errors from no-non-null-assertion fixes Two fixes introduced by the non-null assertion cleanup produced type errors: - scoring-event.ts: `?? ""` was wrong type for a participant object map; restructured to explicit null guards so TypeScript can narrow correctly - standings-sync/index.ts: `?? null` after name-match lookup lost the truthy guarantee, causing TS18047 on the write-back block; added `participant &&` guard before accessing its properties Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add npm run typecheck as Stop hook in Claude settings Runs a full project typecheck at the end of each Claude turn so type errors surface as feedback before the next message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 10:59:51 -07:00
setTeamTimers((currentTimers) => {
const updated = { ...currentTimers };
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
timers.forEach((timer) => {
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
updated[timer.teamId] = timer.timeRemaining;
});
return updated;
});
}
// Sync autodraft settings — teams may have toggled autodraft while
// we were away.
setAutodraftStatus(() => {
const status: Record<string, AutodraftStatusEntry> = {};
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
autodraftSettings.forEach((setting) => {
status[setting.teamId] = {
isEnabled: setting.isEnabled,
mode: setting.mode,
queueOnly: setting.queueOnly,
};
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
});
return status;
});
}
}, [revalidatorState, draftPicks, season, userQueue, timers, autodraftSettings, currentUserId]);
const [picks, setPicks] = useState(draftPicks);
const [currentPick, setCurrentPick] = useState(season.currentPickNumber || 1);
const [searchQuery, setSearchQuery] = useState("");
const [hideDrafted, setHideDrafted] = useState(true);
const [hideIneligible, setHideIneligible] = useState(true);
const [hideCompletedSports, setHideCompletedSports] = useState(false);
const [sportFilters, setSportFilters] = useState<string[]>([]);
const [queue, setQueue] = useState<QueueItem[]>(userQueue);
const [isPaused, setIsPaused] = useState(season.draftPaused || false);
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 [isDraftComplete, setIsDraftComplete] = useState(
season.status === "active" || season.status === "completed"
);
// Sidebar and tabs state
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
if (typeof window === "undefined") return false;
const stored = localStorage.getItem("draftSidebarCollapsed");
return stored ? JSON.parse(stored) : false;
});
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 [activeTab, setActiveTab] = useState<"participants" | "board" | "rosters" | "summary">("participants");
const [mobileTab, setMobileTab] = useState<"available" | "queue" | "board" | "teams" | "controls">(
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
!userTeam && isCommissioner ? "board" : "available"
);
const [teamsSubTab, setTeamsSubTab] = useState<"rosters" | "summary">("rosters");
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 [boardSubTab, setBoardSubTab] = useState<"board" | "pick-list">("board");
const isMobile = useMediaQuery("(max-width: 767px)");
// Track autodraft status for all teams
const [autodraftStatus, setAutodraftStatus] = useState<Record<string, AutodraftStatusEntry>>(() => {
const status: Record<string, AutodraftStatusEntry> = {};
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
autodraftSettings.forEach((setting) => {
status[setting.teamId] = {
isEnabled: setting.isEnabled,
mode: setting.mode,
queueOnly: setting.queueOnly,
};
});
return status;
});
// Track connected teams
const [connectedTeams, setConnectedTeams] = useState<Set<string>>(new Set());
const [teamTimers, setTeamTimers] = useState<Record<string, number>>(() => {
// Initialize with timers from loader data, or use initial time if draft hasn't started
const timersMap: Record<string, number> = {};
const initialTime = season.draftInitialTime || 120;
if (timers.length > 0) {
// Draft has started, use actual timers
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
timers.forEach((timer) => {
timersMap[timer.teamId] = timer.timeRemaining;
});
} else {
// Draft hasn't started, show initial time for all teams
draftSlots.forEach((slot) => {
timersMap[slot.team.id] = initialTime;
});
}
return timersMap;
});
const [forcePickDialogOpen, setForcePickDialogOpen] = useState(false);
const [selectedPickSlot, setSelectedPickSlot] = useState<{
pickNumber: number;
teamId: string;
} | null>(null);
// Replace pick dialog state
const [replacePickDialogOpen, setReplacePickDialogOpen] = useState(false);
const [replacePickSlot, setReplacePickSlot] = useState<{
pickNumber: number;
teamId: string;
oldParticipantId: string;
} | null>(null);
// Rollback confirm dialog state
const [rollbackConfirmOpen, setRollbackConfirmOpen] = useState(false);
const [rollbackPickNumber, setRollbackPickNumber] = useState<number | null>(null);
const [isRollingBack, setIsRollingBack] = useState(false);
// Time bank adjustment dialog state
const [timeBankDialogOpen, setTimeBankDialogOpen] = useState(false);
const [timeBankTeamId, setTimeBankTeamId] = useState<string | null>(null);
const [commissionerAutodraftDialogOpen, setCommissionerAutodraftDialogOpen] = useState(false);
const [commissionerAutodraftTeamId, setCommissionerAutodraftTeamId] = useState<string | null>(null);
const [timeBankAmount, setTimeBankAmount] = useState("1");
const [timeBankUnit, setTimeBankUnit] = useState<"seconds" | "minutes" | "hours">("minutes");
const [timeBankDirection, setTimeBankDirection] = useState<"add" | "remove">("add");
const [isAdjustingTimeBank, setIsAdjustingTimeBank] = useState(false);
// Shared transforms for eligibility calculations
const transformedPicks = useMemo(
() =>
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
picks.map((p) => ({
teamId: p.team.id,
participant: {
id: p.participant.id,
sport: { id: p.sport.id, name: p.sport.name },
},
})),
[picks]
);
const transformedParticipants = useMemo(
() =>
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
availableParticipants.map((p) => ({
id: p.id,
sport: { id: p.sport.id, name: p.sport.name },
})),
[availableParticipants]
);
const seasonSportsData = useMemo(() => {
const sportsMap = new Map<string, { id: string; name: string }>();
availableParticipants.forEach((p) => {
if (!sportsMap.has(p.sport.id)) {
sportsMap.set(p.sport.id, { id: p.sport.id, name: p.sport.name });
}
});
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
return Array.from(sportsMap.values()).toSorted((a, b) =>
a.name.localeCompare(b.name)
);
}, [availableParticipants]);
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]);
// Calculate draft eligibility for current user's team
const eligibility = useMemo(() => {
if (!userTeam) return null;
const userTeamPicks = transformedPicks.filter(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
(p) => p.teamId === userTeam.id
);
return calculateDraftEligibility(
userTeam.id,
userTeamPicks,
transformedPicks,
transformedParticipants,
seasonSportsData,
season.draftRounds,
season.teams
);
}, [userTeam, transformedPicks, transformedParticipants, seasonSportsData, season]);
// Calculate eligibility for force manual pick dialog (selected team)
const forcePickEligibility = useMemo(() => {
if (!selectedPickSlot) return null;
const selectedTeamPicks = transformedPicks.filter(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
(p) => p.teamId === selectedPickSlot.teamId
);
return calculateDraftEligibility(
selectedPickSlot.teamId,
selectedTeamPicks,
transformedPicks,
transformedParticipants,
seasonSportsData,
season.draftRounds,
season.teams
);
}, [selectedPickSlot, transformedPicks, transformedParticipants, seasonSportsData, season]);
// 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(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
(p) => p.participant.id !== replacePickSlot.oldParticipantId
);
const selectedTeamPicks = allPicksExcluding.filter(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
(p) => p.teamId === replacePickSlot.teamId
);
return calculateDraftEligibility(
replacePickSlot.teamId,
selectedTeamPicks,
allPicksExcluding,
transformedParticipants,
seasonSportsData,
season.draftRounds,
season.teams
);
}, [replacePickSlot, transformedPicks, transformedParticipants, seasonSportsData, season]);
// Listen for new picks from other users
useEffect(() => {
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
type PickMadePayload = { pick: (typeof draftPicks)[number] & { team?: { id?: string; name?: string }; participant?: { name?: string } }; nextPickNumber: number; isDraftComplete?: boolean };
const handlePickMade = (data: PickMadePayload) => {
if (isRevalidatingRef.current) {
// Buffer this pick; the revalidation-complete handler will merge it
// into the DB snapshot so it isn't silently dropped.
// Don't update currentPick either — the sync effect will set it from
// fresh season.currentPickNumber once loading → idle, keeping the
// pick counter and the picks list in sync with each other.
pendingPicksDuringRevalidationRef.current.push(data.pick);
} else {
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
setPicks((prev) => [...prev, data.pick]);
setCurrentPick(data.nextPickNumber);
}
Add browser push notifications toggle to draft page (#11) * Add push notifications implementation plan Documents the approach for adding a browser Notification API toggle to the draft page sidebar, including files to create/modify and edge cases to handle. https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Add browser push notifications toggle to draft page Adds a Notifications toggle in the draft sidebar (below Autodraft) that uses the Browser Notification API to alert users when a pick is made or when it's their turn, while the tab is not focused. - New useDraftNotifications hook for state, permission, and localStorage - New NotificationSettings component with Switch toggle - Fires "It's your turn to pick!" when the next pick is the user's - Fires "{Team} picked {Player}" for all other picks - Gracefully hides toggle when Notification API is unavailable https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix all code review issues with push notifications - Extract snake draft order calculation to shared getTeamForPick() helper in lib/draft-order.ts, removing the duplicated logic - Fix notification firing on user's own picks (guard with team id check) - Fix notification firing after draft is complete (early return) - Use a ref for sendNotification to prevent full socket handler re-registration every time the toggle is changed - Add permission drift listener via Permissions API so the UI reacts if the user revokes notification permission in browser settings - Add SSR guard for document.hidden in sendNotification - Remove redundant isSupported prop from NotificationSettings; derive unsupported state from permissionState === "unsupported" - Move NotificationSettings out of QueueSection prop-drilling path; render it via a new settingsSection slot on DraftSidebar instead https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 * Fix second round of notification code review issues - Add My Turn Only / All Picks mode granularity so users can choose to only be notified when it's their pick, or for every pick - Remove double top-border by stripping leftover border-t/pt-4/mt-4 wrapper from NotificationSettings (now provided by DraftSidebar) - Remove unused isSupported from hook return value - Add n.onclick = () => window.focus() so clicking a notification brings the draft tab back into focus - Scope localStorage keys to userId to avoid shared-browser conflicts (keys now: draftNotifications-{userId}-{seasonId}) - Add notificationsModeRef alongside sendNotificationRef so mode changes don't trigger full socket handler re-registration https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 19:30:53 -08:00
// No meaningful "next turn" notification once the draft is over
if (data.isDraftComplete) return;
const notifTitle = `${season.league.name} Draft`;
const nextSlot = getTeamForPick(data.nextPickNumber, draftSlots);
const isNowMyTurn = !!(
userTeam && nextSlot && nextSlot.team.id === userTeam.id
);
if (isNowMyTurn) {
sendNotificationRef.current(notifTitle, `It's your turn to pick!`);
} else if (
notificationsModeRef.current === "all_picks" &&
data.pick?.team?.id !== userTeam?.id
) {
// Only notify about other teams' picks, not the user's own
const pickerName = data.pick?.team?.name || "A team";
const participantName = data.pick?.participant?.name || "a participant";
sendNotificationRef.current(
notifTitle,
`${pickerName} picked ${participantName}`
);
}
};
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const handleTimerUpdate = (data: { teamId: string; timeRemaining: number; currentPickNumber: number | null }) => {
// Bail out without creating a new object reference if nothing changed.
// Without this guard the spread `{ ...prev }` always returns a new object,
// causing a full re-render of the DraftRoom tree on every 1-second tick.
setTeamTimers((prev) => {
if (prev[data.teamId] === data.timeRemaining) return prev;
return { ...prev, [data.teamId]: data.timeRemaining };
});
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// Also sync the current pick number from the server. This is critical
// for mobile reconnection: timer-update events arrive every second, so
// this ensures currentPick is correct within 1s of reconnecting — even
// if the HTTP revalidation hasn't completed yet.
// React's useState already bails out when the new value equals the old
// one (Object.is comparison on primitives), so no manual guard needed.
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
if (data.currentPickNumber !== null) {
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
setCurrentPick(data.currentPickNumber);
}
};
const handleDraftPaused = () => {
setIsPaused(true);
};
const handleDraftResumed = () => {
setIsPaused(false);
};
const handleDraftCompleted = () => {
setIsDraftComplete(true);
};
const handleAutodraftUpdated = (data: {
teamId: string;
isEnabled: boolean;
mode: "next_pick" | "while_on";
queueOnly: boolean;
source?: "commissioner" | "user";
}) => {
setAutodraftStatus((prev) => ({
...prev,
[data.teamId]: { isEnabled: data.isEnabled, mode: data.mode, queueOnly: data.queueOnly },
}));
// Update user's local state if it's their team
if (userTeam && data.teamId === userTeam.id) {
if (data.source === "commissioner") {
if (!data.isEnabled) {
toast.info("Commissioner turned off your autodraft");
} else {
toast.info(`Commissioner changed your autodraft to "${getAutodraftLabel(data.isEnabled, data.mode, data.queueOnly)}"`);
}
} else {
// Detect server-side auto-disable: was enabled with queueOnly, now disabled
const prev = userAutodraftRef.current;
if (!data.isEnabled && prev.isEnabled && prev.queueOnly && prev.mode === "while_on") {
toast.info("Autodraft disabled — your queue is empty");
}
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
}
setUserAutodraft({
isEnabled: data.isEnabled,
mode: data.mode,
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
queueOnly: data.queueOnly,
});
}
};
const handleTeamConnected = (data: { teamId: string }) => {
setConnectedTeams((prev) => new Set(prev).add(data.teamId));
};
const handleTeamDisconnected = (data: { teamId: string }) => {
setConnectedTeams((prev) => {
const newSet = new Set(prev);
newSet.delete(data.teamId);
return newSet;
});
};
const handleConnectedTeamsList = (data: { teamIds: string[] }) => {
setConnectedTeams(new Set(data.teamIds));
};
const handleParticipantRemovedFromQueues = (data: { participantId: string }) => {
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
setQueue((prev) =>
prev.filter((item) => item.participantId !== data.participantId)
);
};
const handleQueueEligibilityPruned = (data: { teamId: string; removedParticipantIds: string[] }) => {
if (data.teamId !== userTeam?.id) return;
const removed = new Set(data.removedParticipantIds);
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
setQueue((prev) => prev.filter((item) => !removed.has(item.participantId)));
};
// Fired when another window/session changes the user's queue (add/remove/reorder/clear).
// Only received by this team's own sockets (emitted to team-${teamId} private room).
// Ignored while this window has in-flight mutations to prevent the server echo
// from clobbering a more-recent optimistic update.
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const handleQueueUpdated = (data: { queue: QueueItem[] }) => {
if (pendingQueueMutationsRef.current > 0) return;
setQueue(data.queue);
};
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const handlePickReplaced = (data: { pickNumber: number; pick: (typeof draftPicks)[number] }) => {
setPicks((prev) =>
prev.map((p) => (p.pickNumber === data.pickNumber ? data.pick : p))
);
};
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const handleDraftRolledBack = (data: { pickNumber: number }) => {
setPicks((prev) =>
prev.filter((p) => p.pickNumber < data.pickNumber)
);
setCurrentPick(data.pickNumber);
setIsDraftComplete(false);
setIsPaused(false);
};
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// Full state sync sent by the server on join-draft. Provides an immediate,
// socket-based path to bring the client up to date — critical for mobile
// reconnection where the HTTP revalidation may be delayed or fail entirely.
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const handleDraftStateSync = (data: { picks: (typeof draftPicks)[number][]; currentPickNumber: number; isPaused: boolean; status: string; timers?: Array<{ teamId: string; timeRemaining: number }>; queue?: QueueItem[] }) => {
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
// If a revalidation is in-flight, let it complete and handle the merge.
// The revalidation path already buffers picks and deduplicates — layering
// this on top would risk conflicts. But if NOT revalidating, apply the
// server snapshot directly.
if (!isRevalidatingRef.current) {
setPicks(data.picks);
setCurrentPick(data.currentPickNumber);
setIsPaused(data.isPaused);
setIsDraftComplete(
data.status === "active" || data.status === "completed"
);
if (data.timers) {
setTeamTimers((prev) => {
const updated = { ...prev };
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
data.timers?.forEach((t) => {
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
updated[t.teamId] = t.timeRemaining;
});
return updated;
});
}
if (data.queue) {
setQueue(data.queue);
}
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
}
};
on("pick-made", handlePickMade);
on("timer-update", handleTimerUpdate);
on("draft-paused", handleDraftPaused);
on("draft-resumed", handleDraftResumed);
on("draft-completed", handleDraftCompleted);
on("autodraft-updated", handleAutodraftUpdated);
on("team-connected", handleTeamConnected);
on("team-disconnected", handleTeamDisconnected);
on("connected-teams-list", handleConnectedTeamsList);
on("participant-removed-from-queues", handleParticipantRemovedFromQueues);
on("queue-eligibility-pruned", handleQueueEligibilityPruned);
on("queue-updated", handleQueueUpdated);
on("pick-replaced", handlePickReplaced);
on("draft-rolled-back", handleDraftRolledBack);
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
on("draft-state-sync", handleDraftStateSync);
return () => {
off("pick-made", handlePickMade);
off("timer-update", handleTimerUpdate);
off("draft-paused", handleDraftPaused);
off("draft-resumed", handleDraftResumed);
off("draft-completed", handleDraftCompleted);
off("autodraft-updated", handleAutodraftUpdated);
off("team-connected", handleTeamConnected);
off("team-disconnected", handleTeamDisconnected);
off("connected-teams-list", handleConnectedTeamsList);
off("participant-removed-from-queues", handleParticipantRemovedFromQueues);
off("queue-eligibility-pruned", handleQueueEligibilityPruned);
off("queue-updated", handleQueueUpdated);
off("pick-replaced", handlePickReplaced);
off("draft-rolled-back", handleDraftRolledBack);
fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms (#48) * fix: sync all draft state on mobile reconnection After a mobile browser returns from a long background period, the draft room had stale picks, wrong "on the clock" display, and inaccurate available player lists. The root cause was that reconnection relied solely on an HTTP revalidation that could fail (expired JWT, flaky network), and the timer-update handler ignored currentPickNumber. Changes: - Server emits draft-state-sync on join-draft with full picks, timers, and season state, giving the client an immediate socket-based sync path that doesn't depend on HTTP revalidation - timer-update handler now syncs currentPickNumber, fixing the "on the clock" display within 1 second of reconnection - Revalidation retry with 3s delay ensures the HTTP path succeeds even when the network is slow to stabilize on mobile return - Revalidation completion now also syncs teamTimers and autodraftStatus - Added draft-state-sync client handler that applies the server snapshot immediately (skipped when revalidation is in-flight to avoid conflicts) Tests: 32 new tests covering reconnection sync, pick buffering/merge, timer-update currentPickNumber sync, draft-state-sync handling, available player filtering, on-the-clock correctness, and revalidation retry logic. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms * fix: guard against stale revalidation overwriting fresh socket data Add a reference-equality check so that if HTTP revalidation fails (network error, expired token), the sync effect does not overwrite fresh data that draft-state-sync already applied. Also adds missing dependency array entries (userQueue, timers, autodraftSettings) to the revalidation sync effect. https://claude.ai/code/session_01JxJ1CYTiFWV4KTPxTnjrms --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-01 12:23:24 -08:00
off("draft-state-sync", handleDraftStateSync);
};
// `on`/`off` are stable useCallback refs. `socketVersion` increments whenever
// useDraftSocket creates a new socket instance, ensuring this effect re-runs
// and re-registers all handlers on the fresh socket.
// `draftSlots`, `userTeam`, and `season.league.name` are intentionally omitted:
// they come from useLoaderData and are immutable for the lifetime of the page load.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [on, off, socketVersion]);
// Persist sidebar collapsed state
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("draftSidebarCollapsed", JSON.stringify(sidebarCollapsed));
}
}, [sidebarCollapsed]);
// Queue handlers
const handleAddToQueue = useCallback(async (participantId: string) => {
if (!userTeam) return;
// Check if participant is already in queue (client-side)
const alreadyInQueue = queue.some((item) => item.participantId === participantId);
if (alreadyInQueue) {
toast.error("This participant is already in your queue");
return;
}
// Optimistic update - create a temporary queue item
const now = new Date();
const tempQueueItem: QueueItem = {
id: `temp-${Date.now()}`,
participantId,
queuePosition: queue.length + 1,
seasonId: season.id,
teamId: userTeam.id,
createdAt: now,
updatedAt: now,
};
// Immediately update the UI
setQueue((prev) => [...prev, tempQueueItem]);
const formData = new FormData();
formData.append("seasonId", season.id);
formData.append("teamId", userTeam.id);
formData.append("participantId", participantId);
pendingQueueMutationsRef.current++;
try {
const response = await authFetch("/api/queue/add", {
method: "POST",
body: formData,
});
if (!response) { setQueue((prev) => prev.filter((item) => item.id !== tempQueueItem.id)); return; }
if (response.ok) {
const data = await response.json();
// Replace temp item with real item from server
setQueue((prev) =>
prev.map((item) =>
item.id === tempQueueItem.id ? data.queueItem : item
)
);
} else {
// Revert the optimistic update on error
setQueue((prev) => prev.filter((item) => item.id !== tempQueueItem.id));
const error = await response.json();
toast.error(error.error || "Failed to add to queue");
}
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
} catch {
// Revert the optimistic update on network error
setQueue((prev) => prev.filter((item) => item.id !== tempQueueItem.id));
toast.error("Network error - failed to add to queue");
} finally {
pendingQueueMutationsRef.current--;
}
// 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).
}, [userTeam, queue, season.id, authFetch]);
const handleRemoveFromQueue = useCallback(async (queueId: string) => {
if (!userTeam) return;
// Optimistically remove the item immediately
let previousQueue: QueueItem[] = [];
setQueue((prev) => {
previousQueue = prev;
return prev.filter((item) => item.id !== queueId);
});
const formData = new FormData();
formData.append("queueId", queueId);
formData.append("teamId", userTeam.id);
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 {
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,
});
if (!response) { setQueue(previousQueue); return; }
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 {
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 {
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");
} finally {
pendingQueueMutationsRef.current--;
}
}, [userTeam, authFetch]);
const handleReorderQueue = useCallback(async (participantIds: string[]) => {
if (!userTeam) return;
// 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);
});
const formData = new FormData();
formData.append("teamId", userTeam.id);
formData.append("seasonId", season.id);
formData.append("participantIds", JSON.stringify(participantIds));
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 {
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,
});
if (!response) { setQueue(previousQueue); return; }
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();
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 {
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");
} finally {
pendingQueueMutationsRef.current--;
}
}, [userTeam, season.id, authFetch]);
const handleStartDraft = async () => {
try {
const formData = new FormData();
formData.append("seasonId", season.id);
const response = await authFetch("/api/draft/start", {
method: "POST",
body: formData,
});
if (!response) return;
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");
}
};
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 {
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,
});
if (!response) return;
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");
}
};
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 {
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,
});
if (!response) return;
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");
}
};
const handleMakePick = useCallback(async (participantId: string) => {
try {
const formData = new FormData();
formData.append("seasonId", season.id);
formData.append("participantId", participantId);
const response = await authFetch("/api/draft/make-pick", {
method: "POST",
body: formData,
});
if (!response) return;
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");
}
}, [season.id, authFetch]);
const handleForceAutopick = useCallback(async (pickNumber: number, teamId: string) => {
try {
const formData = new FormData();
formData.append("seasonId", season.id);
formData.append("teamId", teamId);
formData.append("pickNumber", pickNumber.toString());
const response = await authFetch("/api/draft/force-autopick", {
method: "POST",
body: formData,
});
if (!response) return;
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");
}
}, [season.id, authFetch]);
const handleForceManualPick = async (participantId: string) => {
if (!selectedPickSlot) return;
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());
const response = await authFetch("/api/draft/force-manual-pick", {
method: "POST",
body: formData,
});
if (!response) return;
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");
}
};
const handleReplacePickOpen = useCallback((pickNumber: number, teamId: string) => {
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const pick = picks.find((p) => p.pickNumber === pickNumber);
if (!pick) {
toast.error("Pick not found — try refreshing the page");
return;
}
setReplacePickSlot({ pickNumber, teamId, oldParticipantId: pick.participant.id });
setReplacePickDialogOpen(true);
// picks is read directly to find the pick being replaced.
}, [picks]);
const handleReplacePick = async (participantId: string) => {
if (!replacePickSlot) return;
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());
const response = await authFetch("/api/draft/replace-pick", {
method: "POST",
body: formData,
});
if (!response) return;
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");
}
};
const handleRollbackToPick = useCallback((pickNumber: number) => {
setRollbackPickNumber(pickNumber);
setRollbackConfirmOpen(true);
}, []);
const handleConfirmRollback = async () => {
if (!rollbackPickNumber || isRollingBack) return;
setIsRollingBack(true);
try {
const formData = new FormData();
formData.append("seasonId", season.id);
formData.append("pickNumber", rollbackPickNumber.toString());
const response = await authFetch("/api/draft/rollback", {
method: "POST",
body: formData,
});
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");
}
};
const handleAdjustTimeBankOpen = useCallback((teamId: string) => {
setTimeBankTeamId(teamId);
setTimeBankAmount("1");
setTimeBankUnit("minutes");
setTimeBankDirection("add");
setTimeBankDialogOpen(true);
}, []);
const handleSetAutodraftOpen = useCallback((teamId: string) => {
setCommissionerAutodraftTeamId(teamId);
setCommissionerAutodraftDialogOpen(true);
}, []);
const handleCommissionerAutodraftUpdate = useCallback(() => {
setCommissionerAutodraftDialogOpen(false);
setCommissionerAutodraftTeamId(null);
}, []);
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());
const response = await authFetch("/api/draft/adjust-time-bank", {
method: "POST",
body: formData,
});
if (!response) return;
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);
}
};
const handleForceManualPickOpen = useCallback((pickNumber: number, teamId: string) => {
setSelectedPickSlot({ pickNumber, teamId });
setForcePickDialogOpen(true);
}, []);
// Calculate current round
const currentRound = draftSlots.length > 0 ? Math.ceil(currentPick / draftSlots.length) : 1;
// Determine whose turn it is
const currentDraftSlot = useMemo(
() => getTeamForPick(currentPick, draftSlots),
[currentPick, draftSlots]
);
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
const currentDraftSlotOwnerName = currentDraftSlot ? ownerMap[currentDraftSlot.team.id] : undefined;
const currentClockTime = currentDraftSlot ? teamTimers[currentDraftSlot.team.id] : undefined;
const currentClockColor = getTimerColorClass(currentClockTime);
// Generate snake draft grid structure
const draftGrid = useMemo(() => {
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]));
const grid: Array<
Array<{
pickNumber: number;
round: number;
pickInRound: number;
teamId: string;
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
pick?: (typeof draftPicks)[number];
}>
> = [];
for (let round = 1; round <= rounds; round++) {
const roundPicks = [];
const isOddRound = round % 2 === 1;
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;
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);
roundPicks.push({ pickNumber, round, pickInRound, teamId, pick });
}
grid.push(roundPicks);
}
return grid;
}, [picks, draftSlots, season.draftRounds]);
// Get drafted participant IDs for filtering
const draftedParticipantIds = useMemo(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
() => new Set(picks.map((p) => p.participant.id)),
[picks]
);
// 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
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
.filter((p) => p.team.id === userTeam.id)
.map((p) => p.sport.id)
);
}, [picks, userTeam]);
// 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
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
.filter((p) => p.team.id === userTeam.id)
.map((p) => p.sport.name)
);
}, [picks, userTeam]);
// Get unique sports for filter dropdown
const uniqueSports = useMemo(
() =>
Array.from(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
new Set(availableParticipants.map((p) => p.sport.name))
).sort(),
[availableParticipants]
);
// Filter participants based on search, sport, drafted status, and eligibility
const filteredParticipants = useMemo(() => {
const sportFilterSet = new Set(sportFilters);
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
return availableParticipants.filter((participant) => {
// 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;
}
}
// 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]);
// Shared component props — defined once to avoid duplication between desktop and mobile layouts
const handleHideCompletedSportsChange = useCallback((hide: boolean) => {
setHideCompletedSports(hide);
if (hide) {
setSportFilters((prev) => prev.filter((s) => !userDraftedSportNames.has(s)));
}
}, [userDraftedSportNames]);
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(() => ({
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,
searchQuery,
sportFilters,
hideDrafted,
hideIneligible,
uniqueSports,
draftedParticipantIds,
queue,
eligibility,
canPick,
hasTeam: !!userTeam,
onSearchChange: setSearchQuery,
onSportFiltersChange: setSportFilters,
onHideDraftedChange: setHideDrafted,
onHideIneligibleChange: setHideIneligible,
hideCompletedSports,
userDraftedSportNames,
onHideCompletedSportsChange: handleHideCompletedSportsChange,
onMakePick: handleMakePick,
onAddToQueue: handleAddToQueue,
onRemoveFromQueue: handleRemoveFromQueue,
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
}), [filteredParticipants, participantRanks, miniDraftGrid, searchQuery, sportFilters, hideDrafted, hideIneligible, uniqueSports, draftedParticipantIds, queue, eligibility, canPick, userTeam, hideCompletedSports, userDraftedSportNames, handleHideCompletedSportsChange, handleMakePick, handleAddToQueue, handleRemoveFromQueue]);
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,
onAdjustTimeBankOpen: isCommissioner ? handleAdjustTimeBankOpen : undefined,
onSetAutodraftOpen: isCommissioner ? handleSetAutodraftOpen : undefined,
onForceAutopick: handleForceAutopick,
onForceManualPickOpen: handleForceManualPickOpen,
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined,
};
const summaryViewProps = {
draftSlots,
picks,
sports: seasonSportsData,
ownerMap,
totalRounds: season.draftRounds,
};
const rosterViewProps = {
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,
};
const handleAutodraftUpdate = useCallback(
(isEnabled: boolean, mode: "next_pick" | "while_on", queueOnly: boolean) => {
setUserAutodraft({ isEnabled, mode, queueOnly });
},
[]
);
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,
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,
}
: null;
const mobileTabs = userTeam
? [MOBILE_TABS_BASE[0], QUEUE_TAB, ...MOBILE_TABS_BASE.slice(1)]
: MOBILE_TABS_BASE;
const mobileColCount = mobileTabs.length;
return (
<div className="h-dvh bg-background flex flex-col overflow-hidden">
{/* Draft Completion Banner */}
{isDraftComplete && (
Redesign to dark-mode-only with navy palette and accent colors (#13) Removes light mode entirely in favour of a permanent dark theme with a navy-tinted background and three signature accents (electric blue, amber/gold, coral) exposed as CSS custom properties and Tailwind utilities (bg-electric, text-amber-accent, text-coral-accent). - Set class="dark" on <html> and apply Clerk dark base theme - Rewrite app.css: single :root palette (oklch navy values), custom --electric / --amber-accent / --coral-accent variables, remove duplicate .dark block and light-mode bg-white/bg-gray-950 rule - Install @clerk/themes for Clerk dark modal support - Replace hardcoded Tailwind colors across 30+ files: - Draft grid cells: blue-50/blue-950 → electric/15, green-50/950 → emerald/10 - Timer: green-600/yellow-600/red-600 → emerald-400/amber-accent/coral-accent - Status badges: blue-50/green-50/gray-50 → electric/emerald/muted variants - Success messages: green-500/15 text-green-700 dark:text-green-400 → emerald-500/15 text-emerald-400 - Info cards: blue-50 dark:bg-blue-950 → electric/10 - Warning cards: yellow-500 → amber-accent variants - Medal/placement badges: yellow-500/orange-600 → amber-accent/coral-accent - Movement indicators: green-600/red-600 → emerald-400/coral-accent - Connection dots: green-500/red-500 → emerald-500/coral-accent - Remove dark:hidden/dark:block logo toggle in welcome.tsx (always dark) - Update DraftGrid test assertions to match new class names Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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">
🎉 Draft Complete! The season is now active.
{season.league.isPublicDraftBoard && (
<>
{" "}
<a
href={`/leagues/${season.leagueId}/draft-board/${season.id}`}
Redesign to dark-mode-only with navy palette and accent colors (#13) Removes light mode entirely in favour of a permanent dark theme with a navy-tinted background and three signature accents (electric blue, amber/gold, coral) exposed as CSS custom properties and Tailwind utilities (bg-electric, text-amber-accent, text-coral-accent). - Set class="dark" on <html> and apply Clerk dark base theme - Rewrite app.css: single :root palette (oklch navy values), custom --electric / --amber-accent / --coral-accent variables, remove duplicate .dark block and light-mode bg-white/bg-gray-950 rule - Install @clerk/themes for Clerk dark modal support - Replace hardcoded Tailwind colors across 30+ files: - Draft grid cells: blue-50/blue-950 → electric/15, green-50/950 → emerald/10 - Timer: green-600/yellow-600/red-600 → emerald-400/amber-accent/coral-accent - Status badges: blue-50/green-50/gray-50 → electric/emerald/muted variants - Success messages: green-500/15 text-green-700 dark:text-green-400 → emerald-500/15 text-emerald-400 - Info cards: blue-50 dark:bg-blue-950 → electric/10 - Warning cards: yellow-500 → amber-accent variants - Medal/placement badges: yellow-500/orange-600 → amber-accent/coral-accent - Movement indicators: green-600/red-600 → emerald-400/coral-accent - Connection dots: green-500/red-500 → emerald-500/coral-accent - Remove dark:hidden/dark:block logo toggle in welcome.tsx (always dark) - Update DraftGrid test assertions to match new class names Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 19:26:11 -08:00
className="underline hover:text-emerald-300"
>
View Draft Board
</a>
</>
)}
</div>
)}
{/* Standalone Header - No Main Nav */}
<div className="border-b bg-card flex-shrink-0">
<div className="w-full px-4 py-4">
<div className="flex items-center justify-between">
<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"
>
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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" />
</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="/">
<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>
</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>
{/* Commissioner Controls - hidden on mobile (shown in Controls tab) */}
{isCommissioner && season.status === "pre_draft" && (
<Button className="hidden md:flex" onClick={handleStartDraft}>Start Draft</Button>
)}
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
{isCommissioner && season.status === "draft" && !isDraftComplete && (
<div className="hidden md:flex">
{isPaused ? (
<Button onClick={handleResumeDraft} variant="default">
Resume Draft
</Button>
) : (
<Button onClick={handlePauseDraft} variant="outline">
Pause Draft
</Button>
)}
</div>
)}
<Button variant="outline" asChild className="hidden md:flex">
<Link to={`/leagues/${season.leagueId}`}>Exit Draft Room</Link>
</Button>
</div>
</div>
</div>
</div>
{/* 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>
<div className="text-sm font-bold">
{currentDraftSlotOwnerName ?? currentDraftSlot.team.name}
</div>
{currentDraftSlotOwnerName && (
<div className="text-xs text-muted-foreground">{currentDraftSlot.team.name}</div>
)}
</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>
)}
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>
{/* 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>
)}
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>
)}
{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>
)}
{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>
)}
{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>
)
)}
<Button variant="outline" asChild className="w-full min-h-[48px]">
<Link to={`/leagues/${season.leagueId}`}>Exit Draft Room</Link>
</Button>
</div>
)}
</div>
) : (
<div className="flex h-full overflow-hidden">
{userTeam && queueSectionProps && (
<DraftSidebar
collapsed={sidebarCollapsed}
onCollapsedChange={setSidebarCollapsed}
queueSection={<QueueSection {...queueSectionProps} />}
recentPicksSection={<SidebarRecentPicks picks={picks} />}
/>
)}
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>
</div>
</div>
)}
</div>
{/* Mobile Bottom Nav */}
<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>
<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);
}}
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
open={replacePickDialogOpen && !!replacePickSlot}
onOpenChange={(open) => {
setReplacePickDialogOpen(open);
if (!open) setReplacePickSlot(null);
}}
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}
/>
{/* Rollback Confirmation Dialog */}
<Dialog
open={rollbackConfirmOpen}
onOpenChange={(open) => {
setRollbackConfirmOpen(open);
if (!open) setRollbackPickNumber(null);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Roll Back Draft</DialogTitle>
<DialogDescription>
{rollbackPickNumber !== null && (() => {
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
const count = picks.filter((p) => p.pickNumber >= rollbackPickNumber).length;
return `This will erase ${count} pick${count === 1 ? "" : "s"} (pick #${rollbackPickNumber} through the most recent) and return the draft to pick #${rollbackPickNumber}. This cannot be undone.`;
})()}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setRollbackConfirmOpen(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={handleConfirmRollback} disabled={isRollingBack}>
{isRollingBack ? "Rolling Back..." : "Roll Back"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Time Bank Adjustment Dialog */}
<Dialog
open={timeBankDialogOpen}
onOpenChange={(open) => {
setTimeBankDialogOpen(open);
if (!open) setTimeBankTeamId(null);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Adjust Time Bank</DialogTitle>
<DialogDescription>
{timeBankTeamId
? `Adjusting time for ${draftSlots.find((s) => s.team.id === timeBankTeamId)?.team.name}`
: "Adjust a team's time bank"}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 py-2">
<div className="flex gap-2">
<button
type="button"
onClick={() => setTimeBankDirection("add")}
className={`flex-1 py-2 rounded-md border text-sm font-medium transition-colors ${
timeBankDirection === "add"
? "bg-emerald-500/20 border-emerald-500 text-emerald-400"
: "border-border text-muted-foreground hover:bg-muted"
}`}
>
Add Time
</button>
<button
type="button"
onClick={() => setTimeBankDirection("remove")}
className={`flex-1 py-2 rounded-md border text-sm font-medium transition-colors ${
timeBankDirection === "remove"
? "bg-destructive/20 border-destructive text-destructive"
: "border-border text-muted-foreground hover:bg-muted"
}`}
>
Remove Time
</button>
</div>
<div className="flex gap-2">
<input
type="number"
min="0.001"
step="any"
value={timeBankAmount}
onChange={(e) => setTimeBankAmount(e.target.value)}
className="flex-1 px-3 py-2 border rounded-md bg-background text-foreground text-base md:text-sm"
placeholder="Amount"
/>
<select
value={timeBankUnit}
onChange={(e) =>
setTimeBankUnit(e.target.value as "seconds" | "minutes" | "hours")
}
className="px-3 py-2 border rounded-md bg-background text-foreground text-base md:text-sm"
>
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
</select>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setTimeBankDialogOpen(false)}>
Cancel
</Button>
<Button
onClick={handleConfirmAdjustTimeBank}
disabled={isAdjustingTimeBank}
variant={timeBankDirection === "remove" ? "destructive" : "default"}
>
{isAdjustingTimeBank
? "Adjusting..."
: timeBankDirection === "add"
? "Add Time"
: "Remove Time"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Commissioner Autodraft Override Dialog */}
<Dialog
open={commissionerAutodraftDialogOpen}
onOpenChange={(open) => {
setCommissionerAutodraftDialogOpen(open);
if (!open) setCommissionerAutodraftTeamId(null);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Set Autodraft</DialogTitle>
<DialogDescription>
{commissionerAutodraftTeamId
? `Change autodraft for ${draftSlots.find((s) => s.team.id === commissionerAutodraftTeamId)?.team.name ?? "this team"}`
: "Set a team's autodraft settings"}
</DialogDescription>
</DialogHeader>
{commissionerAutodraftTeamId && (
<AutodraftSettings
seasonId={season.id}
teamId={commissionerAutodraftTeamId}
isEnabled={autodraftStatus[commissionerAutodraftTeamId]?.isEnabled ?? false}
mode={autodraftStatus[commissionerAutodraftTeamId]?.mode ?? "next_pick"}
queueOnly={autodraftStatus[commissionerAutodraftTeamId]?.queueOnly ?? false}
isMyTurn={false}
onUpdate={handleCommissionerAutodraftUpdate}
/>
)}
</DialogContent>
</Dialog>
{/* Connection Overlay - blocks interaction until socket connects */}
<ConnectionOverlay
isConnected={isConnected}
isReconnecting={isReconnecting}
connectionError={connectionError}
/>
{/* Auth Recovery Overlay - blocks interaction when session expires */}
<AuthRecoveryOverlay isAuthDegraded={authDegraded} />
</div>
);
}