Files
Ninjaserver/public/js/dashboard.js
Carsten Graf 2becf784bd 🔧 Fix settings modal: Load current user preferences
- 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)
2025-09-08 19:18:35 +02:00

1060 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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}?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();
}
} 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}?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();
}
}
// 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 = `
<div style="grid-column: 1 / -1; text-align: center; padding: 3rem; color: #8892b0;">
<h3>Noch keine Zeiten aufgezeichnet</h3>
<p>Deine ersten Läufe werden hier angezeigt, sobald du sie abgeschlossen hast!</p>
</div>
`;
return;
}
// Group times by location
const timesByLocation = times.reduce((acc, time) => {
if (!acc[time.location_name]) {
acc[time.location_name] = [];
}
acc[time.location_name].push(time);
return acc;
}, {});
// Generate cards for each location
const cards = Object.entries(timesByLocation).map(([locationName, locationTimes], index) => {
// Sort times by performance (best first)
const sortedTimes = locationTimes.sort((a, b) => {
return convertTimeToSeconds(a.recorded_time) - convertTimeToSeconds(b.recorded_time);
});
// Get best time for this location
const bestTime = sortedTimes[0];
// Generate all runs for expanded view
const allRunsHtml = sortedTimes.map((run, runIndex) => {
let rankBadge = '';
let rankClass = '';
if (runIndex === 0) {
rankBadge = '🥇 Beste';
rankClass = 'best';
} else if (runIndex === 1) {
rankBadge = '🥈 2.';
rankClass = 'second';
} else if (runIndex === 2) {
rankBadge = '🥉 3.';
rankClass = 'third';
} else {
rankBadge = `${runIndex + 1}.`;
rankClass = '';
}
return `
<div class="run-item">
<div>
<div class="run-time">${formatTime(run.recorded_time)}</div>
</div>
<div class="run-details">
<div>${new Date(run.created_at).toLocaleDateString('de-DE')}</div>
<div>${new Date(run.created_at).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}</div>
<span class="run-rank-badge ${rankClass}">${rankBadge}</span>
</div>
</div>
`;
}).join('');
return `
<div class="user-time-card" onclick="toggleTimeCard(this)" data-location="${locationName}">
<div class="card-header">
<div class="time-location-name">${locationName}</div>
<div class="expand-indicator">▼</div>
</div>
<div class="card-main-content">
<div class="time-value-large">${formatTime(bestTime.recorded_time)}</div>
<div class="time-date-info">
<span>${new Date(bestTime.created_at).toLocaleDateString('de-DE')}</span>
<span class="time-rank">${locationTimes.length} Läufe</span>
</div>
</div>
<div class="expanded-content">
<div class="all-runs-title">Alle Läufe an diesem Standort:</div>
${allRunsHtml}
</div>
</div>
`;
}).join('');
timesGrid.innerHTML = cards;
}
// 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');
} else {
cardElement.classList.add('expanded');
}
}
// Helper function to convert time to seconds for comparison
function convertTimeToSeconds(timeValue) {
if (typeof timeValue === 'string') {
// Handle HH:MM:SS format
const parts = timeValue.split(':');
if (parts.length === 3) {
return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseFloat(parts[2]);
}
// Handle MM:SS format
if (parts.length === 2) {
return parseInt(parts[0]) * 60 + parseFloat(parts[1]);
}
}
return parseFloat(timeValue) || 0;
}
// Format time interval to readable format
function formatTime(interval) {
// Postgres interval format: {"hours":0,"minutes":1,"seconds":23.45}
if (typeof interval === 'object') {
const { hours = 0, minutes = 0, seconds = 0 } = 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"
const parts = interval.split(':');
if (parts.length === 3) {
const hours = parseInt(parts[0]);
const minutes = parseInt(parts[1]);
const seconds = parseFloat(parts[2]);
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
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 {
return `${seconds}s`;
}
}
// Show message in modal
function showMessage(containerId, message, type) {
const container = document.getElementById(containerId);
container.innerHTML = `<div class="message ${type}">${message}</div>`;
}
// Initialize when DOM is loaded
// ==================== ACHIEVEMENT FUNCTIONS ====================
// Global variables for achievements
let currentPlayerId = null;
let allAchievements = [];
let playerAchievements = [];
let currentAchievementCategory = 'all';
// Load achievements for the current player
async function loadPlayerAchievements() {
if (!currentPlayerId) {
showAchievementsNotAvailable();
return;
}
try {
// Show loading state
document.getElementById('achievementsLoading').style.display = 'block';
document.getElementById('achievementStats').style.display = 'none';
document.getElementById('achievementCategories').style.display = 'none';
document.getElementById('achievementsNotAvailable').style.display = 'none';
// Load player achievements (includes all achievements with player status)
const response = await fetch(`/api/achievements/player/${currentPlayerId}?t=${Date.now()}`);
if (!response.ok) {
throw new Error('Failed to load player achievements');
}
const result = await response.json();
window.allAchievements = result.data;
playerAchievements = result.data.filter(achievement => achievement.is_completed);
// Load achievement statistics
await loadAchievementStats();
// Show achievements
displayAchievementStats();
displayAchievements();
// Hide loading state
document.getElementById('achievementsLoading').style.display = 'none';
document.getElementById('achievementStats').style.display = 'flex';
document.getElementById('achievementCategories').style.display = 'block';
} catch (error) {
console.error('Error loading achievements:', error);
document.getElementById('achievementsLoading').style.display = 'none';
showAchievementsNotAvailable();
}
}
// Load achievement statistics
async function loadAchievementStats() {
try {
const response = await fetch(`/api/achievements/player/${currentPlayerId}/stats?t=${Date.now()}`);
if (response.ok) {
const result = await response.json();
window.achievementStats = result.data;
}
} catch (error) {
console.error('Error loading achievement stats:', error);
}
}
// Display achievement statistics
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;
document.getElementById('completionPercentage').textContent = `${stats.completion_percentage}%`;
}
// Display achievements in grid
function displayAchievements() {
const achievementsGrid = document.getElementById('achievementsGrid');
if (!window.allAchievements || window.allAchievements.length === 0) {
achievementsGrid.innerHTML = `
<div class="no-achievements">
<div class="no-achievements-icon">🏆</div>
<h3>Noch keine Achievements</h3>
<p>Starte deine ersten Läufe, um Achievements zu sammeln!</p>
</div>
`;
return;
}
// Filter achievements by category
let filteredAchievements = window.allAchievements;
if (currentAchievementCategory !== 'all') {
filteredAchievements = window.allAchievements.filter(achievement =>
achievement.category === currentAchievementCategory
);
}
// Generate achievement cards
const achievementCards = filteredAchievements.map(achievement => {
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')}` :
'Abgeschlossen';
} else if (progress > 0) {
// Show progress for incomplete achievements
const conditionValue = getAchievementConditionValue(achievement.name);
if (conditionValue) {
progressText = `${progress}/${conditionValue}`;
}
}
return `
<div class="achievement-card ${isCompleted ? 'completed' : 'incomplete'}"
onclick="showAchievementDetails('${achievement.id}')">
<div class="achievement-icon">${achievement.icon}</div>
<div class="achievement-content">
<h4 class="achievement-name">${achievement.name}</h4>
<p class="achievement-description">${achievement.description}</p>
<div class="achievement-meta">
<span class="achievement-points">+${achievement.points} Punkte</span>
${progressText ? `<span class="achievement-progress">${progressText}</span>` : ''}
</div>
</div>
<div class="achievement-status">
${isCompleted ? '✅' : '⏳'}
</div>
</div>
`;
}).join('');
achievementsGrid.innerHTML = achievementCards;
}
// Get achievement condition value for progress display
function getAchievementConditionValue(achievementName) {
const conditionMap = {
'Erste Schritte': 1,
'Durchhalter': 3,
'Fleißig': 5,
'Besessen': 10,
'Regelmäßig': 5,
'Stammgast': 10,
'Treue': 20,
'Veteran': 50,
'Fortschritt': 5,
'Durchbruch': 10,
'Transformation': 15,
'Perfektionist': 20
};
return conditionMap[achievementName] || null;
}
// 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();
}
// Show achievement details (placeholder for future modal)
function showAchievementDetails(achievementId) {
const achievement = playerAchievements.find(a => a.id === achievementId);
if (achievement) {
console.log('Achievement details:', achievement);
// TODO: Implement achievement details modal
}
}
// Show achievements not available state
function showAchievementsNotAvailable() {
document.getElementById('achievementsLoading').style.display = 'none';
document.getElementById('achievementStats').style.display = 'none';
document.getElementById('achievementCategories').style.display = 'none';
document.getElementById('achievementsNotAvailable').style.display = 'block';
}
// Check achievements for current player
async function checkPlayerAchievements() {
if (!currentPlayerId) return;
try {
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) {
// Show notification for new achievements
showAchievementNotification(result.data.new_achievements);
// Reload achievements
await loadPlayerAchievements();
}
}
} catch (error) {
console.error('Error checking achievements:', error);
}
}
// Show achievement notification
function showAchievementNotification(newAchievements) {
// Create notification element
const notification = document.createElement('div');
notification.className = 'achievement-notification';
notification.innerHTML = `
<div class="notification-content">
<div class="notification-icon">🏆</div>
<div class="notification-text">
<h4>Neue Achievements erreicht!</h4>
<p>Du hast ${newAchievements.length} neue Achievement${newAchievements.length > 1 ? 's' : ''} erhalten!</p>
</div>
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">×</button>
</div>
`;
// Add to page
document.body.appendChild(notification);
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 5000);
}
// Initialize achievements when player is loaded
function initializeAchievements(playerId) {
currentPlayerId = playerId;
loadPlayerAchievements();
}
// Web Notification Functions
function showWebNotification(title, message, icon = '🏆') {
if ('Notification' in window && Notification.permission === 'granted') {
const notification = new Notification(title, {
body: message,
icon: '/pictures/icon-192.png',
badge: '/pictures/icon-192.png',
tag: 'ninjacross-achievement',
requireInteraction: true
});
// Auto-close after 10 seconds
setTimeout(() => {
notification.close();
}, 10000);
// Handle click
notification.onclick = function () {
window.focus();
notification.close();
};
}
}
// Check for best time achievements and show notifications
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) {
showWebNotification(
'🏆 Tageskönig!',
`Glückwunsch! Du hast die beste Zeit des Tages mit ${daily.best_time} erreicht!`,
'👑'
);
}
if (weekly && weekly.player_id === currentPlayerId) {
showWebNotification(
'🏆 Wochenchampion!',
`Fantastisch! Du bist der Wochenchampion mit ${weekly.best_time}!`,
'🏆'
);
}
if (monthly && monthly.player_id === currentPlayerId) {
showWebNotification(
'🏆 Monatsmeister!',
`Unglaublich! Du bist der Monatsmeister mit ${monthly.best_time}!`,
'🥇'
);
}
}
}
} catch (error) {
console.error('Error checking best time notifications:', error);
}
}
// Check for new achievements and show notifications
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
const earnedAt = new Date(achievement.earned_at);
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
return earnedAt > fiveMinutesAgo;
});
if (newAchievements.length > 0) {
newAchievements.forEach(achievement => {
showWebNotification(
`🏆 ${achievement.name}`,
achievement.description,
achievement.icon || '🏆'
);
});
}
}
} catch (error) {
console.error('Error checking achievement notifications:', error);
}
}
// Periodic check for notifications (every 30 seconds)
setInterval(() => {
checkBestTimeNotifications();
checkAchievementNotifications();
}, 30000);
// Settings Functions
function showSettings() {
const modal = document.getElementById('settingsModal');
if (modal) {
modal.style.display = 'block';
loadSettings();
}
}
async function loadSettings() {
try {
if (!currentUser || !currentUser.id) {
console.error('No user ID available');
return;
}
// 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;
}
}
function updateLeaderboardSetting() {
const checkbox = document.getElementById('showInLeaderboard');
console.log('Leaderboard setting changed:', checkbox.checked);
}
async function saveSettings() {
try {
if (!currentPlayerId) {
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',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
player_id: currentPlayerId,
show_in_leaderboard: showInLeaderboard
})
});
const result = await response.json();
if (result.success) {
showNotification('Einstellungen erfolgreich gespeichert!', 'success');
closeModal('settingsModal');
} else {
showNotification('Fehler beim Speichern der Einstellungen: ' + result.message, 'error');
}
} catch (error) {
console.error('Error saving settings:', error);
showNotification('Fehler beim Speichern der Einstellungen', 'error');
}
}
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'};
color: white;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 10000;
max-width: 300px;
font-weight: 500;
`;
notification.textContent = message;
document.body.appendChild(notification);
// Remove notification after 3 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
document.addEventListener('DOMContentLoaded', function () {
// Add cookie settings button functionality
const cookieSettingsBtn = document.getElementById('cookie-settings-footer');
if (cookieSettingsBtn) {
cookieSettingsBtn.addEventListener('click', function () {
if (window.cookieConsent) {
window.cookieConsent.resetConsent();
}
});
}
});