let users = [] // ๐Ÿ” Load users from server-side users.json async function loadUsers() { try { const res = await fetch("users.json") users = await res.json() } catch (e) { console.error("Failed to load users.json:", e) users = [] } } // ๐Ÿ” Password hashing function hashPassword(password) { const shaObj = new jsSHA("SHA-256", "TEXT") shaObj.update(password) return shaObj.getHash("HEX") } // ๐Ÿ‘ค 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') const user = users.find(u => u.username === uname && u.hash === hash) if (user) { localStorage.setItem('loggedInUser', JSON.stringify(user)) msg.textContent = '' setupSession(user) await showAdmin() } else { msg.textContent = "Invalid username or password." } } // ๐Ÿง  On load window.addEventListener('DOMContentLoaded', async () => { await loadUsers() displayUserDropdown() const savedTheme = localStorage.getItem('theme') || 'playground' document.body.className = savedTheme const dropdown = document.getElementById('themeSelect') if (dropdown) dropdown.value = savedTheme const savedUser = localStorage.getItem('loggedInUser') if (savedUser) setupSession(JSON.parse(savedUser)) }) // ๐Ÿง‘โ€๐Ÿซ Admin setup async function setupSession(user) { // ๐Ÿ”’ Hide everything first for clean slate document.getElementById('loginPanel').classList.add('hidden') document.getElementById('adminPanel').classList.add('hidden') document.getElementById('studentPanel').classList.add('hidden') // ๐Ÿ”“ Show navigation and user info document.getElementById('nav').classList.remove('hidden') document.getElementById('userInfo').textContent = `${user.username} (${user.role})` // ๐ŸŽจ Role-colored 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" // ๐Ÿ™ˆ Hide/show Student Panel button appropriately const studentBtn = document.getElementById('studentBtn') if (user.role === "admin") { studentBtn.classList.remove('hidden') document.getElementById('adminBtn').classList.remove('hidden') console.log("๐Ÿ›  Logged in as Admin") await showAdmin() } else if (user.role === "student") { studentBtn.classList.add('hidden') document.getElementById('adminBtn').classList.add('hidden') console.log("๐ŸŽ“ Logged in as Student") showStudent() } else { console.warn("โ“ Unknown role:", user.role) logoutUser() alert("Unknown role detected. Logging out for security.") return } // ๐Ÿ“ฃ Toast message showToast(`Welcome, ${user.role}!`) } function logoutUser() { // Clear stored user session localStorage.removeItem('loggedInUser') // ๐Ÿ”„ Reset all visible panels 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 = '' // ๐Ÿงผ Reset role banner styling and content const roleBanner = document.getElementById('roleBanner') roleBanner.classList.add('hidden') roleBanner.classList.remove('admin', 'student') roleBanner.textContent = '' // Optional: reset login message (if present) const loginMessage = document.getElementById('loginMessage') if (loginMessage) loginMessage.textContent = '' } async function showAdmin() { const user = JSON.parse(localStorage.getItem('loggedInUser')) if (!user || user.role !== "admin") { console.warn("โ›” Access denied: not an admin") return } document.getElementById('adminPanel').classList.remove('hidden') document.getElementById('studentPanel').classList.add('hidden') try { await loadWords() displayWordList() } catch (err) { console.error("๐Ÿšจ Failed to load words:", err) // Optional: fallback or placeholder UI } await loadUsers() displayUserDropdown() populateWordUserDropdown() } // โž• Add 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) // Live hash preview (optional visual confirmation for admin) if (hashPreview) { hashPreview.textContent = hash } try { await saveUserToServer(username, hash, role) msg.textContent = `โœ… User "${username}" added to server.` // Clear fields after success 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) } } async function saveUserToServer(username, hash, role) { const res = await fetch('/addUser', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, hash, role }) }) const msg = await res.text() alert(msg) } // ๐Ÿ—‘ User deletion function displayUserDropdown() { const dropdown = document.getElementById("userDropdown") dropdown.innerHTML = `` users.forEach(user => { dropdown.innerHTML += `` }) } function selectUserAction(username) { if (!username) return const confirmDelete = confirm(`Are you sure you want to delete user "${username}"?`) if (!confirmDelete) { 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) }) } // ๐ŸŽจ Theme function changeTheme(themeName) { document.body.className = themeName localStorage.setItem('theme', themeName) } // ๐Ÿ“š Word list function saveWords() { const text = document.getElementById('wordInput').value.trim() allWords = text.split('\n').map(w => w.trim()).filter(Boolean) localStorage.setItem('spellingWords', JSON.stringify(allWords)) displayWordList() alert("Word list saved!") } function clearWords() { localStorage.removeItem('spellingWords') allWords = [] displayWordList() alert("Saved words cleared.") } function displayWordList() { const ul = document.getElementById('wordListDisplay') ul.innerHTML = allWords.map(word => `
  • ${word}
  • `).join('') } function handleFileUpload(event) { const file = event.target.files[0] if (!file) return const reader = new FileReader() reader.onload = function(e) { const text = e.target.result const newWords = text.includes(",") ? text.split(",") : text.split("\n") allWords = newWords.map(w => w.trim()).filter(Boolean) localStorage.setItem('spellingWords', JSON.stringify(allWords)) displayWordList() alert("Words uploaded and saved!") } reader.readAsText(file) } // ๐ŸŽฎ Spelling Game let allWords = [], words = [], current = 0, results = [] function showStudent() { document.getElementById('studentPanel').classList.remove('hidden') document.getElementById('adminPanel').classList.add('hidden') } function startGame() { loadWords() if (allWords.length === 0) { alert("No words found. Please add words in Admin Panel.") return } words = [...allWords] current = 0 results = [] document.getElementById('summary').classList.add('hidden') showWord() } function showWord() { const wordBox = document.getElementById('wordBox') const wordEl = document.getElementById('word') wordBox.classList.remove('hidden') wordEl.textContent = words[current] setTimeout(() => { wordBox.classList.add('hidden') document.getElementById('inputSection').classList.remove('hidden') document.getElementById('userInput').value = '' document.getElementById('userInput').focus() }, 2000) } 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() } } 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 `
  • ${word} โ€” ${attempt || '(no answer)'}
  • ` }).join('') summary.innerHTML = `

    Results

    You got ${correct} out of ${words.length} correct (${Math.round((correct / words.length) * 100)}%)

    ${missed.length ? `` : ''} ` summary.classList.remove('hidden') } function retryMissed(missedWords) { words = missedWords current = 0 results = [] document.getElementById('summary').classList.add('hidden') showWord() } async function loadWords() { const user = JSON.parse(localStorage.getItem('loggedInUser')) if (!user) return allWords = [] const res = await fetch(`/getWordList?user=${encodeURIComponent(user.username)}`) allWords = await res.json() displayWordList() } async function saveWords() { const user = JSON.parse(localStorage.getItem('loggedInUser')) if (!user) 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: user.username, words: allWords }) }) displayWordList() alert("โœ… Word list saved to server") } async function clearWords() { const user = JSON.parse(localStorage.getItem('loggedInUser')) if (!user) return allWords = [] await fetch('/saveWordList', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: user.username, words: [] }) }) displayWordList() alert("๐Ÿ—‘ Word list cleared") } function populateWordUserDropdown() { const userSelects = document.querySelectorAll(".word-user-dropdown") if (!users || !users.length) { console.warn("โš ๏ธ No users to populate dropdowns.") return } userSelects.forEach(select => { select.innerHTML = '' users.forEach(user => { const option = document.createElement("option") option.value = user.username option.textContent = user.username select.appendChild(option) }) }) } 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 showToast(message, duration = 3000) { const toast = document.getElementById("toast") toast.textContent = message toast.classList.add("show") setTimeout(() => { toast.classList.remove("show") }, duration) } 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 = "
  • No words found for this user.
  • " return } words.forEach(word => { const li = document.createElement("li") li.textContent = word list.appendChild(li) }) } catch (err) { list.innerHTML = `
  • โŒ Error loading words: ${err.message}
  • ` console.error("Failed to load word list for user:", username, err) } } 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 = '
  • No results available yet.
  • ' return } for (const [username, result] of Object.entries(results)) { const li = document.createElement("li") // Build detailed answer breakdown let answersHTML = '' result.answers.forEach(entry => { const status = entry.correct ? 'โœ…' : 'โŒ' answersHTML += `${status} ${entry.word}
    ` }) li.innerHTML = ` ${username} โ€” Score: ${result.score}, Completed: ${result.completed ? 'โœ…' : 'โŒ'}
    Answers:
    ${answersHTML} ` list.appendChild(li) } } catch (err) { console.error("๐Ÿ”ด Failed to load results:", err) list.innerHTML = '
  • Error loading results.
  • ' } } function switchTab(targetId) { // Hide all tabs document.querySelectorAll('.adminTab').forEach(tab => tab.classList.add('hidden')) // Remove active class from buttons document.querySelectorAll('#adminTabs button').forEach(btn => btn.classList.remove('active')) // Show the selected tab document.getElementById(targetId).classList.remove('hidden') // Highlight active tab button const buttons = document.querySelectorAll('#adminTabs button') buttons.forEach(btn => { if (btn.getAttribute('onclick')?.includes(targetId)) { btn.classList.add('active') } }) }