module change
This commit is contained in:
1 parent
ed1cafe761
commit
77d5f9b196
15 files changed
+886
-698
No files matched your search
@@ -0,0 +1,228 @@
|
||||
// ➕ Add a new user
|
||||
async function addNewUser() {
|
||||
const username = document.getElementById("newUsername").value.trim();
|
||||
const password = document.getElementById("newPassword").value;
|
||||
const role = document.getElementById("newRole").value;
|
||||
const msg = document.getElementById("userAddMessage");
|
||||
const hashPreview = document.getElementById("hashPreview");
|
||||
|
||||
if (!username || !password) {
|
||||
msg.textContent = "Please fill out all fields.";
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = hashPassword(password);
|
||||
if (hashPreview) hashPreview.textContent = hash;
|
||||
|
||||
try {
|
||||
await fetch('/addUser', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, hash, role })
|
||||
});
|
||||
msg.textContent = `✅ User "${username}" added to server.`;
|
||||
|
||||
document.getElementById("newUsername").value = '';
|
||||
document.getElementById("newPassword").value = '';
|
||||
if (hashPreview) hashPreview.textContent = '[auto]';
|
||||
|
||||
await loadUsers();
|
||||
displayUserDropdown();
|
||||
} catch (e) {
|
||||
msg.textContent = "❌ Failed to add user.";
|
||||
console.error("Add user error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 🗑 Populate and delete users
|
||||
function displayUserDropdown() {
|
||||
const dropdown = document.getElementById("userDropdown");
|
||||
dropdown.innerHTML = `<option value="">-- Select a user --</option>`;
|
||||
users.forEach(user => {
|
||||
dropdown.innerHTML += `<option value="${user.username}">${user.username} (${user.role})</option>`;
|
||||
});
|
||||
}
|
||||
|
||||
function selectUserAction(username) {
|
||||
if (!username) return;
|
||||
|
||||
if (!confirm(`Are you sure you want to delete user "${username}"?`)) {
|
||||
document.getElementById("userDropdown").value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/deleteUser", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username })
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(msg => {
|
||||
alert(msg);
|
||||
loadUsers().then(displayUserDropdown);
|
||||
});
|
||||
}
|
||||
|
||||
// 📚 Save or clear word list for current user
|
||||
async function saveWords() {
|
||||
const selectedUser = document.getElementById('wordUserSelect')?.value;
|
||||
if (!selectedUser) {
|
||||
alert("⚠️ Please select a user before saving.");
|
||||
return;
|
||||
}
|
||||
|
||||
const text = document.getElementById('wordInput').value.trim();
|
||||
allWords = text.split('\n').map(w => w.trim()).filter(Boolean);
|
||||
|
||||
await fetch('/saveWordList', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: selectedUser, words: allWords })
|
||||
});
|
||||
|
||||
displayWordList();
|
||||
alert(`✅ Word list saved to server for ${selectedUser}`);
|
||||
}
|
||||
|
||||
async function clearWords() {
|
||||
const selectedUser = document.getElementById('wordUserSelect')?.value;
|
||||
if (!selectedUser) return;
|
||||
|
||||
allWords = [];
|
||||
await fetch('/saveWordList', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: selectedUser, words: [] })
|
||||
});
|
||||
|
||||
displayWordList();
|
||||
alert(`🗑 Word list cleared for ${selectedUser}`);
|
||||
}
|
||||
|
||||
// 🗂 Load and display word lists
|
||||
async function loadWordsForSelectedUser() {
|
||||
const selectedUser = document.getElementById("wordUserSelect").value;
|
||||
if (!selectedUser) return;
|
||||
|
||||
const res = await fetch(`/getWordList?user=${encodeURIComponent(selectedUser)}`);
|
||||
allWords = await res.json();
|
||||
displayWordList();
|
||||
}
|
||||
|
||||
function displayWordList() {
|
||||
const ul = document.getElementById('wordListDisplay');
|
||||
ul.innerHTML = allWords.map(word => `<li>${word}</li>`).join('');
|
||||
}
|
||||
|
||||
function populateWordUserDropdown() {
|
||||
const selects = document.querySelectorAll(".word-user-dropdown");
|
||||
selects.forEach(select => {
|
||||
select.innerHTML = '<option value="">-- Select a user --</option>';
|
||||
users.forEach(user => {
|
||||
const option = document.createElement("option");
|
||||
option.value = user.username;
|
||||
option.textContent = user.username;
|
||||
select.appendChild(option);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function displayWordListForSelectedUser() {
|
||||
const select = document.getElementById("viewUserSelect");
|
||||
const username = select.value;
|
||||
const list = document.getElementById("userWordListDisplay");
|
||||
list.innerHTML = '';
|
||||
|
||||
if (!username) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/getWordList?user=${username}`);
|
||||
const words = await res.json();
|
||||
if (!words.length) {
|
||||
list.innerHTML = "<li><em>No words found for this user.</em></li>";
|
||||
return;
|
||||
}
|
||||
words.forEach(word => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = word;
|
||||
list.appendChild(li);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to load word list for user:", username, err);
|
||||
list.innerHTML = `<li style="color:red">❌ Error loading words</li>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 📊 Student result viewer
|
||||
async function loadStudentResults() {
|
||||
const list = document.getElementById("resultsList");
|
||||
list.innerHTML = '';
|
||||
|
||||
try {
|
||||
const res = await fetch('/getResults');
|
||||
const results = await res.json();
|
||||
|
||||
if (!Object.keys(results).length) {
|
||||
list.innerHTML = '<li><em>No results available yet.</em></li>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [username, result] of Object.entries(results)) {
|
||||
const li = document.createElement("li");
|
||||
let answersHTML = '';
|
||||
result.answers.forEach(entry => {
|
||||
const status = entry.correct ? '✅' : '❌';
|
||||
answersHTML += `${status} ${entry.word}<br>`;
|
||||
});
|
||||
|
||||
li.innerHTML = `
|
||||
<strong>${username}</strong> — Score: ${result.score}, Completed: ${result.completed ? '✅' : '❌'}<br>
|
||||
<em>Answers:</em><br>${answersHTML}
|
||||
`;
|
||||
list.appendChild(li);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("🔴 Failed to load results:", err);
|
||||
list.innerHTML = '<li style="color:red">Error loading results.</li>';
|
||||
}
|
||||
}
|
||||
|
||||
// 🧭 Admin tab switcher
|
||||
function switchTab(targetId) {
|
||||
document.querySelectorAll('.adminTab').forEach(tab => tab.classList.add('hidden'));
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
async function loadUserDropdowns() {
|
||||
try {
|
||||
const res = await fetch('/getUsers');
|
||||
const users = await res.json();
|
||||
|
||||
const dropdowns = [
|
||||
document.getElementById('userDropdown'),
|
||||
document.getElementById('wordUserSelect'),
|
||||
document.getElementById('viewUserSelect')
|
||||
];
|
||||
|
||||
dropdowns.forEach(drop => {
|
||||
if (!drop) return;
|
||||
|
||||
drop.innerHTML = '<option value="">-- Select a user --</option>';
|
||||
users.forEach(u => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = u.username;
|
||||
opt.textContent = u.username;
|
||||
drop.appendChild(opt);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("⚠️ Failed to load user list:", err);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 🔐 Password hashing using jsSHA
|
||||
function hashPassword(password) {
|
||||
const shaObj = new jsSHA("SHA-256", "TEXT");
|
||||
shaObj.update(password);
|
||||
return shaObj.getHash("HEX");
|
||||
}
|
||||
|
||||
// 🌐 Load users from server
|
||||
let users = [];
|
||||
|
||||
// 🔓 Verify user login
|
||||
async function verifyLogin() {
|
||||
const uname = document.getElementById('username').value.trim();
|
||||
const pw = document.getElementById('password').value;
|
||||
const hash = hashPassword(pw);
|
||||
const msg = document.getElementById('loginMessage');
|
||||
|
||||
try {
|
||||
const res = await fetch('/verifyUser', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: uname, hash })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
msg.textContent = "Invalid username or password.";
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await res.json();
|
||||
localStorage.setItem('loggedInUser', JSON.stringify(user));
|
||||
msg.textContent = '';
|
||||
setupSession(user);
|
||||
} catch (err) {
|
||||
console.error("Login error:", err);
|
||||
msg.textContent = "Login failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
// 🚪 Log out user
|
||||
function logoutUser() {
|
||||
localStorage.removeItem('loggedInUser');
|
||||
|
||||
document.getElementById('adminPanel').classList.add('hidden');
|
||||
document.getElementById('studentPanel').classList.add('hidden');
|
||||
document.getElementById('nav').classList.add('hidden');
|
||||
document.getElementById('loginPanel').classList.remove('hidden');
|
||||
document.getElementById('username').value = '';
|
||||
document.getElementById('password').value = '';
|
||||
|
||||
const roleBanner = document.getElementById('roleBanner');
|
||||
roleBanner.classList.add('hidden');
|
||||
roleBanner.classList.remove('admin', 'student');
|
||||
roleBanner.textContent = '';
|
||||
|
||||
const loginMessage = document.getElementById('loginMessage');
|
||||
if (loginMessage) loginMessage.textContent = '';
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// 🌍 Game state
|
||||
let allWords = [], words = [], current = 0, results = [];
|
||||
|
||||
// 👩🎓 Show student interface
|
||||
function showStudent() {
|
||||
console.log("🔊 showStudent() executed");
|
||||
|
||||
const studentPanel = document.getElementById('studentPanel');
|
||||
const adminPanel = document.getElementById('adminPanel');
|
||||
studentPanel?.classList.remove('hidden');
|
||||
adminPanel?.classList.add('hidden');
|
||||
|
||||
// Delay button reveal until DOM is fully settled
|
||||
setTimeout(() => {
|
||||
const btn = document.getElementById('startGameBtn');
|
||||
if (btn) {
|
||||
console.log("🎯 Button found in delayed reveal");
|
||||
btn.classList.remove('hidden');
|
||||
btn.style.display = 'inline-block';
|
||||
} else {
|
||||
console.warn("⚠️ Button still missing after timeout");
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 🚀 Begin game session
|
||||
async function startGame() {
|
||||
const user = JSON.parse(localStorage.getItem('loggedInUser'));
|
||||
if (!user) return;
|
||||
|
||||
const res = await fetch(`/getWordList?user=${encodeURIComponent(user.username)}`);
|
||||
allWords = await res.json();
|
||||
|
||||
if (!allWords.length) {
|
||||
alert("No words found. Please add words in Admin Panel.");
|
||||
return;
|
||||
}
|
||||
|
||||
words = [...allWords];
|
||||
current = 0;
|
||||
results = [];
|
||||
|
||||
document.getElementById('summary')?.classList.add('hidden');
|
||||
showWord();
|
||||
}
|
||||
|
||||
// 🔤 Show a word, then hide after timeout
|
||||
function showWord() {
|
||||
const wordBox = document.getElementById('wordBox');
|
||||
const wordEl = document.getElementById('word');
|
||||
|
||||
if (!words[current]) {
|
||||
endGame(); return;
|
||||
}
|
||||
|
||||
wordEl.textContent = words[current];
|
||||
wordBox.classList.remove('hidden');
|
||||
|
||||
setTimeout(() => {
|
||||
wordBox.classList.add('hidden');
|
||||
document.getElementById('inputSection').classList.remove('hidden');
|
||||
document.getElementById('userInput').value = '';
|
||||
document.getElementById('userInput').focus();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 📝 Check user’s answer and move to next
|
||||
function submitAnswer() {
|
||||
const input = document.getElementById('userInput').value.trim();
|
||||
results.push({ word: words[current], attempt: input });
|
||||
current++;
|
||||
|
||||
document.getElementById('inputSection').classList.add('hidden');
|
||||
|
||||
if (current < words.length) {
|
||||
showWord();
|
||||
} else {
|
||||
showSummary();
|
||||
}
|
||||
}
|
||||
|
||||
// 📊 Show results summary
|
||||
function showSummary() {
|
||||
const summary = document.getElementById('summary');
|
||||
let correct = 0;
|
||||
let missed = [];
|
||||
|
||||
const report = results.map(({ word, attempt }) => {
|
||||
const isCorrect = word.toLowerCase() === attempt.toLowerCase();
|
||||
if (isCorrect) correct++;
|
||||
else missed.push(word);
|
||||
return `<li>${word} — <strong style="color:${isCorrect ? 'green' : 'red'}">${attempt || '(no answer)'}</strong></li>`;
|
||||
}).join('');
|
||||
|
||||
summary.innerHTML = `
|
||||
<h2>Results</h2>
|
||||
<p>You got ${correct} out of ${words.length} correct (${Math.round((correct / words.length) * 100)}%)</p>
|
||||
<ul>${report}</ul>
|
||||
${missed.length ? `<button onclick='retryMissed(${JSON.stringify(missed)})'>Retry Missed Words</button>` : ''}
|
||||
`;
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
const score = correct;
|
||||
const completed = true;
|
||||
|
||||
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()
|
||||
}))
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 🔁 Retry only the missed words
|
||||
function retryMissed(missedWords) {
|
||||
words = missedWords;
|
||||
current = 0;
|
||||
results = [];
|
||||
|
||||
document.getElementById('summary')?.classList.add('hidden');
|
||||
showWord();
|
||||
}
|
||||
|
||||
// added remove later
|
||||
console.log("showStudent fired");
|
||||
// added remove later
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
// 🌐 Load users.json for login validation
|
||||
// await loadUsers();
|
||||
displayUserDropdown();
|
||||
populateWordUserDropdown();
|
||||
|
||||
// 🎨 Restore saved theme
|
||||
applySavedTheme();
|
||||
|
||||
// 🔁 Restore session if user was logged in
|
||||
const savedUser = localStorage.getItem("loggedInUser");
|
||||
if (savedUser) {
|
||||
setupSession(JSON.parse(savedUser));
|
||||
}
|
||||
|
||||
// 🧭 Tab switching for Admin Panel
|
||||
const defaultTab = document.querySelector('#adminTabs button.active');
|
||||
if (defaultTab) {
|
||||
const targetId = defaultTab.getAttribute("onclick")?.match(/'(.*?)'/)?.[1];
|
||||
if (targetId) switchTab(targetId);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// 🎨 Apply saved theme on load
|
||||
function applySavedTheme() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'playground';
|
||||
document.body.className = savedTheme;
|
||||
|
||||
const themeSelector = document.getElementById('themeSelect');
|
||||
if (themeSelector) themeSelector.value = savedTheme;
|
||||
}
|
||||
|
||||
// 🎨 Change theme dynamically
|
||||
function changeTheme(themeName) {
|
||||
document.body.className = themeName;
|
||||
localStorage.setItem('theme', themeName);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 🎭 Role-Based UI Setup
|
||||
function setupSession(user) {
|
||||
document.getElementById('loginPanel').classList.add('hidden');
|
||||
document.getElementById('adminPanel').classList.add('hidden');
|
||||
document.getElementById('studentPanel').classList.add('hidden');
|
||||
document.getElementById('nav').classList.remove('hidden');
|
||||
|
||||
document.getElementById('userInfo').textContent = `${user.username} (${user.role})`;
|
||||
|
||||
// 🎨 Banner Setup
|
||||
const roleBanner = document.getElementById('roleBanner');
|
||||
roleBanner.classList.remove('hidden', 'admin', 'student');
|
||||
roleBanner.classList.add(user.role);
|
||||
roleBanner.textContent = user.role === "admin"
|
||||
? "👨🏫 Admin Access Enabled"
|
||||
: "🎓 Student Mode Active";
|
||||
|
||||
// 🎚 Role-Based Navigation
|
||||
const studentBtn = document.getElementById('studentBtn');
|
||||
const adminBtn = document.getElementById('adminBtn');
|
||||
|
||||
if (user.role === "admin") {
|
||||
studentBtn.classList.remove('hidden');
|
||||
adminBtn.classList.remove('hidden');
|
||||
showAdmin();
|
||||
} else if (user.role === "student") {
|
||||
studentBtn.classList.add('hidden');
|
||||
adminBtn.classList.add('hidden');
|
||||
showStudent();
|
||||
} else {
|
||||
alert("Unknown role. Logging out for safety.");
|
||||
logoutUser();
|
||||
}
|
||||
|
||||
showToast(`Welcome, ${user.role}!`);
|
||||
}
|
||||
|
||||
// 🔔 Toast Notification
|
||||
function showToast(message, duration = 3000) {
|
||||
const toast = document.getElementById("toast");
|
||||
if (!toast) return;
|
||||
|
||||
toast.textContent = message;
|
||||
toast.classList.add("show");
|
||||
setTimeout(() => {
|
||||
toast.classList.remove("show");
|
||||
}, duration);
|
||||
}
|
||||
function showAdmin() {
|
||||
console.log("showAdmin fired");
|
||||
|
||||
const adminPanel = document.getElementById("adminPanel");
|
||||
const studentPanel = document.getElementById("studentPanel");
|
||||
|
||||
loadUserDropdowns();
|
||||
|
||||
adminPanel?.classList.remove("hidden");
|
||||
studentPanel?.classList.add("hidden");
|
||||
|
||||
switchTab("tabUsers"); // optional default tab
|
||||
}
|
||||
Reference in new issue
Block a user