PageSourceSearch

https://www.paradenyc.org/assets/juso/guest-cart.js

js paradenyc.org collected 2026-10-06 05:27:58 UTC 9,763 bytes, 345 lines download raw bytes

1/**
2 * Guest Cart Manager - localStorage based cart for guest users
3 * 
4 * Manages shopping cart items in browser localStorage for users who are not logged in.
5 * Provides seamless transition to database when user logs in.
6 */
7
8class GuestCart {
9    constructor() {
10        this.storageKey = 'plazait_guest_cart';
11        this.apiBase = '/api/cart';
12        
13        // Initialize cart structure
14        this.cart = this.loadFromStorage();
15        
16        // Bind methods
17        this.add = this.add.bind(this);
18        this.remove = this.remove.bind(this);
19        this.update = this.update.bind(this);
20        this.clear = this.clear.bind(this);
21        this.getItems = this.getItems.bind(this);
22        this.mergeToDatabase = this.mergeToDatabase.bind(this);
23    }
24    
25    /**
26     * Load cart from localStorage
27     */
28    loadFromStorage() {
29        try {
30            const stored = localStorage.getItem(this.storageKey);
31            if (stored) {
32                const parsed = JSON.parse(stored);
33                if (parsed && typeof parsed === 'object' && parsed.items) {
34                    return parsed;
35                }
36            }
37        } catch (error) {
38            // Silent error handling
39        }
40        
41        return {
42            items: {},
43            updated_at: Date.now(),
44            version: '1.0'
45        };
46    }
47    
48    /**
49     * Save cart to localStorage
50     */
51    saveToStorage() {
52        try {
53            this.cart.updated_at = Date.now();
54            localStorage.setItem(this.storageKey, JSON.stringify(this.cart));
55            
56            // Dispatch custom event for cart updates
57            window.dispatchEvent(new CustomEvent('guestCartUpdated', {
58                detail: {
59                    itemsCount: this.getItemsCount(),
60                    linesCount: this.getLinesCount(),
61                    items: this.cart.items
62                }
63            }));
64            
65            return true;
66        } catch (error) {
67            return false;
68        }
69    }
70    
71    /**
72     * Generate cart item key
73     */
74    generateKey(productId, variantId = 0) {
75        return `p${productId}v${variantId}`;
76    }
77    
78    /**
79     * Add item to guest cart
80     */
81    add(productId, qty = 1, variantId = 0, meta = {}) {
82        const key = this.generateKey(productId, variantId);
83        
84        // Validate inputs
85        if (!productId || productId <= 0 || !qty || qty <= 0) {
86            throw new Error('Invalid product ID or quantity');
87        }
88        
89        qty = Math.max(1, Math.min(999, parseInt(qty)));
90        
91        // Initialize item if not exists
92        if (!this.cart.items[key]) {
93            this.cart.items[key] = {
94                pid: parseInt(productId),
95                vid: parseInt(variantId) || 0,
96                qty: 0,
97                name: meta.name || '',
98                sku: meta.sku || '',
99                price: parseInt(meta.price) || 0,
100                image_url: meta.image_url || '',
101                product_url: meta.product_url || '/product/' + parseInt(productId),
102                added_at: Date.now()
103            };
104        }
105        
106        // Update quantity
107        this.cart.items[key].qty = Math.max(1, Math.min(999, this.cart.items[key].qty + qty));
108        
109        // Update metadata if provided
110        if (meta.name) this.cart.items[key].name = meta.name;
111        if (meta.sku) this.cart.items[key].sku = meta.sku;
112        if (meta.price) this.cart.items[key].price = parseInt(meta.price);
113        if (meta.image_url) this.cart.items[key].image_url = meta.image_url;
114        if (meta.product_url) this.cart.items[key].product_url = meta.product_url;
115        
116        this.saveToStorage();
117        
118        return {
119            key,
120            item: this.cart.items[key],
121            itemsCount: this.getItemsCount(),
122            linesCount: this.getLinesCount()
123        };
124    }
125    
126    /**
127     * Remove item from guest cart
128     */
129    remove(productId, variantId = 0) {
130        const key = this.generateKey(productId, variantId);
131        
132        if (this.cart.items[key]) {
133            delete this.cart.items[key];
134            this.saveToStorage();
135            
136            return true;
137        }
138        
139        return false;
140    }
141    
142    /**
143     * Update item quantity
144     */
145    update(productId, qty, variantId = 0) {
146        const key = this.generateKey(productId, variantId);
147        
148        if (!this.cart.items[key]) {
149            return false;
150        }
151        
152        qty = parseInt(qty);
153        if (qty <= 0) {
154            return this.remove(productId, variantId);
155        }
156        
157        this.cart.items[key].qty = Math.max(1, Math.min(999, qty));
158        this.saveToStorage();
159        
160        return true;
161    }
162    
163    /**
164     * Clear entire cart
165     */
166    clear() {
167        this.cart = {
168            items: {},
169            updated_at: Date.now(),
170            version: '1.0'
171        };
172        
173        this.saveToStorage();
174        
175        return true;
176    }
177    
178    /**
179     * Get all cart items
180     */
181    getItems() {
182        return this.cart.items;
183    }
184    
185    /**
186     * Get cart items as array
187     */
188    getItemsArray() {
189        return Object.values(this.cart.items);
190    }
191    
192    /**
193     * Get total items count (sum of quantities)
194     */
195    getItemsCount() {
196        return Object.values(this.cart.items).reduce((total, item) => total + (item.qty || 0), 0);
197    }
198    
199    /**
200     * Get lines count (number of different items)
201     */
202    getLinesCount() {
203        return Object.keys(this.cart.items).length;
204    }
205    
206    /**
207     * Get cart summary
208     */
209    getSummary() {
210        const itemsCount = this.getItemsCount();
211        const linesCount = this.getLinesCount();
212        const totalValue = Object.values(this.cart.items).reduce(
213            (total, item) => total + ((item.qty || 0) * (item.price || 0)), 0
214        );
215        
216        return {
217            itemsCount,
218            linesCount,
219            totalValue,
220            updatedAt: this.cart.updated_at
221        };
222    }
223    
224    /**
225     * Check if cart has items
226     */
227    isEmpty() {
228        return this.getLinesCount() === 0;
229    }
230    
231    /**
232     * Merge guest cart to database after user login
233     */
234    async mergeToDatabase() {
235        const items = this.cart.items;
236        
237        if (Object.keys(items).length === 0) {
238            return { ok: true, merged_count: 0, message: 'No items to merge' };
239        }
240        
241        try {
242            // Use new endpoint that handles localStorage format
243            const response = await fetch(`${this.apiBase}/merge-localStorage.php`, {
244                method: 'POST',
245                headers: {
246                    'Content-Type': 'application/json',
247                },
248                credentials: 'same-origin',
249                body: JSON.stringify({
250                    items: items  // Send localStorage format directly
251                })
252            });
253            
254            const result = await response.json();
255            
256            if (result.ok) {
257                // Clear localStorage cart after successful merge
258                this.clear();
259                
260                // Update cart counter
261                if (window.updateCartCounter) {
262                    try {
263                        await window.updateCartCounter();
264                    } catch (e) {
265                        // Silent error handling
266                    }
267                }
268                
269                // Dispatch merge completion event
270                window.dispatchEvent(new CustomEvent('guestCartMerged', {
271                    detail: result
272                }));
273                
274                return result;
275            } else {
276                throw new Error(result.error || 'Failed to merge cart');
277            }
278            
279        } catch (error) {
280            throw error;
281        }
282    }
283    
284    /**
285     * Export cart data (for debugging or backup)
286     */
287    export() {
288        return {
289            ...this.cart,
290            summary: this.getSummary()
291        };
292    }
293    
294    /**
295     * Import cart data (for restore or testing)
296     */
297    import(cartData) {
298        if (cartData && typeof cartData === 'object' && cartData.items) {
299            this.cart = {
300                items: cartData.items,
301                updated_at: cartData.updated_at || Date.now(),
302                version: cartData.version || '1.0'
303            };
304            
305            this.saveToStorage();
306            return true;
307        }
308        
309        return false;
310    }
311}
312
313// Create global instance
314window.guestCart = new GuestCart();
315
316// Auto-merge on page load if user is logged in
317document.addEventListener('DOMContentLoaded', async function() {
318    // Multiple ways to check if user is logged in
319    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
320                      document.body.classList.contains('logged-in') ||
321                      window.userLoggedIn === true ||
322                      document.body.dataset.userLoggedIn === 'true';
323    
324    if (isLoggedIn && !window.guestCart.isEmpty()) {
325        // Small delay to ensure page is fully loaded
326        setTimeout(async () => {
327            try {
328                const result = await window.guestCart.mergeToDatabase();
329            } catch (error) {
330                // Silent error handling
331            }
332        }, 500);
333    }
334});
335
336// Also listen for login events (if login happens via AJAX)
337window.addEventListener('userLoggedIn', async function() {
338    if (!window.guestCart.isEmpty()) {
339        try {
340            await window.guestCart.mergeToDatabase();
341        } catch (error) {
342            // Silent error handling
343        }
344    }
345});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.