1// ==================================== 2// ACCOUNT PAGE FUNCTIONALITY 3// Handles account settings, password changes, and statistics 4// ==================================== 5 6(function (global) { 7 'use strict'; 8 9 var activeController = null; 10 11 function getEl(id, root) { 12 if (root) { 13 return root.querySelector('[data-account-id="' + id + '"]'); 14 } 15 return document.getElementById(id); 16 } 17 18 function init(options) { 19 options = options || {}; 20 var root = options.root || null; 21 22 if (activeController && typeof activeController.abort === 'function') { 23 activeController.abort(); 24 } 25 activeController = typeof AbortController !== 'undefined' ? new AbortController() : null; 26 var signal = activeController ? activeController.signal : null; 27 28 if (typeof global.__sharedUsername === 'undefined') { 29 setTimeout(function () { init(options); }, 100); 30 return; 31 } 32 33 var username = global.__sharedUsername.get(); 34 var hasPassword = localStorage.getItem('puppy_username_password') !== null; 35 var notLoggedIn = getEl('not-logged-in', root); 36 var accountInfo = getEl('account-info', root); 37 38 if (!username || !hasPassword) { 39 if (notLoggedIn) notLoggedIn.style.display = 'block'; 40 if (accountInfo) accountInfo.style.display = 'none'; 41 return; 42 } 43 44 if (notLoggedIn) notLoggedIn.style.display = 'none'; 45 if (accountInfo) accountInfo.style.display = 'block'; 46 47 var usernameEl = getEl('account-username', root); 48 if (usernameEl) usernameEl.textContent = username; 49 50 loadStatistics(username, root); 51 setupStatusBio(username, root, signal); 52 setupPasswordChange(username, root, signal); 53 setupLogout(root, options, signal); 54 } 55 56 function setupStatusBio(username, root, signal) { 57 var input = getEl('account-status-bio', root); 58 var countEl = getEl('account-status-count', root); 59 var saveBtn = getEl('account-status-save', root); 60 var errorEl = getEl('account-status-error', root); 61 if (!input || !saveBtn) return; 62 63 function updateCount() { 64 if (countEl) countEl.textContent = (input.value || '').length; 65 } 66 67 input.addEventListener('input', updateCount, signal ? { signal: signal } : false); 68 69 if (typeof firebase !== 'undefined' && firebase.database) { 70 firebase.database().ref('chatroom/userProfiles').child(username).once('value').then(function (snap) { 71 var data = snap.val(); 72 var status = (data && typeof data.status === 'string') ? data.status : ''; 73 input.value = status; 74 updateCount(); 75 }).catch(function (err) { 76 console.warn('Could not load status:', err); 77 }); 78 } 79 80 saveBtn.addEventListener('click', async function () { 81 var text = (input.value || '').trim().substring(0, 200); 82 if (errorEl) errorEl.style.display = 'none'; 83 saveBtn.disabled = true; 84 saveBtn.textContent = 'Saving...'; 85 try { 86 if (typeof firebase === 'undefined' || !firebase.database) { 87 if (errorEl) { 88 errorEl.textContent = 'Firebase not available.'; 89 errorEl.style.display = 'block'; 90 } 91 return; 92 } 93 await firebase.database().ref('chatroom/userProfiles').child(username).update({ 94 status: text, 95 statusUpdatedAt: Date.now() 96 }); 97 if (errorEl) { 98 errorEl.className = 'account-error account-status-success'; 99 errorEl.textContent = 'Status saved!'; 100 errorEl.style.display = 'block'; 101 setTimeout(function () { 102 errorEl.style.display = 'none'; 103 errorEl.className = 'account-error'; 104 }, 3000); 105 } 106 } catch (err) { 107 console.error('Error saving status:', err); 108 if (errorEl) { 109 errorEl.textContent = 'Failed to save. Try again.'; 110 errorEl.style.display = 'block'; 111 } 112 } finally { 113 saveBtn.disabled = false;
114 saveBtn.textContent = 'Save Status'; 115 } 116 }, signal ? { signal: signal } : false); 117 } 118 119 async function loadStatistics(username, root) { 120 var statsDiv = getEl('account-stats', root); 121 if (!statsDiv) return; 122 123 try { 124 var leaderboards = [ 125 { name: 'Fish Food', ref: firebase.database().ref('fishFoodScores') }, 126 { name: 'Plong', ref: firebase.database().ref('plongScores') }, 127 { name: 'Breakout', ref: firebase.database().ref('breakoutScores') }, 128 { name: 'Puggle', ref: firebase.database().ref('puggleScores') } 129 ]; 130 131 var totalScores = 0; 132 var gamesPlayed = 0; 133 var highestScores = {}; 134 var i; 135 136 for (i = 0; i < leaderboards.length; i++) { 137 var lb = leaderboards[i]; 138 try { 139 var snapshot = await lb.ref.orderByChild('name').equalTo(username).once('value'); 140 var scores = []; 141 snapshot.forEach(function (child) { 142 var entry = child.val(); 143 var score = entry.score || entry.coins || 0; 144 scores.push(score); 145 totalScores++; 146 }); 147 148 if (scores.length > 0) { 149 gamesPlayed++; 150 highestScores[lb.name] = Math.max.apply(null, scores); 151 } 152 } catch (error) { 153 console.error('Error loading ' + lb.name + ' stats:', error); 154 } 155 } 156 157 var statsHTML = '<div class="account-stats-grid">'; 158 statsHTML += '<div class="account-stat-item"><span class="account-stat-num">' + totalScores + '</span><span class="account-stat-label">Total Scores</span></div>'; 159 statsHTML += '<div class="account-stat-item"><span class="account-stat-num">' + gamesPlayed + '</span><span class="account-stat-label">Games Played</span></div>'; 160 statsHTML += '</div>'; 161 162 var gameName; 163 if (Object.keys(highestScores).length > 0) { 164 statsHTML += '<div class="account-stats-highest">'; 165 statsHTML += '<div class="account-stat-label" style="margin-bottom: 8px;">Highest Scores:</div>'; 166 for (gameName in highestScores) { 167 if (Object.prototype.hasOwnProperty.call(highestScores, gameName)) { 168 statsHTML += '<div class="account-stat-row"><span>' + gameName + ':</span><strong class="account-stat-score">' + highestScores[gameName].toLocaleString() + '</strong></div>'; 169 } 170 } 171 statsHTML += '</div>'; 172 } 173 174 statsDiv.innerHTML = statsHTML; 175 } catch (error) { 176 console.error('Error loading statistics:', error); 177 statsDiv.innerHTML = '<div class="account-text-muted account-error">Error loading statistics</div>'; 178 } 179 } 180 181 function setupPasswordChange(username, root, signal) { 182 var changeBtn = getEl('change-password-btn', root); 183 var currentInput = getEl('current-password', root); 184 var newInput = getEl('new-password', root); 185 var confirmInput = getEl('confirm-password', root); 186 var errorMsg = getEl('password-error', root); 187 188 if (!changeBtn) return; 189 190 changeBtn.addEventListener('click', async function () { 191 var current = currentInput.value.trim(); 192 var newPass = newInput.value.trim(); 193 var confirm = confirmInput.value.trim(); 194 195 errorMsg.style.display = 'none'; 196 errorMsg.textContent = ''; 197 198 if (!current || !newPass || !confirm) { 199 errorMsg.textContent = 'Please fill in all fields!'; 200 errorMsg.style.display = 'block'; 201 return; 202 } 203 204 if (newPass !== confirm) { 205 errorMsg.textContent = 'New passwords do not match!'; 206 errorMsg.style.display = 'block'; 207 return; 208 } 209 210 if (newPass.length < 3) { 211 errorMsg.textContent = 'Password must be at least 3 characters!'; 212 errorMsg.style.display = 'block'; 213 return; 214 } 215 216 try { 217 var usernamesRef = firebase.database().ref('usernames'); 218 var usernameData = await usernamesRef.child(username).once('value'); 219 220 if (!usernameData.exists()) { 221 errorMsg.textContent = 'Account not found!'; 222 errorMsg.style.display = 'block'; 223 return; 224 } 225 226 var data = usernameData.val(); 227 var storedPassword = typeof data === 'object' ? data.password : null; 228 229 if (!storedPassword || storedPassword !== current) { 230 errorMsg.textContent = 'Current password is incorrect!'; 231 errorMsg.style.display = 'block'; 232 return; 233 } 234 235 var fingerprint = typeof data === 'object' ? data.fingerprint : data; 236 await usernamesRef.child(username).set({ 237 fingerprint: fingerprint, 238 password: newPass 239 }); 240 241 localStorage.setItem('puppy_username_password', newPass); 242 243 currentInput.value = ''; 244 newInput.value = ''; 245 confirmInput.value = ''; 246 247 errorMsg.className = 'account-error account-status-success'; 248 errorMsg.textContent = 'Password changed successfully!'; 249 errorMsg.style.display = 'block'; 250 251 setTimeout(function () { 252 errorMsg.style.display = 'none'; 253 errorMsg.className = 'account-error'; 254 }, 3000); 255 } catch (error) { 256 console.error('Error changing password:', error); 257 errorMsg.textContent = 'Error changing password. Please try again.'; 258 errorMsg.style.display = 'block'; 259 } 260 }, signal ? { signal: signal }
260 : false); 261 } 262 263 function setupLogout(root, options, signal) { 264 var logoutBtn = getEl('logout-btn', root); 265 if (!logoutBtn) return; 266 267 logoutBtn.addEventListener('click', function () { 268 if (!confirm('Are you sure you want to log out? You will need to enter your password to log back in.')) { 269 return; 270 } 271 if (options && typeof options.onLogout === 'function') { 272 if (global.__sharedUsername && typeof global.__sharedUsername.clear === 'function') { 273 global.__sharedUsername.clear(); 274 } else { 275 localStorage.removeItem('puppy_shared_username'); 276 localStorage.removeItem('puppy_username_password'); 277 localStorage.removeItem('puppy_guest_mode'); 278 } 279 if (global.AccountIndicator && typeof global.AccountIndicator.update === 'function') { 280 global.AccountIndicator.update(); 281 } 282 try { 283 global.dispatchEvent(new CustomEvent('usernameSet', { detail: { username: null } })); 284 } catch (e) { /* ignore */ } 285 options.onLogout(); 286 return; 287 } 288 if (global.__sharedUsername && typeof global.__sharedUsername.logout === 'function') { 289 global.__sharedUsername.logout(); 290 } else { 291 localStorage.removeItem('puppy_shared_username'); 292 localStorage.removeItem('puppy_username_password'); 293 global.location.reload(); 294 } 295 }, signal ? { signal: signal } : false); 296 } 297 298 function destroy() { 299 if (activeController && typeof activeController.abort === 'function') { 300 activeController.abort(); 301 } 302 activeController = null; 303 } 304 305 global.AccountPage = { 306 init: init, 307 destroy: destroy 308 }; 309 310 if (document.body && document.body.classList.contains('account-page')) { 311 if (document.readyState === 'loading') { 312 document.addEventListener('DOMContentLoaded', function () { init(); }); 313 } else { 314 init(); 315 } 316 } 317})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.