feat: implement wishlist feature with admin toggle

- Add WishlistController with full CRUD API
- Create wishlist page in My Account
- Add heart icon to all product card layouts (always visible)
- Implement useWishlist hook for state management
- Add wishlist toggle in admin Settings > Customer
- Fix wishlist menu visibility based on admin settings
- Fix double navigation in wishlist page
- Fix variable product navigation to use React Router
- Add TypeScript type casting fix for addresses
This commit is contained in:
Dwindi Ramadhana
2025-12-26 01:44:15 +07:00
parent 100f9cce55
commit 0b08ddefa1
9 changed files with 608 additions and 10 deletions

View File

@@ -0,0 +1,111 @@
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;
}
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
const isEnabled = (window as any).woonoowCustomer?.settings?.wishlist_enabled !== false;
const isLoggedIn = (window as any).woonoowCustomer?.user?.isLoggedIn;
// Load wishlist on mount
useEffect(() => {
if (isEnabled && isLoggedIn) {
loadWishlist();
}
}, [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) => {
if (!isLoggedIn) {
toast.error('Please login to add items to wishlist');
return false;
}
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, loadWishlist]);
const removeFromWishlist = useCallback(async (productId: number) => {
if (!isLoggedIn) return false;
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, items]);
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,
};
}