/** * 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; if (count > 0) { cartBadge.classList.add("show"); } else { cartBadge.classList.remove("show"); } } // Update wishlist badge const wishlistBadge = document.getElementById("wishlistCount"); if (wishlistBadge) { const count = this.wishlist.length; wishlistBadge.textContent = count; if (count > 0) { wishlistBadge.classList.add("show"); } else { wishlistBadge.classList.remove("show"); } } } renderCartDropdown() { const cartContent = document.getElementById("cartContent"); if (!cartContent) return; if (this.cart.length === 0) { cartContent.innerHTML = '
Your cart is empty
$${price}
Subtotal: $${subtotal}
Your wishlist is empty
$${price}