// static/shop/js/shop-layout.js /** * Shop Layout Component * Provides base functionality for vendor shop pages * Works with vendor-specific themes */ const shopLog = { info: (...args) => console.info('🛒 [SHOP]', ...args), warn: (...args) => console.warn('⚠️ [SHOP]', ...args), error: (...args) => console.error('❌ [SHOP]', ...args), debug: (...args) => console.log('🔍 [SHOP]', ...args) }; /** * Shop Layout Data * Base Alpine.js component for shop pages */ function shopLayoutData() { return { // Theme state dark: localStorage.getItem('shop-theme') === 'dark', // UI state mobileMenuOpen: false, searchOpen: false, loading: false, cartCount: 0, // Cart state cart: [], sessionId: null, // Initialize init() { shopLog.info('Shop layout initializing...'); // Get or create session ID this.sessionId = this.getOrCreateSessionId(); shopLog.debug('Session ID:', this.sessionId); // Load cart from localStorage this.loadCart(); // Listen for cart updates window.addEventListener('cart-updated', () => { this.loadCart(); }); shopLog.info('Shop layout initialized'); }, // Get or create session ID getOrCreateSessionId() { let sessionId = localStorage.getItem('cart_session_id'); if (!sessionId) { sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('cart_session_id', sessionId); shopLog.info('Created new session ID:', sessionId); } return sessionId; }, // Theme management toggleTheme() { this.dark = !this.dark; localStorage.setItem('shop-theme', this.dark ? 'dark' : 'light'); shopLog.debug('Theme toggled:', this.dark ? 'dark' : 'light'); }, // Mobile menu toggleMobileMenu() { this.mobileMenuOpen = !this.mobileMenuOpen; if (this.mobileMenuOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } }, closeMobileMenu() { this.mobileMenuOpen = false; document.body.style.overflow = ''; }, // Search openSearch() { this.searchOpen = true; shopLog.debug('Search opened'); // Focus search input after a short delay setTimeout(() => { const input = document.querySelector('#search-input'); if (input) input.focus(); }, 100); }, closeSearch() { this.searchOpen = false; }, // Cart management loadCart() { try { const cartData = localStorage.getItem('shop-cart'); if (cartData) { this.cart = JSON.parse(cartData); this.cartCount = this.cart.reduce((sum, item) => sum + item.quantity, 0); } } catch (error) { shopLog.error('Failed to load cart:', error); this.cart = []; this.cartCount = 0; } }, addToCart(product, quantity = 1) { shopLog.info('Adding to cart:', product.name, 'x', quantity); // Find existing item const existingIndex = this.cart.findIndex(item => item.id === product.id); if (existingIndex !== -1) { // Update quantity this.cart[existingIndex].quantity += quantity; } else { // Add new item this.cart.push({ id: product.id, name: product.name, price: product.price, image: product.image, quantity: quantity }); } // Save and update this.saveCart(); this.showToast(`${product.name} added to cart`, 'success'); }, updateCartItem(productId, quantity) { const index = this.cart.findIndex(item => item.id === productId); if (index !== -1) { if (quantity <= 0) { this.cart.splice(index, 1); } else { this.cart[index].quantity = quantity; } this.saveCart(); } }, removeFromCart(productId) { this.cart = this.cart.filter(item => item.id !== productId); this.saveCart(); this.showToast('Item removed from cart', 'info'); }, clearCart() { this.cart = []; this.saveCart(); this.showToast('Cart cleared', 'info'); }, saveCart() { try { localStorage.setItem('shop-cart', JSON.stringify(this.cart)); this.cartCount = this.cart.reduce((sum, item) => sum + item.quantity, 0); // Dispatch custom event window.dispatchEvent(new CustomEvent('cart-updated')); shopLog.debug('Cart saved:', this.cart.length, 'items'); } catch (error) { shopLog.error('Failed to save cart:', error); } }, // Get cart total get cartTotal() { return this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); }, // Toast notifications showToast(message, type = 'info') { const container = document.getElementById('toast-container'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast toast-${type} transform transition-all duration-300 mb-2`; // Color based on type const colors = { success: 'bg-green-500', error: 'bg-red-500', warning: 'bg-yellow-500', info: 'bg-blue-500' }; toast.innerHTML = `