feat: login page background (heroes) + Epic icon fix + 72px buttons#151
Conversation
…buttons Background: - 6 hero portraits split left/right (gideon, kallari, countess | aurora, the-fey, serath) - Blurred (5px), desaturated (0.5), 22% opacity, faded to center - 3 color orbs (violet, teal, blue) - Subtle 40px dot grid overlay - Card with stronger backdrop-blur (24px) to stand out from background Epic Games icon fixed: - Smaller viewBox (60×66) so text renders larger in pixel terms - White filled shield shape matching official logo - Impact font for EPIC (dark, bold), Arial for GAMES - Downward chevron at shield point Buttons: 64px → 72px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d8d4d1066
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <div style={{ width: '100%', maxWidth: 380, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2rem' }}> | ||
|
|
||
| {/* Brand */} | ||
| <div style={{ minHeight: '100vh', width: '100vw', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'fixed', inset: 0, zIndex: 50, overflow: 'hidden' }}> |
There was a problem hiding this comment.
Restore an opaque background behind the login overlay
When an already-authenticated user visits /login, App.tsx renders this page inside the authenticated workspace layout (with the sidebar and WorkspaceHeader still mounted), and this root fixed overlay no longer paints a solid background. Because the new orbs/grid/hero layers are partially transparent, the underlying workspace bleeds through the login screen, unlike the previous background: 'var(--bg-dark)'; add an opaque base background to this container or an underlay before the decorative layers.
Useful? React with 👍 / 👎.
…ot a relation
linkedPlayerId is a String? scalar on User, not a relation.
Including it in Prisma's include{} block caused an INTERNAL_ERROR on all
login and session refresh requests. Scalar fields are returned automatically
when using findUnique without a select — no include needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AppContent returned the full sidebar layout when internalLoading=true because the unauthenticated check required !internalLoading to be true. During the auth request (~100-300ms), users briefly saw the main app. Fix: early return null while internalLoading — the dark body background shows, then the correct view (landing or app) appears without flash. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mins only WorkspaceHeader: - Non-admin users: no left-side title/subtitle — clean header - All users: Patch badge + user chip with avatar/initials - Admin only: pred.gg connected/disconnected status chip - User chip: 22px avatar circle (image if avatarUrl set, else initials on violet bg) + user name — replaces the plain text + KeyRound icon SessionUser: add avatarUrl?: string | null internal-auth /me + /refresh: include avatarUrl in session response Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sidebar: remove sidebar-auth section entirely (name, email, role badge, logout) - WorkspaceHeader: user chip now links to /profile (clickable) - WorkspaceHeader: logout icon button (LogOut 13px) next to user chip · Subtle border, transparent bg · Hover: red color + red border tint · tooltip 'Cerrar sesión' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
profile.ts: fix req.user!.id → req.user!.userId (SessionUser uses userId, not id — caused 500 on all profile endpoint calls) Dashboard: - JUGADOR: 'Matches' link replaced by 'Mis partidas' → player profile page 'Partidas del equipo' wording removed — links to own player scouting - JUGADOR: hero display shows name instead of slug - Standalone PLAYER (no team): new PlayerStandaloneView component · Fetches profile via user.linkedPlayerId if set · Shows KDA, WR, partidas count, main hero stats · Last 8 matches W/L strip with K/D/A · Quick links to own player profile page · If no linkedPlayerId: informative message instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…imReport subtitle + planning notes
- App.tsx: menu item label 'Player Analysis' → 'Player Scouting' (matches page h1)
- ScrimReport.tsx: subtitle now role-aware:
PLAYER/no-team: 'Aggregated weekly performance summary.'
COACH/MANAGER/ANALISTA: 'Pre-match intelligence report for coaching staff.'
- docs/planning.md: documented future work for differentiated player reports
(Weekly Performance Summary B2C, Player Development individual,
Scrim Report inapplicable for standalone players)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…layers
Backend:
- POST /profile/link-player: search + self-link Player record by playerId
Prevents duplicate claims across user accounts (409 if already linked)
- DELETE /profile/link-player: unlink player
Frontend:
- LinkPlayerModal: search by game name → results list → 'Soy yo' button
Shows player name, region, console badge; spinner on link action
- Dashboard PlayerStandaloneView: 'Buscar mi perfil en Predecessor' CTA
button opens LinkPlayerModal instead of 'contact admin' dead-end message
- ProfilePage Connections tab: Predecessor/pred.gg section at top
Shows linked status; 'Vincular perfil' button or 'Desvincular' if linked
- MatchList: standalone PLAYER users are redirected immediately
· If linkedPlayerId: → /analysis/players?id={playerId} (own profile)
· If no linkedPlayerId: → /analysis/players (search to find themselves)
No more 'no hay partidas' empty state with team selectors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndalone players WeeklyReportsPage component replaces static ComingSoon: - PLAYER/no-team: section='Weekly Reports', description='Aggregated weekly performance summary.' - Staff (COACH/MANAGER/ANALISTA): section='Weekly Team Reports', description='...for the coaching staff.' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…chList CTA internal-auth /me: now does DB lookup for linkedPlayerId + avatarUrl. Previously /me only read from JWT (which never contains these fields), so after linking a player the UI never reflected the change. Dashboard: remove window.location.reload() after linking — state update via setLinkedIdState(pid) is sufficient now that /me returns fresh data. MatchList: standalone PLAYER with no linkedPlayerId now shows the same 'Vincular mi perfil' CTA as the Dashboard instead of a blank redirect. After linking, navigates directly to the player's scouting profile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs causing the dashboard to render briefly then unmount: 1. apiClient.patches.list() → patches.latest() patches.list() is undefined — calling it throws TypeError at runtime, React catches the error in the effect and unmounts the component tree leaving only the body background visible. 2. PlayerStandaloneView: useState calls after useEffect React hooks must be declared before any effects. Moved all useState declarations to the top of the function, before the useEffect. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…failed count MatchList: all useState/useEffect moved to top (before conditional returns) to comply with React Rules of Hooks — was causing crash/blank screen. Dashboard: only fetch OWN team data when user has actual team memberships. Previously fetched all OWN teams and used the first one, causing standalone PLAYER users to see [QA] Alpha (or any other OWN team) as their team. Now filters teams by user.memberships.teamId so only their own team shows. Admin sync-status: - Added matchesFailed = count(eventStreamFailed=true) - matchesPartial now excludes failed matches (they'll never be synced) - Response includes both 'partial' (truly pending) and 'failed' (permanent) DataQualityPage: - 'Partial (sin event stream)' → 'Pending (sin event stream)' for pending only - New row 'No disponible en pred.gg' for eventStreamFailed matches (187) - Sync button label shows pending + unavailable counts separately - 187 eventStreamFailed matches no longer shown as 'partidas pendientes' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rs button repairEventStreamPlayerIds: - Was loading 5.2M records into Node.js memory → OOM/crash - Rewritten with 4 raw SQL UPDATE...FROM statements — runs entirely in DB No more timeout, completes in seconds regardless of data volume - Test updated to mock $executeRaw instead of findMany/update Sync All Stale Players (opción B): - POST /admin/sync-stale-all: loops through ALL stale players in batches of 30 with 1.5s delay between batches to respect pred.gg rate limits Safety cap at 2000 batches (~60k players max per run) - Added apiClient.admin.syncStaleAll() - DataQualityPage: new 'Sync All Stale Players' button above single-batch button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onsole players Problem: 11,712 console players (isConsole=true) have no pred.gg account and can never be synced by display name. They were clogging the stale queue permanently, causing 90%+ skip rates per sync batch. Fixes: - syncStalePlayers: added isConsole: false filter — only queues PC players that can actually be looked up on pred.gg - Total stale count now only includes syncable players - DB: bulk-stamped 11,556 stale console players with lastSynced=NOW() to clear them from the queue immediately - Result: stale queue reduced from 28,802 → 12,800 (all PC syncable) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vice admin.ts imported syncIncompleteMatches but the function didn't exist, causing the API to fail on startup. Re-implemented using resyncMatch (batch of 200 matches with < 10 MatchPlayers, re-fetches roster from pred.gg). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Imported in admin.ts (used by the global sync cron) but was missing
from sync-service.ts after a previous refactor. Re-implemented using
fetchPlayerDetail + persistRecentMatches, returns { newMatches, newMatchUuids }.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ectly Previously /heroes/*.webp was proxied from Express API static files. When the API was down, hero images failed to load everywhere (landing page, player scouting, team analysis, etc.) even though the files exist in the repo. Moving to apps/web/public/heroes/ makes Vite serve them natively without any proxy or API dependency. Removed /heroes proxy from vite.config.ts. 51 hero portraits + 51 promo images (102 total webp files). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…public/ Dashboard crashes fixed: - apiClient.players.get() → apiClient.players.getProfile() (method didn't exist) Affected: JUGADOR useEffect (line 144) + PlayerStandaloneView (line 508) TypeError: not a function caused React to unmount entire component tree - review.list called without teamId → added ownTeam guard and correct teamId Assets moved to apps/web/public/ (Vite serves directly, no API dependency): - icons/ (1 file), items/ (540), maps/ (1), ranks/ (6) - vite.config.ts: removed /items, /icons, /ranks, /maps proxies (previously proxied to Express API static files, failed when API was down) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…COLORS) ReferenceError: TIER_COLORS is not defined on render of user list row. The constant was renamed to PLAYER_TIER_COLORS when the dual-tier system was implemented, but the JSX wasn't updated. React crashes entire tree on ReferenceError, unmounting sidebar+header leaving only body background. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…er linking After linking a player profile, user.linkedPlayerId in the auth state was not updated (auth cookie still had the old value). On navigation away and back, PlayerStandaloneView remounted with linkedId=null from stale auth. Fixes: - Dashboard PlayerStandaloneView: call refreshInternalSession() after link → /me does DB lookup for linkedPlayerId → updates auth state - Added useEffect to sync linkedIdState from auth when linkedId changes (handles case where auth state updates after refreshInternalSession) - ProfilePage: same refreshInternalSession() call after linking Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProfilePage: call refreshInternalSession() after saving profile and email so the auth state (used by WorkspaceHeader badge) updates with new name. internal-auth /me: extend DB lookup to include name + email (not just linkedPlayerId + avatarUrl). Name and email can be changed via PATCH /profile but the JWT still has the old values — /me now returns fresh DB values. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PlayerScouting uses location.state.autoLoadPlayerId to auto-load a profile,
not URL query params (?id=xxx was silently ignored).
Dashboard JUGADOR links now pass state={{ autoLoadPlayerId: playerId }}:
- 'Mi perfil en el juego' → /analysis/players with state → auto-loads profile
- 'Mis partidas' → same, opens player profile with full match history
- QuickLink component updated to accept optional state prop
MatchList redirects:
- Standalone PLAYER (linkedPlayerId) → /analysis/players with state
- JUGADOR in team (membership.playerId) → /analysis/players with state
Both now auto-open the player's profile instead of the search page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ugador/Player New useViewAs hook + ViewAsProvider context: - Stores selected preview role in sessionStorage (auto-clears on browser close) - ViewAsSelector dropdown in WorkspaceHeader (visible only to PLATFORM_ADMIN) - Options: Admin (real) / Manager / Coach / Analista / Jugador / Player (solo) - Color-coded by role, highlighted border when active Sidebar section filtering: - Respects viewAs — hides/shows sections as the selected role would see - Platform Admin section hidden when previewing as another role Dashboard: - isPlatformAdmin = false when viewAs is set (shows team/player dashboard) - teamRole uses viewAs when set (shows MANAGER/COACH/ANALISTA/JUGADOR views) - viewAs='PLAYER' → shows standalone player view even when admin has teams All purely frontend — does NOT modify DB, session or permissions. Admin remains PLATFORM_ADMIN in all backend checks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MatchList crash fix:
- Replaced render-phase <Navigate> with useEffect + navigate()
Rendering <Navigate> in the render phase (before hooks) can cause
React to crash in some navigation sequences, leaving only the body background
Effect-based navigation is safe — fires after render is committed
'Mis Partidas' / links in PlayerStandaloneView fix:
- Changed Link to=`/analysis/players?id=${linkedId}` (query param, ignored)
→ Link to='/analysis/players' state={{ autoLoadPlayerId: linkedId }}
PlayerScouting uses location.state.autoLoadPlayerId to auto-load,
NOT ?id= query params. Links in standalone player view now correctly
open the player's full profile with all stats and match history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h error Instead of crashing the entire UI tree, the error boundary catches the error and displays the error message inside the profile panel area, allowing the rest of the UI to remain functional. This will reveal the exact cause. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… violation)
PlayerGoalsSection had two React.useEffect declarations AFTER a conditional
early return ('if (!loading && !canManageGoals) return ...').
React Rules of Hooks: all hooks must be called unconditionally in the same
order on every render. The early return caused 'Rendered fewer hooks than
expected' on the second render (when loading=false, canManageGoals=false),
crashing the entire PlayerScouting component tree.
Fix: moved both useEffect declarations before the early return.
The guard now fires AFTER all hooks are declared.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ScoutingReport used narrowDepth (from config) at line 1783 but the variable was only defined in DraftHeroPool (line 960). Added useConfig() + narrowDepth to ScoutingReport so it reads the correct configurable value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fondo con héroes difuminados, orbs de color, grid, tarjeta con glassmorphism. Epic Games icon corregido. Botones 72px.