From 64ff0e414f04bcece0b518607c16572ffbfa1a94 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 18:20:32 +0000
Subject: [PATCH 1/3] Initial plan
From 40ce3fbb18b4cbf9d5b68d1a1b2a4c101ebbad6f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 18:26:35 +0000
Subject: [PATCH 2/3] Implement shopping cart functionality
Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
---
frontend/src/App.tsx | 31 +++--
frontend/src/components/Cart.tsx | 114 ++++++++++++++++++
frontend/src/components/Checkout.tsx | 102 ++++++++++++++++
frontend/src/components/Navigation.tsx | 16 ++-
.../components/entity/product/Products.tsx | 37 ++++--
frontend/src/context/CartContext.tsx | 91 ++++++++++++++
6 files changed, 362 insertions(+), 29 deletions(-)
create mode 100644 frontend/src/components/Cart.tsx
create mode 100644 frontend/src/components/Checkout.tsx
create mode 100644 frontend/src/context/CartContext.tsx
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d0b02da..1cae7f3 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,14 +1,17 @@
-import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
-import Navigation from './components/Navigation';
-import Welcome from './components/Welcome';
-import About from './components/About';
-import Footer from './components/Footer';
-import Products from './components/entity/product/Products';
-import Login from './components/Login';
-import { AuthProvider } from './context/AuthContext';
-import { ThemeProvider } from './context/ThemeContext';
-import AdminProducts from './components/admin/AdminProducts';
-import { useTheme } from './context/ThemeContext';
+import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
+import Navigation from "./components/Navigation";
+import Welcome from "./components/Welcome";
+import About from "./components/About";
+import Footer from "./components/Footer";
+import Products from "./components/entity/product/Products";
+import Login from "./components/Login";
+import Cart from "./components/Cart";
+import Checkout from "./components/Checkout";
+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";
// Wrapper component to apply theme classes
function ThemedApp() {
@@ -24,6 +27,8 @@ function ThemedApp() {
} />
} />
} />
+ } />
+ } />
} />
@@ -37,7 +42,9 @@ function App() {
return (
-
+
+
+
);
diff --git a/frontend/src/components/Cart.tsx b/frontend/src/components/Cart.tsx
new file mode 100644
index 0000000..f81279d
--- /dev/null
+++ b/frontend/src/components/Cart.tsx
@@ -0,0 +1,114 @@
+import { useCart } from "../context/CartContext";
+import { useTheme } from "../context/ThemeContext";
+import { useNavigate } from "react-router-dom";
+
+export default function Cart() {
+ const cartData = useCart();
+ const theme = useTheme();
+ const nav = useNavigate();
+
+ const isDark = theme.darkMode;
+ const cartItems = cartData.items;
+ const total = cartData.totalPrice;
+
+ const handleRemove = (id: number) => {
+ cartData.removeFromCart(id);
+ };
+
+ const handleUpdateQty = (id: number, qty: number) => {
+ cartData.updateQuantity(id, qty);
+ };
+
+ const handleClearAll = () => {
+ cartData.clearCart();
+ };
+
+ if (cartItems.length === 0) {
+ return (
+
+
+
Shopping Cart
+
+
Your cart is empty
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
Shopping Cart
+
+
+
+
+ {cartItems.map(product => {
+ const finalPrice = product.discount ? product.price * (1 - product.discount) : product.price;
+ const lineTotal = finalPrice * product.quantity;
+
+ return (
+
+
+

+
+
+
{product.name}
+
+ {product.discount ? (
+
+ ${product.price.toFixed(2)}
+ ${finalPrice.toFixed(2)}
+
+ ) : (
+
${product.price.toFixed(2)}
+ )}
+
+
+
+
+
+
+ {product.quantity}
+
+
+
+
+ ${lineTotal.toFixed(2)}
+
+
+
+
+
+
+ );
+ })}
+
+
+
+
+ Total
+ ${total.toFixed(2)}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/Checkout.tsx b/frontend/src/components/Checkout.tsx
new file mode 100644
index 0000000..94ac90d
--- /dev/null
+++ b/frontend/src/components/Checkout.tsx
@@ -0,0 +1,102 @@
+import { useCart } from "../context/CartContext";
+import { useTheme } from "../context/ThemeContext";
+import { useNavigate } from "react-router-dom";
+import { useState } from "react";
+
+export default function Checkout() {
+ const cartData = useCart();
+ const theme = useTheme();
+ const nav = useNavigate();
+ const [orderPlaced, setOrderPlaced] = useState(false);
+
+ const isDark = theme.darkMode;
+ const cartItems = cartData.items;
+ const total = cartData.totalPrice;
+
+ const handlePlaceOrder = () => {
+ setOrderPlaced(true);
+ setTimeout(() => {
+ cartData.clearCart();
+ nav("/");
+ }, 2000);
+ };
+
+ if (cartItems.length === 0 && !orderPlaced) {
+ return (
+
+
+
Checkout
+
+
Your cart is empty
+
+
+
+
+ );
+ }
+
+ if (orderPlaced) {
+ return (
+
+
+
+
✓
+
Order Placed Successfully!
+
Thank you for your order. Redirecting to home...
+
+
+
+ );
+ }
+
+ return (
+
+
+
Checkout
+
+
+
+
Order Summary
+
+ {cartItems.map(item => {
+ const finalPrice = item.discount ? item.price * (1 - item.discount) : item.price;
+ const lineTotal = finalPrice * item.quantity;
+
+ return (
+
+
+
{item.name}
+
Qty: {item.quantity}
+
+
${lineTotal.toFixed(2)}
+
+ );
+ })}
+
+ Total
+ ${total.toFixed(2)}
+
+
+
+
+
+
Shipping Information
+
+
+ This is a demo checkout page. No actual payment will be processed.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx
index d7b393b..2a6a194 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 { useState } from 'react';
+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 (
@@ -83,6 +85,12 @@ export default function Navigation() {
)}
+
+
+ {totalItems > 0 && {totalItems}}
+
{isLoggedIn ? (
<>
diff --git a/frontend/src/components/entity/product/Products.tsx b/frontend/src/components/entity/product/Products.tsx
index af2319e..d641008 100644
--- a/frontend/src/components/entity/product/Products.tsx
+++ b/frontend/src/components/entity/product/Products.tsx
@@ -1,8 +1,9 @@
-import { useState } from 'react';
-import axios from 'axios';
-import { useQuery } from 'react-query';
-import { api } from '../../../api/config';
-import { useTheme } from '../../../context/ThemeContext';
+import { useState } from "react";
+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;
@@ -23,11 +24,12 @@ const fetchProducts = async (): Promise => {
export default function Products() {
const [quantities, setQuantities] = useState>({});
- const [searchTerm, setSearchTerm] = useState('');
+ const [searchTerm, setSearchTerm] = useState("");
const [selectedProduct, setSelectedProduct] = useState(null);
const [showModal, setShowModal] = useState(false);
- const { data: products, isLoading, error } = useQuery('products', fetchProducts);
+ 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,12 +46,21 @@ 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`);
- setQuantities(prev => ({
- ...prev,
- [productId]: 0
- }));
+ 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..5c0d6d9
--- /dev/null
+++ b/frontend/src/context/CartContext.tsx
@@ -0,0 +1,91 @@
+import { createContext, useContext, useState, ReactNode } from "react";
+
+interface CartItem {
+ productId: number;
+ name: string;
+ price: number;
+ quantity: number;
+ imgName: string;
+ 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(undefined);
+
+export function CartProvider({ children }: { children: ReactNode }) {
+ const [items, setItems] = useState([]);
+
+ const addToCart = (item: Omit & { quantity: number }) => {
+ setItems(prevItems => {
+ const existingItem = prevItems.find(i => i.productId === item.productId);
+ if (existingItem) {
+ return prevItems.map(i =>
+ i.productId === item.productId
+ ? { ...i, quantity: i.quantity + item.quantity }
+ : i
+ );
+ }
+ return [...prevItems, item];
+ });
+ };
+
+ const removeFromCart = (productId: number) => {
+ setItems(prevItems => prevItems.filter(item => item.productId !== productId));
+ };
+
+ const updateQuantity = (productId: number, quantity: number) => {
+ if (quantity <= 0) {
+ removeFromCart(productId);
+ return;
+ }
+ setItems(prevItems =>
+ prevItems.map(item =>
+ item.productId === productId ? { ...item, quantity } : item
+ )
+ );
+ };
+
+ const clearCart = () => {
+ setItems([]);
+ };
+
+ const totalItems = items.reduce((sum, item) => sum + item.quantity, 0);
+
+ const totalPrice = items.reduce((sum, item) => {
+ const price = item.discount ? item.price * (1 - item.discount) : item.price;
+ return sum + price * item.quantity;
+ }, 0);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useCart() {
+ const context = useContext(CartContext);
+ if (context === undefined) {
+ throw new Error("useCart must be used within a CartProvider");
+ }
+ return context;
+}
From 0e011fc07a4b495db0ddbfefae5aeaf963795159 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 18:30:33 +0000
Subject: [PATCH 3/3] Improve variable naming consistency per code review
Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
---
frontend/src/components/Cart.tsx | 51 ++++++++++++-------------
frontend/src/components/Checkout.tsx | 57 ++++++++++++++--------------
2 files changed, 53 insertions(+), 55 deletions(-)
diff --git a/frontend/src/components/Cart.tsx b/frontend/src/components/Cart.tsx
index f81279d..62c4927 100644
--- a/frontend/src/components/Cart.tsx
+++ b/frontend/src/components/Cart.tsx
@@ -3,34 +3,33 @@ import { useTheme } from "../context/ThemeContext";
import { useNavigate } from "react-router-dom";
export default function Cart() {
- const cartData = useCart();
- const theme = useTheme();
- const nav = useNavigate();
+ const cart = useCart();
+ const { darkMode } = useTheme();
+ const navigate = useNavigate();
- const isDark = theme.darkMode;
- const cartItems = cartData.items;
- const total = cartData.totalPrice;
+ const cartItems = cart.items;
+ const total = cart.totalPrice;
const handleRemove = (id: number) => {
- cartData.removeFromCart(id);
+ cart.removeFromCart(id);
};
const handleUpdateQty = (id: number, qty: number) => {
- cartData.updateQuantity(id, qty);
+ cart.updateQuantity(id, qty);
};
const handleClearAll = () => {
- cartData.clearCart();
+ cart.clearCart();
};
if (cartItems.length === 0) {
return (
-
+
-
Shopping Cart
-
+
Shopping Cart
+
Your cart is empty
-
@@ -40,11 +39,11 @@ export default function Cart() {
}
return (
-
+
-
Shopping Cart
-
+ Shopping Cart
+
Clear All
@@ -55,12 +54,12 @@ export default function Cart() {
const lineTotal = finalPrice * product.quantity;
return (
-
+
-

+
-
{product.name}
+
{product.name}
{product.discount ? (
@@ -74,17 +73,17 @@ export default function Cart() {
-
+
handleUpdateQty(product.productId, product.quantity - 1)} className="w-7 h-7 hover:text-primary">-
{product.quantity}
handleUpdateQty(product.productId, product.quantity + 1)} className="w-7 h-7 hover:text-primary">+
- ${lineTotal.toFixed(2)}
+ ${lineTotal.toFixed(2)}
-
handleRemove(product.productId)} className={`${isDark ? "text-gray-400 hover:text-red-400" : "text-gray-600 hover:text-red-600"}`}>
+ handleRemove(product.productId)} className={`${darkMode ? "text-gray-400 hover:text-red-400" : "text-gray-600 hover:text-red-600"}`}>
✕
@@ -94,16 +93,16 @@ export default function Cart() {
})}
-
+
- Total
+ Total
${total.toFixed(2)}
- nav("/products")} className={`flex-1 px-6 py-3 rounded ${isDark ? "bg-gray-700 hover:bg-gray-600 text-light" : "bg-gray-200 hover:bg-gray-300 text-gray-800"}`}>
+ navigate("/products")} className={`flex-1 px-6 py-3 rounded ${darkMode ? "bg-gray-700 hover:bg-gray-600 text-light" : "bg-gray-200 hover:bg-gray-300 text-gray-800"}`}>
Continue Shopping
- nav("/checkout")} className="flex-1 bg-primary hover:bg-accent text-white px-6 py-3 rounded">
+ navigate("/checkout")} className="flex-1 bg-primary hover:bg-accent text-white px-6 py-3 rounded">
Checkout
diff --git a/frontend/src/components/Checkout.tsx b/frontend/src/components/Checkout.tsx
index 94ac90d..ac47c54 100644
--- a/frontend/src/components/Checkout.tsx
+++ b/frontend/src/components/Checkout.tsx
@@ -4,31 +4,30 @@ import { useNavigate } from "react-router-dom";
import { useState } from "react";
export default function Checkout() {
- const cartData = useCart();
- const theme = useTheme();
- const nav = useNavigate();
+ const cart = useCart();
+ const { darkMode } = useTheme();
+ const navigate = useNavigate();
const [orderPlaced, setOrderPlaced] = useState(false);
- const isDark = theme.darkMode;
- const cartItems = cartData.items;
- const total = cartData.totalPrice;
+ const cartItems = cart.items;
+ const total = cart.totalPrice;
const handlePlaceOrder = () => {
setOrderPlaced(true);
setTimeout(() => {
- cartData.clearCart();
- nav("/");
+ cart.clearCart();
+ navigate("/");
}, 2000);
};
if (cartItems.length === 0 && !orderPlaced) {
return (
-
+
-
Checkout
-
+
Checkout
+
Your cart is empty
-
nav("/products")} className="bg-primary text-white px-6 py-2 rounded hover:bg-accent">
+ navigate("/products")} className="bg-primary text-white px-6 py-2 rounded hover:bg-accent">
Browse Products
@@ -39,12 +38,12 @@ export default function Checkout() {
if (orderPlaced) {
return (
-
+
-
+
✓
Order Placed Successfully!
-
Thank you for your order. Redirecting to home...
+
Thank you for your order. Redirecting to home...
@@ -52,45 +51,45 @@ export default function Checkout() {
}
return (
-
+
-
Checkout
+
Checkout
-
Order Summary
-
+
Order Summary
+
{cartItems.map(item => {
const finalPrice = item.discount ? item.price * (1 - item.discount) : item.price;
const lineTotal = finalPrice * item.quantity;
return (
-
+
-
{item.name}
-
Qty: {item.quantity}
+
{item.name}
+
Qty: {item.quantity}
-
${lineTotal.toFixed(2)}
+
${lineTotal.toFixed(2)}
);
})}
-
-
Total
+
+ Total
${total.toFixed(2)}
-
Shipping Information
-
-
+
Shipping Information
+
+
This is a demo checkout page. No actual payment will be processed.
Place Order
-
nav("/cart")} className={`w-full mt-3 px-6 py-3 rounded ${isDark ? "bg-gray-700 hover:bg-gray-600 text-light" : "bg-gray-200 hover:bg-gray-300 text-gray-800"}`}>
+ navigate("/cart")} className={`w-full mt-3 px-6 py-3 rounded ${darkMode ? "bg-gray-700 hover:bg-gray-600 text-light" : "bg-gray-200 hover:bg-gray-300 text-gray-800"}`}>
Back to Cart