diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d0b02da..085e7e2 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';
import { useTheme } from './context/ThemeContext';
// Wrapper component to apply theme classes
@@ -25,6 +27,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..5efcebd 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 (
@@ -30,6 +32,17 @@ export default function Navigation() {
Home
Products
About us
+
+
+ Wishlist
+ {wishlistItems.length > 0 && (
+
+ {wishlistItems.length > 99 ? '99+' : wishlistItems.length}
+
+ )}
+
{isAdmin && (
)}
+ {
+ e.stopPropagation();
+ if (isInWishlist(product.productId)) {
+ removeFromWishlist(product.productId);
+ } else {
+ addToWishlist({
+ productId: product.productId,
+ name: product.name,
+ description: product.description,
+ price: product.price,
+ imgName: product.imgName,
+ discount: product.discount,
+ });
+ }
+ }}
+ className="absolute top-2 right-2 p-1.5 rounded-full bg-white/80 hover:bg-white shadow transition-colors duration-200"
+ aria-label={isInWishlist(product.productId) ? `Remove ${product.name} from wishlist` : `Add ${product.name} to wishlist`}
+ >
+
+
diff --git a/frontend/src/context/WishlistContext.tsx b/frontend/src/context/WishlistContext.tsx
new file mode 100644
index 0000000..41600d0
--- /dev/null
+++ b/frontend/src/context/WishlistContext.tsx
@@ -0,0 +1,63 @@
+/* eslint-disable react-refresh/only-export-components */
+import React, { useContext, useState, useEffect } from 'react';
+import { WishlistContext, WishlistItem } from './wishlistContextUtils';
+
+const WISHLIST_STORAGE_KEY = 'octocat-wishlist';
+
+function loadFromStorage(): WishlistItem[] {
+ try {
+ const stored = localStorage.getItem(WISHLIST_STORAGE_KEY);
+ return stored ? (JSON.parse(stored) as WishlistItem[]) : [];
+ } catch {
+ return [];
+ }
+}
+
+function saveToStorage(items: WishlistItem[]): void {
+ try {
+ localStorage.setItem(WISHLIST_STORAGE_KEY, JSON.stringify(items));
+ } catch {
+ // Gracefully handle private browsing / quota exceeded
+ }
+}
+
+export const useWishlist = () => {
+ const context = useContext(WishlistContext);
+ if (context === undefined) {
+ throw new Error('useWishlist must be used within a WishlistProvider');
+ }
+ return context;
+};
+
+export function WishlistProvider({ children }: { children: React.ReactNode }) {
+ const [wishlistItems, setWishlistItems] = useState(loadFromStorage);
+
+ useEffect(() => {
+ saveToStorage(wishlistItems);
+ }, [wishlistItems]);
+
+ const addToWishlist = (item: Omit) => {
+ setWishlistItems(prev => {
+ if (prev.some(w => w.productId === item.productId)) return prev;
+ return [...prev, { ...item, dateAdded: new Date().toISOString() }];
+ });
+ };
+
+ const removeFromWishlist = (productId: number) => {
+ setWishlistItems(prev => prev.filter(w => w.productId !== productId));
+ };
+
+ const isInWishlist = (productId: number): boolean => {
+ return wishlistItems.some(w => w.productId === productId);
+ };
+
+ const clearWishlist = () => {
+ setWishlistItems([]);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/context/wishlistContextUtils.tsx b/frontend/src/context/wishlistContextUtils.tsx
new file mode 100644
index 0000000..794cef2
--- /dev/null
+++ b/frontend/src/context/wishlistContextUtils.tsx
@@ -0,0 +1,21 @@
+import { createContext } from 'react';
+
+export interface WishlistItem {
+ productId: number;
+ name: string;
+ description: string;
+ price: number;
+ imgName: string;
+ discount?: number;
+ dateAdded: string;
+}
+
+export type WishlistContextType = {
+ wishlistItems: WishlistItem[];
+ addToWishlist: (item: Omit) => void;
+ removeFromWishlist: (productId: number) => void;
+ isInWishlist: (productId: number) => boolean;
+ clearWishlist: () => void;
+};
+
+export const WishlistContext = createContext(undefined);