Commit graph

15 commits

Author SHA1 Message Date
Chris Parsons
a447ba54de
Add Cloudinary-backed avatar system (#385) 2026-05-06 14:47:37 -07:00
Chris Parsons
dbc23f14ce
Add overnight pause for draft timers (#335)
* Add overnight pause feature for draft timers

Protects players from having their pick timer expire while asleep.
Admins configure a nightly window (league-wide or per-user timezone);
the timer freezes during that window while autodraft can still fire.

Fixes #66

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix TS error: guard getUserDisplayName against undefined user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 22:31:52 -07:00
Chris Parsons
1bf65e33f7
Add commish right-click context menus to MiniDraftGrid team headers (#327)
* Add commish right-click context menus to MiniDraftGrid team headers

Adds onAdjustTimeBankOpen and onSetAutodraftOpen props to MiniDraftGrid
so the last-two-rounds display on the Participants tab shows the same
"Adjust Time Bank…" / "Set Autodraft…" context menu on right-click that
the full Draft Board already exposes. Also passes those callbacks from
the route's miniDraftGrid useMemo so they are wired up for commissioners.

https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1

* Address all code review issues for draft room context menus

Layout bug: flex-1 min-w-20 was nested inside headerInner instead of the
direct flex-child wrapper in MiniDraftGrid, breaking equal column sizing.
Fixed by moving the classes to the outermost element in both the menu and
non-menu paths (matching DraftGridSection's pattern).

MiniDraftGrid improvements:
- Hoist hasHeaderMenu constant above the draftSlots.map() loop
- Add optional connectedTeams prop; disconnected teams render italic +
  muted-foreground, consistent with DraftGridSection
- connectedTeams now wired through the route's miniDraftGrid useMemo

DraftGridSection interface:
- Make onForceAutopick and onForceManualPickOpen optional (?) to match
  every other commissioner callback; add null checks at all three call
  sites (context menu, mobile MoreVertical button, mobile Sheet)
- Gate those two callbacks behind isCommissioner at the route level

Tests (new files):
- app/components/__tests__/MiniDraftGrid.test.tsx — covers layout classes,
  connected/disconnected styling, and all four context menu interactions
- app/components/__tests__/DraftGridSection.test.tsx — covers all three
  context menu surfaces for both commissioner and non-commissioner roles

Storybook: add CommissionerView and DisconnectedTeams stories to
MiniDraftGrid.stories.tsx

https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1

* Move hasHeaderMenu out of IIFE into component body

Computing it before the return statement is the right place since it only
depends on props, not loop variables. Removes the IIFE entirely and fixes
the indentation of the map callback body.

https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1

* Mock HTMLElement.scrollTo in test setup

jsdom does not implement scrollTo on elements, causing any component that
calls element.scrollTo() (e.g. MiniDraftGrid's scroll-to-current-cell
effect) to throw in unit tests.

https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-25 07:42:43 -07:00
Chris Parsons
9ed0282fd0
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
Chris Parsons
e2b178221a
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
Chris Parsons
40167cfa5e
feat: enhance autodraft functionality with detailed settings and commissioner controls (#61) 2026-03-03 20:14:38 -08:00
Chris Parsons
ca2fd288ab
perf: fix draft room lag from excessive re-renders and listener leaks (#54)
Primary fix: setTeamTimers now bails out with `return prev` when the
value hasn't changed, preventing a full DraftRoom re-render on every
1-second timer tick (was 33% of profiler samples).

Memoization: wrap AvailableParticipantsSection, TeamsDraftedGrid,
QueueSection, SidebarRecentPicks, and DraftGridSection in React.memo
so timer ticks don't cascade into heavy components that don't use
timer state.

Stable refs: wrap nine action handlers in useCallback and extract two
inline arrow functions from props objects so memo() comparisons
actually bail out. Memoize the { numFlexPicks } object passed to
TeamsDraftedGrid.

socketVersion: expose an incrementing counter from useDraftSocket so
the socket handler effect re-registers on socket recreation.

Async cleanup: add abort flag + in-flight guard to the visibilitychange
JWT refresh handler to prevent concurrent executions and stale state
updates after unmount. Add abort flag to useDraftNotifications
permissions.query() to prevent dangling onchange if unmounted
mid-promise.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 13:18:47 -08:00
Chris Parsons
3343e7a405
fix: address code review issues from draft board scrolling and team creation changes (#35)
- DraftGrid: wrap header and rows in a single min-w-max container so
  column widths share a sizing context and can't desync
- Settings: wrap team + draft slot creation in a DB transaction so a
  slot insert failure can't leave orphaned teams with no slots
- Settings: replace Math.max(...spread) with reduce to avoid the JS
  argument limit (defensive, given the 6-16 team cap)
- Settings: remove now-unused createManyTeams import

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:01:27 -08:00
Chris Parsons
77e408cad8
Make draft room fully usable on mobile (#30)
- Add bottom nav bar (Lobby/Board/Roster/Controls) with 56px touch targets
- Add mobile card list view in AvailableParticipantsSection with 44px Draft/Queue buttons
- Add round labels and sticky header row to DraftGridSection grid
- Add mobile Sheet for commissioner actions in DraftGridSection (replaces right-click context menu)
- Use useMediaQuery to render a single layout tree, eliminating duplicate component
  instances (previously both desktop and mobile layouts were always in the DOM)
- Add compact header on mobile (text-lg vs text-3xl); hide commissioner buttons
  and Exit link in header on mobile — surfaced in Controls tab instead
- Add "On Clock" bar on mobile showing current team and timer during active draft
- Add "your turn" indicator dot on Lobby bottom nav tab
- Default mobile tab to "board" for commissioner-only viewers
- Fix sticky corner cell z-index in TeamsDraftedGrid (z-10 → z-20)
- Fix round column spacer in DraftGridSection header not being sticky-left
- Extract shared prop objects to eliminate copy-paste between layouts
- Extract getParticipantState() helper to deduplicate participant render logic
- Move MobileSheetData type and MOBILE_TABS array to module scope
- Remove unnecessary non-null assertions in DraftGridSection Sheet handlers

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 22:36:12 -08:00
Chris Parsons
ab3437cd73
Display team owner names instead of team names in draft UI (#24)
* Show manager username instead of team name on draft board

Both the read-only draft board and the active draft room now display
the manager's username (falling back to displayName if no username is
set, and to team name if the team is unassigned) in the column headers
of the draft grid.

https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1

* Address code review: extract ownerMap helper, fix TeamsDraftedGrid

- Extract ownerMap building into app/lib/owner-map.ts to eliminate
  duplication across the two loaders
- Guard against null username AND displayName so the map only contains
  real string values
- Apply username display to TeamsDraftedGrid (the "Teams" tab in the
  active draft room), which was missed in the initial change

https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-22 16:56:07 -08:00
Chris Parsons
51cffe1762
Add commissioner time bank adjustment feature for draft (#22)
* Add commissioner time bank adjustment via right-click on draft board team headers

Commissioners can right-click any team header on the Draft Board tab to open
an "Adjust Time Bank..." dialog. The dialog supports adding or removing an
arbitrary amount of time in seconds, minutes, or hours. The change is applied
immediately to the database and broadcast to all clients via the existing
timer-update Socket.IO event so every participant sees the updated clock
in real time.

- New API route: POST /api/draft/adjust-time-bank (commissioner-only)
- DraftGridSection: wraps team headers in a ContextMenu for commissioners
- Draft room: dialog state, handler, and updated DraftGridSection props

https://claude.ai/code/session_013wxPKzLUCx3nC3LpxgjvQL

* Fix code review issues in commissioner time bank adjustment

- Wrap fetch in try/finally so isAdjustingTimeBank is always reset,
  even on network errors that cause fetch to throw
- Guard against totalSeconds rounding to 0 for tiny fractional inputs
- Reject API requests when the draft is not in 'draft' status (409)
- Set input min to 0.001 so the browser rejects zero in native validation

https://claude.ai/code/session_013wxPKzLUCx3nC3LpxgjvQL

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-22 16:16:51 -08:00
Chris Parsons
7a8ea60e77
Add draft clock UI, Fischer increment timer logic, and security fixes (#19)
- Add prominent clock badge to tab bar (lights up on your turn) with
  correct Fischer increment chess-clock model: bank starts at initialTime,
  += incrementTime after each pick, other teams' banks untouched
- Extract pure timer helpers to app/lib/draft-timer.ts and add 29 unit
  tests covering formatClockTime, calculateTimeAfterPick, getTimerColorClass,
  and full snake-draft lifecycle regression scenarios
- Fix make-pick.ts: add status !== 'draft' and draftPaused server guards
- Fix draft-utils.ts: replace (global as any).__socketIO with getSocketIO(),
  fix timeUsed to store actual timeRemaining at pick moment (not always 120),
  add null timer warning, move timer fetch before pick insert
- Fix draft.start.ts: batch timer inserts, guard against empty draftSlots
- Fix DraftGridSection: memoize currentTeamId, widen teamTimers type to
  Record<string, number | undefined>
- Fix duplicate animate-pulse (getTimerColorClass already includes it)
- Clamp negative seconds in formatClockTime to guard against timer drift

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 16:51:12 -08:00
Chris Parsons
41f3654956
Add commissioner replace pick and draft rollback features (#18)
Commissioners can now right-click any picked cell in the draft grid to
replace the drafted participant (available during and after draft) or
roll back the draft to that pick number (available during draft only).

Replace pick enforces sport eligibility with the replaced slot treated
as free, updates the pick in-place, and broadcasts a pick-replaced
socket event so all clients update in real time. Rollback deletes all
picks from the selected pick onward, resets the season pick number,
clears all timers, and creates a fresh timer for the team now on the
clock.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:47:29 -08:00
Chris Parsons
1584d34b89
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
Chris Parsons
2cd4096e70 feat: Implement draft room redesign with collapsible sidebar and tabbed content
- Added AvailableParticipantsSection for participant search and filtering.
- Created DraftGridSection for displaying the draft grid with team timers and context menu.
- Developed QueueSection for managing participant queue and autodraft settings.
- Introduced RecentPicksSection to show recent draft picks.
- Implemented TeamsDraftedGrid to visualize drafted participants by team and sport.
- Added collapsible UI components for better layout management.
- Established tabbed navigation for draft board, recent picks, and teams drafted.
- Enhanced responsiveness for mobile and desktop views.
- Updated styles for a cohesive design across components.
2025-10-25 03:23:41 -07:00