From 091eeef7d88338c7f93621ccb69b90c16ac5c82f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 6 Jun 2026 19:40:35 +0700 Subject: [PATCH] feat(spa): add wishlist functionality with localStorage and UI integration --- apps/spa/src/App.tsx | 2 + apps/spa/src/components/Navbar.tsx | 31 +++++++++++- apps/spa/src/components/ProductCard.tsx | 27 ++++++++-- apps/spa/src/pages/WishlistPage.tsx | 67 +++++++++++++++++++++++++ apps/spa/src/store/wishlistStore.ts | 33 ++++++++++++ 5 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 apps/spa/src/pages/WishlistPage.tsx create mode 100644 apps/spa/src/store/wishlistStore.ts diff --git a/apps/spa/src/App.tsx b/apps/spa/src/App.tsx index 6b6fe1c..ddea641 100644 --- a/apps/spa/src/App.tsx +++ b/apps/spa/src/App.tsx @@ -17,6 +17,7 @@ import CheckoutPage from './pages/CheckoutPage' import CouponsPage from './pages/CouponsPage' import OrderSuccessPage from './pages/OrderSuccessPage' import TrackOrderPage from './pages/TrackOrderPage' +import WishlistPage from './pages/WishlistPage' import CartDrawer from './components/CartDrawer' function App() { @@ -42,6 +43,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/apps/spa/src/components/Navbar.tsx b/apps/spa/src/components/Navbar.tsx index 3b08a1d..c76eb15 100644 --- a/apps/spa/src/components/Navbar.tsx +++ b/apps/spa/src/components/Navbar.tsx @@ -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) { + + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + + setMobileOpen(false)} + className="relative p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all" + aria-label="Wishlist" + > + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + +
diff --git a/apps/spa/src/pages/WishlistPage.tsx b/apps/spa/src/pages/WishlistPage.tsx new file mode 100644 index 0000000..38990ad --- /dev/null +++ b/apps/spa/src/pages/WishlistPage.tsx @@ -0,0 +1,67 @@ +import { useState, useEffect } from 'react' +import { Link } from 'react-router-dom' +import Navbar from '../components/Navbar' +import Footer from '../components/Footer' +import ProductCard from '../components/ProductCard' +import { API_BASE, type Category } from '../types' +import { useWishlistStore } from '../store/wishlistStore' +import { Heart } from 'lucide-react' + +export default function WishlistPage() { + const [categories, setCategories] = useState([]) + const { items } = useWishlistStore() + + useEffect(() => { + fetch(`${API_BASE}/categories`) + .then(res => res.json()) + .then(setCategories) + .catch(console.error) + }, []) + + return ( +
+ + +
+ + {/* Header */} +
+
+ +
+

Your Wishlist

+

+ Items you've loved. Keep them here until you're ready to make them yours. +

+
+ + {items.length === 0 ? ( +
+
+ +
+

Your wishlist is empty

+

Explore our products and tap the heart icon to save your favorites.

+
+ + Browse Shop + +
+
+ ) : ( +
+ {items.map(product => ( + + ))} +
+ )} + +
+ +
+
+ ) +} diff --git a/apps/spa/src/store/wishlistStore.ts b/apps/spa/src/store/wishlistStore.ts new file mode 100644 index 0000000..cdb53a3 --- /dev/null +++ b/apps/spa/src/store/wishlistStore.ts @@ -0,0 +1,33 @@ +import { create } from 'zustand' +import { persist } from 'zustand/middleware' +import { Product } from '../types' + +interface WishlistState { + items: Product[] + addItem: (product: Product) => void + removeItem: (productId: number) => void + isInWishlist: (productId: number) => boolean +} + +export const useWishlistStore = create()( + persist( + (set, get) => ({ + items: [], + addItem: (product) => { + const { items } = get() + if (!items.find(item => item.id === product.id)) { + set({ items: [...items, product] }) + } + }, + removeItem: (productId) => { + set({ items: get().items.filter(item => item.id !== productId) }) + }, + isInWishlist: (productId) => { + return get().items.some(item => item.id === productId) + } + }), + { + name: 'homaroma-wishlist', + } + ) +)