/** * Wishlist Manager * Created: 2026-01-17 * * Handles wishlist functionality for both guest and authenticated users. * - Guest users: Wishlist stored in localStorage * - Authenticated users: Wishlist stored in database * - On login: Automatically syncs localStorage to database */ (function() { 'use strict'; const STORAGE_KEY = 'fabrilife_wishlist'; // Wishlist Manager Object window.WishlistManager = { items: [], /** * Initialize wishlist manager */ init: function() { this.loadFromStorage(); this.updateBadges(); // If user is logged in, sync localStorage to server if (window.isLoggedIn && this.items.length > 0) { this.syncToServer(); } // If logged in, fetch from server if (window.isLoggedIn) { this.fetchFromServer(); } // Setup event listeners this.setupEventListeners(); }, /** * Load wishlist from localStorage */ loadFromStorage: function() { try { const stored = localStorage.getItem(STORAGE_KEY); this.items = stored ? JSON.parse(stored) : []; } catch (e) { console.error('Error loading wishlist from storage:', e); this.items = []; } }, /** * Save wishlist to localStorage */ saveToStorage: function() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.items)); } catch (e) { console.error('Error saving wishlist to storage:', e); } }, /** * Fetch wishlist from server (for logged-in users) */ fetchFromServer: async function() { try { const response = await fetch('/api/wishlist/', { method: 'GET', headers: { 'Accept': 'application/json', 'X-CSRF-TOKEN': window.csrfToken } }); const data = await response.json(); if (data.success && data.items) { this.items = data.items; this.saveToStorage(); this.updateBadges(); } } catch (e) { console.error('Error fetching wishlist from server:', e); } }, /** * Sync localStorage wishlist to server on login */ syncToServer: async function() { if (!window.isLoggedIn || this.items.length === 0) return; try { const response = await fetch('/api/wishlist/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': window.csrfToken }, body: JSON.stringify({ items: this.items }) }); const data = await response.json(); if (data.success && data.items) { this.items = data.items; this.saveToStorage(); this.updateBadges(); } } catch (e) { console.error('Error syncing wishlist to server:', e); } }, /** * Add product to wishlist * @param {number} productId */ add: async function(productId) { productId = parseInt(productId); if (this.has(productId)) { return { success: false, message: 'Already in wishlist' }; } // Add to local array this.items.push(productId); this.saveToStorage(); this.updateBadges(); // If logged in, sync to server if (window.isLoggedIn) { try { await fetch('/api/wishlist/add', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': window.csrfToken }, body: JSON.stringify({ product_id: productId }) }); } catch (e) { console.error('Error adding to wishlist on server:', e); } } // Dispatch custom event window.dispatchEvent(new CustomEvent('wishlistUpdated', { detail: { action: 'add', productId: productId, count: this.items.length } })); return { success: true, message: 'Added to wishlist', count: this.items.length }; }, /** * Remove product from wishlist * @param {number} productId */ remove: async function(productId) { productId = parseInt(productId); const index = this.items.indexOf(productId); if (index === -1) { return { success: false, message: 'Not in wishlist' }; } // Remove from local array this.items.splice(index, 1); this.saveToStorage(); this.updateBadges(); // If logged in, sync to server if (window.isLoggedIn) { try { await fetch('/api/wishlist/remove', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': window.csrfToken }, body: JSON.stringify({ product_id: productId }) }); } catch (e) { console.error('Error removing from wishlist on server:', e); } } // Dispatch custom event window.dispatchEvent(new CustomEvent('wishlistUpdated', { detail: { action: 'remove', productId: productId, count: this.items.length } })); return { success: true, message: 'Removed from wishlist', count: this.items.length }; }, /** * Toggle product in wishlist * @param {number} productId */ toggle: async function(productId) { productId = parseInt(productId); if (this.has(productId)) { return this.remove(productId); } else { return this.add(productId); } }, /** * Check if product is in wishlist * @param {number} productId * @returns {boolean} */ has: function(productId) { return this.items.includes(parseInt(productId)); }, /** * Get wishlist count * @returns {number} */ count: function() { return this.items.length; }, /** * Get all wishlist items * @returns {array} */ getAll: function() { return this.items; }, /** * Clear entire wishlist */ clear: function() { this.items = []; this.saveToStorage(); this.updateBadges(); window.dispatchEvent(new CustomEvent('wishlistUpdated', { detail: { action: 'clear', count: 0 } })); }, /** * Update all wishlist badges on page */ updateBadges: function() { const count = this.items.length; // Update mobile header badge (mobile-top-nav) const wishlistBadge = document.getElementById('wishlistBadge'); if (wishlistBadge) { wishlistBadge.textContent = count; wishlistBadge.style.display = count > 0 ? 'flex' : 'none'; } // Update desktop header badge (layout.blade.php - main mobile nav) const wishlistBadgeMobile = document.getElementById('wishlistBadgeMobile'); if (wishlistBadgeMobile) { wishlistBadgeMobile.textContent = count; wishlistBadgeMobile.style.display = count > 0 ? 'inline-block' : 'none'; } // Update desktop header badge (layout.blade.php - desktop nav) const wishlistBadgeDesktop = document.getElementById('wishlistBadgeDesktop'); if (wishlistBadgeDesktop) { wishlistBadgeDesktop.textContent = count; wishlistBadgeDesktop.style.display = count > 0 ? 'inline-block' : 'none'; } // Update side menu badge const sideMenuBadges = document.querySelectorAll('.wishlist-count'); sideMenuBadges.forEach(badge => { badge.textContent = count; badge.style.display = count > 0 ? 'inline-block' : 'none'; }); // Update heart icons on product cards this.updateHeartIcons(); }, /** * Update heart icons on product cards */ updateHeartIcons: function() { const wishlistBtns = document.querySelectorAll('[data-wishlist-btn]'); wishlistBtns.forEach(btn => { const productId = parseInt(btn.dataset.productId); const icon = btn.querySelector('i'); if (this.has(productId)) { btn.classList.add('active'); if (icon) { icon.classList.remove('fa-heart-o'); icon.classList.add('fa-heart'); } } else { btn.classList.remove('active'); if (icon) { icon.classList.remove('fa-heart'); icon.classList.add('fa-heart-o'); } } }); }, /** * Setup event listeners */ setupEventListeners: function() { // Delegate click events for wishlist buttons document.addEventListener('click', (e) => { const wishlistBtn = e.target.closest('[data-wishlist-btn]'); if (wishlistBtn) { e.preventDefault(); e.stopPropagation(); // Check if user is logged in if (!window.isLoggedIn) { // Show login modal for guests if (typeof invokeLogin === 'function') { invokeLogin(); } else if (window.showToast) { window.showToast('Please log in to add items to your wishlist', 'error'); } return; } const productId = wishlistBtn.dataset.productId; this.toggle(productId).then(result => { // Show toast notification if (window.showToast) { window.showToast(result.message, result.success ? 'success' : 'error'); } }); } }); // Listen for login events to sync wishlist window.addEventListener('userLoggedIn', () => { this.syncToServer(); }); // Listen for Algolia render events (new products loaded via infinite scroll) document.addEventListener('algolia:rendered', () => { this.updateHeartIcons(); }); } }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => WishlistManager.init()); } else { WishlistManager.init(); } })();