/** * Enhanced Main Application JavaScript * Production-Ready with No Console Errors * Proper State Management & API Integration */ (function () { "use strict"; // Production mode check const isDevelopment = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; // Safe console wrapper const logger = { log: (...args) => isDevelopment && console.log(...args), error: (...args) => console.error(...args), warn: (...args) => isDevelopment && console.warn(...args), info: (...args) => isDevelopment && console.info(...args), }; // ======================================== // GLOBAL STATE MANAGEMENT // ======================================== window.AppState = { cart: [], wishlist: [], products: [], settings: null, user: null, _saveCartTimeout: null, _saveWishlistTimeout: null, _initialized: false, // Initialize state init() { if (this._initialized) { logger.warn("[AppState] Already initialized"); return; } logger.info("[AppState] Initializing..."); this.loadCart(); this.loadWishlist(); this.updateUI(); this._initialized = true; logger.info( "[AppState] Initialized - Cart:", this.cart.length, "items, Wishlist:", this.wishlist.length, "items" ); // Dispatch ready event window.dispatchEvent(new CustomEvent("appstate-ready")); }, // ======================================== // CART MANAGEMENT // ======================================== loadCart() { try { const saved = localStorage.getItem("cart"); this.cart = saved ? JSON.parse(saved) : []; // Validate cart items this.cart = this.cart.filter((item) => item && item.id && item.price); } catch (error) { logger.error("Error loading cart:", error); this.cart = []; } }, saveCart() { if (this._saveCartTimeout) { clearTimeout(this._saveCartTimeout); } this._saveCartTimeout = setTimeout(() => { try { localStorage.setItem("cart", JSON.stringify(this.cart)); this.updateUI(); window.dispatchEvent( new CustomEvent("cart-updated", { detail: this.cart }) ); } catch (error) { logger.error("Error saving cart:", error); this.showNotification("Error saving cart", "error"); } }, 100); }, addToCart(product, quantity = 1) { if (!product || !product.id) { logger.error("[AppState] Invalid product:", product); this.showNotification("Invalid product", "error"); return false; } try { const existing = this.cart.find((item) => item.id === product.id); if (existing) { existing.quantity = (existing.quantity || 1) + quantity; logger.info("[AppState] Updated cart quantity:", existing); } else { this.cart.push({ ...product, quantity, addedAt: new Date().toISOString(), }); logger.info("[AppState] Added to cart:", product.name); } this.saveCart(); this.showNotification( `${product.name || "Item"} added to cart`, "success" ); return true; } catch (error) { logger.error("[AppState] Error adding to cart:", error); this.showNotification("Error adding to cart", "error"); return false; } }, removeFromCart(productId) { if (!productId) { logger.error("[AppState] Invalid productId"); return false; } const initialLength = this.cart.length; this.cart = this.cart.filter((item) => item.id !== productId); if (this.cart.length < initialLength) { this.saveCart(); this.showNotification("Removed from cart", "info"); return true; } return false; }, updateCartQuantity(productId, quantity) { const item = this.cart.find((item) => item.id === productId); if (item) { item.quantity = Math.max(1, parseInt(quantity) || 1); this.saveCart(); return true; } return false; }, clearCart() { this.cart = []; this.saveCart(); this.showNotification("Cart cleared", "info"); }, getCartTotal() { return this.cart.reduce((sum, item) => { const price = parseFloat(item.price) || 0; const quantity = parseInt(item.quantity) || 1; return sum + price * quantity; }, 0); }, getCartCount() { return this.cart.reduce( (sum, item) => sum + (parseInt(item.quantity) || 1), 0 ); }, // ======================================== // WISHLIST MANAGEMENT // ======================================== loadWishlist() { try { const saved = localStorage.getItem("wishlist"); this.wishlist = saved ? JSON.parse(saved) : []; // Validate wishlist items this.wishlist = this.wishlist.filter((item) => item && item.id); } catch (error) { logger.error("Error loading wishlist:", error); this.wishlist = []; } }, saveWishlist() { if (this._saveWishlistTimeout) { clearTimeout(this._saveWishlistTimeout); } this._saveWishlistTimeout = setTimeout(() => { try { localStorage.setItem("wishlist", JSON.stringify(this.wishlist)); this.updateUI(); window.dispatchEvent( new CustomEvent("wishlist-updated", { detail: this.wishlist }) ); } catch (error) { logger.error("Error saving wishlist:", error); } }, 100); }, addToWishlist(product) { if (!product || !product.id) { logger.error("[AppState] Invalid product:", product); this.showNotification("Invalid product", "error"); return false; } try { const exists = this.wishlist.some((item) => item.id === product.id); if (exists) { this.showNotification("Already in wishlist", "info"); return false; } this.wishlist.push({ ...product, addedAt: new Date().toISOString(), }); this.saveWishlist(); this.showNotification( `${product.name || "Item"} added to wishlist`, "success" ); return true; } catch (error) { logger.error("[AppState] Error adding to wishlist:", error); this.showNotification("Error adding to wishlist", "error"); return false; } }, removeFromWishlist(productId) { if (!productId) return false; const initialLength = this.wishlist.length; this.wishlist = this.wishlist.filter((item) => item.id !== productId); if (this.wishlist.length < initialLength) { this.saveWishlist(); this.showNotification("Removed from wishlist", "info"); return true; } return false; }, isInWishlist(productId) { return this.wishlist.some((item) => item.id === productId); }, getWishlistCount() { return this.wishlist.length; }, // ======================================== // UI UPDATES // ======================================== updateUI() { this.updateCartBadge(); this.updateWishlistBadge(); this.updateCartDropdown(); this.updateWishlistDropdown(); }, updateCartBadge() { const badges = document.querySelectorAll( ".cart-count, .cart-badge, #cartCount" ); const count = this.getCartCount(); badges.forEach((badge) => { badge.textContent = count; if (count > 0) { badge.classList.add("show"); } else { badge.classList.remove("show"); } }); }, updateWishlistBadge() { const badges = document.querySelectorAll( ".wishlist-count, .wishlist-badge, #wishlistCount" ); const count = this.getWishlistCount(); badges.forEach((badge) => { badge.textContent = count; if (count > 0) { badge.classList.add("show"); } else { badge.classList.remove("show"); } }); }, updateCartDropdown() { const container = document.querySelector("#cart-items"); if (!container) return; if (this.cart.length === 0) { container.innerHTML = `

Your cart is empty

`; const totalEl = document.querySelector(".cart-total-value"); if (totalEl) totalEl.textContent = "$0.00"; return; } container.innerHTML = this.cart .map((item) => this.renderCartItem(item)) .join(""); const totalEl = document.querySelector(".cart-total-value"); if (totalEl) { totalEl.textContent = `$${this.getCartTotal().toFixed(2)}`; } this.attachCartEventListeners(); }, updateWishlistDropdown() { const container = document.querySelector("#wishlist-items"); if (!container) return; if (this.wishlist.length === 0) { container.innerHTML = `

Your wishlist is empty

`; return; } container.innerHTML = this.wishlist .map((item) => this.renderWishlistItem(item)) .join(""); this.attachWishlistEventListeners(); }, renderCartItem(item) { const price = parseFloat(item.price) || 0; const quantity = parseInt(item.quantity) || 1; const imageUrl = this.getProductImage(item); const name = this.sanitizeHTML(item.name || "Product"); return `
${name}
${name}
$${price.toFixed(2)}
`; }, renderWishlistItem(item) { const price = parseFloat(item.price) || 0; const imageUrl = this.getProductImage(item); const name = this.sanitizeHTML(item.name || "Product"); return `
${name}
${name}
$${price.toFixed(2)}
`; }, attachCartEventListeners() { document.querySelectorAll(".cart-item").forEach((item) => { const productId = item.dataset.productId; // Quantity controls const decreaseBtn = item.querySelector('[data-action="decrease"]'); const increaseBtn = item.querySelector('[data-action="increase"]'); const quantityInput = item.querySelector(".quantity-input"); if (decreaseBtn) { decreaseBtn.addEventListener("click", () => { const currentQty = parseInt(quantityInput.value) || 1; if (currentQty > 1) { quantityInput.value = currentQty - 1; this.updateCartQuantity(productId, currentQty - 1); } }); } if (increaseBtn) { increaseBtn.addEventListener("click", () => { const currentQty = parseInt(quantityInput.value) || 1; if (currentQty < 99) { quantityInput.value = currentQty + 1; this.updateCartQuantity(productId, currentQty + 1); } }); } if (quantityInput) { quantityInput.addEventListener("change", (e) => { const newQty = parseInt(e.target.value) || 1; this.updateCartQuantity(productId, newQty); }); } // Remove button const removeBtn = item.querySelector('[data-action="remove"]'); if (removeBtn) { removeBtn.addEventListener("click", () => { this.removeFromCart(productId); }); } }); }, attachWishlistEventListeners() { document.querySelectorAll(".wishlist-item").forEach((item) => { const productId = item.dataset.productId; // Add to cart button const addBtn = item.querySelector(".btn-add-to-cart"); if (addBtn) { addBtn.addEventListener("click", () => { const product = this.wishlist.find((p) => p.id === productId); if (product) { this.addToCart(product); } }); } // Remove button const removeBtn = item.querySelector('[data-action="remove"]'); if (removeBtn) { removeBtn.addEventListener("click", () => { this.removeFromWishlist(productId); }); } }); }, // ======================================== // NOTIFICATIONS // ======================================== showNotification(message, type = "info") { if (!message) return; let container = document.querySelector(".notification-container"); if (!container) { container = document.createElement("div"); container.className = "notification-container"; document.body.appendChild(container); } const notification = document.createElement("div"); notification.className = `notification ${type}`; const icon = type === "success" ? "check-circle-fill" : type === "error" ? "exclamation-circle-fill" : "info-circle-fill"; notification.innerHTML = ` ${this.sanitizeHTML(message)} `; container.appendChild(notification); setTimeout(() => { notification.style.animation = "slideOut 0.3s ease forwards"; setTimeout(() => notification.remove(), 300); }, 3000); }, // ======================================== // API INTEGRATION // ======================================== async fetchProducts() { try { const response = await fetch("/api/products"); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (data.success && Array.isArray(data.products)) { this.products = data.products; window.dispatchEvent( new CustomEvent("products-loaded", { detail: this.products }) ); return this.products; } throw new Error("Invalid API response"); } catch (error) { logger.error("Error fetching products:", error); this.showNotification("Error loading products", "error"); return []; } }, async fetchSettings() { try { const response = await fetch("/api/settings"); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (data.success && data.settings) { this.settings = data.settings; window.dispatchEvent( new CustomEvent("settings-loaded", { detail: this.settings }) ); return this.settings; } throw new Error("Invalid API response"); } catch (error) { logger.error("Error fetching settings:", error); return null; } }, // ======================================== // UTILITY METHODS // ======================================== getProductImage(product) { if (!product) return "/assets/img/placeholder.jpg"; // Check various image properties if (product.image_url) return product.image_url; if (product.imageUrl) return product.imageUrl; if ( product.images && Array.isArray(product.images) && product.images.length > 0 ) { return ( product.images[0].image_url || product.images[0].url || "/assets/img/placeholder.jpg" ); } if (product.thumbnail) return product.thumbnail; return "/assets/img/placeholder.jpg"; }, sanitizeHTML(str) { if (!str) return ""; const div = document.createElement("div"); div.textContent = str; return div.innerHTML; }, formatPrice(price) { const num = parseFloat(price) || 0; return `$${num.toFixed(2)}`; }, debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, }; // ======================================== // DROPDOWN MANAGEMENT // ======================================== window.DropdownManager = { activeDropdown: null, init() { this.attachEventListeners(); logger.info("[DropdownManager] Initialized"); }, attachEventListeners() { // Cart toggle const cartBtn = document.querySelector("#cart-btn"); if (cartBtn) { cartBtn.addEventListener("click", (e) => { e.stopPropagation(); this.toggle("cart"); }); } // Wishlist toggle const wishlistBtn = document.querySelector("#wishlist-btn"); if (wishlistBtn) { wishlistBtn.addEventListener("click", (e) => { e.stopPropagation(); this.toggle("wishlist"); }); } // Close on outside click document.addEventListener("click", (e) => { if (!e.target.closest(".dropdown") && !e.target.closest(".icon-btn")) { this.closeAll(); } }); // Close on escape key document.addEventListener("keydown", (e) => { if (e.key === "Escape") { this.closeAll(); } }); }, toggle(type) { const dropdown = document.querySelector(`#${type}-dropdown`); if (!dropdown) return; if (this.activeDropdown === dropdown) { this.close(dropdown); } else { this.closeAll(); this.open(dropdown, type); } }, open(dropdown, type) { dropdown.style.display = "flex"; this.activeDropdown = dropdown; // Update content if (type === "cart") { window.AppState.updateCartDropdown(); } else if (type === "wishlist") { window.AppState.updateWishlistDropdown(); } }, close(dropdown) { if (dropdown) { dropdown.style.display = "none"; } this.activeDropdown = null; }, closeAll() { document.querySelectorAll(".dropdown").forEach((dropdown) => { dropdown.style.display = "none"; }); this.activeDropdown = null; }, }; // ======================================== // MOBILE MENU // ======================================== window.MobileMenu = { menu: null, overlay: null, isOpen: false, init() { this.menu = document.querySelector(".mobile-menu"); this.overlay = document.querySelector(".mobile-menu-overlay"); if (!this.menu || !this.overlay) { logger.warn("[MobileMenu] Elements not found"); return; } this.attachEventListeners(); logger.info("[MobileMenu] Initialized"); }, attachEventListeners() { // Toggle button const toggleBtn = document.querySelector(".mobile-menu-toggle"); if (toggleBtn) { toggleBtn.addEventListener("click", () => this.toggle()); } // Close button const closeBtn = document.querySelector(".mobile-menu-close"); if (closeBtn) { closeBtn.addEventListener("click", () => this.close()); } // Overlay click if (this.overlay) { this.overlay.addEventListener("click", () => this.close()); } // Menu links this.menu.querySelectorAll("a").forEach((link) => { link.addEventListener("click", () => { setTimeout(() => this.close(), 100); }); }); // Escape key document.addEventListener("keydown", (e) => { if (e.key === "Escape" && this.isOpen) { this.close(); } }); }, toggle() { this.isOpen ? this.close() : this.open(); }, open() { this.menu.classList.add("active"); this.overlay.classList.add("active"); this.isOpen = true; document.body.style.overflow = "hidden"; }, close() { this.menu.classList.remove("active"); this.overlay.classList.remove("active"); this.isOpen = false; document.body.style.overflow = ""; }, }; // ======================================== // INITIALIZATION // ======================================== function initialize() { logger.info("[App] Initializing..."); // Initialize state if (window.AppState) { window.AppState.init(); } // Initialize dropdown manager if (window.DropdownManager) { window.DropdownManager.init(); } // Initialize mobile menu if (window.MobileMenu) { window.MobileMenu.init(); } // Fetch initial data if (window.AppState.fetchSettings) { window.AppState.fetchSettings(); } logger.info("[App] Initialization complete"); } // Run on DOM ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initialize); } else { initialize(); } // Export for debugging in development if (isDevelopment) { window.DEBUG = { AppState: window.AppState, DropdownManager: window.DropdownManager, MobileMenu: window.MobileMenu, logger, }; } })();