/** * Shows a specific form (by ID) and hides others. * Also clears any existing success or error messages. */ function showForm(formId) { document.querySelectorAll('.form').forEach(form => { form.classList.remove('active'); }); // Clear all existing error/success messages document.querySelectorAll('.error, .success').forEach(msg => { msg.textContent = ''; }); // Add slight delay for smooth CSS transition setTimeout(() => { document.getElementById(formId).classList.add('active'); }, 100); } /** * Displays a message in a specified element with optional success styling. */ function showMessage(elementId, message, isSuccess = false) { const element = document.getElementById(elementId); element.textContent = message; element.className = isSuccess ? 'success' : 'error'; } /** * Sets loading state for a button (e.g., during form submission). * Disables the button and clears the text when loading, restores after. */ function setButtonLoading(button, isLoading) { if (isLoading) { button.classList.add('loading'); button.disabled = true; button.textContent = ''; } else { button.classList.remove('loading'); button.disabled = false; button.textContent = button.getAttribute('data-original-text') || 'Submit'; } } // Initialize when the DOM is fully loaded document.addEventListener('DOMContentLoaded', function() { /** * Store original button texts (used to restore text after loading). */ document.querySelectorAll('button[type="submit"]').forEach(btn => { btn.setAttribute('data-original-text', btn.textContent); }); /** * Handles admin login form submission. * Sends login data to server and displays result or error messages. */ document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const submitBtn = e.target.querySelector('button[type="submit"]'); setButtonLoading(submitBtn, true); const username = document.getElementById('loginUsername').value; const password = document.getElementById('loginPassword').value; try { const response = await fetch('/admin/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }) }); if (response.ok) { showMessage('loginMessage', 'Login successful! Redirecting...', true); setTimeout(() => { window.location.href = '/'; }, 1500); } else { const data = await response.json(); showMessage('loginMessage', data.detail || 'Login failed'); } } catch (error) { showMessage('loginMessage', 'An error occurred'); } finally { setButtonLoading(submitBtn, false); submitBtn.textContent = 'Login'; } }); /** * Adds a ripple click effect to all buttons. * Creates a circle animation where the button is clicked. */ document.querySelectorAll('button').forEach(button => { button.addEventListener('click', function(e) { const ripple = document.createElement('div'); const rect = this.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; ripple.style.cssText = ` position: absolute; width: ${size}px; height: ${size}px; background: rgba(255,255,255,0.3); border-radius: 50%; transform: scale(0); left: ${x}px; top: ${y}px; animation: ripple 0.6s ease-out; pointer-events: none; `; this.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 600); }); }); /** * Injects keyframe animation styles for ripple effect and ensures buttons are styled to allow overflow for the animation. */ const style = document.createElement('style'); style.textContent = ` @keyframes ripple { to { transform: scale(2); opacity: 0; } } button { position: relative; overflow: hidden; } `; document.head.appendChild(style); });