Commit graph

21 commits

Author SHA1 Message Date
Chris Parsons
4bbcac1949
fix: resolve all 48 WCAG 2.2 AA accessibility issues (#439)
* fix: resolve all 48 WCAG 2.2 AA accessibility issues

Critical fixes:
- Add aria-label to all unlabeled inputs/selects in draft dialogs (ParticipantSelectionDialog, TimeBankAdjustmentDialog, AvailableParticipantsSection)
- Add role="dialog" + aria-modal + focus trap to ConnectionOverlay and AuthRecoveryOverlay
- Add aria-live region and connection status announcement to ConnectionOverlay

Serious fixes:
- Add skip-to-content link in root.tsx with id="main-content" on <main>
- Add aria-label to UserMenu trigger button
- Add aria-describedby + role="alert" to all auth form error messages (login, register, onboarding, forgot-password, reset-password)
- Replace emoji column headers in StandingsTable with aria-label + aria-hidden spans
- Add aria-live="assertive" to "It's your turn" desktop and mobile on-clock indicators
- Add aria-live="polite" to draft room countdown timer
- Add pause button to SportTicker (WCAG 2.2.2); add aria-hidden to ticker content
- Fix Footer text contrast (changed from 28% to text-muted-foreground)
- Fix OvernightPauseSettings: add htmlFor/id pairs and role="radiogroup"+aria-checked to mode buttons
- Fix DraftSetupSection: replace broken htmlFor with aria-label on date picker button
- Add aria-label to PeopleSection owner and commissioner selects
- Add labels to ScoringPresetPicker score inputs; add role="radiogroup"+aria-checked to preset buttons
- Add role="radiogroup"+aria-checked to AutodraftSettings option buttons
- Add accessible names, aria-current="step", and <ol> list semantics to WizardStepper

Moderate fixes:
- Add aria-controls to RecentPicksFeed toggle button; wrap picks list in aria-live region
- Add role="tab"+aria-selected+aria-controls to mobile board sub-tabs + role="tabpanel"
- Add role="radiogroup"+aria-checked to TimerModeSelector
- Add aria-current="page" + aria-label to SettingsDesktopNav
- Add aria-label="Admin navigation" to admin sidebar nav
- Add scope="col" + <caption> to StandingsTable and ScoringTables
- Add ARIA table roles (role="table/rowgroup/row/columnheader/rowheader/cell") to DraftSummaryView CSS grid

Minor fixes:
- Add aria-hidden="true" to decorative trend icons in StandingsTable
- Add aria-hidden="true" to desktop column header labels row in AvailableParticipantsSection
- Replace title with aria-label on all icon-only buttons (watchlist, queue) in AvailableParticipantsSection
- Add aria-label to NotificationSettings switchOnly Switch
- Add prefers-reduced-motion check to SlotMachineHeadline JS animation
- Bump --muted-foreground from 55% to 62% opacity for improved contrast margin

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

* Fix code review findings from WCAG compliance pass

- Add Arrow key navigation + roving tabindex to all role=radiogroup
  components (AutodraftSettings x2, TimerModeSelector,
  OvernightPauseSettings, ScoringPresetPicker) per ARIA radio pattern
- Extract shared focus-trap logic into useFocusTrap hook; update
  ConnectionOverlay and AuthRecoveryOverlay to use it
- Add tabIndex={-1} to ConnectionOverlay Card so focus can land in
  spinner-only state (no interactive children)
- Replace aria-live on loading dots container with sr-only span so
  status changes are announced by text content, not aria-label
- Remove contradictory aria-hidden+role=columnheader from
  AvailableParticipantsSection visual-only header row
- Remove invalid scope="col" from div[role=columnheader] in
  DraftSummaryView (scope is only valid on <th>)
- Remove redundant aria-label from ParticipantSelectionDialog sport
  select (htmlFor label is sufficient)
- Change WizardStepper connector <li> to role=presentation
- Revert muted-foreground from 62% to 55% (original already passes
  contrast; footer was fixed separately via text-muted-foreground)

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

* Fix lint error and update tests for WCAG role changes

- Replace el! non-null assertion with optional chaining in useFocusTrap
- Update AutodraftSettings tests to query role="radio" instead of
  role="button" (buttons have an explicit radio role since the WCAG pass)
- Update AvailableParticipantsSection watchlist tests to use
  getByRole/getAllByRole instead of getByTitle/getAllByTitle (watchlist
  buttons now use aria-label instead of title)

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 20:11:38 -07:00
Chris Parsons
6f70a19b73
Add admin user management interface with search and username editing (#431)
* Add admin users management page with pagination, search, and inline username editing

https://claude.ai/code/session_01E4UUQqQedhFP2F5YcBRArs

* Fix five issues from admin users page code review

- admin.users: use useEffect to close inline edit on success so validation
  errors are not silently discarded when the save button fires onClick
- root: add /admin to ONBOARDING_EXEMPT so admin users without a username
  are not redirect-looped away from admin pages
- settings: skip username validation when the username field is empty so
  updating timezone alone does not break for accounts without a username
- models/user: exclude soft-deleted users from findUsersPaginated
- admin.users: remove unused hasMore from loader return; simplify header div

https://claude.ai/code/session_01E4UUQqQedhFP2F5YcBRArs

* Fix lint: rename shadowed variables in user model

- findUserByUsername: use imported sql directly instead of destructuring
  it from the callback (shadowed the top-level import)
- findUsersPaginated: rename orderBy callback param from users to t
  (shadowed the outer users result variable)

https://claude.ai/code/session_01E4UUQqQedhFP2F5YcBRArs

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 12:07:51 -07:00
Chris Parsons
215b0cf6b5
Add user onboarding flow with username selection (#430)
* Add username onboarding prompt for new signups

New users (especially via OAuth/Google) are redirected to /onboarding
to choose a display username before accessing the app. Adds unique
constraint on users.username and case-insensitive uniqueness validation.

https://claude.ai/code/session_01UinBMAy9d6dQzzbcUAdq8J

* Address all code review issues on username onboarding

- Replace case-sensitive unique constraint with functional unique index on
  lower(username) so DB-level uniqueness is case-insensitive (fix #1)
- Share USERNAME_RE from models/user.ts; add same format + uniqueness
  validation to the settings update-profile action (fix #2)
- Wrap updateUser calls in try/catch to handle race-condition DB errors
  gracefully in both onboarding and settings (fix #3)
- Add unit tests for suggestUsername, safeRedirectTo, USERNAME_RE, and
  isOnboardingExempt (fix #4)
- Guard suggestUsername against returning strings shorter than 3 chars (fix #5)
- Preserve the user's intended destination via redirectTo query param
  through the onboarding flow (fix #6)
- Treat empty username in settings as a validation error instead of a
  silent no-op (fix #7)
- Use exact/sub-path matching in isOnboardingExempt to prevent
  false-positive prefix matches like /loginpage (fix #8)

https://claude.ai/code/session_01UinBMAy9d6dQzzbcUAdq8J

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 11:17:15 -07:00
Chris Parsons
a447ba54de
Add Cloudinary-backed avatar system (#385) 2026-05-06 14:47:37 -07:00
Chris Parsons
31e04d9c74 Fix admin navbar link: use isUserAdmin DB call instead of session.user.isAdmin
BetterAuth additionalFields doesn't reliably populate isAdmin in the session
user object. Fall back to a direct DB query (same pattern as admin.tsx).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 23:10:03 -07:00
Chris Parsons
ba9bf64e37
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)

Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.

**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field

**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed

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

* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict

better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
Chris Parsons
72f33cef2a Url fix. 2026-04-23 14:01:05 -07:00
Chris Parsons
6926fb96bc
Cache-bust logos via Vite ?url import and extract shared stat helpers (#310)
Logo SVGs referenced by string path (/logo.svg) were cached by CDNs and
browsers after deployments. Import them with Vite's ?url suffix so the
build emits content-hashed filenames that force a refresh on change.

LeagueRow and StandingsPreview had duplicated StatColumn, StatDivider,
and rank-change indicator helpers. Extract them into a shared
StatHelpers module so both pages render rank and points identically
(#3 style with consistent rank-change arrows).
2026-04-23 13:52:34 -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
f97ee4e4d8
Add NProgress navigation progress bar (#139)
Installs nprogress and wires it to React Router's useNavigation() hook
so a progress bar appears at the top of the page during route transitions.
Themed to the site's --electric CSS variable with the spinner disabled.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:02:48 -07:00
Chris Parsons
35a3b71579
feat: add Sentry error monitoring (#132)
* feat: add Sentry error monitoring (#77)

Installs and configures @sentry/react-router with server and client
instrumentation. Disabled in development to avoid noise; only active
in production.

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

* chore: add VSCode Sentry MCP server config

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

* fix: pass vite env to sentryReactRouter plugin

sentryReactRouter requires the ConfigEnv as a second argument to read
the vite `command` property.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 23:35:33 -07:00
Chris Parsons
435acdeef0
Add favicons and logo (#62) 2026-03-04 00:23:45 -08:00
Chris Parsons
8a444a51a1
Improve error boundary UI with status-specific error pages (#21)
* Improve error pages with styled, status-aware UI

Replace the bare-bones error boundary with a proper error page that:
- Renders a minimal Brackt header so users can navigate home
- Shows distinct messages and icons for 401, 403, 404, 500+ errors
- Displays a "Sign in from the home page" hint on 401 so logged-out
  users know how to recover
- Hides internal details in production (stack traces dev-only)
- Uses existing dark theme Tailwind classes for visual consistency

https://claude.ai/code/session_011jv8desa5vhSkjZHSjWZiV

* Address code review feedback on error pages

- Move statusIcon out of ErrorBoundary render as a plain module-level
  function to avoid React re-creating a component type on every render
- Collapse 401 action into a single descriptive button label
  ("Go to Home Page to Sign In") instead of a fragmented button + hint
- Fix details/summary border radius collision by using overflow-hidden
  on the container and removing rounded-md from the summary element;
  separate the pre block with a border-t instead

https://claude.ai/code/session_011jv8desa5vhSkjZHSjWZiV

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-21 23:47:42 -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
c9d3ee73cd feat: Update DraftSidebar to include recent picks section and refactor layout; enhance AvailableParticipantsSection and QueueSection styling 2025-10-25 18:25:26 -07:00
Chris Parsons
c162258c99 feat: add admin navigation link for authenticated admin users 2025-10-13 09:44:59 -07:00
Chris Parsons
226cdd0ea7 feat: implement middleware and context provider for Express integration 2025-10-11 20:56:00 -07:00
Chris Parsons
9dc033b2a0 feat: add commissioner model and league management features 2025-10-11 00:29:04 -07:00
Chris Parsons
6d998a5f85 feat: add theme system with dark mode support and navbar component 2025-10-10 23:30:26 -07:00
Chris Parsons
d9f96efd44 feat: add Clerk authentication with sign-in button and user profile UI 2025-10-10 23:18:16 -07:00
Chris Parsons
3117080592 Initial commit from create-react-router 2025-10-10 23:04:50 -07:00