d595b468e9
- Created a new Impressum page with legal information and contact details. - Developed admin link page for linking QR codes to events with form functionality. - Implemented QR code dashboard for managing QR code sets, including creation and quick actions. - Added detailed view for QR code sets, allowing addition of QR codes and management actions. - Introduced static file serving for favicon and related images. - Established views for static content pages (about, contact, privacy, terms). - Implemented translation management scripts for compiling and updating translations.
191 lines
4.9 KiB
JavaScript
191 lines
4.9 KiB
JavaScript
/**
|
|
* LeagueLedger Main JavaScript
|
|
* Version: 1.0.0
|
|
* Date: April 13, 2025
|
|
*/
|
|
|
|
// Initialize all components when document is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initializeQrScanner();
|
|
setupModalHandlers();
|
|
initializeTooltips();
|
|
initializeDropdowns();
|
|
});
|
|
|
|
/**
|
|
* QR Code Scanner initialization
|
|
*/
|
|
function initializeQrScanner() {
|
|
const scannerContainer = document.getElementById('qr-reader');
|
|
|
|
if (!scannerContainer) return; // Exit if scanner container doesn't exist
|
|
|
|
// Check if camera access is available
|
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
// QR Scanner library configuration
|
|
const html5QrCode = new Html5Qrcode("qr-reader");
|
|
const config = { fps: 10, qrbox: 250 };
|
|
|
|
// Remove overlay once scanner is ready
|
|
const overlay = document.getElementById('scanner-overlay');
|
|
if (overlay) {
|
|
overlay.style.display = 'none';
|
|
}
|
|
|
|
// Start scanner
|
|
html5QrCode.start(
|
|
{ facingMode: "environment" }, // Use rear camera
|
|
config,
|
|
onScanSuccess,
|
|
onScanFailure
|
|
);
|
|
|
|
// Save scanner instance for later use
|
|
window.qrScanner = html5QrCode;
|
|
} else {
|
|
// Camera unavailable, show manual entry form
|
|
const manualEntry = document.getElementById('manual-entry-container');
|
|
if (manualEntry) {
|
|
manualEntry.style.display = 'block';
|
|
}
|
|
|
|
const scannerElement = document.getElementById('scanner-container');
|
|
if (scannerElement) {
|
|
scannerElement.style.display = 'none';
|
|
}
|
|
|
|
console.warn("Camera access not available");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle successful QR code scan
|
|
*/
|
|
function onScanSuccess(decodedText) {
|
|
// Stop scanner once code is detected
|
|
if (window.qrScanner) {
|
|
window.qrScanner.stop();
|
|
}
|
|
|
|
// Extract code from URL if present
|
|
let code = decodedText;
|
|
if (decodedText.includes('/redeem/')) {
|
|
code = decodedText.split('/redeem/').pop();
|
|
}
|
|
|
|
// Redirect to redemption page
|
|
window.location.href = '/redeem/' + code;
|
|
}
|
|
|
|
/**
|
|
* Handle QR scan errors
|
|
*/
|
|
function onScanFailure(error) {
|
|
// We don't need to show errors for normal operation
|
|
console.debug("QR scan error: " + error);
|
|
}
|
|
|
|
/**
|
|
* Initialize modal handlers
|
|
*/
|
|
function setupModalHandlers() {
|
|
// Find all elements meant to open modals
|
|
const modalOpeners = document.querySelectorAll('[data-modal-target]');
|
|
const modalClosers = document.querySelectorAll('[data-modal-close]');
|
|
|
|
modalOpeners.forEach(opener => {
|
|
opener.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const modalId = opener.getAttribute('data-modal-target');
|
|
const modal = document.getElementById(modalId);
|
|
|
|
if (modal) {
|
|
modal.classList.remove('hidden');
|
|
}
|
|
});
|
|
});
|
|
|
|
modalClosers.forEach(closer => {
|
|
closer.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const modal = closer.closest('.modal');
|
|
|
|
if (modal) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Close modal when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
const modals = document.querySelectorAll('.modal:not(.hidden)');
|
|
modals.forEach(modal => {
|
|
if (e.target === modal) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize tooltips
|
|
*/
|
|
function initializeTooltips() {
|
|
const tooltips = document.querySelectorAll('[data-tooltip]');
|
|
|
|
tooltips.forEach(tooltip => {
|
|
tooltip.addEventListener('mouseenter', (e) => {
|
|
const text = tooltip.getAttribute('data-tooltip');
|
|
|
|
// Create tooltip element
|
|
const tooltipEl = document.createElement('div');
|
|
tooltipEl.classList.add('tooltip');
|
|
tooltipEl.textContent = text;
|
|
|
|
// Position the tooltip
|
|
const rect = tooltip.getBoundingClientRect();
|
|
tooltipEl.style.top = (rect.top - 30) + 'px';
|
|
tooltipEl.style.left = (rect.left + rect.width/2) + 'px';
|
|
|
|
// Add to DOM
|
|
document.body.appendChild(tooltipEl);
|
|
|
|
// Save reference to remove it later
|
|
tooltip._tooltipElement = tooltipEl;
|
|
});
|
|
|
|
tooltip.addEventListener('mouseleave', () => {
|
|
if (tooltip._tooltipElement) {
|
|
tooltip._tooltipElement.remove();
|
|
tooltip._tooltipElement = null;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize dropdown menus
|
|
*/
|
|
function initializeDropdowns() {
|
|
const dropdowns = document.querySelectorAll('.dropdown-toggle');
|
|
|
|
dropdowns.forEach(dropdown => {
|
|
dropdown.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const menu = dropdown.nextElementSibling;
|
|
if (menu && menu.classList.contains('dropdown-menu')) {
|
|
menu.classList.toggle('hidden');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Close dropdowns when clicking elsewhere
|
|
document.addEventListener('click', () => {
|
|
const openDropdowns = document.querySelectorAll('.dropdown-menu:not(.hidden)');
|
|
openDropdowns.forEach(menu => {
|
|
menu.classList.add('hidden');
|
|
});
|
|
});
|
|
} |