bunch of fixes
This commit is contained in:
1 parent
462c7908a1
commit
266a064330
14 files changed
+1211
-868
No files matched your search
+229
-86
@@ -1,3 +1,84 @@
|
||||
// Global variables for admin functionality
|
||||
let allWords = [];
|
||||
let adminUsers = [];
|
||||
|
||||
// 🧭 Admin tab switcher (make it globally accessible)
|
||||
window.switchTab = function switchTab(targetId) {
|
||||
console.log('🔄 Switching to tab:', targetId);
|
||||
|
||||
// Hide all tabs
|
||||
document.querySelectorAll('.adminTab').forEach(tab => {
|
||||
tab.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Remove active class from all buttons
|
||||
document.querySelectorAll('#adminTabs button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show the selected tab
|
||||
const targetTab = document.getElementById(targetId);
|
||||
if (targetTab) {
|
||||
targetTab.classList.remove('hidden');
|
||||
console.log('✅ Showing tab:', targetId);
|
||||
} else {
|
||||
console.error('❌ Tab not found:', targetId);
|
||||
}
|
||||
|
||||
// Highlight the active button
|
||||
document.querySelectorAll('#adminTabs button').forEach(btn => {
|
||||
const onclick = btn.getAttribute('onclick');
|
||||
if (onclick && onclick.includes(targetId)) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// 🔁 Trigger tab-specific logic
|
||||
switch(targetId) {
|
||||
case 'tabSessions':
|
||||
if (typeof populateSessionUserDropdown === 'function') {
|
||||
populateSessionUserDropdown();
|
||||
}
|
||||
const sessionList = document.getElementById("userSessionList");
|
||||
if (sessionList) sessionList.innerHTML = "";
|
||||
break;
|
||||
case 'tabReports':
|
||||
if (typeof loadStudentNamesForReport === 'function') {
|
||||
loadStudentNamesForReport();
|
||||
}
|
||||
break;
|
||||
case 'tabBadges':
|
||||
if (typeof loadBadgeViewer === 'function') {
|
||||
loadBadgeViewer();
|
||||
}
|
||||
break;
|
||||
case 'tabAnalytics':
|
||||
if (typeof refreshAnalytics === 'function') {
|
||||
refreshAnalytics();
|
||||
}
|
||||
break;
|
||||
case 'tabResults':
|
||||
if (typeof loadStudentResults === 'function') {
|
||||
loadStudentResults();
|
||||
}
|
||||
break;
|
||||
case 'tabWords':
|
||||
loadUserDropdowns();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Load users from server
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const res = await fetch('/getUsers');
|
||||
adminUsers = await res.json();
|
||||
} catch (err) {
|
||||
console.error("Failed to load users:", err);
|
||||
adminUsers = [];
|
||||
}
|
||||
}
|
||||
|
||||
// ➕ Add a new user
|
||||
async function addNewUser() {
|
||||
const username = document.getElementById("newUsername").value.trim();
|
||||
@@ -26,8 +107,9 @@ async function addNewUser() {
|
||||
document.getElementById("newPassword").value = '';
|
||||
if (hashPreview) hashPreview.textContent = '[auto]';
|
||||
|
||||
await loadUsers();
|
||||
displayUserDropdown();
|
||||
await loadUsers();
|
||||
displayUserDropdown();
|
||||
populateWordUserDropdown();
|
||||
} catch (e) {
|
||||
msg.textContent = "❌ Failed to add user.";
|
||||
console.error("Add user error:", e);
|
||||
@@ -38,7 +120,7 @@ async function addNewUser() {
|
||||
function displayUserDropdown() {
|
||||
const dropdown = document.getElementById("userDropdown");
|
||||
dropdown.innerHTML = `<option value="">-- Select a user --</option>`;
|
||||
users.forEach(user => {
|
||||
adminUsers.forEach(user => {
|
||||
dropdown.innerHTML += `<option value="${user.username}">${user.username} (${user.role})</option>`;
|
||||
});
|
||||
}
|
||||
@@ -104,8 +186,9 @@ async function loadWordsForSelectedUser() {
|
||||
const selectedUser = document.getElementById("wordUserSelect").value;
|
||||
if (!selectedUser) return;
|
||||
|
||||
const res = await fetch(`/getWordList?user=${encodeURIComponent(selectedUser)}`);
|
||||
allWords = await res.json();
|
||||
const res = await fetch(`/getWordList?username=${encodeURIComponent(selectedUser)}`);
|
||||
const data = await res.json();
|
||||
allWords = data.words || [];
|
||||
displayWordList();
|
||||
}
|
||||
|
||||
@@ -118,7 +201,7 @@ function populateWordUserDropdown() {
|
||||
const selects = document.querySelectorAll(".word-user-dropdown");
|
||||
selects.forEach(select => {
|
||||
select.innerHTML = '<option value="">-- Select a user --</option>';
|
||||
users.forEach(user => {
|
||||
adminUsers.forEach(user => {
|
||||
const option = document.createElement("option");
|
||||
option.value = user.username;
|
||||
option.textContent = user.username;
|
||||
@@ -136,8 +219,9 @@ async function displayWordListForSelectedUser() {
|
||||
if (!username) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/getWordList?user=${username}`);
|
||||
const words = await res.json();
|
||||
const res = await fetch(`/getWordList?username=${username}`);
|
||||
const data = await res.json();
|
||||
const words = data.words || [];
|
||||
if (!words.length) {
|
||||
list.innerHTML = "<li><em>No words found for this user.</em></li>";
|
||||
return;
|
||||
@@ -154,7 +238,7 @@ async function displayWordListForSelectedUser() {
|
||||
}
|
||||
|
||||
// 📊 Student result viewer
|
||||
async function loadStudentResults() {
|
||||
window.loadStudentResults = async function() {
|
||||
const list = document.getElementById("resultsList");
|
||||
list.innerHTML = '';
|
||||
|
||||
@@ -167,19 +251,38 @@ async function loadStudentResults() {
|
||||
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>`;
|
||||
});
|
||||
for (const [username, userResults] of Object.entries(results)) {
|
||||
// Handle array of results per user
|
||||
if (Array.isArray(userResults) && userResults.length > 0) {
|
||||
userResults.forEach((result, index) => {
|
||||
const li = document.createElement("li");
|
||||
let answersHTML = '';
|
||||
|
||||
// Check if result and answers exist and are valid
|
||||
if (result && result.answers && Array.isArray(result.answers)) {
|
||||
result.answers.forEach(entry => {
|
||||
const status = entry.correct ? '✅' : '❌';
|
||||
answersHTML += `${status} ${entry.word}<br>`;
|
||||
});
|
||||
} else {
|
||||
answersHTML = '<em>No answer data available</em>';
|
||||
}
|
||||
|
||||
li.innerHTML = `
|
||||
<strong>${username}</strong> — Score: ${result.score}, Completed: ${result.completed ? '✅' : '❌'}<br>
|
||||
<em>Answers:</em><br>${answersHTML}
|
||||
`;
|
||||
list.appendChild(li);
|
||||
const timestamp = result.timestamp ? new Date(result.timestamp).toLocaleString() : 'Unknown time';
|
||||
li.innerHTML = `
|
||||
<strong>${username}</strong> (Session ${index + 1}) — Score: ${result?.score || 'N/A'}, Completed: ${result?.completed ? '✅' : '❌'}<br>
|
||||
<em>Date:</em> ${timestamp}<br>
|
||||
<em>Answers:</em><br>${answersHTML}
|
||||
<hr style="margin: 1rem 0; opacity: 0.3;">
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
// Fallback for single result format
|
||||
const li = document.createElement("li");
|
||||
li.innerHTML = `<strong>${username}</strong> — No valid results found`;
|
||||
list.appendChild(li);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("🔴 Failed to load results:", err);
|
||||
@@ -187,40 +290,6 @@ async function loadStudentResults() {
|
||||
}
|
||||
}
|
||||
|
||||
// 🧭 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')
|
||||
);
|
||||
|
||||
// Show the selected tab
|
||||
document.getElementById(targetId).classList.remove('hidden');
|
||||
|
||||
// Highlight the active button
|
||||
document.querySelectorAll('#adminTabs button').forEach(btn => {
|
||||
if (btn.getAttribute('onclick')?.includes(targetId)) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// 🔁 Trigger tab-specific logic
|
||||
if (targetId === 'tabSessions') {
|
||||
loadSessionHistory();
|
||||
}
|
||||
if (targetId === 'tabReports') {
|
||||
loadStudentNamesForReport();
|
||||
}
|
||||
if (targetId === 'tabBadges') {
|
||||
loadBadgeViewer();
|
||||
}
|
||||
if (targetId === 'tabSessions') {
|
||||
populateSessionUserDropdown();
|
||||
document.getElementById("userSessionList").innerHTML = "";
|
||||
}
|
||||
}
|
||||
async function loadUserDropdowns() {
|
||||
try {
|
||||
const res = await fetch('/getUsers');
|
||||
@@ -243,6 +312,8 @@ async function loadUserDropdowns() {
|
||||
drop.appendChild(opt);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('✅ User dropdowns loaded');
|
||||
} catch (err) {
|
||||
console.error("⚠️ Failed to load user list:", err);
|
||||
}
|
||||
@@ -352,44 +423,73 @@ async function loadStudentNamesForReport() {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadStudentReport(username) {
|
||||
window.loadStudentReport = async function(username) {
|
||||
if (!username) return;
|
||||
|
||||
const res1 = await fetch('/getResults');
|
||||
const results = await res1.json();
|
||||
const res2 = await fetch('/getBadges');
|
||||
const badges = await res2.json();
|
||||
try {
|
||||
const res1 = await fetch('/getResults');
|
||||
const results = await res1.json();
|
||||
const res2 = await fetch('/getBadges');
|
||||
const badges = await res2.json();
|
||||
|
||||
const report = results[username];
|
||||
const badgeList = badges[username] || [];
|
||||
const userResults = results[username];
|
||||
const badgeList = badges[username] || [];
|
||||
|
||||
document.getElementById("reportTitle").textContent = `Report for ${username}`;
|
||||
document.getElementById("reportScore").textContent = `${report.score}/${report.answers.length} correct`;
|
||||
|
||||
const wordList = document.getElementById("reportWords");
|
||||
wordList.innerHTML = "";
|
||||
report.answers.forEach(({ word, correct }) => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = `${word} — ${correct ? '✅' : '❌'}`;
|
||||
wordList.appendChild(li);
|
||||
});
|
||||
// Handle case where userResults might be an array or undefined
|
||||
let latestReport = null;
|
||||
if (Array.isArray(userResults) && userResults.length > 0) {
|
||||
// Get the most recent result
|
||||
latestReport = userResults[userResults.length - 1];
|
||||
} else if (userResults && typeof userResults === 'object') {
|
||||
// Handle single result object
|
||||
latestReport = userResults;
|
||||
}
|
||||
|
||||
const badgeContainer = document.getElementById("reportBadges");
|
||||
badgeContainer.innerHTML = "";
|
||||
if (badgeList.length) {
|
||||
badgeList.forEach(b => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = `🏅 ${b}`;
|
||||
badgeContainer.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
badgeContainer.innerHTML = "<li><em>No badges earned yet.</em></li>";
|
||||
if (!latestReport) {
|
||||
document.getElementById("reportContent").classList.add("hidden");
|
||||
alert(`No results found for ${username}`);
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("reportTitle").textContent = `Report for ${username}`;
|
||||
|
||||
// Safe access to score and answers
|
||||
const score = latestReport.score || 0;
|
||||
const answers = latestReport.answers || [];
|
||||
document.getElementById("reportScore").textContent = `${score}/${answers.length} correct`;
|
||||
|
||||
const wordList = document.getElementById("reportWords");
|
||||
wordList.innerHTML = "";
|
||||
if (answers.length > 0) {
|
||||
answers.forEach(({ word, correct }) => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = `${word} — ${correct ? '✅' : '❌'}`;
|
||||
wordList.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
wordList.innerHTML = "<li><em>No word data available.</em></li>";
|
||||
}
|
||||
|
||||
const badgeContainer = document.getElementById("reportBadges");
|
||||
badgeContainer.innerHTML = "";
|
||||
if (badgeList.length) {
|
||||
badgeList.forEach(b => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = `🏅 ${b}`;
|
||||
badgeContainer.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
badgeContainer.innerHTML = "<li><em>No badges earned yet.</em></li>";
|
||||
}
|
||||
|
||||
document.getElementById("reportContent").classList.remove("hidden");
|
||||
} catch (error) {
|
||||
console.error("Error loading student report:", error);
|
||||
alert("Error loading student report. Please try again.");
|
||||
}
|
||||
|
||||
document.getElementById("reportContent").classList.remove("hidden");
|
||||
}
|
||||
|
||||
function copyReportToClipboard() {
|
||||
window.copyReportToClipboard = function() {
|
||||
const name = document.getElementById("reportTitle").textContent;
|
||||
const score = document.getElementById("reportScore").textContent;
|
||||
const words = Array.from(document.querySelectorAll("#reportWords li")).map(li => li.textContent).join("\n");
|
||||
@@ -457,4 +557,47 @@ async function loadUserSessions(username) {
|
||||
sessionItem.innerHTML = html;
|
||||
list.appendChild(sessionItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// File upload handler for word lists
|
||||
function handleFileUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const content = e.target.result;
|
||||
const words = content.split(/\r?\n/).map(w => w.trim()).filter(Boolean);
|
||||
|
||||
document.getElementById('wordInput').value = words.join('\n');
|
||||
showToast(`📁 Loaded ${words.length} words from file`);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
// Add event listeners for admin tabs as backup to onclick
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Setup tab click listeners
|
||||
setTimeout(() => {
|
||||
const tabButtons = document.querySelectorAll('#adminTabs button');
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const onclick = button.getAttribute('onclick');
|
||||
if (onclick) {
|
||||
const match = onclick.match(/switchTab\('(.+?)'\)/);
|
||||
if (match) {
|
||||
const tabId = match[1];
|
||||
console.log('Tab clicked:', tabId);
|
||||
if (typeof window.switchTab === 'function') {
|
||||
window.switchTab(tabId);
|
||||
} else {
|
||||
console.error('switchTab function not available');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log('✅ Tab event listeners added to', tabButtons.length, 'buttons');
|
||||
}, 300);
|
||||
});
|
||||
+92
-19
@@ -1,8 +1,24 @@
|
||||
// 🔐 Password hashing using jsSHA
|
||||
function hashPassword(password) {
|
||||
const shaObj = new jsSHA("SHA-256", "TEXT");
|
||||
shaObj.update(password);
|
||||
return shaObj.getHash("HEX");
|
||||
try {
|
||||
if (typeof jsSHA === 'undefined') {
|
||||
console.error('jsSHA library not loaded');
|
||||
throw new Error('jsSHA library not available');
|
||||
}
|
||||
const shaObj = new jsSHA("SHA-256", "TEXT");
|
||||
shaObj.update(password);
|
||||
return shaObj.getHash("HEX");
|
||||
} catch (error) {
|
||||
console.error('Error hashing password:', error);
|
||||
// Fallback: simple hash (not secure, but for debugging)
|
||||
let hash = 0;
|
||||
for (let i = 0; i < password.length; i++) {
|
||||
const char = password.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return Math.abs(hash).toString(16);
|
||||
}
|
||||
}
|
||||
|
||||
// 🌐 Load users from server
|
||||
@@ -12,28 +28,43 @@ let users = [];
|
||||
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');
|
||||
|
||||
if (!uname || !pw) {
|
||||
msg.textContent = "Please enter both username and password.";
|
||||
msg.style.color = 'var(--error)';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Attempting login for:', uname);
|
||||
const hash = hashPassword(pw);
|
||||
console.log('Generated hash:', hash);
|
||||
|
||||
const res = await fetch('/verifyUser', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: uname, hash })
|
||||
});
|
||||
|
||||
const responseText = await res.text();
|
||||
console.log('Server response:', res.status, responseText);
|
||||
|
||||
if (!res.ok) {
|
||||
msg.textContent = "Invalid username or password.";
|
||||
msg.style.color = 'var(--error)';
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await res.json();
|
||||
const user = JSON.parse(responseText);
|
||||
console.log('Login successful for user:', user);
|
||||
localStorage.setItem('loggedInUser', JSON.stringify(user));
|
||||
msg.textContent = '';
|
||||
setupSession(user);
|
||||
} catch (err) {
|
||||
console.error("Login error:", err);
|
||||
msg.textContent = "Login failed. Please try again.";
|
||||
msg.style.color = 'var(--error)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,18 +72,60 @@ async function verifyLogin() {
|
||||
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 = '';
|
||||
|
||||
// Hide all panels
|
||||
document.getElementById('adminPanel')?.classList.add('hidden');
|
||||
document.getElementById('studentPanel')?.classList.add('hidden');
|
||||
document.getElementById('nav')?.classList.add('hidden');
|
||||
|
||||
// Show login panel
|
||||
const loginPanel = document.getElementById('loginPanel');
|
||||
loginPanel?.classList.remove('hidden');
|
||||
|
||||
// Clear login form
|
||||
const usernameField = document.getElementById('username');
|
||||
const passwordField = document.getElementById('password');
|
||||
const loginMessage = document.getElementById('loginMessage');
|
||||
if (loginMessage) loginMessage.textContent = '';
|
||||
}
|
||||
|
||||
if (usernameField) usernameField.value = '';
|
||||
if (passwordField) passwordField.value = '';
|
||||
if (loginMessage) {
|
||||
loginMessage.textContent = '';
|
||||
loginMessage.style.color = '';
|
||||
}
|
||||
|
||||
// Reset banner
|
||||
const roleBanner = document.getElementById('roleBanner');
|
||||
if (roleBanner) {
|
||||
roleBanner.classList.add('hidden');
|
||||
roleBanner.classList.remove('admin', 'student');
|
||||
roleBanner.textContent = '';
|
||||
}
|
||||
|
||||
// Focus on username field
|
||||
if (usernameField) {
|
||||
setTimeout(() => usernameField.focus(), 100);
|
||||
}
|
||||
|
||||
showToast('Logged out successfully');
|
||||
}
|
||||
|
||||
// Add Enter key support for login form
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const loginForm = document.querySelector('#loginPanel form');
|
||||
const usernameField = document.getElementById('username');
|
||||
const passwordField = document.getElementById('password');
|
||||
|
||||
if (loginForm && usernameField && passwordField) {
|
||||
// Focus username field on page load
|
||||
usernameField.focus();
|
||||
|
||||
// Add Enter key support
|
||||
[usernameField, passwordField].forEach(field => {
|
||||
field.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
verifyLogin();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
+42
-2
@@ -3,7 +3,46 @@ let words = [];
|
||||
let currentWord = '';
|
||||
let currentIndex = 0;
|
||||
let results = [];
|
||||
let typingWords = [];
|
||||
// 📝 Check user's answer and move to next
|
||||
window.submitAnswer = function() {
|
||||
const input = document.getElementById('userInput').value.trim();
|
||||
|
||||
// Record the attempt
|
||||
results.push({
|
||||
word: currentWord,
|
||||
attempt: input
|
||||
});
|
||||
|
||||
if (input.toLowerCase() === currentWord.toLowerCase()) {
|
||||
currentIndex++;
|
||||
document.getElementById('inputSection').classList.add('hidden');
|
||||
setTimeout(() => showWord(), 500); // Brief pause before next word
|
||||
} else {
|
||||
// Show feedback for incorrect answer
|
||||
const inputEl = document.getElementById('userInput');
|
||||
inputEl.style.borderColor = 'var(--error)';
|
||||
inputEl.style.backgroundColor = '#fef2f2';
|
||||
|
||||
setTimeout(() => {
|
||||
inputEl.style.borderColor = '';
|
||||
inputEl.style.backgroundColor = '';
|
||||
inputEl.focus();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Add Enter key support for game input
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const gameInput = document.getElementById('userInput');
|
||||
if (gameInput) {
|
||||
gameInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
submitAnswer();
|
||||
}
|
||||
});
|
||||
}
|
||||
});[];
|
||||
let currentSentence = '';
|
||||
|
||||
function showStudent() {
|
||||
console.log("🔊 Showing student panel");
|
||||
@@ -34,7 +73,7 @@ function showStudent() {
|
||||
}
|
||||
|
||||
// 🚀 Begin game session
|
||||
async function startGame() {
|
||||
window.startGame = async function() {
|
||||
console.log('🎮 Starting spelling game');
|
||||
|
||||
const user = JSON.parse(localStorage.getItem('loggedInUser'));
|
||||
@@ -62,6 +101,7 @@ async function startGame() {
|
||||
|
||||
// Start game
|
||||
words = [...data.words];
|
||||
typingWords = [...data.words]; // Initialize typing words too
|
||||
currentIndex = 0;
|
||||
results = []; // Reset results array
|
||||
showWord();
|
||||
|
||||
+46
-14
@@ -1,22 +1,54 @@
|
||||
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));
|
||||
console.log('🚀 Application starting...');
|
||||
|
||||
// Load users for admin functionality
|
||||
try {
|
||||
await loadUsers();
|
||||
displayUserDropdown();
|
||||
populateWordUserDropdown();
|
||||
} catch (e) {
|
||||
console.warn('Could not load users (normal if not admin):', e);
|
||||
}
|
||||
|
||||
// 🧭 Tab switching for Admin Panel
|
||||
// Restore saved theme
|
||||
applySavedTheme();
|
||||
|
||||
// Restore session if user was logged in
|
||||
const savedUser = localStorage.getItem("loggedInUser");
|
||||
if (savedUser) {
|
||||
try {
|
||||
const user = JSON.parse(savedUser);
|
||||
console.log('👤 Restoring session for:', user.username);
|
||||
setupSession(user);
|
||||
} catch (e) {
|
||||
console.error('Error restoring session:', e);
|
||||
localStorage.removeItem("loggedInUser");
|
||||
}
|
||||
} else {
|
||||
console.log('👤 No saved session found');
|
||||
// Focus on login form
|
||||
const usernameField = document.getElementById('username');
|
||||
if (usernameField) {
|
||||
setTimeout(() => usernameField.focus(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Set default admin tab
|
||||
const defaultTab = document.querySelector('#adminTabs button.active');
|
||||
if (defaultTab) {
|
||||
const targetId = defaultTab.getAttribute("onclick")?.match(/'(.*?)'/)?.[1];
|
||||
if (targetId) switchTab(targetId);
|
||||
if (targetId) {
|
||||
console.log('🔧 Setting default admin tab:', targetId);
|
||||
// Use a small delay to ensure all scripts are loaded
|
||||
setTimeout(() => {
|
||||
if (typeof window.switchTab === 'function') {
|
||||
window.switchTab(targetId);
|
||||
} else {
|
||||
console.warn('switchTab function not yet available');
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Application initialized');
|
||||
});
|
||||
+64
-5
@@ -1,14 +1,73 @@
|
||||
// 🎨 Apply saved theme on load
|
||||
// Modern theme system
|
||||
function applySavedTheme() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'playground';
|
||||
document.body.className = savedTheme;
|
||||
const savedTheme = localStorage.getItem('theme') || 'default';
|
||||
applyTheme(savedTheme);
|
||||
|
||||
const themeSelector = document.getElementById('themeSelect');
|
||||
if (themeSelector) themeSelector.value = savedTheme;
|
||||
}
|
||||
|
||||
// 🎨 Change theme dynamically
|
||||
function changeTheme(themeName) {
|
||||
document.body.className = themeName;
|
||||
applyTheme(themeName);
|
||||
localStorage.setItem('theme', themeName);
|
||||
showToast(`🎨 Theme changed to ${themeName}`);
|
||||
}
|
||||
|
||||
function applyTheme(themeName) {
|
||||
// Remove existing theme classes
|
||||
document.body.className = '';
|
||||
|
||||
// Apply theme-specific styles
|
||||
switch(themeName) {
|
||||
case 'playground':
|
||||
document.body.className = 'theme-playground';
|
||||
document.documentElement.style.setProperty('--primary', '#ff6b6b');
|
||||
document.documentElement.style.setProperty('--accent', '#4ecdc4');
|
||||
document.documentElement.style.setProperty('--bg-primary', '#fff5f5');
|
||||
document.documentElement.style.setProperty('--bg-secondary', '#ffebee');
|
||||
break;
|
||||
|
||||
case 'chalkboard':
|
||||
document.body.className = 'theme-chalkboard';
|
||||
document.documentElement.style.setProperty('--primary', '#4ade80');
|
||||
document.documentElement.style.setProperty('--accent', '#22d3ee');
|
||||
document.documentElement.style.setProperty('--bg-primary', '#1f2937');
|
||||
document.documentElement.style.setProperty('--bg-secondary', '#374151');
|
||||
document.documentElement.style.setProperty('--text-primary', '#f9fafb');
|
||||
document.documentElement.style.setProperty('--text-secondary', '#d1d5db');
|
||||
document.documentElement.style.setProperty('--border', '#4b5563');
|
||||
break;
|
||||
|
||||
case 'galaxy':
|
||||
document.body.className = 'theme-galaxy';
|
||||
document.documentElement.style.setProperty('--primary', '#8b5cf6');
|
||||
document.documentElement.style.setProperty('--accent', '#06b6d4');
|
||||
document.documentElement.style.setProperty('--bg-primary', '#0f0f23');
|
||||
document.documentElement.style.setProperty('--bg-secondary', '#1a1a3f');
|
||||
document.documentElement.style.setProperty('--text-primary', '#e0e7ff');
|
||||
document.documentElement.style.setProperty('--text-secondary', '#a5b4fc');
|
||||
document.documentElement.style.setProperty('--border', '#3730a3');
|
||||
break;
|
||||
|
||||
case 'nature':
|
||||
document.body.className = 'theme-nature';
|
||||
document.documentElement.style.setProperty('--primary', '#059669');
|
||||
document.documentElement.style.setProperty('--accent', '#84cc16');
|
||||
document.documentElement.style.setProperty('--bg-primary', '#f0fdf4');
|
||||
document.documentElement.style.setProperty('--bg-secondary', '#dcfce7');
|
||||
document.documentElement.style.setProperty('--text-primary', '#14532d');
|
||||
break;
|
||||
|
||||
default: // Modern default theme
|
||||
document.body.className = 'theme-default';
|
||||
// Reset to original CSS custom properties
|
||||
document.documentElement.style.setProperty('--primary', '#3b82f6');
|
||||
document.documentElement.style.setProperty('--accent', '#06b6d4');
|
||||
document.documentElement.style.setProperty('--bg-primary', '#ffffff');
|
||||
document.documentElement.style.setProperty('--bg-secondary', '#f8fafc');
|
||||
document.documentElement.style.setProperty('--text-primary', '#1e293b');
|
||||
document.documentElement.style.setProperty('--text-secondary', '#64748b');
|
||||
document.documentElement.style.setProperty('--border', '#e2e8f0');
|
||||
break;
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -5,7 +5,8 @@ const typingState = {
|
||||
currentWord: ''
|
||||
};
|
||||
|
||||
async function startTypingPractice() {
|
||||
// Make functions globally accessible
|
||||
window.startTypingPractice = async function() {
|
||||
console.log('Starting typing practice');
|
||||
|
||||
const user = JSON.parse(localStorage.getItem('loggedInUser'));
|
||||
@@ -34,7 +35,7 @@ async function startTypingPractice() {
|
||||
}
|
||||
}
|
||||
|
||||
function showNextWord() {
|
||||
window.showNextWord = function() {
|
||||
const promptEl = document.getElementById('typingPrompt');
|
||||
const inputEl = document.getElementById('typingInput');
|
||||
if (!promptEl || !inputEl) return;
|
||||
@@ -58,14 +59,8 @@ function showNextWord() {
|
||||
inputEl.focus();
|
||||
}
|
||||
|
||||
|
||||
// Reset input
|
||||
inputEl.value = '';
|
||||
inputEl.disabled = false;
|
||||
inputEl.focus();
|
||||
|
||||
|
||||
function submitTyping() {
|
||||
window.submitTyping = function() {
|
||||
const input = document.getElementById('typingInput')?.value.trim() || '';
|
||||
const feedback = document.getElementById('typingFeedback');
|
||||
if (!feedback) return;
|
||||
|
||||
+51
-9
@@ -46,16 +46,58 @@ function showToast(message, duration = 3000) {
|
||||
toast.classList.remove("show");
|
||||
}, duration);
|
||||
}
|
||||
function showAdmin() {
|
||||
console.log("showAdmin fired");
|
||||
|
||||
const adminPanel = document.getElementById("adminPanel");
|
||||
const studentPanel = document.getElementById("studentPanel");
|
||||
function showStudent() {
|
||||
console.log("🎓 Showing student panel");
|
||||
|
||||
loadUserDropdowns();
|
||||
// Hide other panels
|
||||
document.getElementById('adminPanel')?.classList.add('hidden');
|
||||
document.getElementById('loginPanel')?.classList.add('hidden');
|
||||
|
||||
// Show student panel
|
||||
const studentPanel = document.getElementById('studentPanel');
|
||||
if (studentPanel) {
|
||||
studentPanel.classList.remove('hidden');
|
||||
|
||||
// Ensure buttons are visible and functional
|
||||
const gameBtn = document.getElementById('startGameBtn');
|
||||
const typingBtn = document.getElementById('startTypingBtn');
|
||||
|
||||
if (gameBtn && typingBtn) {
|
||||
gameBtn.classList.remove('hidden');
|
||||
typingBtn.classList.remove('hidden');
|
||||
console.log("✅ Student buttons are now visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adminPanel?.classList.remove("hidden");
|
||||
studentPanel?.classList.add("hidden");
|
||||
function showAdmin() {
|
||||
console.log("👨💼 Showing admin panel");
|
||||
|
||||
switchTab("tabUsers"); // optional default tab
|
||||
// Hide other panels
|
||||
document.getElementById('studentPanel')?.classList.add('hidden');
|
||||
document.getElementById('loginPanel')?.classList.add('hidden');
|
||||
|
||||
const adminPanel = document.getElementById("adminPanel");
|
||||
if (adminPanel) {
|
||||
adminPanel.classList.remove("hidden");
|
||||
|
||||
// Load user data for dropdowns
|
||||
loadUserDropdowns().then(() => {
|
||||
console.log("✅ User dropdowns loaded");
|
||||
}).catch(e => {
|
||||
console.warn("Could not load user dropdowns:", e);
|
||||
});
|
||||
|
||||
// Set default tab - use a slight delay to ensure DOM is ready
|
||||
setTimeout(() => {
|
||||
if (typeof window.switchTab === 'function') {
|
||||
window.switchTab("tabUsers");
|
||||
console.log("✅ Default tab set");
|
||||
} else {
|
||||
console.error("switchTab function not available");
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
console.error("Admin panel not found");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user