mirror of
https://github.com/myronblair/tomsjavajive-app
synced 2026-06-30 17:50:56 -05:00
395 lines
12 KiB
JavaScript
395 lines
12 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
|
|
<span>${message}</span>
|
|
`;
|
|
|
|
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 = '<span class="loading"></span> 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);
|
|
}
|
|
};
|