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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user