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.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

-
@@ -55,12 +54,12 @@ export default function Cart() { const lineTotal = finalPrice * product.quantity; return ( -
+
- {product.name} + {product.name}
-

{product.name}

+

{product.name}

{product.discount ? (
@@ -74,17 +73,17 @@ export default function Cart() {
-
+
{product.quantity}
- ${lineTotal.toFixed(2)} + ${lineTotal.toFixed(2)}
-
@@ -94,16 +93,16 @@ export default function Cart() { })}
-
+
- Total + Total ${total.toFixed(2)}
- -
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

-
@@ -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.

-