// Supabase configuration const SUPABASE_URL = 'https://lfxlplnypzvjrhftaoog.supabase.co'; const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxmeGxwbG55cHp2anJoZnRhb29nIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDkyMTQ3NzIsImV4cCI6MjA2NDc5MDc3Mn0.XR4preBqWAQ1rT4PFbpkmRdz57BTwIusBI89fIxDHM8'; // Initialize Supabase client const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); // Global variables let currentUser = null; // Check authentication and load dashboard 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 currentUser = { id: '9966cffd-2088-423c-b852-0ca7996cda97', email: 'admin@speedrun-arena.com' }; displayUserInfo({ email: 'admin@speedrun-arena.com' }); showDashboard(); // Check times section checkLinkStatusAndLoadTimes(); return; } if (!session) { // No session, redirect to login window.location.href = '/login'; return; } // User is authenticated, show dashboard if (session.user) { console.log('User data:', session.user); currentUser = session.user; displayUserInfo(session.user); } else { // Fallback if no user data currentUser = { id: '9966cffd-2088-423c-b852-0ca7996cda97', email: 'admin@speedrun-arena.com' }; displayUserInfo({ email: 'admin@speedrun-arena.com' }); } showDashboard(); // Load times section checkLinkStatusAndLoadTimes(); } catch (error) { console.error('An unexpected error occurred:', error); // window.location.href = '/login'; } } // Display user information function displayUserInfo(user) { const userEmail = document.getElementById('userEmail'); const userAvatar = document.getElementById('userAvatar'); userEmail.textContent = user.email; userAvatar.textContent = user.email.charAt(0).toUpperCase(); } // Show dashboard content function showDashboard() { document.getElementById('loading').style.display = 'none'; document.getElementById('dashboardContent').style.display = 'block'; } // Logout function async function logout() { try { const { error } = await supabase.auth.signOut(); if (error) { console.error('Error logging out:', error); } else { window.location.href = '/'; } } catch (error) { console.error('Error during logout:', error); } } // Listen for auth state changes supabase.auth.onAuthStateChange((event, session) => { if (event === 'SIGNED_OUT' || !session) { window.location.href = '/login'; } }); // Initialize dashboard when page loads initDashboard(); // Modal functions function openModal(modalId) { document.getElementById(modalId).style.display = 'block'; } function closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; // Reset modal state if (modalId === 'rfidModal') { stopQRScanner(); document.getElementById('manualRfidInput').value = ''; } } // Close modal when clicking outside window.onclick = function(event) { if (event.target.classList.contains('modal')) { closeModal(event.target.id); } } // QR Scanner variables let qrStream = null; let qrScanning = false; // Show RFID Settings async function showRFIDSettings() { openModal('rfidModal'); // Reset scanner state stopQRScanner(); } // Check link status and load times async function checkLinkStatusAndLoadTimes() { if (!currentUser) { showTimesNotLinked(); return; } try { // Check if user has a linked player const response = await fetch(`/api/v1/public/user-player/${currentUser.id}`); if (response.ok) { const result = await response.json(); // User is linked, load times await loadUserTimesSection(result.data); } else { // User is not linked showTimesNotLinked(); } } catch (error) { console.error('Error checking link status:', error); showTimesNotLinked(); } } // Start QR Scanner async function startQRScanner() { try { // Request camera access 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'); } } // 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'; document.getElementById('stopScanBtn').style.display = 'none'; document.getElementById('scanningStatus').style.display = 'none'; } // 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); } } // Format RFID UID to match database format 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 cleanUid = '00' + cleanUid; } else if (cleanUid.length === 8) { // Already correct length } else if (cleanUid.length < 6) { // Pad shorter UIDs to 8 digits cleanUid = cleanUid.padStart(8, '0'); } 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(':'); } // 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'); } } // 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'); } } // Link user by RFID UID (core function) async function linkUserByRfidUid(rfidUid) { if (!currentUser) { showMessage('rfidMessage', 'Benutzer nicht authentifiziert', 'error'); return; } try { // First, find the player with this RFID UID const response = await fetch('/api/v1/public/link-by-rfid', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rfiduid: rfidUid, supabase_user_id: currentUser.id }) }); const result = await response.json(); if (response.ok) { showMessage('rfidMessage', `✅ RFID erfolgreich verknüpft!\nSpieler: ${result.data.firstname} ${result.data.lastname}`, 'success'); setTimeout(() => { closeModal('rfidModal'); // Reload times section after successful linking checkLinkStatusAndLoadTimes(); }, 2000); } else { showMessage('rfidMessage', result.message || 'Fehler beim Verknüpfen', 'error'); } } catch (error) { console.error('Error linking RFID:', error); showMessage('rfidMessage', 'Fehler beim Verknüpfen der RFID', 'error'); } } // Show not linked state function showTimesNotLinked() { document.getElementById('timesLoading').style.display = 'none'; document.getElementById('timesNotLinked').style.display = 'block'; document.getElementById('timesDisplay').style.display = 'none'; } // Show loading state function showTimesLoading() { document.getElementById('timesLoading').style.display = 'block'; document.getElementById('timesNotLinked').style.display = 'none'; document.getElementById('timesDisplay').style.display = 'none'; } // Load user times for the section async function loadUserTimesSection(playerData) { showTimesLoading(); try { const response = await fetch(`/api/v1/public/user-times/${currentUser.id}`); 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(); } } // Update stats cards function updateTimesStats(times, playerData) { // Total runs document.getElementById('totalRuns').textContent = times.length; // Best time if (times.length > 0) { const bestTimeValue = times.reduce((best, current) => { const currentSeconds = convertTimeToSeconds(current.recorded_time); const bestSeconds = convertTimeToSeconds(best.recorded_time); return currentSeconds < bestSeconds ? current : best; }); document.getElementById('bestTime').textContent = formatTime(bestTimeValue.recorded_time); } 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}`; } // Display user times in grid function displayUserTimes(times) { const timesGrid = document.getElementById('userTimesGrid'); if (times.length === 0) { timesGrid.innerHTML = `
Deine ersten Läufe werden hier angezeigt, sobald du sie abgeschlossen hast!
Starte deine ersten Läufe, um Achievements zu sammeln!
${achievement.description}
Du hast ${newAchievements.length} neue Achievement${newAchievements.length > 1 ? 's' : ''} erhalten!