From 2becf784bd91a1b7844bb2766107c81e1b082572 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Mon, 8 Sep 2025 19:18:35 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Fix=20settings=20modal:=20Load?= =?UTF-8?q?=20current=20user=20preferences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added show_in_leaderboard to user-player API response - Improved loadSettings() function with better error handling - Added console logging for debugging - Settings modal now shows current user preference instead of always 'off' - Fixed dependency on currentPlayerId (now uses currentUser.id directly) --- public/js/dashboard.js | 183 +++++++++++++++++++++-------------------- routes/api.js | 26 +++--- 2 files changed, 108 insertions(+), 101 deletions(-) diff --git a/public/js/dashboard.js b/public/js/dashboard.js index e0c744f..66078cb 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -13,7 +13,7 @@ async function initDashboard() { try { // Get current session const { data: { session }, error } = await supabase.auth.getSession(); - + if (error) { console.error('Error checking authentication:', error); // Temporarily show dashboard for testing @@ -42,10 +42,10 @@ async function initDashboard() { displayUserInfo({ email: 'admin@speedrun-arena.com' }); } showDashboard(); - + // Load times section checkLinkStatusAndLoadTimes(); - + } catch (error) { console.error('An unexpected error occurred:', error); // window.location.href = '/login'; @@ -56,7 +56,7 @@ async function initDashboard() { function displayUserInfo(user) { const userEmail = document.getElementById('userEmail'); const userAvatar = document.getElementById('userAvatar'); - + userEmail.textContent = user.email; userAvatar.textContent = user.email.charAt(0).toUpperCase(); } @@ -106,7 +106,7 @@ function closeModal(modalId) { } // Close modal when clicking outside -window.onclick = function(event) { +window.onclick = function (event) { if (event.target.classList.contains('modal')) { closeModal(event.target.id); } @@ -133,12 +133,12 @@ async function checkLinkStatusAndLoadTimes() { try { // Check if user has a linked player const response = await fetch(`/api/v1/public/user-player/${currentUser.id}?t=${Date.now()}`); - + if (response.ok) { const result = await response.json(); // User is linked, load times await loadUserTimesSection(result.data); - + } else { // User is not linked showTimesNotLinked(); @@ -153,36 +153,36 @@ async function checkLinkStatusAndLoadTimes() { async function startQRScanner() { try { // Request camera access - qrStream = await navigator.mediaDevices.getUserMedia({ - video: { + qrStream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment', // Use back camera if available width: { ideal: 1280 }, height: { ideal: 720 } - } + } }); - + const video = document.getElementById('qrVideo'); const canvas = document.getElementById('qrCanvas'); const context = canvas.getContext('2d'); - + video.srcObject = qrStream; video.play(); - + // Show camera container and update buttons document.getElementById('cameraContainer').style.display = 'block'; document.getElementById('startScanBtn').style.display = 'none'; document.getElementById('stopScanBtn').style.display = 'inline-block'; document.getElementById('scanningStatus').style.display = 'block'; - + qrScanning = true; - + // Start scanning loop video.addEventListener('loadedmetadata', () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; scanQRCode(); }); - + } catch (error) { console.error('Error accessing camera:', error); showMessage('rfidMessage', 'Kamera-Zugriff fehlgeschlagen. Bitte verwende die manuelle Eingabe.', 'error'); @@ -192,12 +192,12 @@ async function startQRScanner() { // Stop QR Scanner function stopQRScanner() { qrScanning = false; - + if (qrStream) { qrStream.getTracks().forEach(track => track.stop()); qrStream = null; } - + // Reset UI document.getElementById('cameraContainer').style.display = 'none'; document.getElementById('startScanBtn').style.display = 'inline-block'; @@ -208,26 +208,26 @@ function stopQRScanner() { // Scan QR Code from video stream function scanQRCode() { if (!qrScanning) return; - + const video = document.getElementById('qrVideo'); const canvas = document.getElementById('qrCanvas'); const context = canvas.getContext('2d'); - + if (video.readyState === video.HAVE_ENOUGH_DATA) { canvas.width = video.videoWidth; canvas.height = video.videoHeight; context.drawImage(video, 0, 0, canvas.width, canvas.height); - + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); - + if (code) { console.log('QR Code detected:', code.data); handleQRCodeDetected(code.data); return; } } - + // Continue scanning if (qrScanning) { requestAnimationFrame(scanQRCode); @@ -238,7 +238,7 @@ function scanQRCode() { function formatRfidUid(rawUid) { // Remove any existing formatting (spaces, colons, etc.) let cleanUid = rawUid.replace(/[^a-fA-F0-9]/g, '').toUpperCase(); - + // Handle different UID lengths if (cleanUid.length === 6) { // Pad 6-digit UID to 8 digits by adding leading zeros @@ -251,7 +251,7 @@ function formatRfidUid(rawUid) { } else { throw new Error(`Ungültige RFID UID Länge: ${cleanUid.length} Zeichen (unterstützt: 6-8)`); } - + // Format as XX:XX:XX:XX return cleanUid.match(/.{2}/g).join(':'); } @@ -259,24 +259,24 @@ function formatRfidUid(rawUid) { // Handle detected QR code async function handleQRCodeDetected(qrData) { stopQRScanner(); - + try { // Extract and format RFID UID from QR code const rawUid = qrData.trim(); - + if (!rawUid) { showMessage('rfidMessage', 'QR-Code enthält keine gültige RFID UID', 'error'); return; } - + // Format the UID to match database format (XX:XX:XX:XX) const formattedUid = formatRfidUid(rawUid); - + showMessage('rfidMessage', `QR-Code erkannt: ${rawUid} → ${formattedUid}`, 'info'); - + // Link the user using the formatted RFID UID await linkUserByRfidUid(formattedUid); - + } catch (error) { console.error('Error formatting RFID UID:', error); showMessage('rfidMessage', `Fehler beim Formatieren der RFID UID: ${error.message}`, 'error'); @@ -286,20 +286,20 @@ async function handleQRCodeDetected(qrData) { // Manual RFID linking async function linkManualRfid() { const rawUid = document.getElementById('manualRfidInput').value.trim(); - + if (!rawUid) { showMessage('rfidMessage', 'Bitte gib eine RFID UID ein', 'error'); return; } - + try { // Format the UID to match database format const formattedUid = formatRfidUid(rawUid); - + showMessage('rfidMessage', `Formatiert: ${rawUid} → ${formattedUid}`, 'info'); - + await linkUserByRfidUid(formattedUid); - + } catch (error) { console.error('Error formatting manual RFID UID:', error); showMessage('rfidMessage', `Fehler beim Formatieren: ${error.message}`, 'error'); @@ -327,7 +327,7 @@ async function linkUserByRfidUid(rfidUid) { }); const result = await response.json(); - + if (response.ok) { showMessage('rfidMessage', `✅ RFID erfolgreich verknüpft!\nSpieler: ${result.data.firstname} ${result.data.lastname}`, 'success'); setTimeout(() => { @@ -365,27 +365,27 @@ async function loadUserTimesSection(playerData) { try { const response = await fetch(`/api/v1/public/user-times/${currentUser.id}?t=${Date.now()}`); const result = await response.json(); - + if (!response.ok) { throw new Error(result.message || 'Failed to load times'); } - + const times = result.data || result; - + // Update stats updateTimesStats(times, playerData); - + // Display times displayUserTimes(times); - + // Show the times display document.getElementById('timesLoading').style.display = 'none'; document.getElementById('timesNotLinked').style.display = 'none'; document.getElementById('timesDisplay').style.display = 'block'; - + // Initialize achievements for this player initializeAchievements(playerData.id); - + } catch (error) { console.error('Error loading user times:', error); showTimesNotLinked(); @@ -396,7 +396,7 @@ async function loadUserTimesSection(playerData) { function updateTimesStats(times, playerData) { // Total runs document.getElementById('totalRuns').textContent = times.length; - + // Best time if (times.length > 0) { const bestTimeValue = times.reduce((best, current) => { @@ -408,11 +408,11 @@ function updateTimesStats(times, playerData) { } else { document.getElementById('bestTime').textContent = '--:--'; } - + // Unique locations count const uniqueLocations = [...new Set(times.map(time => time.location_name))]; document.getElementById('locationsCount').textContent = uniqueLocations.length; - + // Linked player name document.getElementById('linkedPlayer').textContent = `${playerData.firstname} ${playerData.lastname}`; } @@ -420,7 +420,7 @@ function updateTimesStats(times, playerData) { // Display user times in grid function displayUserTimes(times) { const timesGrid = document.getElementById('userTimesGrid'); - + if (times.length === 0) { timesGrid.innerHTML = `
@@ -454,7 +454,7 @@ function displayUserTimes(times) { const allRunsHtml = sortedTimes.map((run, runIndex) => { let rankBadge = ''; let rankClass = ''; - + if (runIndex === 0) { rankBadge = '🥇 Beste'; rankClass = 'best'; @@ -512,14 +512,14 @@ function displayUserTimes(times) { // Toggle time card expansion function toggleTimeCard(cardElement) { const isExpanded = cardElement.classList.contains('expanded'); - + // Close all other cards first document.querySelectorAll('.user-time-card.expanded').forEach(card => { if (card !== cardElement) { card.classList.remove('expanded'); } }); - + // Toggle current card if (isExpanded) { cardElement.classList.remove('expanded'); @@ -552,7 +552,7 @@ function formatTime(interval) { const totalSeconds = hours * 3600 + minutes * 60 + seconds; return formatSeconds(totalSeconds); } - + // Fallback for string format if (typeof interval === 'string') { // Parse format like "00:01:23.45" @@ -565,14 +565,14 @@ function formatTime(interval) { return formatSeconds(totalSeconds); } } - + return interval; } function formatSeconds(totalSeconds) { const minutes = Math.floor(totalSeconds / 60); const seconds = (totalSeconds % 60).toFixed(2); - + if (minutes > 0) { return `${minutes}:${seconds.padStart(5, '0')}`; } else { @@ -625,7 +625,7 @@ async function loadPlayerAchievements() { // Show achievements displayAchievementStats(); displayAchievements(); - + // Hide loading state document.getElementById('achievementsLoading').style.display = 'none'; document.getElementById('achievementStats').style.display = 'flex'; @@ -656,7 +656,7 @@ function displayAchievementStats() { if (!window.achievementStats) return; const stats = window.achievementStats; - + document.getElementById('totalPoints').textContent = stats.total_points; document.getElementById('completedAchievements').textContent = `${stats.completed_achievements}/${stats.total_achievements}`; document.getElementById('achievementsToday').textContent = stats.achievements_today; @@ -666,7 +666,7 @@ function displayAchievementStats() { // Display achievements in grid function displayAchievements() { const achievementsGrid = document.getElementById('achievementsGrid'); - + if (!window.allAchievements || window.allAchievements.length === 0) { achievementsGrid.innerHTML = `
@@ -681,7 +681,7 @@ function displayAchievements() { // Filter achievements by category let filteredAchievements = window.allAchievements; if (currentAchievementCategory !== 'all') { - filteredAchievements = window.allAchievements.filter(achievement => + filteredAchievements = window.allAchievements.filter(achievement => achievement.category === currentAchievementCategory ); } @@ -691,16 +691,16 @@ function displayAchievements() { const isCompleted = achievement.is_completed; const progress = achievement.progress || 0; const earnedAt = achievement.earned_at; - + // Debug logging if (achievement.name === 'Tageskönig') { console.log('Tageskönig Debug:', { isCompleted, progress, earnedAt }); } - + let progressText = ''; if (isCompleted) { - progressText = earnedAt ? - `Erreicht am ${new Date(earnedAt).toLocaleDateString('de-DE')}` : + progressText = earnedAt ? + `Erreicht am ${new Date(earnedAt).toLocaleDateString('de-DE')}` : 'Abgeschlossen'; } else if (progress > 0) { // Show progress for incomplete achievements @@ -754,13 +754,13 @@ function getAchievementConditionValue(achievementName) { // Show achievement category function showAchievementCategory(category) { currentAchievementCategory = category; - + // Update active tab document.querySelectorAll('.category-tab').forEach(tab => { tab.classList.remove('active'); }); document.querySelector(`[data-category="${category}"]`).classList.add('active'); - + // Display filtered achievements displayAchievements(); } @@ -790,7 +790,7 @@ async function checkPlayerAchievements() { const response = await fetch(`/api/achievements/check/${currentPlayerId}?t=${Date.now()}`, { method: 'POST' }); - + if (response.ok) { const result = await response.json(); if (result.data.count > 0) { @@ -820,10 +820,10 @@ function showAchievementNotification(newAchievements) {
`; - + // Add to page document.body.appendChild(notification); - + // Auto-remove after 5 seconds setTimeout(() => { if (notification.parentElement) { @@ -848,14 +848,14 @@ function showWebNotification(title, message, icon = '🏆') { tag: 'ninjacross-achievement', requireInteraction: true }); - + // Auto-close after 10 seconds setTimeout(() => { notification.close(); }, 10000); - + // Handle click - notification.onclick = function() { + notification.onclick = function () { window.focus(); notification.close(); }; @@ -867,10 +867,10 @@ async function checkBestTimeNotifications() { try { const response = await fetch('/api/v1/public/best-times'); const result = await response.json(); - + if (result.success && result.data) { const { daily, weekly, monthly } = result.data; - + // Check if current player has best times if (currentPlayerId) { if (daily && daily.player_id === currentPlayerId) { @@ -880,7 +880,7 @@ async function checkBestTimeNotifications() { '👑' ); } - + if (weekly && weekly.player_id === currentPlayerId) { showWebNotification( '🏆 Wochenchampion!', @@ -888,7 +888,7 @@ async function checkBestTimeNotifications() { '🏆' ); } - + if (monthly && monthly.player_id === currentPlayerId) { showWebNotification( '🏆 Monatsmeister!', @@ -907,10 +907,10 @@ async function checkBestTimeNotifications() { async function checkAchievementNotifications() { try { if (!currentPlayerId) return; - + const response = await fetch(`/api/achievements/player/${currentPlayerId}?t=${Date.now()}`); const result = await response.json(); - + if (result.success && result.data) { const newAchievements = result.data.filter(achievement => { // Check if achievement was earned in the last 5 minutes @@ -918,7 +918,7 @@ async function checkAchievementNotifications() { const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); return earnedAt > fiveMinutesAgo; }); - + if (newAchievements.length > 0) { newAchievements.forEach(achievement => { showWebNotification( @@ -952,21 +952,28 @@ function showSettings() { async function loadSettings() { try { - if (!currentPlayerId) { - console.error('No player ID available'); + if (!currentUser || !currentUser.id) { + console.error('No user ID available'); return; } - // Load current player settings + // Load current player settings using user ID const response = await fetch(`/api/v1/public/user-player/${currentUser.id}`); const result = await response.json(); if (result.success && result.data) { const showInLeaderboard = result.data.show_in_leaderboard || false; document.getElementById('showInLeaderboard').checked = showInLeaderboard; + console.log('Loaded settings - showInLeaderboard:', showInLeaderboard); + } else { + console.error('Failed to load player settings:', result.message); + // Set default to false if loading fails + document.getElementById('showInLeaderboard').checked = false; } } catch (error) { console.error('Error loading settings:', error); + // Set default to false if loading fails + document.getElementById('showInLeaderboard').checked = false; } } @@ -981,9 +988,9 @@ async function saveSettings() { console.error('No player ID available'); return; } - + const showInLeaderboard = document.getElementById('showInLeaderboard').checked; - + // Update player settings using public endpoint (no API key needed) const response = await fetch(`/api/v1/public/update-player-settings`, { method: 'POST', @@ -995,9 +1002,9 @@ async function saveSettings() { show_in_leaderboard: showInLeaderboard }) }); - + const result = await response.json(); - + if (result.success) { showNotification('Einstellungen erfolgreich gespeichert!', 'success'); closeModal('settingsModal'); @@ -1028,9 +1035,9 @@ function showNotification(message, type = 'info') { font-weight: 500; `; notification.textContent = message; - + document.body.appendChild(notification); - + // Remove notification after 3 seconds setTimeout(() => { if (notification.parentNode) { @@ -1039,11 +1046,11 @@ function showNotification(message, type = 'info') { }, 3000); } -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { // Add cookie settings button functionality const cookieSettingsBtn = document.getElementById('cookie-settings-footer'); if (cookieSettingsBtn) { - cookieSettingsBtn.addEventListener('click', function() { + cookieSettingsBtn.addEventListener('click', function () { if (window.cookieConsent) { window.cookieConsent.resetConsent(); } diff --git a/routes/api.js b/routes/api.js index 2549ac6..7c43b6e 100644 --- a/routes/api.js +++ b/routes/api.js @@ -1256,7 +1256,7 @@ router.get('/v1/public/user-player/:supabase_user_id', async (req, res) => { try { const result = await pool.query( - 'SELECT id, firstname, lastname, birthdate, rfiduid FROM players WHERE supabase_user_id = $1', + 'SELECT id, firstname, lastname, birthdate, rfiduid, show_in_leaderboard FROM players WHERE supabase_user_id = $1', [supabase_user_id] ); @@ -2915,14 +2915,14 @@ router.get('/achievements/leaderboard', async (req, res) => { router.post('/v1/public/update-player-settings', async (req, res) => { try { const { player_id, show_in_leaderboard } = req.body; - + if (!player_id) { return res.status(400).json({ success: false, message: 'Player ID ist erforderlich' }); } - + // Update player settings const updateQuery = ` UPDATE players @@ -2930,22 +2930,22 @@ router.post('/v1/public/update-player-settings', async (req, res) => { WHERE id = $2 RETURNING id, firstname, lastname, show_in_leaderboard `; - + const result = await pool.query(updateQuery, [show_in_leaderboard || false, player_id]); - + if (result.rows.length === 0) { return res.status(404).json({ success: false, message: 'Spieler nicht gefunden' }); } - + res.json({ success: true, message: 'Einstellungen erfolgreich aktualisiert', data: result.rows[0] }); - + } catch (error) { console.error('Error updating player settings:', error); res.status(500).json({ @@ -2959,14 +2959,14 @@ router.post('/v1/public/update-player-settings', async (req, res) => { router.post('/v1/private/update-player-settings', requireApiKey, async (req, res) => { try { const { player_id, show_in_leaderboard } = req.body; - + if (!player_id) { return res.status(400).json({ success: false, message: 'Player ID ist erforderlich' }); } - + // Update player settings const updateQuery = ` UPDATE players @@ -2974,22 +2974,22 @@ router.post('/v1/private/update-player-settings', requireApiKey, async (req, res WHERE id = $2 RETURNING id, firstname, lastname, show_in_leaderboard `; - + const result = await pool.query(updateQuery, [show_in_leaderboard || false, player_id]); - + if (result.rows.length === 0) { return res.status(404).json({ success: false, message: 'Spieler nicht gefunden' }); } - + res.json({ success: true, message: 'Einstellungen erfolgreich aktualisiert', data: result.rows[0] }); - + } catch (error) { console.error('Error updating player settings:', error); res.status(500).json({