feat(spa): add wishlist functionality with localStorage and UI integration
This commit is contained in:
1 parent
ca31564ee7
commit
091eeef7d8
5 files changed
+155
-5
No files matched your search
@@ -1,8 +1,9 @@
|
||||
import { Link, NavLink, useNavigate } from 'react-router-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import type { Category } from '../types'
|
||||
import { ShoppingBag, MapPin, Menu as MenuIcon, X } from 'lucide-react'
|
||||
import { ShoppingBag, MapPin, Menu as MenuIcon, X, Heart } from 'lucide-react'
|
||||
import { useCartStore } from '../store/cartStore'
|
||||
import { useWishlistStore } from '../store/wishlistStore'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
import { logoutLiff, loginLiff } from '../lib/liff'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -21,6 +22,7 @@ export default function Navbar({ categories = [] }: NavbarProps) {
|
||||
|
||||
const { toggleCart, getCartCount } = useCartStore()
|
||||
const cartCount = getCartCount()
|
||||
const wishlistCount = useWishlistStore(state => state.items.length)
|
||||
const { customer } = useAuthStore()
|
||||
const { t, i18n } = useTranslation()
|
||||
|
||||
@@ -140,6 +142,19 @@ export default function Navbar({ categories = [] }: NavbarProps) {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
to="/wishlist"
|
||||
className="relative p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="w-4 h-4 group-hover:text-rose-500 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => toggleCart(true)}
|
||||
className="relative p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
||||
@@ -242,6 +257,20 @@ export default function Navbar({ categories = [] }: NavbarProps) {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="relative p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="w-4 h-4" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => { toggleCart(true); setMobileOpen(false) }}
|
||||
className="relative p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import type { Product } from '../types'
|
||||
import { getCategoryImage, formatPrice } from '../types'
|
||||
import { Heart } from 'lucide-react'
|
||||
import { useWishlistStore } from '../store/wishlistStore'
|
||||
|
||||
interface ProductCardProps {
|
||||
product: Product
|
||||
}
|
||||
|
||||
export default function ProductCard({ product }: ProductCardProps) {
|
||||
const { addItem, removeItem, isInWishlist } = useWishlistStore()
|
||||
const isWished = isInWishlist(product.id)
|
||||
|
||||
const handleWishlistClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault() // prevent navigating to product detail
|
||||
if (isWished) {
|
||||
removeItem(product.id)
|
||||
} else {
|
||||
addItem(product)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/products/${product.id}`}
|
||||
@@ -22,10 +36,15 @@ export default function ProductCard({ product }: ProductCardProps) {
|
||||
<div className="absolute top-3 left-3 bg-white/90 backdrop-blur-sm px-2.5 py-1 rounded-md text-[10px] font-bold uppercase tracking-widest text-spa-forest shadow-sm">
|
||||
{product.category.name}
|
||||
</div>
|
||||
{/* Available badge */}
|
||||
<div className="absolute top-3 right-3 bg-spa-forest/90 backdrop-blur-sm px-2.5 py-1 rounded-md text-[10px] font-bold uppercase tracking-widest text-white shadow-sm">
|
||||
Available
|
||||
</div>
|
||||
{/* Wishlist Button */}
|
||||
<button
|
||||
onClick={handleWishlistClick}
|
||||
className="absolute top-3 right-3 p-2 bg-white/90 backdrop-blur-sm rounded-full shadow-sm hover:scale-110 transition-transform z-10"
|
||||
>
|
||||
<Heart
|
||||
className={`w-4 h-4 transition-colors ${isWished ? 'fill-rose-500 text-rose-500' : 'text-spa-stone/60 hover:text-rose-500'}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 flex flex-col flex-1">
|
||||
|
||||
Reference in new issue
Block a user