Files
2026-10-23 07:01:49 +11:00

182 lines
6.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Leaderboard page: public leaderboard with tabs for Level, Gold, Kills.
// Uses createElement + textContent only — DOM property is never overwritten.
const VALUE_LABELS = { level: 'Level', gold: 'Gold', kills: 'Kills' };
let currentType = 'level';
let currentPage = 1;
// ─── API helper (no auth needed) ──────────────────────────────────────────
async function apiGet(path) {
const res = await fetch(path);
return res.json();
}
// ─── Render: leaderboard entries ──────────────────────────────────────────
function renderLeaderboard(entries, pagination) {
const tbody = document.getElementById('leaderboard-body');
tbody.replaceChildren();
if (entries.length === 0) {
const tr = document.createElement('tr');
const td = document.createElement('td');
td.className = 'placeholder';
td.textContent = 'No heroes yet.';
td.setAttribute('colspan', '4');
tr.appendChild(td);
tbody.appendChild(tr);
return;
}
for (const entry of entries) {
const tr = document.createElement('tr');
// Rank
const rankTd = document.createElement('td');
rankTd.className = 'rank-cell';
rankTd.textContent = entry.rank;
tr.appendChild(rankTd);
// Name (link to profile)
const nameTd = document.createElement('td');
nameTd.className = 'name-cell';
const nameLink = document.createElement('a');
nameLink.href = `/profile.html?name=${encodeURIComponent(entry.name)}`;
nameLink.textContent = entry.name;
nameTd.appendChild(nameLink);
tr.appendChild(nameTd);
// Class
const classTd = document.createElement('td');
classTd.className = 'class-cell';
classTd.textContent = entry.class;
tr.appendChild(classTd);
// Value (level / gold / kills)
const valueTd = document.createElement('td');
valueTd.className = 'value-cell';
valueTd.textContent = entry[currentType];
tr.appendChild(valueTd);
tbody.appendChild(tr);
}
}
// ─── Render: pagination ──────────────────────────────────────────────────
function renderPagination(page, totalPages) {
const container = document.getElementById('pagination');
container.replaceChildren();
if (totalPages <= 1) return;
// Previous button
const prevBtn = document.createElement('button');
prevBtn.textContent = '‹';
prevBtn.disabled = page <= 1;
prevBtn.addEventListener('click', () => {
currentPage = page - 1;
loadLeaderboard();
});
container.appendChild(prevBtn);
// Page number buttons (show up to 5 pages)
let startPage = Math.max(1, page - 2);
let endPage = Math.min(totalPages, startPage + 4);
if (endPage - startPage < 4) startPage = Math.max(1, endPage - 4);
for (let p = startPage; p <= endPage; p++) {
const btn = document.createElement('button');
btn.textContent = p;
if (p === page) btn.className = 'active';
btn.addEventListener('click', () => {
currentPage = p;
loadLeaderboard();
});
container.appendChild(btn);
}
// Next button
const nextBtn = document.createElement('button');
nextBtn.textContent = '›';
nextBtn.disabled = page >= totalPages;
nextBtn.addEventListener('click', () => {
currentPage = page + 1;
loadLeaderboard();
});
container.appendChild(nextBtn);
}
// ─── Load leaderboard data ───────────────────────────────────────────────
async function loadLeaderboard() {
const tbody = document.getElementById('leaderboard-body');
const tr = document.createElement('tr');
const td = document.createElement('td');
td.className = 'placeholder';
td.textContent = 'Loading...';
td.setAttribute('colspan', '4');
tr.appendChild(td);
tbody.replaceChildren(tr);
try {
const data = await apiGet(`/api/leaderboard?type=${currentType}&page=${currentPage}`);
if (!data || !data.ok) {
throw new Error(data?.error?.message || 'Failed to load leaderboard.');
}
renderLeaderboard(data.data.entries, data.data.pagination);
renderPagination(data.data.pagination.page, data.data.pagination.totalPages);
} catch (err) {
const errorTr = document.createElement('tr');
const errorTd = document.createElement('td');
errorTd.className = 'placeholder';
errorTd.textContent = 'Failed to load leaderboard.';
errorTd.setAttribute('colspan', '4');
errorTr.appendChild(errorTd);
tbody.replaceChildren(errorTr);
}
}
// ─── Tab switching ───────────────────────────────────────────────────────
function switchTab(type) {
currentType = type;
currentPage = 1;
document.querySelectorAll('#leaderboard-tabs .tab').forEach((t) => {
const isActive = t.dataset.type === type;
t.classList.toggle('active', isActive);
t.setAttribute('aria-selected', isActive);
});
// Update header
document.getElementById('value-header').textContent = VALUE_LABELS[type];
loadLeaderboard();
}
// ─── Init ─────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('#leaderboard-tabs .tab').forEach((tab) => {
tab.addEventListener('click', () => switchTab(tab.dataset.type));
});
loadLeaderboard();
});
// Logout handler
document.getElementById('logout-btn')?.addEventListener('click', async (e) => {
e.preventDefault();
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
});
} catch (_) {
// Ignore
}
window.location.href = '/login.html';
});