1017 lines
35 KiB
JavaScript
1017 lines
35 KiB
JavaScript
// ===== GLOBAL VARIABLES =====
|
||
let products = [];
|
||
let cart = JSON.parse(localStorage.getItem('sweetDreamsCart')) || [];
|
||
let filteredProducts = [];
|
||
|
||
// EmailJS Configuration
|
||
const emailJSConfig = {
|
||
serviceID: 'YOUR_SERVICE_ID', // Replace with actual EmailJS service ID
|
||
templateID: 'YOUR_TEMPLATE_ID', // Replace with actual EmailJS template ID
|
||
userID: 'YOUR_USER_ID' // Replace with actual EmailJS user ID
|
||
};
|
||
|
||
// ===== INITIALIZATION =====
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializeApp();
|
||
});
|
||
|
||
function initializeApp() {
|
||
// Initialize EmailJS
|
||
if (typeof emailjs !== 'undefined') {
|
||
emailjs.init(emailJSConfig.userID);
|
||
}
|
||
|
||
// Load products
|
||
loadProducts();
|
||
|
||
// Initialize event listeners
|
||
initializeEventListeners();
|
||
|
||
// Update cart UI
|
||
updateCartUI();
|
||
|
||
// Initialize smooth scrolling
|
||
initializeSmoothScrolling();
|
||
|
||
// Initialize header scroll effect
|
||
initializeHeaderEffects();
|
||
}
|
||
|
||
// ===== PRODUCT LOADING =====
|
||
async function loadProducts() {
|
||
try {
|
||
showLoading();
|
||
|
||
const response = await fetch('cake_inventory.csv');
|
||
const csvText = await response.text();
|
||
|
||
products = parseCSV(csvText);
|
||
filteredProducts = [...products];
|
||
|
||
hideLoading();
|
||
renderProducts(filteredProducts);
|
||
|
||
} catch (error) {
|
||
console.error('Error loading products:', error);
|
||
hideLoading();
|
||
showError('Failed to load products. Using demo data.');
|
||
|
||
// Fallback to demo data if CSV fails to load
|
||
products = getDemoProducts();
|
||
filteredProducts = [...products];
|
||
renderProducts(filteredProducts);
|
||
}
|
||
}
|
||
|
||
function parseCSV(csvText) {
|
||
const lines = csvText.trim().split('\n');
|
||
const headers = lines[0].split(',');
|
||
const products = [];
|
||
|
||
for (let i = 1; i < lines.length; i++) {
|
||
const values = parseCSVLine(lines[i]);
|
||
const product = {};
|
||
|
||
headers.forEach((header, index) => {
|
||
const key = header.trim();
|
||
let value = values[index] ? values[index].trim() : '';
|
||
|
||
// Convert numeric fields
|
||
if (['id', 'price', 'size_inches', 'serves_people', 'weight_kg', 'stock_quantity'].includes(key)) {
|
||
value = parseFloat(value) || 0;
|
||
}
|
||
|
||
// Convert boolean fields
|
||
if (['featured', 'custom_available'].includes(key)) {
|
||
value = value.toLowerCase() === 'true';
|
||
}
|
||
|
||
product[key] = value;
|
||
});
|
||
|
||
products.push(product);
|
||
}
|
||
|
||
return products;
|
||
}
|
||
|
||
function parseCSVLine(line) {
|
||
const result = [];
|
||
let current = '';
|
||
let inQuotes = false;
|
||
|
||
for (let i = 0; i < line.length; i++) {
|
||
const char = line[i];
|
||
|
||
if (char === '"') {
|
||
inQuotes = !inQuotes;
|
||
} else if (char === ',' && !inQuotes) {
|
||
result.push(current);
|
||
current = '';
|
||
} else {
|
||
current += char;
|
||
}
|
||
}
|
||
|
||
result.push(current);
|
||
return result;
|
||
}
|
||
|
||
function getDemoProducts() {
|
||
// Fallback demo data in case CSV loading fails
|
||
return [
|
||
{
|
||
id: 1,
|
||
name: "Classic Vanilla Wedding Cake",
|
||
description: "Elegant three-tier vanilla sponge with buttercream roses and pearl details. Perfect centerpiece for your special day.",
|
||
price: 299.99,
|
||
category: "Wedding Cakes",
|
||
size_inches: 10,
|
||
serves_people: 50,
|
||
weight_kg: 3.5,
|
||
flavor: "Vanilla",
|
||
image_url: "https://images.unsplash.com/photo-1578985545062-69928b1d9587?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80",
|
||
image_alt: "Three-tier vanilla wedding cake with white buttercream roses",
|
||
stock_quantity: 2,
|
||
featured: true,
|
||
custom_available: true,
|
||
dietary_options: "gluten-free available, vegan available",
|
||
baker_name: "Sarah Mitchell",
|
||
occasion_type: "wedding"
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "Chocolate Decadence Birthday Cake",
|
||
description: "Rich chocolate cake layered with dark chocolate ganache and fresh berries. A chocolate lover's dream.",
|
||
price: 89.99,
|
||
category: "Birthday Cakes",
|
||
size_inches: 8,
|
||
serves_people: 12,
|
||
weight_kg: 2.2,
|
||
flavor: "Dark Chocolate",
|
||
image_url: "https://images.unsplash.com/photo-1606313564200-e75d5e30476c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80",
|
||
image_alt: "Dark chocolate birthday cake with ganache and berries",
|
||
stock_quantity: 5,
|
||
featured: true,
|
||
custom_available: true,
|
||
dietary_options: "gluten-free available",
|
||
baker_name: "Sarah Mitchell",
|
||
occasion_type: "birthday"
|
||
}
|
||
];
|
||
}
|
||
|
||
// ===== PRODUCT RENDERING =====
|
||
function renderProducts(productsToRender) {
|
||
const grid = document.getElementById('products-grid');
|
||
const noResults = document.getElementById('no-results');
|
||
|
||
if (productsToRender.length === 0) {
|
||
grid.innerHTML = '';
|
||
noResults.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
noResults.style.display = 'none';
|
||
|
||
const html = productsToRender.map(product => createProductCard(product)).join('');
|
||
grid.innerHTML = html;
|
||
|
||
// Add event listeners to product cards
|
||
addProductEventListeners();
|
||
}
|
||
|
||
function createProductCard(product) {
|
||
const isOutOfStock = product.stock_quantity === 0;
|
||
const featuredBadge = product.featured ? '<div class="product__badge">Featured</div>' : '';
|
||
|
||
return `
|
||
<article class="product__card" data-product-id="${product.id}">
|
||
<div class="product__image">
|
||
<img src="${product.image_url}" alt="${product.image_alt}" loading="lazy"
|
||
onerror="this.src='https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80'">
|
||
${featuredBadge}
|
||
</div>
|
||
<div class="product__content">
|
||
<div class="product__category">${product.category}</div>
|
||
<h3 class="product__name">${product.name}</h3>
|
||
<p class="product__description">${product.description}</p>
|
||
<div class="product__meta">
|
||
<span><i class="fas fa-users"></i> Serves ${product.serves_people}</span>
|
||
<span><i class="fas fa-ruler"></i> ${product.size_inches}"</span>
|
||
</div>
|
||
<div class="product__footer">
|
||
<div class="product__price">$${product.price.toFixed(2)}</div>
|
||
<button class="add-to-cart" data-product-id="${product.id}" ${isOutOfStock ? 'disabled' : ''}>
|
||
<i class="fas fa-cart-plus"></i>
|
||
${isOutOfStock ? 'Out of Stock' : 'Add to Cart'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
`;
|
||
}
|
||
|
||
function addProductEventListeners() {
|
||
// Product card click to show details
|
||
document.querySelectorAll('.product__card').forEach(card => {
|
||
card.addEventListener('click', function(e) {
|
||
if (!e.target.closest('.add-to-cart')) {
|
||
const productId = parseInt(this.dataset.productId);
|
||
showProductDetails(productId);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Add to cart buttons
|
||
document.querySelectorAll('.add-to-cart').forEach(button => {
|
||
button.addEventListener('click', function(e) {
|
||
e.stopPropagation();
|
||
const productId = parseInt(this.dataset.productId);
|
||
addToCart(productId);
|
||
});
|
||
});
|
||
}
|
||
|
||
// ===== PRODUCT FILTERING & SEARCH =====
|
||
function initializeFiltering() {
|
||
const searchInput = document.getElementById('search-input');
|
||
const categoryFilter = document.getElementById('category-filter');
|
||
const sortSelect = document.getElementById('sort-select');
|
||
|
||
searchInput.addEventListener('input', debounce(filterProducts, 300));
|
||
categoryFilter.addEventListener('change', filterProducts);
|
||
sortSelect.addEventListener('change', filterProducts);
|
||
}
|
||
|
||
function filterProducts() {
|
||
const searchTerm = document.getElementById('search-input').value.toLowerCase();
|
||
const category = document.getElementById('category-filter').value;
|
||
const sortBy = document.getElementById('sort-select').value;
|
||
|
||
// Filter by search term
|
||
filteredProducts = products.filter(product => {
|
||
const searchFields = [
|
||
product.name,
|
||
product.description,
|
||
product.flavor,
|
||
product.category,
|
||
product.occasion_type
|
||
].join(' ').toLowerCase();
|
||
|
||
return searchFields.includes(searchTerm);
|
||
});
|
||
|
||
// Filter by category
|
||
if (category !== 'all') {
|
||
filteredProducts = filteredProducts.filter(product => product.category === category);
|
||
}
|
||
|
||
// Sort products
|
||
filteredProducts.sort((a, b) => {
|
||
switch (sortBy) {
|
||
case 'price-low':
|
||
return a.price - b.price;
|
||
case 'price-high':
|
||
return b.price - a.price;
|
||
case 'name':
|
||
return a.name.localeCompare(b.name);
|
||
case 'featured':
|
||
default:
|
||
return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);
|
||
}
|
||
});
|
||
|
||
renderProducts(filteredProducts);
|
||
}
|
||
|
||
// ===== SHOPPING CART =====
|
||
function addToCart(productId, quantity = 1) {
|
||
const product = products.find(p => p.id === productId);
|
||
if (!product) return;
|
||
|
||
if (product.stock_quantity === 0) {
|
||
showNotification('This item is currently out of stock', 'error');
|
||
return;
|
||
}
|
||
|
||
const existingItem = cart.find(item => item.id === productId);
|
||
|
||
if (existingItem) {
|
||
const newQuantity = existingItem.quantity + quantity;
|
||
if (newQuantity <= product.stock_quantity) {
|
||
existingItem.quantity = newQuantity;
|
||
showNotification(`Updated ${product.name} quantity in cart`, 'success');
|
||
} else {
|
||
showNotification(`Only ${product.stock_quantity} items available`, 'warning');
|
||
return;
|
||
}
|
||
} else {
|
||
cart.push({
|
||
id: product.id,
|
||
name: product.name,
|
||
price: product.price,
|
||
image_url: product.image_url,
|
||
quantity: quantity,
|
||
max_quantity: product.stock_quantity
|
||
});
|
||
showNotification(`Added ${product.name} to cart`, 'success');
|
||
}
|
||
|
||
saveCart();
|
||
updateCartUI();
|
||
}
|
||
|
||
function removeFromCart(productId) {
|
||
cart = cart.filter(item => item.id !== productId);
|
||
saveCart();
|
||
updateCartUI();
|
||
renderCartItems();
|
||
}
|
||
|
||
function updateCartQuantity(productId, quantity) {
|
||
const item = cart.find(item => item.id === productId);
|
||
if (!item) return;
|
||
|
||
if (quantity <= 0) {
|
||
removeFromCart(productId);
|
||
return;
|
||
}
|
||
|
||
if (quantity > item.max_quantity) {
|
||
showNotification(`Only ${item.max_quantity} items available`, 'warning');
|
||
return;
|
||
}
|
||
|
||
item.quantity = quantity;
|
||
saveCart();
|
||
updateCartUI();
|
||
renderCartItems();
|
||
}
|
||
|
||
function saveCart() {
|
||
localStorage.setItem('sweetDreamsCart', JSON.stringify(cart));
|
||
}
|
||
|
||
function updateCartUI() {
|
||
const cartCount = document.getElementById('cart-count');
|
||
const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
|
||
cartCount.textContent = totalItems;
|
||
cartCount.style.display = totalItems > 0 ? 'flex' : 'none';
|
||
}
|
||
|
||
function getCartTotal() {
|
||
return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
||
}
|
||
|
||
// ===== MODAL MANAGEMENT =====
|
||
function initializeModals() {
|
||
// Cart modal
|
||
const cartBtn = document.getElementById('cart-btn');
|
||
const cartModal = document.getElementById('cart-modal');
|
||
const cartClose = document.getElementById('cart-modal-close');
|
||
|
||
cartBtn.addEventListener('click', showCartModal);
|
||
cartClose.addEventListener('click', () => hideModal('cart-modal'));
|
||
|
||
// Product modal
|
||
const productModal = document.getElementById('product-modal');
|
||
const productClose = document.getElementById('product-modal-close');
|
||
productClose.addEventListener('click', () => hideModal('product-modal'));
|
||
|
||
// Checkout modal
|
||
const checkoutBtn = document.getElementById('checkout-btn');
|
||
const checkoutModal = document.getElementById('checkout-modal');
|
||
const checkoutClose = document.getElementById('checkout-modal-close');
|
||
|
||
checkoutBtn.addEventListener('click', showCheckoutModal);
|
||
checkoutClose.addEventListener('click', () => hideModal('checkout-modal'));
|
||
|
||
// Close modals on backdrop click
|
||
[cartModal, productModal, checkoutModal].forEach(modal => {
|
||
modal.addEventListener('click', function(e) {
|
||
if (e.target === this) {
|
||
hideModal(this.id);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Close modals on Escape key
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
const activeModal = document.querySelector('.modal.active');
|
||
if (activeModal) {
|
||
hideModal(activeModal.id);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function showModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
modal.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function hideModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
modal.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
function showCartModal() {
|
||
renderCartItems();
|
||
showModal('cart-modal');
|
||
}
|
||
|
||
function renderCartItems() {
|
||
const cartItems = document.getElementById('cart-items');
|
||
const cartEmpty = document.getElementById('cart-empty');
|
||
const cartFooter = document.getElementById('cart-footer');
|
||
const cartTotal = document.getElementById('cart-total');
|
||
|
||
if (cart.length === 0) {
|
||
cartItems.innerHTML = '';
|
||
cartEmpty.style.display = 'block';
|
||
cartFooter.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
cartEmpty.style.display = 'none';
|
||
cartFooter.style.display = 'block';
|
||
|
||
const html = cart.map(item => `
|
||
<div class="cart__item" data-product-id="${item.id}">
|
||
<img src="${item.image_url}" alt="${item.name}" class="cart__item-image"
|
||
onerror="this.src='https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80'">
|
||
<div class="cart__item-content">
|
||
<div class="cart__item-name">${item.name}</div>
|
||
<div class="cart__item-price">$${item.price.toFixed(2)} each</div>
|
||
<div class="cart__item-controls">
|
||
<button class="quantity-btn" onclick="updateCartQuantity(${item.id}, ${item.quantity - 1})">
|
||
<i class="fas fa-minus"></i>
|
||
</button>
|
||
<input type="number" class="quantity-input" value="${item.quantity}"
|
||
min="1" max="${item.max_quantity}"
|
||
onchange="updateCartQuantity(${item.id}, parseInt(this.value))">
|
||
<button class="quantity-btn" onclick="updateCartQuantity(${item.id}, ${item.quantity + 1})">
|
||
<i class="fas fa-plus"></i>
|
||
</button>
|
||
<button class="remove-item" onclick="removeFromCart(${item.id})">
|
||
<i class="fas fa-trash"></i> Remove
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
cartItems.innerHTML = html;
|
||
cartTotal.textContent = `$${getCartTotal().toFixed(2)}`;
|
||
}
|
||
|
||
function showProductDetails(productId) {
|
||
const product = products.find(p => p.id === productId);
|
||
if (!product) return;
|
||
|
||
const modalTitle = document.getElementById('product-modal-title');
|
||
const productDetail = document.getElementById('product-detail');
|
||
|
||
modalTitle.textContent = product.name;
|
||
|
||
const isOutOfStock = product.stock_quantity === 0;
|
||
|
||
productDetail.innerHTML = `
|
||
<div class="product__detail-content">
|
||
<div class="product__detail-image">
|
||
<img src="${product.image_url}" alt="${product.image_alt}"
|
||
onerror="this.src='https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80'">
|
||
</div>
|
||
<div class="product__detail-info">
|
||
<div class="product__category">${product.category}</div>
|
||
<h3 class="product__name">${product.name}</h3>
|
||
<p class="product__description">${product.description}</p>
|
||
|
||
<div class="product__specs">
|
||
<h4>Specifications</h4>
|
||
<ul>
|
||
<li><strong>Size:</strong> ${product.size_inches}" diameter</li>
|
||
<li><strong>Serves:</strong> ${product.serves_people} people</li>
|
||
<li><strong>Weight:</strong> ${product.weight_kg} kg</li>
|
||
<li><strong>Flavor:</strong> ${product.flavor}</li>
|
||
<li><strong>Baker:</strong> ${product.baker_name}</li>
|
||
</ul>
|
||
</div>
|
||
|
||
${product.dietary_options && product.dietary_options !== 'none available' ? `
|
||
<div class="product__dietary">
|
||
<h4>Dietary Options</h4>
|
||
<p>${product.dietary_options}</p>
|
||
</div>
|
||
` : ''}
|
||
|
||
${product.custom_available ? `
|
||
<div class="product__custom">
|
||
<i class="fas fa-palette"></i>
|
||
<span>Custom designs available - contact us for personalization</span>
|
||
</div>
|
||
` : ''}
|
||
|
||
<div class="product__price-section">
|
||
<div class="product__price">$${product.price.toFixed(2)}</div>
|
||
<button class="btn btn--primary" onclick="addToCart(${product.id})" ${isOutOfStock ? 'disabled' : ''}>
|
||
<i class="fas fa-cart-plus"></i>
|
||
${isOutOfStock ? 'Out of Stock' : 'Add to Cart'}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="product__stock">
|
||
${product.stock_quantity > 0 ?
|
||
`<i class="fas fa-check-circle"></i> ${product.stock_quantity} available` :
|
||
'<i class="fas fa-times-circle"></i> Currently out of stock'
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
showModal('product-modal');
|
||
}
|
||
|
||
// ===== CHECKOUT =====
|
||
function showCheckoutModal() {
|
||
if (cart.length === 0) {
|
||
showNotification('Your cart is empty', 'warning');
|
||
return;
|
||
}
|
||
|
||
renderOrderSummary();
|
||
calculateOrderTotal();
|
||
hideModal('cart-modal');
|
||
showModal('checkout-modal');
|
||
}
|
||
|
||
function renderOrderSummary() {
|
||
const orderSummary = document.getElementById('order-summary');
|
||
|
||
const html = cart.map(item => `
|
||
<div class="order__item">
|
||
<span>${item.name} × ${item.quantity}</span>
|
||
<span>$${(item.price * item.quantity).toFixed(2)}</span>
|
||
</div>
|
||
`).join('');
|
||
|
||
orderSummary.innerHTML = html;
|
||
}
|
||
|
||
function calculateOrderTotal() {
|
||
const deliverySelect = document.getElementById('checkout-delivery');
|
||
const subtotal = getCartTotal();
|
||
const deliveryFee = deliverySelect.value === 'delivery' ? 15 : 0;
|
||
const total = subtotal + deliveryFee;
|
||
|
||
document.getElementById('order-subtotal').textContent = `$${subtotal.toFixed(2)}`;
|
||
document.getElementById('order-delivery').textContent = `$${deliveryFee.toFixed(2)}`;
|
||
document.getElementById('order-total').textContent = `$${total.toFixed(2)}`;
|
||
}
|
||
|
||
// ===== FORM HANDLING =====
|
||
function initializeForms() {
|
||
// Contact form
|
||
const contactForm = document.getElementById('contact-form');
|
||
contactForm.addEventListener('submit', handleContactForm);
|
||
|
||
// Checkout form
|
||
const checkoutForm = document.getElementById('checkout-form');
|
||
checkoutForm.addEventListener('submit', handleCheckoutForm);
|
||
|
||
// Newsletter form
|
||
const newsletterForm = document.getElementById('newsletter-form');
|
||
newsletterForm.addEventListener('submit', handleNewsletterForm);
|
||
|
||
// Delivery method change
|
||
const deliverySelect = document.getElementById('checkout-delivery');
|
||
deliverySelect.addEventListener('change', function() {
|
||
const deliveryAddress = document.getElementById('delivery-address');
|
||
const isDelivery = this.value === 'delivery';
|
||
|
||
deliveryAddress.style.display = isDelivery ? 'block' : 'none';
|
||
|
||
// Update required attributes
|
||
const addressInputs = deliveryAddress.querySelectorAll('input');
|
||
addressInputs.forEach(input => {
|
||
input.required = isDelivery;
|
||
});
|
||
|
||
calculateOrderTotal();
|
||
});
|
||
}
|
||
|
||
async function handleContactForm(e) {
|
||
e.preventDefault();
|
||
|
||
const form = e.target;
|
||
const submitBtn = form.querySelector('button[type="submit"]');
|
||
const formData = new FormData(form);
|
||
|
||
// Show loading state
|
||
submitBtn.classList.add('loading');
|
||
|
||
try {
|
||
// Prepare email data
|
||
const emailData = {
|
||
from_name: formData.get('name'),
|
||
from_email: formData.get('email'),
|
||
phone: formData.get('phone') || 'Not provided',
|
||
inquiry_type: formData.get('inquiry_type'),
|
||
message: formData.get('message')
|
||
};
|
||
|
||
// Send email via EmailJS
|
||
if (typeof emailjs !== 'undefined' && emailJSConfig.serviceID !== 'YOUR_SERVICE_ID') {
|
||
await emailjs.send(emailJSConfig.serviceID, emailJSConfig.templateID, emailData);
|
||
showNotification('Message sent successfully! We\'ll get back to you soon.', 'success');
|
||
} else {
|
||
// Demo mode - simulate success
|
||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
showNotification('Demo mode: Message would be sent via EmailJS', 'info');
|
||
console.log('Contact form data:', emailData);
|
||
}
|
||
|
||
form.reset();
|
||
|
||
} catch (error) {
|
||
console.error('Error sending message:', error);
|
||
showNotification('Failed to send message. Please try again.', 'error');
|
||
} finally {
|
||
submitBtn.classList.remove('loading');
|
||
}
|
||
}
|
||
|
||
async function handleCheckoutForm(e) {
|
||
e.preventDefault();
|
||
|
||
const form = e.target;
|
||
const submitBtn = form.querySelector('button[type="submit"]');
|
||
const formData = new FormData(form);
|
||
|
||
// Validate form
|
||
if (!validateCheckoutForm(formData)) {
|
||
return;
|
||
}
|
||
|
||
// Show loading state
|
||
submitBtn.classList.add('loading');
|
||
|
||
try {
|
||
// Prepare order data
|
||
const orderData = {
|
||
customer: {
|
||
firstName: formData.get('first_name'),
|
||
lastName: formData.get('last_name'),
|
||
email: formData.get('email'),
|
||
phone: formData.get('phone')
|
||
},
|
||
delivery: {
|
||
method: formData.get('delivery_method'),
|
||
address: formData.get('address') || 'Pickup at bakery',
|
||
city: formData.get('city') || '',
|
||
zip: formData.get('zip') || ''
|
||
},
|
||
event: {
|
||
date: formData.get('event_date'),
|
||
time: formData.get('event_time') || 'Flexible',
|
||
specialRequests: formData.get('special_requests') || 'None'
|
||
},
|
||
items: cart,
|
||
totals: {
|
||
subtotal: getCartTotal(),
|
||
delivery: formData.get('delivery_method') === 'delivery' ? 15 : 0,
|
||
total: getCartTotal() + (formData.get('delivery_method') === 'delivery' ? 15 : 0)
|
||
},
|
||
orderDate: new Date().toISOString()
|
||
};
|
||
|
||
// Send order via EmailJS
|
||
if (typeof emailjs !== 'undefined' && emailJSConfig.serviceID !== 'YOUR_SERVICE_ID') {
|
||
await sendOrderEmails(orderData);
|
||
showNotification('Order placed successfully! Check your email for confirmation.', 'success');
|
||
} else {
|
||
// Demo mode - simulate success
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
showNotification('Demo mode: Order would be processed via EmailJS', 'info');
|
||
console.log('Order data:', orderData);
|
||
}
|
||
|
||
// Clear cart and close modal
|
||
cart = [];
|
||
saveCart();
|
||
updateCartUI();
|
||
hideModal('checkout-modal');
|
||
|
||
// Show success message
|
||
showOrderConfirmation(orderData);
|
||
|
||
} catch (error) {
|
||
console.error('Error processing order:', error);
|
||
showNotification('Failed to process order. Please try again.', 'error');
|
||
} finally {
|
||
submitBtn.classList.remove('loading');
|
||
}
|
||
}
|
||
|
||
async function handleNewsletterForm(e) {
|
||
e.preventDefault();
|
||
|
||
const form = e.target;
|
||
const email = form.querySelector('input[type="email"]').value;
|
||
|
||
try {
|
||
// In a real application, this would be sent to your email service
|
||
console.log('Newsletter subscription:', email);
|
||
showNotification('Successfully subscribed to newsletter!', 'success');
|
||
form.reset();
|
||
} catch (error) {
|
||
showNotification('Failed to subscribe. Please try again.', 'error');
|
||
}
|
||
}
|
||
|
||
function validateCheckoutForm(formData) {
|
||
const requiredFields = ['first_name', 'last_name', 'email', 'phone', 'delivery_method', 'event_date'];
|
||
|
||
for (const field of requiredFields) {
|
||
if (!formData.get(field)) {
|
||
showNotification(`Please fill in the ${field.replace('_', ' ')} field`, 'warning');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Validate email
|
||
const email = formData.get('email');
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(email)) {
|
||
showNotification('Please enter a valid email address', 'warning');
|
||
return false;
|
||
}
|
||
|
||
// Validate event date (should be in the future)
|
||
const eventDate = new Date(formData.get('event_date'));
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
|
||
if (eventDate < today) {
|
||
showNotification('Event date must be in the future', 'warning');
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// ===== EMAIL INTEGRATION =====
|
||
async function sendOrderEmails(orderData) {
|
||
// Prepare email templates
|
||
const customerEmailData = {
|
||
to_name: `${orderData.customer.firstName} ${orderData.customer.lastName}`,
|
||
to_email: orderData.customer.email,
|
||
order_number: generateOrderNumber(),
|
||
order_items: formatOrderItems(orderData.items),
|
||
order_total: orderData.totals.total.toFixed(2),
|
||
event_date: orderData.event.date,
|
||
delivery_method: orderData.delivery.method,
|
||
special_requests: orderData.event.specialRequests
|
||
};
|
||
|
||
const businessEmailData = {
|
||
customer_name: `${orderData.customer.firstName} ${orderData.customer.lastName}`,
|
||
customer_email: orderData.customer.email,
|
||
customer_phone: orderData.customer.phone,
|
||
order_details: JSON.stringify(orderData, null, 2)
|
||
};
|
||
|
||
// Send customer confirmation email
|
||
await emailjs.send(emailJSConfig.serviceID, 'customer_confirmation', customerEmailData);
|
||
|
||
// Send business notification email
|
||
await emailjs.send(emailJSConfig.serviceID, 'business_notification', businessEmailData);
|
||
}
|
||
|
||
function generateOrderNumber() {
|
||
const timestamp = Date.now().toString().slice(-6);
|
||
const random = Math.random().toString(36).substr(2, 3).toUpperCase();
|
||
return `SD${timestamp}${random}`;
|
||
}
|
||
|
||
function formatOrderItems(items) {
|
||
return items.map(item =>
|
||
`${item.name} (Quantity: ${item.quantity}) - $${(item.price * item.quantity).toFixed(2)}`
|
||
).join('\n');
|
||
}
|
||
|
||
// ===== NAVIGATION & UI =====
|
||
function initializeNavigation() {
|
||
const navToggle = document.getElementById('nav-toggle');
|
||
const navClose = document.getElementById('nav-close');
|
||
const navMenu = document.getElementById('nav-menu');
|
||
const navLinks = document.querySelectorAll('.nav__link');
|
||
|
||
// Mobile menu toggle
|
||
navToggle.addEventListener('click', () => {
|
||
navMenu.classList.add('show-menu');
|
||
});
|
||
|
||
navClose.addEventListener('click', () => {
|
||
navMenu.classList.remove('show-menu');
|
||
});
|
||
|
||
// Close menu on link click
|
||
navLinks.forEach(link => {
|
||
link.addEventListener('click', () => {
|
||
navMenu.classList.remove('show-menu');
|
||
});
|
||
});
|
||
|
||
// Active link highlighting
|
||
updateActiveLink();
|
||
window.addEventListener('scroll', updateActiveLink);
|
||
}
|
||
|
||
function updateActiveLink() {
|
||
const sections = document.querySelectorAll('section[id]');
|
||
const navLinks = document.querySelectorAll('.nav__link');
|
||
|
||
let current = '';
|
||
|
||
sections.forEach(section => {
|
||
const sectionTop = section.offsetTop - 100;
|
||
const sectionHeight = section.offsetHeight;
|
||
|
||
if (window.scrollY >= sectionTop && window.scrollY < sectionTop + sectionHeight) {
|
||
current = section.getAttribute('id');
|
||
}
|
||
});
|
||
|
||
navLinks.forEach(link => {
|
||
link.classList.remove('active-link');
|
||
if (link.getAttribute('href') === `#${current}`) {
|
||
link.classList.add('active-link');
|
||
}
|
||
});
|
||
}
|
||
|
||
function initializeSmoothScrolling() {
|
||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||
anchor.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const target = document.querySelector(this.getAttribute('href'));
|
||
if (target) {
|
||
const headerHeight = document.querySelector('.header').offsetHeight;
|
||
const targetPosition = target.offsetTop - headerHeight;
|
||
|
||
window.scrollTo({
|
||
top: targetPosition,
|
||
behavior: 'smooth'
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function initializeHeaderEffects() {
|
||
const header = document.getElementById('header');
|
||
|
||
window.addEventListener('scroll', () => {
|
||
if (window.scrollY > 100) {
|
||
header.style.background = 'rgba(250, 248, 245, 0.98)';
|
||
header.style.boxShadow = '0 2px 20px rgba(61, 53, 48, 0.1)';
|
||
} else {
|
||
header.style.background = 'rgba(250, 248, 245, 0.95)';
|
||
header.style.boxShadow = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== UTILITY FUNCTIONS =====
|
||
function showLoading() {
|
||
document.getElementById('products-loading').style.display = 'block';
|
||
}
|
||
|
||
function hideLoading() {
|
||
document.getElementById('products-loading').style.display = 'none';
|
||
}
|
||
|
||
function showNotification(message, type = 'info') {
|
||
const notification = document.getElementById('notification');
|
||
const icon = notification.querySelector('.notification__icon');
|
||
const messageElement = notification.querySelector('.notification__message');
|
||
|
||
// Set icon based on type
|
||
const icons = {
|
||
success: 'fas fa-check-circle',
|
||
error: 'fas fa-exclamation-circle',
|
||
warning: 'fas fa-exclamation-triangle',
|
||
info: 'fas fa-info-circle'
|
||
};
|
||
|
||
icon.className = `notification__icon ${icons[type]}`;
|
||
messageElement.textContent = message;
|
||
notification.className = `notification ${type} show`;
|
||
|
||
// Auto-hide after 5 seconds
|
||
setTimeout(() => {
|
||
notification.classList.remove('show');
|
||
}, 5000);
|
||
}
|
||
|
||
function showError(message) {
|
||
showNotification(message, 'error');
|
||
}
|
||
|
||
function debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
function showOrderConfirmation(orderData) {
|
||
const confirmationModal = document.createElement('div');
|
||
confirmationModal.className = 'modal active';
|
||
confirmationModal.innerHTML = `
|
||
<div class="modal__content">
|
||
<div class="modal__header">
|
||
<h3 class="modal__title">Order Confirmed!</h3>
|
||
<button class="modal__close" onclick="this.closest('.modal').remove()">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
<div class="modal__body">
|
||
<div class="order-confirmation">
|
||
<div class="confirmation__icon">
|
||
<i class="fas fa-check-circle"></i>
|
||
</div>
|
||
<h4>Thank you, ${orderData.customer.firstName}!</h4>
|
||
<p>Your order has been received and we'll contact you within 24 hours to:</p>
|
||
<ul>
|
||
<li>Confirm your order details</li>
|
||
<li>Discuss any customization requirements</li>
|
||
<li>Arrange payment and delivery/pickup</li>
|
||
<li>Finalize your cake design</li>
|
||
</ul>
|
||
<div class="order-summary">
|
||
<h5>Order Summary:</h5>
|
||
<p><strong>Event Date:</strong> ${orderData.event.date}</p>
|
||
<p><strong>Total:</strong> $${orderData.totals.total.toFixed(2)}</p>
|
||
<p><strong>Delivery:</strong> ${orderData.delivery.method === 'delivery' ? 'Home Delivery' : 'Pickup at Bakery'}</p>
|
||
</div>
|
||
<p><strong>What's next?</strong> Check your email for a detailed confirmation. We'll be in touch soon!</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(confirmationModal);
|
||
|
||
// Auto-remove after user interaction
|
||
confirmationModal.addEventListener('click', function(e) {
|
||
if (e.target === this || e.target.closest('.modal__close')) {
|
||
this.remove();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== EVENT LISTENERS INITIALIZATION =====
|
||
function initializeEventListeners() {
|
||
initializeNavigation();
|
||
initializeModals();
|
||
initializeForms();
|
||
initializeFiltering();
|
||
|
||
// Close notifications
|
||
document.addEventListener('click', function(e) {
|
||
if (e.target.closest('.notification')) {
|
||
e.target.closest('.notification').classList.remove('show');
|
||
}
|
||
});
|
||
}
|
||
|
||
// ===== ERROR HANDLING =====
|
||
window.addEventListener('error', function(e) {
|
||
console.error('Application error:', e.error);
|
||
showNotification('An unexpected error occurred. Please refresh the page.', 'error');
|
||
});
|
||
|
||
// ===== SERVICE WORKER (OPTIONAL) =====
|
||
if ('serviceWorker' in navigator) {
|
||
window.addEventListener('load', function() {
|
||
navigator.serviceWorker.register('/sw.js')
|
||
.then(function(registration) {
|
||
console.log('ServiceWorker registration successful');
|
||
}, function(err) {
|
||
console.log('ServiceWorker registration failed');
|
||
});
|
||
});
|
||
} |