feat(spa): add wishlist functionality with localStorage and UI integration

This commit is contained in:
Your Name committed 2026-06-06 19:40:35 +07:00
1 parent ca31564ee7
commit 091eeef7d8
5 files changed
+155 -5

No files matched your search

+30 -1
View File
@@ -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"
+23 -4
View File
@@ -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">