diff --git a/data/badges.json b/data/badges.json new file mode 100644 index 0000000..7ede664 --- /dev/null +++ b/data/badges.json @@ -0,0 +1,9 @@ +{ + "nate": [ + "Perfect Round", + "No Misses", + "Accuracy Master", + "Practice Streak", + "Vocabulary Pro" + ] +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 150523e..686ee78 100644 --- a/public/index.html +++ b/public/index.html @@ -51,6 +51,7 @@ + @@ -119,7 +120,15 @@ - + + - + diff --git a/public/js/admin.js b/public/js/admin.js index 73818f2..6429b0b 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -193,12 +193,18 @@ function switchTab(targetId) { document.querySelectorAll('#adminTabs button').forEach(btn => btn.classList.remove('active')); document.getElementById(targetId).classList.remove('hidden'); + const buttons = document.querySelectorAll('#adminTabs button'); buttons.forEach(btn => { if (btn.getAttribute('onclick')?.includes(targetId)) { btn.classList.add('active'); } }); + + // 🏆 Load badge viewer if needed + if (targetId === 'tabBadges') { + loadBadgeViewer(); + } } async function loadUserDropdowns() { try { @@ -277,4 +283,43 @@ async function refreshAnalytics() { console.error("📉 Failed to refresh analytics:", err); summary.innerHTML = '
  • Error loading analytics.
  • '; } +} +async function loadBadgeViewer() { + const res = await fetch('/getBadges'); + const badgeData = await res.json(); + + const userList = document.getElementById("badgeUserList"); + userList.innerHTML = ""; + + for (const username in badgeData) { + const li = document.createElement("li"); + li.innerHTML = ``; + userList.appendChild(li); + } +} + +function showUserBadges(username) { + fetch('/getBadges') + .then(res => res.json()) + .then(data => { + const badges = data[username] || []; + const title = document.getElementById("badgeUserTitle"); + const badgeList = document.getElementById("badgeUserBadges"); + const details = document.getElementById("badgeDetails"); + + title.textContent = `Badges for ${username}`; + badgeList.innerHTML = ""; + + if (!badges.length) { + badgeList.innerHTML = "
  • No badges earned yet.
  • "; + } else { + badges.forEach(b => { + const li = document.createElement("li"); + li.textContent = `🏅 ${b}`; + badgeList.appendChild(li); + }); + } + + details.classList.remove("hidden"); + }); } \ No newline at end of file diff --git a/public/js/game.js b/public/js/game.js index 5f86faa..a809172 100644 --- a/public/js/game.js +++ b/public/js/game.js @@ -10,7 +10,6 @@ function showStudent() { studentPanel?.classList.remove('hidden'); adminPanel?.classList.add('hidden'); - // Delay button reveal until DOM is fully settled setTimeout(() => { const btn = document.getElementById('startGameBtn'); if (btn) { @@ -41,6 +40,7 @@ async function startGame() { results = []; document.getElementById('summary')?.classList.add('hidden'); + document.getElementById('badgeDisplay')?.classList.add('hidden'); showWord(); } @@ -101,32 +101,57 @@ function showSummary() { summary.classList.remove('hidden'); - // 📨 Save results to server -const user = JSON.parse(localStorage.getItem('loggedInUser')); -if (user) { - console.log("📡 Sending results to server:", { - username: user.username, - results - }); + // 🏆 Badge evaluation + const earnedBadges = []; + if (correct === words.length) earnedBadges.push("Perfect Round"); + if (missed.length === 0) earnedBadges.push("No Misses"); -const score = correct; -const completed = true; + const user = JSON.parse(localStorage.getItem('loggedInUser')); + if (user) { + // 📡 Save structured results + fetch('/saveResults', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: user.username, + result: { + score: correct, + completed: true, + answers: results.map(({ word, attempt }) => ({ + word, + correct: word.toLowerCase() === attempt.toLowerCase() + })) + } + }) + }); -fetch('/saveResults', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - username: user.username, - result: { - score, - completed, - answers: results.map(({ word, attempt }) => ({ - word, - correct: word.toLowerCase() === attempt.toLowerCase() - })) + // 🎖 Send earned badges + if (earnedBadges.length) { + fetch('/awardBadges', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: user.username, + badges: earnedBadges + }) + }); + + // 🎨 Display badges + const badgeSection = document.getElementById("badgeDisplay"); + const badgeList = document.getElementById("badgeList"); + + badgeList.innerHTML = ""; + earnedBadges.forEach(b => { + const li = document.createElement("li"); + li.textContent = `🏅 ${b}`; + badgeList.appendChild(li); + }); + + badgeSection.classList.remove("hidden"); + } else { + document.getElementById("badgeDisplay")?.classList.add("hidden"); } - }) -}); + } } // 🔁 Retry only the missed words @@ -136,10 +161,6 @@ function retryMissed(missedWords) { results = []; document.getElementById('summary')?.classList.add('hidden'); + document.getElementById('badgeDisplay')?.classList.add('hidden'); showWord(); -} - -// added remove later -console.log("showStudent fired"); -// added remove later } \ No newline at end of file diff --git a/server.js b/server.js index 987aac1..4b93c3f 100644 --- a/server.js +++ b/server.js @@ -15,7 +15,8 @@ if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR); const files = { users: 'users.json', wordlists: 'wordlists.json', - results: 'results.json' + results: 'results.json', + badges: 'badges.json' }; for (const file of Object.values(files)) { @@ -163,4 +164,31 @@ app.post('/saveResults', (req, res) => { app.get('/getUsers', (req, res) => { const users = readJsonSafe(path.join(DATA_DIR, files.users), []); res.json(users); +}); + + +const badgePath = path.join(DATA_DIR, files.badges); +app.post('/awardBadges', (req, res) => { + const { username, badges } = req.body; + if ( + typeof username !== 'string' || + !Array.isArray(badges) + ) return res.status(400).send("Invalid badge format"); + + const allBadges = readJsonSafe(badgePath); + if (!allBadges[username]) allBadges[username] = []; + + badges.forEach(b => { + if (!allBadges[username].includes(b)) { + allBadges[username].push(b); + } + }); + + fs.writeFileSync(badgePath, JSON.stringify(allBadges, null, 2)); + res.send(`🎉 Badges updated for ${username}`); +}); +app.get('/getBadges', (req, res) => { + const badgePath = path.join(DATA_DIR, files.badges); + const badges = readJsonSafe(badgePath); + res.json(badges); }); \ No newline at end of file