mirror of
https://github.com/myronblair/tomsjavajive-app
synced 2026-06-30 17:50:56 -05:00
v1.0.0 - Initial backup
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user