/** * Shopping Cart & Wishlist System * Complete, simple, reliable implementation */ (function () { "use strict"; console.log("[ShopSystem] Loading..."); // ======================================== // UTILS - Fallback if main.js not loaded // ======================================== if (!window.Utils) { window.Utils = { formatCurrency(amount) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(amount); }, escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; }, }; } // ======================================== // VALIDATION UTILITIES // ======================================== const ValidationUtils = { validateProduct(product) { if (!product || !product.id) { return { valid: false, error: "Invalid product: missing ID" }; } const price = parseFloat(product.price); if (isNaN(price) || price < 0) { return { valid: false, error: "Invalid product price" }; } return { valid: true, price }; }, sanitizeProduct(product, price) { return { id: product.id, name: product.name || product.title || "Product", price: price, imageurl: product.imageurl || product.imageUrl || product.image_url || "", }; }, validateQuantity(quantity) { return Math.max(1, parseInt(quantity) || 1); }, sanitizeItems(items, includeQuantity = false) { return items .filter( (item) => item && item.id && typeof item.price !== "undefined" && (!includeQuantity || item.quantity > 0) ) .map((item) => ({ ...item, price: parseFloat(item.price) || 0, ...(includeQuantity && { quantity: Math.max(1, parseInt(item.quantity) || 1), }), })); }, }; // ======================================== // CART & WISHLIST STATE MANAGEMENT // ======================================== class ShopState { constructor() { this.cart = []; this.wishlist = []; this.init(); } init() { console.log("[ShopState] Initializing..."); this.loadFromStorage(); this.updateAllBadges(); console.log( "[ShopState] Initialized - Cart:", this.cart.length, "Wishlist:", this.wishlist.length ); } // Load data from localStorage loadFromStorage() { try { const [cartData, wishlistData] = [ localStorage.getItem("skyart_cart"), localStorage.getItem("skyart_wishlist"), ]; // Parse and validate data this.cart = this._parseAndValidate(cartData, "cart"); this.wishlist = this._parseAndValidate(wishlistData, "wishlist"); // Sanitize items this.cart = ValidationUtils.sanitizeItems(this.cart, true); this.wishlist = ValidationUtils.sanitizeItems(this.wishlist, false); } catch (e) { console.error("[ShopState] Load error:", e); this._clearCorruptedData(); } } _parseAndValidate(data, type) { const parsed = data ? JSON.parse(data) : []; if (!Array.isArray(parsed)) { console.warn(`[ShopState] Invalid ${type} data, resetting`); return []; } return parsed; } _clearCorruptedData() { localStorage.removeItem("skyart_cart"); localStorage.removeItem("skyart_wishlist"); this.cart = []; this.wishlist = []; } // Save data to localStorage saveToStorage() { try { // Check localStorage availability if (typeof localStorage === "undefined") { console.error("[ShopState] localStorage not available"); return false; } const cartJson = JSON.stringify(this.cart); const wishlistJson = JSON.stringify(this.wishlist); // Check size (5MB limit for most browsers) if (cartJson.length + wishlistJson.length > 4000000) { console.warn( "[ShopState] Storage data too large, trimming old items" ); // Keep only last 50 cart items and 100 wishlist items this.cart = this.cart.slice(-50); this.wishlist = this.wishlist.slice(-100); } localStorage.setItem("skyart_cart", JSON.stringify(this.cart)); localStorage.setItem("skyart_wishlist", JSON.stringify(this.wishlist)); return true; } catch (e) { console.error("[ShopState] Save error:", e); // Handle quota exceeded error if (e.name === "QuotaExceededError" || e.code === 22) { console.warn("[ShopState] Storage quota exceeded, clearing old data"); // Try to recover by keeping only essential items this.cart = this.cart.slice(-20); this.wishlist = this.wishlist.slice(-30); try { localStorage.setItem("skyart_cart", JSON.stringify(this.cart)); localStorage.setItem( "skyart_wishlist", JSON.stringify(this.wishlist) ); this.showNotification( "Storage limit reached. Older items removed.", "info" ); } catch (retryError) { console.error("[ShopState] Failed to recover storage:", retryError); } } return false; } } // ======================================== // CART METHODS // ======================================== addToCart(product, quantity = 1) { console.log("[ShopState] Adding to cart:", product); const validation = ValidationUtils.validateProduct(product); if (!validation.valid) { console.error("[ShopState] Invalid product:", product); this.showNotification(validation.error, "error"); return false; } quantity = ValidationUtils.validateQuantity(quantity); const existing = this._findById(this.cart, product.id); if (existing) { existing.quantity = Math.min(existing.quantity + quantity, 999); } else { const sanitized = ValidationUtils.sanitizeProduct( product, validation.price ); this.cart.push({ ...sanitized, quantity }); } return this._saveAndUpdate( "cart", product.name || product.title || "Item", "added to cart" ); } removeFromCart(productId) { console.log("[ShopState] Removing from cart:", productId); this.cart = this.cart.filter( (item) => String(item.id) !== String(productId) ); this.saveToStorage(); this.updateAllBadges(); this.renderCartDropdown(); this.showNotification("Item removed from cart", "info"); // Dispatch event for cart.js compatibility window.dispatchEvent( new CustomEvent("cart-updated", { detail: this.cart }) ); } updateCartQuantity(productId, quantity) { const item = this.cart.find( (item) => String(item.id) === String(productId) ); if (item) { item.quantity = Math.max(1, quantity); this.saveToStorage(); this.updateAllBadges(); this.renderCartDropdown(); // Dispatch event for cart.js compatibility window.dispatchEvent( new CustomEvent("cart-updated", { detail: this.cart }) ); } } getCartTotal() { return this.cart.reduce((sum, item) => { const price = parseFloat(item.price) || 0; const quantity = parseInt(item.quantity) || 0; return sum + price * quantity; }, 0); } getCartCount() { return this.cart.reduce((sum, item) => { const quantity = parseInt(item.quantity) || 0; return sum + quantity; }, 0); } // ======================================== // WISHLIST METHODS // ======================================== addToWishlist(product) { console.log("[ShopState] Adding to wishlist:", product); const validation = ValidationUtils.validateProduct(product); if (!validation.valid) { console.error("[ShopState] Invalid product:", product); this.showNotification(validation.error, "error"); return false; } if (this._findById(this.wishlist, product.id)) { this.showNotification("Already in wishlist", "info"); return false; } const sanitized = ValidationUtils.sanitizeProduct( product, validation.price ); this.wishlist.push(sanitized); return this._saveAndUpdate( "wishlist", product.name || product.title || "Item", "added to wishlist" ); } removeFromWishlist(productId) { console.log("[ShopState] Removing from wishlist:", productId); this.wishlist = this.wishlist.filter( (item) => String(item.id) !== String(productId) ); this.saveToStorage(); this.updateAllBadges(); this.renderWishlistDropdown(); this.showNotification("Item removed from wishlist", "info"); // Dispatch event for cart.js compatibility window.dispatchEvent( new CustomEvent("wishlist-updated", { detail: this.wishlist }) ); } isInWishlist(productId) { return !!this._findById(this.wishlist, productId); } isInCart(productId) { return !!this._findById(this.cart, productId); } // Helper methods _findById(collection, id) { return collection.find((item) => String(item.id) === String(id)); } _saveAndUpdate(type, productName, action) { if (!this.saveToStorage()) return false; this.updateAllBadges(); if (type === "cart") { this.renderCartDropdown(); } else { this.renderWishlistDropdown(); } this.showNotification(`${productName} ${action}`, "success"); window.dispatchEvent( new CustomEvent(`${type}-updated`, { detail: this[type] }) ); return true; } // ======================================== // UI UPDATE METHODS // ======================================== updateAllBadges() { // Update cart badge const cartBadge = document.getElementById("cartCount"); if (cartBadge) { const count = this.getCartCount(); cartBadge.textContent = count; cartBadge.style.display = count > 0 ? "flex" : "none"; } // Update wishlist badge const wishlistBadge = document.getElementById("wishlistCount"); if (wishlistBadge) { const count = this.wishlist.length; wishlistBadge.textContent = count; wishlistBadge.style.display = count > 0 ? "flex" : "none"; } } renderCartDropdown() { const cartContent = document.getElementById("cartContent"); if (!cartContent) return; if (this.cart.length === 0) { cartContent.innerHTML = '


Your cart is empty

'; this.updateCartFooter(0); return; } cartContent.innerHTML = this.cart .map((item) => this.createCartItemHTML(item)) .join(""); this.updateCartFooter(this.getCartTotal()); this.attachCartEventListeners(); } createCartItemHTML(item) { const imageUrl = item.imageurl || item.imageUrl || item.image_url || "/assets/images/placeholder.jpg"; const price = parseFloat(item.price || 0).toFixed(2); const subtotal = (parseFloat(item.price || 0) * item.quantity).toFixed(2); return `
${this.escapeHtml(
        item.name
      )}

${this.escapeHtml(item.name)}

$${price}

${item.quantity}

Subtotal: $${subtotal}

`; } attachCartEventListeners() { // Remove buttons document.querySelectorAll(".cart-item-remove").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = e.currentTarget.dataset.id; this.removeFromCart(id); }); }); // Quantity minus document.querySelectorAll(".quantity-minus").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = e.currentTarget.dataset.id; const item = this.cart.find((i) => String(i.id) === String(id)); if (item && item.quantity > 1) { this.updateCartQuantity(id, item.quantity - 1); } }); }); // Quantity plus document.querySelectorAll(".quantity-plus").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = e.currentTarget.dataset.id; const item = this.cart.find((i) => String(i.id) === String(id)); if (item) { this.updateCartQuantity(id, item.quantity + 1); } }); }); } updateCartFooter(total) { const footer = document.querySelector("#cartPanel .dropdown-foot"); if (!footer) return; if (total === 0) { footer.innerHTML = 'Continue Shopping'; } else { footer.innerHTML = `
Total: $${total.toFixed(2)}
Continue Shopping `; } } renderWishlistDropdown() { const wishlistContent = document.getElementById("wishlistContent"); if (!wishlistContent) return; if (this.wishlist.length === 0) { wishlistContent.innerHTML = '


Your wishlist is empty

'; return; } wishlistContent.innerHTML = this.wishlist .map((item) => this.createWishlistItemHTML(item)) .join(""); this.attachWishlistEventListeners(); } createWishlistItemHTML(item) { const imageUrl = item.imageurl || item.imageUrl || item.image_url || "/assets/images/placeholder.jpg"; const price = parseFloat(item.price || 0).toFixed(2); return `
${this.escapeHtml(
        item.name
      )}

${this.escapeHtml(item.name)}

$${price}

`; } attachWishlistEventListeners() { // Remove buttons document.querySelectorAll(".wishlist-item-remove").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = e.currentTarget.dataset.id; this.removeFromWishlist(id); }); }); // Add to cart buttons document.querySelectorAll(".btn-add-to-cart").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const id = e.currentTarget.dataset.id; const item = this.wishlist.find((i) => String(i.id) === String(id)); if (item) { this.addToCart(item, 1); } }); }); } // ======================================== // DROPDOWN TOGGLE METHODS // ======================================== setupDropdowns() { // Cart dropdown const cartToggle = document.getElementById("cartToggle"); const cartPanel = document.getElementById("cartPanel"); const cartClose = document.getElementById("cartClose"); if (cartToggle && cartPanel) { cartToggle.addEventListener("click", () => { cartPanel.classList.toggle("active"); this.renderCartDropdown(); }); } if (cartClose) { cartClose.addEventListener("click", () => { cartPanel.classList.remove("active"); }); } // Wishlist dropdown const wishlistToggle = document.getElementById("wishlistToggle"); const wishlistPanel = document.getElementById("wishlistPanel"); const wishlistClose = document.getElementById("wishlistClose"); if (wishlistToggle && wishlistPanel) { wishlistToggle.addEventListener("click", () => { wishlistPanel.classList.toggle("active"); this.renderWishlistDropdown(); }); } if (wishlistClose) { wishlistClose.addEventListener("click", () => { wishlistPanel.classList.remove("active"); }); } // Close dropdowns when clicking outside document.addEventListener("click", (e) => { if (!e.target.closest(".cart-dropdown-wrapper") && cartPanel) { cartPanel.classList.remove("active"); } if (!e.target.closest(".wishlist-dropdown-wrapper") && wishlistPanel) { wishlistPanel.classList.remove("active"); } }); } // ======================================== // NOTIFICATION SYSTEM // ======================================== showNotification(message, type = "info") { // Remove existing notifications document .querySelectorAll(".shop-notification") .forEach((n) => n.remove()); const notification = document.createElement("div"); notification.className = `shop-notification notification-${type}`; notification.textContent = message; const bgColors = { success: "#10b981", error: "#ef4444", info: "#3b82f6", }; notification.style.cssText = ` position: fixed; top: 80px; right: 20px; background: ${bgColors[type] || bgColors.info}; color: white; padding: 12px 24px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; animation: slideInRight 0.3s ease; `; document.body.appendChild(notification); setTimeout(() => { notification.style.animation = "slideOutRight 0.3s ease"; setTimeout(() => notification.remove(), 300); }, 3000); } // ======================================== // UTILITY METHODS // ======================================== escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } } // ======================================== // INITIALIZE SYSTEM // ======================================== // Create global instance window.ShopSystem = new ShopState(); // ======================================== // APPSTATE COMPATIBILITY LAYER // ======================================== // Provide AppState interface for cart.js compatibility window.AppState = { get cart() { return window.ShopSystem.cart; }, get wishlist() { return window.ShopSystem.wishlist; }, addToCart: (product, quantity = 1) => { window.ShopSystem.addToCart(product, quantity); }, removeFromCart: (productId) => { window.ShopSystem.removeFromCart(productId); }, updateCartQuantity: (productId, quantity) => { window.ShopSystem.updateCartQuantity(productId, quantity); }, getCartTotal: () => { return window.ShopSystem.getCartTotal(); }, getCartCount: () => { return window.ShopSystem.getCartCount(); }, addToWishlist: (product) => { window.ShopSystem.addToWishlist(product); }, removeFromWishlist: (productId) => { window.ShopSystem.removeFromWishlist(productId); }, isInWishlist: (productId) => { return window.ShopSystem.isInWishlist(productId); }, showNotification: (message, type) => { window.ShopSystem.showNotification(message, type); }, }; console.log("[ShopSystem] AppState compatibility layer installed"); // Setup dropdowns when DOM is ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { window.ShopSystem.setupDropdowns(); }); } else { window.ShopSystem.setupDropdowns(); } // Add animation styles if (!document.getElementById("shop-system-styles")) { const style = document.createElement("style"); style.id = "shop-system-styles"; style.textContent = ` @keyframes slideInRight { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(400px); opacity: 0; } } `; document.head.appendChild(style); } console.log("[ShopSystem] Ready!"); })();