From cc4b9ca8749e2f5b7bab61824b4fe576d69891ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:51:34 +0000 Subject: [PATCH 1/3] Initial plan From 092cf73b254820ea246a2c52eae59b680d352698 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:54:46 +0000 Subject: [PATCH 2/3] Implement localStorage-based wishlist feature Co-authored-by: thomasiverson <12767513+thomasiverson@users.noreply.github.com> --- frontend/src/App.tsx | 7 +- frontend/src/components/Navigation.tsx | 10 ++ frontend/src/components/Wishlist.tsx | 126 ++++++++++++++++++ .../components/entity/product/Products.tsx | 29 ++++ frontend/src/context/WishlistContext.tsx | 64 +++++++++ 5 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/Wishlist.tsx create mode 100644 frontend/src/context/WishlistContext.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d0b02da..1df0c75 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,9 +4,11 @@ import Welcome from './components/Welcome'; import About from './components/About'; import Footer from './components/Footer'; import Products from './components/entity/product/Products'; +import Wishlist from './components/Wishlist'; 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 { useTheme } from './context/ThemeContext'; @@ -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..b4bcb8b 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 { wishlistItems } = useWishlist(); const [adminMenuOpen, setAdminMenuOpen] = useState(false); return ( @@ -29,6 +31,14 @@ export default function Navigation() {
Home Products + + Wishlist + {wishlistItems.length > 0 && ( + + {wishlistItems.length} + + )} + About us {isAdmin && (
diff --git a/frontend/src/components/Wishlist.tsx b/frontend/src/components/Wishlist.tsx new file mode 100644 index 0000000..8970af5 --- /dev/null +++ b/frontend/src/components/Wishlist.tsx @@ -0,0 +1,126 @@ +import { useQuery } from 'react-query'; +import axios from 'axios'; +import { useWishlist } from '../context/WishlistContext'; +import { useTheme } from '../context/ThemeContext'; +import { api } from '../api/config'; + +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 { wishlistItems, removeFromWishlist } = useWishlist(); + const { darkMode } = useTheme(); + const { data: allProducts, isLoading } = useQuery('products', fetchProducts); + + const wishlistProducts = allProducts?.filter(product => + wishlistItems.includes(product.productId) + ) || []; + + if (isLoading) { + return ( +
+
+
+
+
+
+
+ ); + } + + return ( +
+
+
+

+ My Wishlist +

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

Your wishlist is empty

+

Add products to your wishlist to save them for later

+
+ ) : ( +
+ {wishlistProducts.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/components/entity/product/Products.tsx b/frontend/src/components/entity/product/Products.tsx index af2319e..463710a 100644 --- a/frontend/src/components/entity/product/Products.tsx +++ b/frontend/src/components/entity/product/Products.tsx @@ -3,6 +3,7 @@ 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; @@ -28,6 +29,7 @@ export default function Products() { const [showModal, setShowModal] = useState(false); const { data: products, isLoading, error } = useQuery('products', fetchProducts); const { darkMode } = useTheme(); + const { addToWishlist, removeFromWishlist, isInWishlist } = useWishlist(); const filteredProducts = products?.filter(product => product.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -53,6 +55,15 @@ export default function Products() { } }; + const handleWishlistToggle = (productId: number, event: React.MouseEvent) => { + event.stopPropagation(); + if (isInWishlist(productId)) { + removeFromWishlist(productId); + } else { + addToWishlist(productId); + } + }; + const handleProductClick = (product: Product) => { setSelectedProduct(product); setShowModal(true); @@ -125,6 +136,24 @@ export default function Products() { {Math.round(product.discount * 100)}% OFF
)} +
diff --git a/frontend/src/context/WishlistContext.tsx b/frontend/src/context/WishlistContext.tsx new file mode 100644 index 0000000..56c009d --- /dev/null +++ b/frontend/src/context/WishlistContext.tsx @@ -0,0 +1,64 @@ +/* eslint-disable react-refresh/only-export-components */ +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +interface WishlistContextType { + wishlistItems: number[]; + addToWishlist: (productId: number) => void; + removeFromWishlist: (productId: number) => void; + isInWishlist: (productId: number) => boolean; +} + +const WishlistContext = createContext(undefined); + +const WISHLIST_KEY = 'octocat-wishlist'; + +export function WishlistProvider({ children }: { children: ReactNode }) { + const [wishlistItems, setWishlistItems] = useState(() => { + try { + const saved = localStorage.getItem(WISHLIST_KEY); + return saved ? JSON.parse(saved) : []; + } catch (error) { + console.error('Error loading wishlist from localStorage:', error); + return []; + } + }); + + useEffect(() => { + try { + localStorage.setItem(WISHLIST_KEY, JSON.stringify(wishlistItems)); + } catch (error) { + console.error('Error saving wishlist to localStorage:', error); + } + }, [wishlistItems]); + + const addToWishlist = (productId: number) => { + setWishlistItems(prev => { + if (!prev.includes(productId)) { + return [...prev, productId]; + } + return prev; + }); + }; + + const removeFromWishlist = (productId: number) => { + setWishlistItems(prev => prev.filter(id => id !== productId)); + }; + + const isInWishlist = (productId: number) => { + return wishlistItems.includes(productId); + }; + + return ( + + {children} + + ); +} + +export function useWishlist() { + const context = useContext(WishlistContext); + if (!context) { + throw new Error('useWishlist must be used within a WishlistProvider'); + } + return context; +} From 3bd3d414814cdf5f7102fdab9da999a0fc200351 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:58:00 +0000 Subject: [PATCH 3/3] Add focus styles for keyboard accessibility on wishlist buttons Co-authored-by: thomasiverson <12767513+thomasiverson@users.noreply.github.com> --- frontend/src/components/Wishlist.tsx | 2 +- frontend/src/components/entity/product/Products.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Wishlist.tsx b/frontend/src/components/Wishlist.tsx index 8970af5..24bd8d5 100644 --- a/frontend/src/components/Wishlist.tsx +++ b/frontend/src/components/Wishlist.tsx @@ -85,7 +85,7 @@ export default function Wishlist() { )}