diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d0b02da..cad836a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,7 +7,9 @@ import Products from './components/entity/product/Products'; import Login from './components/Login'; import { AuthProvider } from './context/AuthContext'; import { ThemeProvider } from './context/ThemeContext'; +import { WishlistProvider } from './context/WishlistContext'; import AdminProducts from './components/admin/AdminProducts'; +import Wishlist from './components/wishlist/Wishlist'; import { useTheme } from './context/ThemeContext'; // Wrapper component to apply theme classes @@ -23,6 +25,7 @@ function ThemedApp() { } /> } /> } /> + } /> } /> } /> @@ -37,7 +40,9 @@ function App() { return ( - + + + ); diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index d7b393b..921a40e 100644 --- a/frontend/src/components/Navigation.tsx +++ b/frontend/src/components/Navigation.tsx @@ -1,11 +1,13 @@ import { Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { useTheme } from '../context/ThemeContext'; +import { useWishlist } from '../context/WishlistContext'; import { useState } from 'react'; export default function Navigation() { const { isLoggedIn, isAdmin, logout } = useAuth(); const { darkMode, toggleTheme } = useTheme(); + const { wishlistCount } = useWishlist(); const [adminMenuOpen, setAdminMenuOpen] = useState(false); return ( @@ -30,6 +32,19 @@ export default function Navigation() { Home Products About us + {isLoggedIn && ( + + Wishlist + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + )} {isAdmin && (
)} +
diff --git a/frontend/src/components/wishlist/Wishlist.tsx b/frontend/src/components/wishlist/Wishlist.tsx new file mode 100644 index 0000000..d0cc42c --- /dev/null +++ b/frontend/src/components/wishlist/Wishlist.tsx @@ -0,0 +1,117 @@ +import axios from 'axios'; +import { useQuery } from 'react-query'; +import { api } from '../../api/config'; +import { useTheme } from '../../context/ThemeContext'; +import { useWishlist } from '../../context/WishlistContext'; + +interface Product { + productId: number; + name: string; + description: string; + price: number; + imgName: string; + sku: string; + unit: string; + supplierId: number; + discount?: number; +} + +const fetchProducts = async (): Promise => { + const { data } = await axios.get(`${api.baseURL}${api.endpoints.products}`); + return data; +}; + +export default function Wishlist() { + const { darkMode } = useTheme(); + const { wishlistIds, toggleWishlist, isWishlisted } = useWishlist(); + const { data: products, isLoading, error } = useQuery('products', fetchProducts); + + if (isLoading) { + return ( +
+
+
+
+
+
+
+ ); + } + + if (error) { + return ( +
+
+
Failed to fetch products
+
+
+ ); + } + + const wishlistedProducts = products?.filter(p => wishlistIds.has(p.productId)) ?? []; + + return ( +
+
+
+

Wishlist

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

+ Your wishlist is empty — browse products to save items for later +

+ ) : ( +
+ {wishlistedProducts.map(product => ( +
+
+ {product.name} + {product.discount && ( +
+ {Math.round(product.discount * 100)}% OFF +
+ )} + +
+ +
+

{product.name}

+

{product.description}

+
+ {product.discount ? ( +
+ ${product.price.toFixed(2)} + ${(product.price * (1 - product.discount)).toFixed(2)} +
+ ) : ( + ${product.price.toFixed(2)} + )} +
+
+
+ ))} +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/context/WishlistContext.tsx b/frontend/src/context/WishlistContext.tsx new file mode 100644 index 0000000..f116efd --- /dev/null +++ b/frontend/src/context/WishlistContext.tsx @@ -0,0 +1,54 @@ +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +interface WishlistContextType { + wishlistIds: Set; + toggleWishlist: (productId: number) => void; + isWishlisted: (productId: number) => boolean; + wishlistCount: number; +} + +const WishlistContext = createContext(null); + +export function WishlistProvider({ children }: { children: ReactNode }) { + const [wishlistIds, setWishlistIds] = useState>(() => { + try { + const stored = localStorage.getItem('wishlist'); + return stored ? new Set(JSON.parse(stored)) : new Set(); + } catch { + return new Set(); + } + }); + + useEffect(() => { + localStorage.setItem('wishlist', JSON.stringify(Array.from(wishlistIds))); + }, [wishlistIds]); + + const toggleWishlist = (productId: number) => { + setWishlistIds(prev => { + const next = new Set(prev); + if (next.has(productId)) { + next.delete(productId); + } else { + next.add(productId); + } + return next; + }); + }; + + const isWishlisted = (productId: number) => wishlistIds.has(productId); + + return ( + + {children} + + ); +} + +// eslint-disable-next-line react-refresh/only-export-components +export function useWishlist() { + const context = useContext(WishlistContext); + if (!context) { + throw new Error('useWishlist must be used within a WishlistProvider'); + } + return context; +}