This commit is contained in:
nate@necloud.us committed 2025-07-03 10:25:32 -04:00
1 parent c4844eecff
commit 6c77a8f407
5 files changed
+147 -32

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
{
"nate": [
"Perfect Round",
"No Misses",
"Accuracy Master",
"Practice Streak",
"Vocabulary Pro"
]
}
+14 -2
View File
@@ -51,6 +51,7 @@
<button onclick="switchTab('tabWords')">📝 Word Lists</button>
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
<button onclick="switchTab('tabResults')">📊 Results</button>
<button onclick="switchTab('tabBadges')">🎖 Badges</button>
<button onclick="switchTab('tabTheme')">🎨 Theme</button>
</div>
@@ -119,7 +120,15 @@
<button onclick="refreshAnalytics()">🔄 Refresh</button>
</div>
<!-- 🎖 Badges -->
<div id="tabBadges" class="adminTab hidden">
<h3>🎖 Badge Viewer</h3>
<ul id="badgeUserList"></ul>
<div id="badgeDetails" class="hidden">
<h4 id="badgeUserTitle"></h4>
<ul id="badgeUserBadges"></ul>
</div>
</div>
<!-- 🎨 Theme Tab -->
<div id="tabTheme" class="adminTab hidden">
<h3>🎨 Select Theme</h3>
@@ -146,7 +155,10 @@
<input type="text" id="userInput" placeholder="Type your answer" autocomplete="off" />
<button onclick="submitAnswer()">Submit</button>
</div>
<div id="badgeDisplay" class="badgeSection hidden">
<h3>🎖 Badges Earned</h3>
<ul id="badgeList"></ul>
</div>
<div id="summary" class="hidden"></div>
</div>
</body>
+45
View File
@@ -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 = '<li style="color:red">Error loading analytics.</li>';
}
}
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 = `<button onclick="showUserBadges('${username}')">${username}</button>`;
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 = "<li><em>No badges earned yet.</em></li>";
} else {
badges.forEach(b => {
const li = document.createElement("li");
li.textContent = `🏅 ${b}`;
badgeList.appendChild(li);
});
}
details.classList.remove("hidden");
});
}
+50 -29
View File
@@ -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
}
+29 -1
View File
@@ -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);
});