Echo / src /ui /LeaderboardUI.js
mlmihjaz's picture
Clear leaderboard: remove fake peer seed, use backend leaderboard API
94ef118
Raw
History Blame Contribute Delete
2.96 kB
/**
* LeaderboardUI — renders the live leaderboard and user chip.
*/
import { Auth } from '../auth/Auth.js';
import { Leaderboard } from '../auth/Leaderboard.js';
export class LeaderboardUI {
constructor() {
this._interval = null;
}
$(id) { return document.getElementById(id); }
startAutoRefresh() {
if (this._interval) clearInterval(this._interval);
this._interval = setInterval(() => { Leaderboard.bumpPeers(); this.render(); }, 12000);
}
refreshUserChip() {
const u = Auth.current();
if (u) {
this.$('userName').textContent = u.name || u.email.split('@')[0];
this.$('userAvatar').textContent = (u.name || u.email).slice(0, 1).toUpperCase();
this.$('signOutBtn').style.display = '';
// Sync persisted star count to header
this.$('t-stars').textContent = (u.stars || 0).toLocaleString();
} else {
this.$('userName').textContent = 'Guest';
this.$('userAvatar').textContent = 'G';
this.$('signOutBtn').style.display = 'none';
this.$('t-stars').textContent = '0';
}
}
render() {
const u = Auth.current();
const rows = Leaderboard.combinedRanks(u);
const top = rows.slice(0, 8);
const meRow = rows.find(r => r.me);
if (meRow && !top.find(r => r.me)) top.push(meRow);
const html = top.map(r => {
const rankCls = r.rank <= 3 ? ` r${r.rank}` : '';
const meCls = r.me ? ' me' : '';
const rankIcon = r.rank <= 3 ? ['🥇', '🥈', '🥉'][r.rank - 1] : r.rank;
return `<div class="lb-row${rankCls}${meCls}">
<div class="rank">${rankIcon}</div>
<div class="who"><div class="a">${r.avatar || r.name.slice(0, 1).toUpperCase()}</div><div class="n">${r.me ? '<b>You</b>' : r.name}</div></div>
<div class="pts">⭐ ${r.stars.toLocaleString()}</div>
</div>`;
}).join('');
this.$('lbList').innerHTML = html;
this.$('lb-total').textContent = rows.length.toLocaleString();
this.$('lb-globalStars').textContent = rows.reduce((s, r) => s + r.stars, 0).toLocaleString();
this.$('onlineCount').textContent = Leaderboard.onlineCount();
if (u && meRow) this.$('userRank').textContent = `Rank #${meRow.rank} · ⭐ ${u.stars || 0}`;
else this.$('userRank').textContent = u ? `⭐ ${u.stars || 0}` : 'Guest mode';
}
bumpUserStats(deltaStars, deltaMastered, currentLen) {
const u = Auth.current();
if (!u) return;
const patch = {
stars: (u.stars || 0) + deltaStars,
mastered: (u.mastered || 0) + deltaMastered,
grade: currentLen,
};
Auth.update(patch);
// Also push to the backend (no-op when offline).
Auth.saveProgress(patch).catch(() => {});
this.refreshUserChip();
this.render();
const fresh = Auth.current();
const el = document.getElementById('t-stars');
el.textContent = (fresh.stars || 0).toLocaleString();
el.classList.remove('bump'); void el.offsetWidth; el.classList.add('bump');
}
}