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

@@ -1,10 +1,11 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import { ShoppingCart, Heart } from 'lucide-react';
import { formatPrice, formatDiscount } from '@/lib/currency';
import { Button } from './ui/button';
import { useLayout } from '@/contexts/ThemeContext';
import { useShopSettings } from '@/hooks/useAppearanceSettings';
import { useWishlist } from '@/hooks/useWishlist';
interface ProductCardProps {
product: {
@@ -23,8 +24,18 @@ interface ProductCardProps {
}
export function ProductCard({ product, onAddToCart }: ProductCardProps) {
const navigate = useNavigate();
const { isClassic, isModern, isBoutique, isLaunch } = useLayout();
const { layout, elements, addToCart, saleBadge, isLoading } = useShopSettings();
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
const inWishlist = wishlistEnabled && isInWishlist(product.id);
const handleWishlistClick = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
await toggleWishlist(product.id);
};
// Aspect ratio classes
const aspectRatioClass = {
@@ -41,7 +52,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
// Variable products need to go to product page for attribute selection
if (isVariable) {
window.location.href = `/product/${product.slug}`;
navigate(`/product/${product.slug}`);
return;
}
@@ -130,12 +141,22 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</div>
)}
{/* Quick Actions */}
<div className="absolute top-2 left-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="font-[inherit] p-2 bg-white rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center">
<Heart className="w-4 h-4 block" />
</button>
</div>
{/* Wishlist Button */}
{wishlistEnabled && (
<div className="absolute top-2 left-2 z-10">
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
{/* Hover/Overlay Button */}
{showButtonOnHover && (
@@ -224,6 +245,23 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</div>
)}
{/* Wishlist Button */}
{wishlistEnabled && (
<div className="absolute top-4 right-4 z-10">
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
{/* Hover Overlay - Only show if position is hover/overlay */}
{showButtonOnHover && (
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-300 flex items-center justify-center">
@@ -326,6 +364,23 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
{discount}
</div>
)}
{/* Wishlist Button */}
{wishlistEnabled && (
<div className="absolute top-6 left-6 z-10">
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
</div>
{/* Content */}
@@ -383,6 +438,23 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
No Image
</div>
)}
{/* Wishlist Button */}
{wishlistEnabled && (
<div className="absolute top-3 right-3 z-10">
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
</div>
<div className="p-4 text-center">