// FavouritesService.js - Manages favourites functionality class FavouritesService { constructor(apiUrl, nonce) { this.apiUrl = apiUrl; this.nonce = nonce; this.cache = {}; this.eventHandlersBound = false; this.initialized = false; } /** * Initialize the service */ async init() { if (this.initialized) return; // Load cached favourites from localStorage this.loadFromLocalStorage(); // Bind event handlers if not already bound if (!this.eventHandlersBound) { this.bindEventHandlers(); } // Mark as initialized this.initialized = true; // If user is logged in, fetch favourites from server if (feedSettings.currentUser) { await this.fetchFavourites(); } } /** * Load favourites from localStorage */ loadFromLocalStorage() { try { const storedCache = localStorage.getItem('userFavourites'); if (storedCache) { this.cache = JSON.parse(storedCache); } else { this.cache = {}; localStorage.setItem('userFavourites', '{}'); } } catch (e) { console.error('Error loading favourites from localStorage', e); this.cache = {}; localStorage.setItem('userFavourites', '{}'); } } /** * Save favourites to localStorage */ saveToLocalStorage() { try { localStorage.setItem('userFavourites', JSON.stringify(this.cache)); } catch (e) { console.error('Error saving favourites to localStorage', e); } } /** * Bind event handlers for favourites */ bindEventHandlers() { // Listen for favourites updated events document.addEventListener('favourites-updated', this.handleFavouritesUpdate.bind(this)); // Listen for document clicks to handle favourite buttons document.addEventListener('click', this.handleClick.bind(this)); this.eventHandlersBound = true; } /** * Handle click events for favourite buttons */ handleClick(event) { const target = event.target.closest('button.favourite'); if (!target) return; // Check if user is logged in if (!feedSettings.currentUser) { // Redirect to login if not logged in window.location.href = feedSettings.loginUrl; return; } // Get favourite data const type = target.dataset.type; const id = target.dataset.id; const artistId = target.dataset.artist || ''; // Toggle favourite const isFavourited = target.classList.contains('favourited'); this.toggleFavourite(type, id, !isFavourited, artistId); // Prevent default behavior event.preventDefault(); event.stopPropagation(); } /** * Handle favourites update events */ handleFavouritesUpdate(event) { const { type, id, isFavourited } = event.detail; // Update cache this.updateCache(type, id, isFavourited); // Update UI this.updateUI(type, id, isFavourited); } /** * Update UI elements for a favourite */ updateUI(type, id, isFavourited) { // Find all matching buttons const buttons = document.querySelectorAll( `button.favourite[data-type="${type.replace('e_', '')}"][data-id="${id}"]` ); // Update each button buttons.forEach(button => { button.classList.toggle('favourited', isFavourited); button.innerHTML = feedSettings.icons[isFavourited ? 'heart-filled' : 'heart']; button.title = isFavourited ? 'Remove from favourites' : 'Add to favourites'; }); } /** * Toggle a favourite */ async toggleFavourite(type, id, favourite, artistId = '') { try { // Prepare request data const data = new FormData(); data.append('user_id', feedSettings.currentUser.id); data.append('type', `e_${type}`); data.append('target_id', id); data.append('isFavourited', favourite ? '1' : '0'); // Optional artist data if (artistId) { data.append('artist_id', artistId); } // Make request const response = await fetch(`${this.apiUrl}favourites/toggle`, { method: 'POST', headers: { 'X-WP-Nonce': this.nonce }, body: data }); if (!response.ok) { throw new Error('Failed to toggle favourite'); } // Update cache this.updateCache(`e_${type}`, id, favourite); // Dispatch event for other components document.dispatchEvent(new CustomEvent('favourites-updated', { detail: { type: `e_${type}`, id: id, isFavourited: favourite } })); return true; } catch (error) { console.error('Error toggling favourite:', error); // Show error notification if (window.jvbNotifications) { window.jvbNotifications.queuePopupNotification({ type: 'error', message: 'Failed to update favourite', priority: 'medium', displayDuration: 3000 }); } return false; } } /** * Fetch favourites from the server */ async fetchFavourites() { if (!feedSettings.currentUser) return; try { const userId = feedSettings.currentUser.id; const queryParams = new URLSearchParams({ user_id: userId }); const response = await fetch(`${this.apiUrl}favourites/all?${queryParams.toString()}`, { headers: { 'X-WP-Nonce': this.nonce } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data && data.favourites) { // Convert to cache format const newCache = {}; data.favourites.forEach(fav => { newCache[`${fav.type}_${fav.target_id}`] = true; }); // Update cache this.cache = newCache; this.saveToLocalStorage(); } return this.cache; } catch (error) { console.error('Failed to load favourites:', error); return this.cache; } } /** * Check if an item is favourited */ isFavourited(type, id) { // Ensure type has prefix if (!type.startsWith('e_')) { type = `e_${type}`; } return !!this.cache[`${type}_${id}`]; } /** * Update the cache */ updateCache(type, id, isFavourited) { // Ensure type has prefix if (!type.startsWith('e_')) { type = `e_${type}`; } // Update cache if (isFavourited) { this.cache[`${type}_${id}`] = true; } else { delete this.cache[`${type}_${id}`]; } // Save to localStorage this.saveToLocalStorage(); } } export default FavouritesService;