From 72b28a49325fd9061df5a32eadec24b3759f887c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:24:53 +0000 Subject: [PATCH 1/2] Initial plan From 7a8a57c407a8eea80899fa5a1d6fbab684466c91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:33:18 +0000 Subject: [PATCH 2/2] Implement shopping cart with CartContext, Cart page, and nav icon Agent-Logs-Url: https://github.com/yortch/GitHubCopilot_Customized/sessions/569b5636-fced-4f62-8768-36b967281dad Co-authored-by: yortch <4576246+yortch@users.noreply.github.com> --- frontend/src/App.tsx | 7 +- frontend/src/components/Navigation.tsx | 16 ++ frontend/src/components/entity/cart/Cart.tsx | 141 ++++++++++++++++++ .../components/entity/product/Products.tsx | 14 +- frontend/src/context/CartContext.tsx | 82 ++++++++++ 5 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/entity/cart/Cart.tsx create mode 100644 frontend/src/context/CartContext.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d0b02da..db4e244 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 Cart from './components/entity/cart/Cart'; import Login from './components/Login'; import { AuthProvider } from './context/AuthContext'; import { ThemeProvider } from './context/ThemeContext'; +import { CartProvider } from './context/CartContext'; 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..0943f17 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 { useCart } from '../context/CartContext'; import { useState } from 'react'; export default function Navigation() { const { isLoggedIn, isAdmin, logout } = useAuth(); const { darkMode, toggleTheme } = useTheme(); + const { totalItems } = useCart(); const [adminMenuOpen, setAdminMenuOpen] = useState(false); return ( @@ -68,6 +70,20 @@ export default function Navigation() {
+ + + + + {totalItems > 0 && ( + + {totalItems > 99 ? '99+' : totalItems} + + )} +
+ ); + } + + return ( +
+
+
+

+ Your Cart +

+ +
+ +
+ {items.map((item: CartItem) => { + const effectivePrice = item.discount ? item.price * (1 - item.discount) : item.price; + return ( +
+ {item.name} +
+

+ {item.name} +

+

+ ${effectivePrice.toFixed(2)} + {item.discount && ( + + ${item.price.toFixed(2)} + + )} +

+
+
+
+ + + {item.quantity} + + +
+

+ ${(effectivePrice * item.quantity).toFixed(2)} +

+ +
+
+ ); + })} +
+ + {/* Order Summary */} +
+

+ Order Summary +

+
+ Total + ${totalPrice.toFixed(2)} +
+ + + Continue Shopping + +
+
+
+ ); +} diff --git a/frontend/src/components/entity/product/Products.tsx b/frontend/src/components/entity/product/Products.tsx index af2319e..2dba5d9 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 { useCart } from '../../../context/CartContext'; 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 { addToCart } = useCart(); const filteredProducts = products?.filter(product => product.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -44,8 +46,16 @@ export default function Products() { const handleAddToCart = (productId: number) => { const quantity = quantities[productId] || 0; if (quantity > 0) { - // TODO: Implement cart functionality - alert(`Added ${quantity} items to cart`); + const product = products?.find(p => p.productId === productId); + if (product) { + addToCart({ + productId: product.productId, + name: product.name, + price: product.price, + imgName: product.imgName, + discount: product.discount, + }, quantity); + } setQuantities(prev => ({ ...prev, [productId]: 0 diff --git a/frontend/src/context/CartContext.tsx b/frontend/src/context/CartContext.tsx new file mode 100644 index 0000000..bdcda1b --- /dev/null +++ b/frontend/src/context/CartContext.tsx @@ -0,0 +1,82 @@ +/* eslint-disable react-refresh/only-export-components */ +import { createContext, useContext, useState, useCallback, useMemo, ReactNode } from 'react'; + +export interface CartItem { + productId: number; + name: string; + price: number; + imgName: string; + quantity: number; + discount?: number; +} + +interface CartContextType { + items: CartItem[]; + addToCart: (item: Omit, quantity: number) => void; + removeFromCart: (productId: number) => void; + updateQuantity: (productId: number, quantity: number) => void; + clearCart: () => void; + totalItems: number; + totalPrice: number; +} + +const CartContext = createContext(null); + +export function CartProvider({ children }: { children: ReactNode }) { + const [items, setItems] = useState([]); + + const addToCart = useCallback((product: Omit, quantity: number) => { + setItems(prev => { + const existing = prev.find(i => i.productId === product.productId); + if (existing) { + return prev.map(i => + i.productId === product.productId + ? { ...i, quantity: i.quantity + quantity } + : i + ); + } + return [...prev, { ...product, quantity }]; + }); + }, []); + + const removeFromCart = useCallback((productId: number) => { + setItems(prev => prev.filter(i => i.productId !== productId)); + }, []); + + const updateQuantity = useCallback((productId: number, quantity: number) => { + if (quantity <= 0) { + setItems(prev => prev.filter(i => i.productId !== productId)); + return; + } + setItems(prev => + prev.map(i => (i.productId === productId ? { ...i, quantity } : i)) + ); + }, []); + + const clearCart = useCallback(() => setItems([]), []); + + const totalItems = useMemo(() => items.reduce((sum, i) => sum + i.quantity, 0), [items]); + const totalPrice = useMemo(() => items.reduce((sum, i) => { + const effectivePrice = i.discount ? i.price * (1 - i.discount) : i.price; + return sum + effectivePrice * i.quantity; + }, 0), [items]); + + const value = useMemo( + () => ({ items, addToCart, removeFromCart, updateQuantity, clearCart, totalItems, totalPrice }), + [items, addToCart, removeFromCart, updateQuantity, clearCart, totalItems, totalPrice] + ); + + return ( + + {children} + + ); +} + +export function useCart() { + const context = useContext(CartContext); + if (!context) { + throw new Error('useCart must be used within a CartProvider'); + } + return context; +}