/** * Tom's Java Jive - Main JavaScript */ // ================================ // Toast Notifications // ================================ const ToastManager = { container: null, init() { if (!this.container) { this.container = document.createElement('div'); this.container.className = 'toast-container'; document.body.appendChild(this.container); } }, show(message, type = 'success', duration = 3000) { this.init(); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.innerHTML = ` ${message} `; this.container.appendChild(toast); setTimeout(() => { toast.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => toast.remove(), 300); }, duration); }, success(message) { this.show(message, 'success'); }, error(message) { this.show(message, 'error'); }, info(message) { this.show(message, 'info'); } }; // ================================ // Cart Functions // ================================ async function addToCart(productId, quantity = 1) { try { const response = await fetch('/api/cart.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'add', product_id: productId, quantity: parseInt(quantity) }) }); const data = await response.json(); if (data.error) { ToastManager.error(data.error); return; } // Update cart count in header updateCartCount(data.cart_count); ToastManager.success(data.message || 'Added to cart!'); } catch (error) { console.error('Add to cart error:', error); ToastManager.error('Failed to add item to cart'); } } async function updateCartItem(productId, quantity) { try { const response = await fetch('/api/cart.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'update', product_id: productId, quantity: parseInt(quantity) }) }); const data = await response.json(); if (data.error) { ToastManager.error(data.error); return; } // Update cart count updateCartCount(data.cart_count); // Reload page if cart is now empty or update display if (data.cart_count === 0) { location.reload(); } else if (typeof updateCartDisplay === 'function') { updateCartDisplay(data); } else { location.reload(); } } catch (error) { console.error('Update cart error:', error); ToastManager.error('Failed to update cart'); } } async function removeFromCart(productId) { try { const response = await fetch('/api/cart.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'remove', product_id: productId }) }); const data = await response.json(); if (data.error) { ToastManager.error(data.error); return; } updateCartCount(data.cart_count); // Remove item from DOM or reload const cartItem = document.querySelector(`.cart-item[data-product-id="${productId}"]`); if (cartItem) { cartItem.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => { cartItem.remove(); if (data.cart_count === 0) { location.reload(); } }, 300); } else { location.reload(); } ToastManager.success('Item removed from cart'); } catch (error) { console.error('Remove from cart error:', error); ToastManager.error('Failed to remove item'); } } function updateCartCount(count) { const cartCountEl = document.querySelector('.cart-count'); if (cartCountEl) { if (count > 0) { cartCountEl.textContent = count; cartCountEl.style.display = 'flex'; } else { cartCountEl.style.display = 'none'; } } else if (count > 0) { const cartLink = document.querySelector('.cart-link'); if (cartLink) { const badge = document.createElement('span'); badge.className = 'cart-count'; badge.textContent = count; cartLink.appendChild(badge); } } } // ================================ // Quantity Selectors // ================================ document.addEventListener('DOMContentLoaded', function() { // Quantity +/- buttons document.querySelectorAll('.qty-minus').forEach(btn => { btn.addEventListener('click', function() { const input = this.closest('.quantity-selector').querySelector('.qty-input'); const current = parseInt(input.value) || 1; if (current > 1) { input.value = current - 1; input.dispatchEvent(new Event('change')); } }); }); document.querySelectorAll('.qty-plus').forEach(btn => { btn.addEventListener('click', function() { const input = this.closest('.quantity-selector').querySelector('.qty-input'); const current = parseInt(input.value) || 1; const max = parseInt(input.max) || 999; if (current < max) { input.value = current + 1; input.dispatchEvent(new Event('change')); } }); }); // Add to cart buttons document.querySelectorAll('.add-to-cart-btn').forEach(btn => { btn.addEventListener('click', function(e) { if (!this.dataset.productId) return; e.preventDefault(); const productId = this.dataset.productId; const qtyInput = document.querySelector('.qty-input'); const quantity = qtyInput ? parseInt(qtyInput.value) : 1; addToCart(productId, quantity); }); }); // Mobile menu toggle const menuToggle = document.querySelector('.mobile-menu-toggle'); const navMenu = document.querySelector('.nav-menu'); if (menuToggle && navMenu) { menuToggle.addEventListener('click', function() { navMenu.classList.toggle('active'); this.querySelector('i').classList.toggle('fa-bars'); this.querySelector('i').classList.toggle('fa-times'); }); } // Confirm delete dialogs document.querySelectorAll('[data-confirm]').forEach(el => { el.addEventListener('click', function(e) { if (!confirm(this.dataset.confirm)) { e.preventDefault(); } }); }); // Auto-hide alerts document.querySelectorAll('.alert').forEach(alert => { setTimeout(() => { alert.style.opacity = '0'; alert.style.transform = 'translateY(-10px)'; setTimeout(() => alert.remove(), 300); }, 5000); }); }); // ================================ // Form Helpers // ================================ function serializeForm(form) { const formData = new FormData(form); const data = {}; formData.forEach((value, key) => { data[key] = value; }); return data; } async function submitForm(form, url, method = 'POST') { const data = serializeForm(form); try { const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return await response.json(); } catch (error) { console.error('Form submit error:', error); throw error; } } // ================================ // Format Helpers // ================================ function formatCurrency(amount) { return '$' + parseFloat(amount).toFixed(2); } function formatDate(date) { return new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } // ================================ // Loading States // ================================ function setLoading(button, isLoading) { if (isLoading) { button.dataset.originalText = button.innerHTML; button.innerHTML = ' Loading...'; button.disabled = true; } else { button.innerHTML = button.dataset.originalText || button.innerHTML; button.disabled = false; } } // ================================ // Image Preview // ================================ function previewImage(input, previewId) { const preview = document.getElementById(previewId); if (!preview) return; if (input.files && input.files[0]) { const reader = new FileReader(); reader.onload = function(e) { preview.src = e.target.result; preview.style.display = 'block'; }; reader.readAsDataURL(input.files[0]); } } // ================================ // Newsletter Subscription // ================================ document.querySelectorAll('.newsletter-form').forEach(form => { form.addEventListener('submit', async function(e) { e.preventDefault(); const email = this.querySelector('input[type="email"]').value; const button = this.querySelector('button'); setLoading(button, true); try { const response = await fetch('/api/subscribe.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); const data = await response.json(); if (data.error) { ToastManager.error(data.error); } else { ToastManager.success('Thank you for subscribing!'); this.reset(); } } catch (error) { ToastManager.error('Subscription failed. Please try again.'); } finally { setLoading(button, false); } }); }); // ================================ // Debounce Helper // ================================ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // ================================ // Local Storage Helpers // ================================ const Storage = { get(key, defaultValue = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue; } catch { return defaultValue; } }, set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) { console.error('Storage error:', e); } }, remove(key) { localStorage.removeItem(key); } };