feat: Guest wishlist localStorage + visual state

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
This commit is contained in:
Dwindi Ramadhana
2025-12-26 23:07:18 +07:00
parent 0247f1edd8
commit e64045b0e1

View File

@@ -16,6 +16,8 @@ interface WishlistItem {
added_at: string; added_at: string;
} }
const GUEST_WISHLIST_KEY = 'woonoow_guest_wishlist';
export function useWishlist() { export function useWishlist() {
const [items, setItems] = useState<WishlistItem[]>([]); const [items, setItems] = useState<WishlistItem[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -26,10 +28,36 @@ export function useWishlist() {
const isEnabled = settings?.wishlist_enabled !== false; const isEnabled = settings?.wishlist_enabled !== false;
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn; 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 // Load wishlist on mount
useEffect(() => { useEffect(() => {
if (isEnabled && isLoggedIn) { if (isEnabled) {
loadWishlist(); if (isLoggedIn) {
loadWishlist();
} else {
loadGuestWishlist();
}
} }
}, [isEnabled, isLoggedIn]); }, [isEnabled, isLoggedIn]);
@@ -49,6 +77,17 @@ export function useWishlist() {
}, [isLoggedIn]); }, [isLoggedIn]);
const addToWishlist = useCallback(async (productId: number) => { 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 { try {
await api.post('/account/wishlist', { product_id: productId }); await api.post('/account/wishlist', { product_id: productId });
await loadWishlist(); // Reload to get full product details await loadWishlist(); // Reload to get full product details
@@ -59,9 +98,20 @@ export function useWishlist() {
toast.error(message); toast.error(message);
return false; return false;
} }
}, [loadWishlist]); }, [isLoggedIn, productIds, loadWishlist, saveGuestWishlist]);
const removeFromWishlist = useCallback(async (productId: number) => { 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 { try {
await api.delete(`/account/wishlist/${productId}`); await api.delete(`/account/wishlist/${productId}`);
setItems(items.filter(item => item.product_id !== productId)); setItems(items.filter(item => item.product_id !== productId));
@@ -76,7 +126,7 @@ export function useWishlist() {
toast.error('Failed to remove from wishlist'); toast.error('Failed to remove from wishlist');
return false; return false;
} }
}, [items]); }, [isLoggedIn, productIds, items, saveGuestWishlist]);
const toggleWishlist = useCallback(async (productId: number) => { const toggleWishlist = useCallback(async (productId: number) => {
if (productIds.has(productId)) { if (productIds.has(productId)) {