Guest Wishlist Implementation: Problem: Guests couldn't persist wishlist, no visual feedback on wishlisted items Solution: Implemented localStorage-based guest wishlist system Changes: 1. localStorage Storage: - Key: 'woonoow_guest_wishlist' - Stores array of product IDs - Persists across browser sessions - Loads on mount for guests 2. Dual Mode Logic: - Guest (not logged in): localStorage only - Logged in: API + database - isInWishlist() works for both modes 3. Visual State: - productIds Set tracks wishlisted items - Heart icons show filled state when in wishlist - Works in ProductCard, Product page, etc. Result: ✅ Guests can add/remove items (persists in browser) ✅ Heart icons show filled state for wishlisted items ✅ No login required when guest wishlist enabled ✅ Seamless experience for both guests and logged-in users Files Modified: - customer-spa/src/hooks/useWishlist.ts (localStorage implementation) - customer-spa/dist/app.js (rebuilt) Note: Categories/Tags/Attributes pages already exist as placeholder pages
156 lines
4.4 KiB
TypeScript
156 lines
4.4 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { api } from '@/lib/api/client';
|
|
import { toast } from 'sonner';
|
|
|
|
interface WishlistItem {
|
|
product_id: number;
|
|
name: string;
|
|
slug: string;
|
|
price: string;
|
|
regular_price?: string;
|
|
sale_price?: string;
|
|
image?: string;
|
|
on_sale?: boolean;
|
|
stock_status?: string;
|
|
type?: string;
|
|
added_at: string;
|
|
}
|
|
|
|
const GUEST_WISHLIST_KEY = 'woonoow_guest_wishlist';
|
|
|
|
export function useWishlist() {
|
|
const [items, setItems] = useState<WishlistItem[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [productIds, setProductIds] = useState<Set<number>>(new Set());
|
|
|
|
// Check if wishlist is enabled (default true if not explicitly set to false)
|
|
const settings = (window as any).woonoowCustomer?.settings;
|
|
const isEnabled = settings?.wishlist_enabled !== false;
|
|
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn;
|
|
|
|
// Load guest wishlist from localStorage
|
|
const loadGuestWishlist = useCallback(() => {
|
|
try {
|
|
const stored = localStorage.getItem(GUEST_WISHLIST_KEY);
|
|
if (stored) {
|
|
const guestIds = JSON.parse(stored) as number[];
|
|
setProductIds(new Set(guestIds));
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load guest wishlist:', error);
|
|
}
|
|
}, []);
|
|
|
|
// Save guest wishlist to localStorage
|
|
const saveGuestWishlist = useCallback((ids: Set<number>) => {
|
|
try {
|
|
localStorage.setItem(GUEST_WISHLIST_KEY, JSON.stringify(Array.from(ids)));
|
|
} catch (error) {
|
|
console.error('Failed to save guest wishlist:', error);
|
|
}
|
|
}, []);
|
|
|
|
// Load wishlist on mount
|
|
useEffect(() => {
|
|
if (isEnabled) {
|
|
if (isLoggedIn) {
|
|
loadWishlist();
|
|
} else {
|
|
loadGuestWishlist();
|
|
}
|
|
}
|
|
}, [isEnabled, isLoggedIn]);
|
|
|
|
const loadWishlist = useCallback(async () => {
|
|
if (!isLoggedIn) return;
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
const data = await api.get<WishlistItem[]>('/account/wishlist');
|
|
setItems(data);
|
|
setProductIds(new Set(data.map(item => item.product_id)));
|
|
} catch (error) {
|
|
console.error('Failed to load wishlist:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [isLoggedIn]);
|
|
|
|
const addToWishlist = useCallback(async (productId: number) => {
|
|
// Guest mode: store in localStorage only
|
|
if (!isLoggedIn) {
|
|
const newIds = new Set(productIds);
|
|
newIds.add(productId);
|
|
setProductIds(newIds);
|
|
saveGuestWishlist(newIds);
|
|
toast.success('Added to wishlist');
|
|
return true;
|
|
}
|
|
|
|
// Logged in: use API
|
|
try {
|
|
await api.post('/account/wishlist', { product_id: productId });
|
|
await loadWishlist(); // Reload to get full product details
|
|
toast.success('Added to wishlist');
|
|
return true;
|
|
} catch (error: any) {
|
|
const message = error?.message || 'Failed to add to wishlist';
|
|
toast.error(message);
|
|
return false;
|
|
}
|
|
}, [isLoggedIn, productIds, loadWishlist, saveGuestWishlist]);
|
|
|
|
const removeFromWishlist = useCallback(async (productId: number) => {
|
|
// Guest mode: remove from localStorage only
|
|
if (!isLoggedIn) {
|
|
const newIds = new Set(productIds);
|
|
newIds.delete(productId);
|
|
setProductIds(newIds);
|
|
saveGuestWishlist(newIds);
|
|
toast.success('Removed from wishlist');
|
|
return true;
|
|
}
|
|
|
|
// Logged in: use API
|
|
try {
|
|
await api.delete(`/account/wishlist/${productId}`);
|
|
setItems(items.filter(item => item.product_id !== productId));
|
|
setProductIds(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.delete(productId);
|
|
return newSet;
|
|
});
|
|
toast.success('Removed from wishlist');
|
|
return true;
|
|
} catch (error) {
|
|
toast.error('Failed to remove from wishlist');
|
|
return false;
|
|
}
|
|
}, [isLoggedIn, productIds, items, saveGuestWishlist]);
|
|
|
|
const toggleWishlist = useCallback(async (productId: number) => {
|
|
if (productIds.has(productId)) {
|
|
return await removeFromWishlist(productId);
|
|
} else {
|
|
return await addToWishlist(productId);
|
|
}
|
|
}, [productIds, addToWishlist, removeFromWishlist]);
|
|
|
|
const isInWishlist = useCallback((productId: number) => {
|
|
return productIds.has(productId);
|
|
}, [productIds]);
|
|
|
|
return {
|
|
items,
|
|
isLoading,
|
|
isEnabled,
|
|
isLoggedIn,
|
|
count: items.length,
|
|
addToWishlist,
|
|
removeFromWishlist,
|
|
toggleWishlist,
|
|
isInWishlist,
|
|
refresh: loadWishlist,
|
|
};
|
|
}
|