Notifications, div fixes, kekse für last location
This commit is contained in:
105
public/js/cookie-utils.js
Normal file
105
public/js/cookie-utils.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// Cookie Utility Functions
|
||||
class CookieManager {
|
||||
// Set a cookie
|
||||
static setCookie(name, value, days = 30) {
|
||||
const expires = new Date();
|
||||
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
|
||||
}
|
||||
|
||||
// Get a cookie
|
||||
static getCookie(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Delete a cookie
|
||||
static deleteCookie(name) {
|
||||
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
|
||||
}
|
||||
|
||||
// Check if cookies are enabled
|
||||
static areCookiesEnabled() {
|
||||
try {
|
||||
this.setCookie('test', 'test');
|
||||
const enabled = this.getCookie('test') === 'test';
|
||||
this.deleteCookie('test');
|
||||
return enabled;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Location-specific cookie functions
|
||||
class LocationCookieManager {
|
||||
static COOKIE_NAME = 'ninjacross_last_location';
|
||||
static COOKIE_EXPIRY_DAYS = 90; // 3 months
|
||||
|
||||
// Save last selected location
|
||||
static saveLastLocation(locationId, locationName) {
|
||||
if (!locationId || !locationName) return;
|
||||
|
||||
const locationData = {
|
||||
id: locationId,
|
||||
name: locationName,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
try {
|
||||
CookieManager.setCookie(
|
||||
this.COOKIE_NAME,
|
||||
JSON.stringify(locationData),
|
||||
this.COOKIE_EXPIRY_DAYS
|
||||
);
|
||||
console.log('✅ Location saved to cookie:', locationName);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save location to cookie:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get last selected location
|
||||
static getLastLocation() {
|
||||
try {
|
||||
const cookieValue = CookieManager.getCookie(this.COOKIE_NAME);
|
||||
if (!cookieValue) return null;
|
||||
|
||||
const locationData = JSON.parse(cookieValue);
|
||||
|
||||
// Check if cookie is not too old (optional: 30 days max)
|
||||
const cookieDate = new Date(locationData.timestamp);
|
||||
const maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
|
||||
if (Date.now() - cookieDate.getTime() > maxAge) {
|
||||
this.clearLastLocation();
|
||||
return null;
|
||||
}
|
||||
|
||||
return locationData;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to parse location cookie:', error);
|
||||
this.clearLastLocation();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear last location
|
||||
static clearLastLocation() {
|
||||
CookieManager.deleteCookie(this.COOKIE_NAME);
|
||||
console.log('🗑️ Location cookie cleared');
|
||||
}
|
||||
|
||||
// Check if location cookie exists
|
||||
static hasLastLocation() {
|
||||
return this.getLastLocation() !== null;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other scripts
|
||||
window.CookieManager = CookieManager;
|
||||
window.LocationCookieManager = LocationCookieManager;
|
||||
@@ -132,12 +132,13 @@ async function checkLinkStatusAndLoadTimes() {
|
||||
|
||||
try {
|
||||
// Check if user has a linked player
|
||||
const response = await fetch(`/api/v1/public/user-player/${currentUser.id}`);
|
||||
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();
|
||||
@@ -362,7 +363,7 @@ async function loadUserTimesSection(playerData) {
|
||||
showTimesLoading();
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/public/user-times/${currentUser.id}`);
|
||||
const response = await fetch(`/api/v1/public/user-times/${currentUser.id}?t=${Date.now()}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -608,14 +609,15 @@ async function loadPlayerAchievements() {
|
||||
document.getElementById('achievementCategories').style.display = 'none';
|
||||
document.getElementById('achievementsNotAvailable').style.display = 'none';
|
||||
|
||||
// Load player achievements
|
||||
const response = await fetch(`/api/achievements/player/${currentPlayerId}`);
|
||||
// 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 achievements');
|
||||
throw new Error('Failed to load player achievements');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
playerAchievements = result.data;
|
||||
window.allAchievements = result.data;
|
||||
playerAchievements = result.data.filter(achievement => achievement.is_completed);
|
||||
|
||||
// Load achievement statistics
|
||||
await loadAchievementStats();
|
||||
@@ -639,7 +641,7 @@ async function loadPlayerAchievements() {
|
||||
// Load achievement statistics
|
||||
async function loadAchievementStats() {
|
||||
try {
|
||||
const response = await fetch(`/api/achievements/player/${currentPlayerId}/stats`);
|
||||
const response = await fetch(`/api/achievements/player/${currentPlayerId}/stats?t=${Date.now()}`);
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
window.achievementStats = result.data;
|
||||
@@ -665,7 +667,7 @@ function displayAchievementStats() {
|
||||
function displayAchievements() {
|
||||
const achievementsGrid = document.getElementById('achievementsGrid');
|
||||
|
||||
if (playerAchievements.length === 0) {
|
||||
if (!window.allAchievements || window.allAchievements.length === 0) {
|
||||
achievementsGrid.innerHTML = `
|
||||
<div class="no-achievements">
|
||||
<div class="no-achievements-icon">🏆</div>
|
||||
@@ -677,9 +679,9 @@ function displayAchievements() {
|
||||
}
|
||||
|
||||
// Filter achievements by category
|
||||
let filteredAchievements = playerAchievements;
|
||||
let filteredAchievements = window.allAchievements;
|
||||
if (currentAchievementCategory !== 'all') {
|
||||
filteredAchievements = playerAchievements.filter(achievement =>
|
||||
filteredAchievements = window.allAchievements.filter(achievement =>
|
||||
achievement.category === currentAchievementCategory
|
||||
);
|
||||
}
|
||||
@@ -690,6 +692,11 @@ function displayAchievements() {
|
||||
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 ?
|
||||
@@ -780,7 +787,7 @@ async function checkPlayerAchievements() {
|
||||
if (!currentPlayerId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/achievements/check/${currentPlayerId}`, {
|
||||
const response = await fetch(`/api/achievements/check/${currentPlayerId}?t=${Date.now()}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
@@ -831,6 +838,109 @@ function initializeAchievements(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/favicon.ico',
|
||||
badge: '/pictures/favicon.ico',
|
||||
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);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Add cookie settings button functionality
|
||||
const cookieSettingsBtn = document.getElementById('cookie-settings-footer');
|
||||
|
||||
@@ -10,6 +10,58 @@ const socket = io();
|
||||
|
||||
// Global variable to store locations with coordinates
|
||||
let locationsData = [];
|
||||
let lastSelectedLocation = null;
|
||||
|
||||
// Cookie Functions (inline implementation)
|
||||
function setCookie(name, value, days = 30) {
|
||||
const expires = new Date();
|
||||
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function loadLastSelectedLocation() {
|
||||
try {
|
||||
const cookieValue = getCookie('ninjacross_last_location');
|
||||
if (cookieValue) {
|
||||
const lastLocation = JSON.parse(cookieValue);
|
||||
lastSelectedLocation = lastLocation;
|
||||
console.log('📍 Last selected location loaded:', lastLocation.name);
|
||||
return lastLocation;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading last location:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function saveLocationSelection(locationId, locationName) {
|
||||
try {
|
||||
// Remove emoji from location name for storage
|
||||
const cleanName = locationName.replace(/^📍\s*/, '');
|
||||
const locationData = {
|
||||
id: locationId,
|
||||
name: cleanName,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
setCookie('ninjacross_last_location', JSON.stringify(locationData), 90);
|
||||
lastSelectedLocation = { id: locationId, name: cleanName };
|
||||
console.log('💾 Location saved to cookie:', cleanName);
|
||||
} catch (error) {
|
||||
console.error('Error saving location:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket Event Handlers
|
||||
socket.on('connect', () => {
|
||||
@@ -129,6 +181,23 @@ async function loadLocations() {
|
||||
locationSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Load and set last selected location
|
||||
const lastLocation = loadLastSelectedLocation();
|
||||
if (lastLocation) {
|
||||
// Find the option that matches the last location name
|
||||
const matchingOption = Array.from(locationSelect.options).find(option =>
|
||||
option.textContent === `📍 ${lastLocation.name}` || option.value === lastLocation.name
|
||||
);
|
||||
if (matchingOption) {
|
||||
locationSelect.value = matchingOption.value;
|
||||
console.log('📍 Last selected location restored:', lastLocation.name);
|
||||
// Update the current selection display
|
||||
updateCurrentSelection();
|
||||
// Load data for the restored location
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading locations:', error);
|
||||
}
|
||||
@@ -489,7 +558,15 @@ function updateLeaderboard(data) {
|
||||
// Event Listeners Setup
|
||||
function setupEventListeners() {
|
||||
// Location select event listener
|
||||
document.getElementById('locationSelect').addEventListener('change', loadData);
|
||||
document.getElementById('locationSelect').addEventListener('change', function() {
|
||||
// Save location selection to cookie
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
if (selectedOption.value) {
|
||||
saveLocationSelection(selectedOption.value, selectedOption.textContent);
|
||||
}
|
||||
// Load data
|
||||
loadData();
|
||||
});
|
||||
|
||||
// Time tab event listeners
|
||||
document.querySelectorAll('.time-tab').forEach(tab => {
|
||||
|
||||
Reference in New Issue
Block a user