Skip to content

feat: login page background (heroes) + Epic icon fix + 72px buttons#151

Merged
saggacce merged 31 commits into
mainfrom
fix/login-background-epic-icon
May 14, 2026
Merged

feat: login page background (heroes) + Epic icon fix + 72px buttons#151
saggacce merged 31 commits into
mainfrom
fix/login-background-epic-icon

Conversation

@saggacce
Copy link
Copy Markdown
Owner

Fondo con héroes difuminados, orbs de color, grid, tarjeta con glassmorphism. Epic Games icon corregido. Botones 72px.

saggacce and others added 2 commits May 14, 2026 00:33
…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>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/web/src/pages/Login.tsx Outdated
<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' }}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

saggacce and others added 27 commits May 14, 2026 00:37
…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>
saggacce and others added 2 commits May 14, 2026 07:20
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>
@saggacce saggacce merged commit 76bad10 into main May 14, 2026
1 check passed
@saggacce saggacce deleted the fix/login-background-epic-icon branch May 14, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant