/** * Shopping/Products Component * Handles product display, filtering, and interactions */ (function () { "use strict"; class ShoppingPage { constructor() { this.productsContainer = document.getElementById("productsContainer"); this.loadingIndicator = document.getElementById("loadingIndicator"); this.errorContainer = document.getElementById("errorContainer"); this.currentCategory = window.Utils.getUrlParameter("category") || "all"; this.currentSort = "newest"; this.products = []; this.init(); } async init() { this.setupEventListeners(); await this.loadProducts(); } setupEventListeners() { // Category filters document.querySelectorAll("[data-category]").forEach((btn) => { btn.addEventListener("click", (e) => { e.preventDefault(); this.currentCategory = e.currentTarget.dataset.category; this.filterProducts(); }); }); // Sort dropdown const sortSelect = document.getElementById("sortSelect"); if (sortSelect) { sortSelect.addEventListener("change", (e) => { this.currentSort = e.target.value; this.filterProducts(); }); } // Search const searchInput = document.getElementById("productSearch"); if (searchInput) { searchInput.addEventListener( "input", window.Utils.debounce((e) => { this.searchProducts(e.target.value); }, 300) ); } } async loadProducts() { if (!this.productsContainer) return; try { this.showLoading(); const response = await window.API.getProducts(); this.products = response.products || response.data || []; this.renderProducts(this.products); this.hideLoading(); } catch (error) { console.error("Error loading products:", error); this.showError("Failed to load products. Please try again later."); this.hideLoading(); } } filterProducts() { let filtered = [...this.products]; // Filter by category if (this.currentCategory && this.currentCategory !== "all") { filtered = filtered.filter( (p) => p.category?.toLowerCase() === this.currentCategory.toLowerCase() ); } // Sort products filtered = this.sortProducts(filtered); this.renderProducts(filtered); } sortProducts(products) { switch (this.currentSort) { case "price-low": return products.sort((a, b) => (a.price || 0) - (b.price || 0)); case "price-high": return products.sort((a, b) => (b.price || 0) - (a.price || 0)); case "name": return products.sort((a, b) => (a.title || a.name || "").localeCompare(b.title || b.name || "") ); case "newest": default: return products.sort( (a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0) ); } } searchProducts(query) { if (!query.trim()) { this.filterProducts(); return; } const searchTerm = query.toLowerCase(); const filtered = this.products.filter((p) => { const title = (p.title || p.name || "").toLowerCase(); const description = (p.description || "").toLowerCase(); const category = (p.category || "").toLowerCase(); return ( title.includes(searchTerm) || description.includes(searchTerm) || category.includes(searchTerm) ); }); this.renderProducts(filtered); } renderProducts(products) { if (!this.productsContainer) return; if (products.length === 0) { this.productsContainer.innerHTML = `

No products found

`; return; } const html = products .map((product) => this.renderProductCard(product)) .join(""); this.productsContainer.innerHTML = html; // Setup product card listeners this.setupProductListeners(); } renderProductCard(product) { const id = product.id; const title = window.Utils?.escapeHtml ? window.Utils.escapeHtml(product.title || product.name || "Product") : product.title || product.name || "Product"; const price = window.Utils?.formatCurrency ? window.Utils.formatCurrency(product.price || 0) : `$${parseFloat(product.price || 0).toFixed(2)}`; // Get image URL from multiple possible sources let imageUrl = "/assets/images/placeholder.jpg"; if ( product.images && Array.isArray(product.images) && product.images.length > 0 ) { const primaryImg = product.images.find((img) => img.is_primary); imageUrl = primaryImg ? primaryImg.image_url : product.images[0].image_url; } else if (product.imageUrl) { imageUrl = product.imageUrl; } else if (product.image_url) { imageUrl = product.image_url; } // Get description const description = product.shortdescription || (product.description ? product.description.substring(0, 100) + "..." : ""); const isInWishlist = window.AppState?.isInWishlist(id) || false; return `
${title}

${title}

${ description ? `
${description}
` : "" }

${price}

`; } setupProductListeners() { // Add to cart buttons this.productsContainer .querySelectorAll(".btn-add-to-cart") .forEach((btn) => { btn.addEventListener("click", (e) => { e.preventDefault(); const id = parseInt(e.currentTarget.dataset.id); const product = this.products.find((p) => p.id === id); if (product) { window.AppState.addToCart(product); } }); }); // Wishlist buttons this.productsContainer .querySelectorAll(".wishlist-btn") .forEach((btn) => { btn.addEventListener("click", (e) => { e.preventDefault(); const id = parseInt(e.currentTarget.dataset.id); const product = this.products.find((p) => p.id === id); if (product) { if (window.AppState.isInWishlist(id)) { window.AppState.removeFromWishlist(id); } else { window.AppState.addToWishlist(product); } this.renderProducts(this.products); } }); }); } showLoading() { if (this.loadingIndicator) { this.loadingIndicator.style.display = "flex"; } if (this.productsContainer) { this.productsContainer.style.opacity = "0.5"; } } hideLoading() { if (this.loadingIndicator) { this.loadingIndicator.style.display = "none"; } if (this.productsContainer) { this.productsContainer.style.opacity = "1"; } } showError(message) { if (this.errorContainer) { this.errorContainer.innerHTML = ` `; this.errorContainer.style.display = "block"; } } } // Initialize on shop/products pages if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { if ( window.location.pathname.includes("/shop") || window.location.pathname.includes("/products") ) { new ShoppingPage(); } }); } else { if ( window.location.pathname.includes("/shop") || window.location.pathname.includes("/products") ) { new ShoppingPage(); } } })();