import { directus } from './directus.js' import { auth } from './auth.js' const ANON_KEY = 'kashilo_favorites' class FavoritesService { constructor() { this.ids = new Set() this.loaded = false this.syncing = false this.listeners = [] } init() { // Migrate old localStorage key const oldFavs = localStorage.getItem('favorites') if (oldFavs && !localStorage.getItem(ANON_KEY)) { localStorage.setItem(ANON_KEY, oldFavs) localStorage.removeItem('favorites') } this.ids = new Set(this.getAnonIds()) this.loaded = true auth.subscribe((loggedIn) => { if (loggedIn) { this.mergeOnLogin() } else { this.saveAnonIds() this.loaded = true this.notify() } }) if (auth.isLoggedIn()) { this.mergeOnLogin() } } subscribe(cb) { this.listeners.push(cb) return () => { this.listeners = this.listeners.filter(l => l !== cb) } } notify() { this.listeners.forEach(cb => cb(this.ids)) window.dispatchEvent(new CustomEvent('favorites-changed')) } // localStorage helpers getAnonIds() { try { return JSON.parse(localStorage.getItem(ANON_KEY) || '[]') } catch { return [] } } saveAnonIds() { localStorage.setItem(ANON_KEY, JSON.stringify([...this.ids])) } // Public API isFavorite(id) { return this.ids.has(id) } getAll() { return [...this.ids] } async toggle(id) { const wasFavorite = this.ids.has(id) // Optimistic update if (wasFavorite) { this.ids.delete(id) } else { this.ids.add(id) } this.notify() if (auth.isLoggedIn()) { try { if (wasFavorite) { await this.removeRemote(id) } else { await this.addRemote(id) } } catch (e) { // Revert on failure if (wasFavorite) { this.ids.add(id) } else { this.ids.delete(id) } this.notify() console.error('Failed to sync favorite:', e) } } else { this.saveAnonIds() } } // Directus API async addRemote(listingId) { const user = await auth.getUser() if (!user) return const existing = await directus.get('/items/favorites', { filter: { user: { _eq: user.id }, listing: { _eq: listingId } }, fields: ['id'], limit: 1 }) if (existing.data?.length > 0) return await directus.post('/items/favorites', { user: user.id, listing: listingId }) } async removeRemote(listingId) { const user = await auth.getUser() if (!user) return const response = await directus.get('/items/favorites', { filter: { user: { _eq: user.id }, listing: { _eq: listingId } }, fields: ['id'], limit: 1 }) const item = response.data?.[0] if (item) { await directus.delete(`/items/favorites/${item.id}`) } } async fetchRemote() { const user = await auth.getUser() if (!user) return [] const response = await directus.get('/items/favorites', { filter: { user: { _eq: user.id } }, fields: ['listing'], limit: 200 }) return (response.data || []).map(f => f.listing).filter(Boolean) } async mergeOnLogin() { this.syncing = true try { const remoteIds = await this.fetchRemote() const localIds = this.getAnonIds() // Union merge const merged = new Set([...remoteIds, ...localIds]) this.ids = merged // Push local-only favorites to server const remoteSet = new Set(remoteIds) const toAdd = localIds.filter(id => !remoteSet.has(id)) for (const id of toAdd) { try { await this.addRemote(id) } catch (e) { console.error('Failed to sync favorite to server:', e) } } localStorage.removeItem(ANON_KEY) this.loaded = true this.notify() } catch (e) { console.error('Failed to merge favorites:', e) this.loaded = true } this.syncing = false } } export const favoritesService = new FavoritesService() export default favoritesService