/**
* 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);
}
};