Files
hermes-skills/skills/devops/python-web-service-deployment/references/fantasy-game-frontend-pattern.md
T

4.2 KiB

PWA Frontend Integration for Fantasy Draft Games

Auth flow (index.html)

  1. Dual-mode form: Registration + Login toggled by isLoginMode boolean
  2. On success: Store token in localStorage.getItem('shark_token'), user in localStorage.setItem('shark_user')
  3. Post-auth: GET /api/leagues — branch by count:
    • 0 leagues → show create/join UI
    • 1 league → auto-redirect draft-room.html?league_id=X
    • Multiple → show league picker
  4. Auth guard on every protected page: if (!getToken()) window.location.href = 'index.html'
  5. Register response: {user_id, token, display_name} — backend must pass display_name from request

League management (index.html)

  • Create: POST /api/leagues {name} → returns {id, invite_code}
  • Join: POST /api/leagues/join {invite_code} → returns {league_id, league_name}
  • Redirect after create/join: window.location.href = 'draft-room.html?league_id=' + result.id

Draft room (draft-room.html)

League picker overlay

  • GET /api/leagues on load
  • If no league_id URL param, show overlay with league cards
  • On select: history.replaceState(null, '', '?league_id=' + id) + loadAll()

Data loading

async function loadAll() {
  const [league, board, scores, regions] = await Promise.all([
    apiCall('/api/leagues/' + leagueId),
    apiCall('/api/leagues/' + leagueId + '/draft'),
    apiCall('/api/leagues/' + leagueId + '/scores'),
    apiCall('/api/regions'),
  ]);
  renderAll();
}

Draft board tabs

  • Draft tab: Region cards grid + status bar (round/turn info)
  • Team tab: User's drafted regions + cumulative points
  • Leaderboard tab: Ranked user list with scores
  • League info tab: Invite code (copyable), member list, start draft (commissioner only)

Card states

  • Available: teal border, "DRAFT" button
  • Your pick: amber glow border + " YOUR PICK"
  • Taken: grayed out, "Drafted by [player]"
  • WAITING (draft not started): dimmed, "WAITING" label

Pitfalls

  • league_id from URL vs API: After creating a league, the API returns result.id, not result.league_id. Use result.id in redirect URL.
  • .hidden CSS class: Every standalone page MUST define .hidden { display: none !important; } in its own <style> block. Sibling pages don't inherit it.
  • Overlay staying visible: Missing .hidden class causes the league picker overlay to remain on screen after selecting a league — the JS adds the class but the browser has no matching CSS rule.
  • API calls that work from curl may fail from the browser if CORS headers are missing on the backend. FastAPI's CORSMiddleware with allow_origins=["*"] fixes this during development.
  • Token expiry: JWT tokens set for 30 days. The frontend doesn't handle token refresh — if the token expires, the user gets "Invalid token" error and must re-login.

HTML table templates (score charts, stats)

When rendering leaderboard/team data as HTML inside JS template literals, use inline <table> elements matching the game's dark theme:

function renderLeaderboard(data) {
  return `<table style="width:100%;border-collapse:collapse;font-size:13px;">
    <tr style="background:rgba(15,23,42,0.8);">
      <th style="padding:8px;text-align:left;font-weight:600;">Player</th>
      <th style="padding:8px;text-align:center;font-weight:600;">Points</th>
    </tr>
    ${data.map(p => `<tr>
      <td style="padding:8px 10px;border-bottom:1px solid rgba(255,255,255,0.05);">${p.display_name}</td>
      <td style="padding:8px 10px;text-align:center;border-bottom:1px solid rgba(255,255,255,0.05);color:var(--amber);">${p.total_points}</td>
    </tr>`).join('')}
  </table>`;
}

Common pitfalls

  • Registration displays name as null — backend must include display_name=req.display_name in AuthResponse (fix at /api/auth/register endpoint)
  • Mobile Safari caching — after updating frontend files, users may see cached versions. Hard refresh or clear Safari cache is needed.
  • League creation redirect — API returns id, not league_id. Use result.id.
  • API call errors — Use try/catch + user-facing error messages. The apiCall() helper throws on non-ok status codes.