From a0f4f5f7bd0d2055cc2d203db4121de1c8393177 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:29:15 +0000 Subject: [PATCH 1/3] Initial plan From 365062d98ed0434125afb97ba7439de3b46e0c98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:34:14 +0000 Subject: [PATCH 2/3] Add shopping cart feature with context, UI components, and integration Co-authored-by: webmaxru <1560278+webmaxru@users.noreply.github.com> --- frontend/src/App.tsx | 7 +- frontend/src/components/Navigation.tsx | 26 ++ frontend/src/components/entity/cart/Cart.tsx | 262 ++++++++++++++++++ .../components/entity/product/Products.tsx | 25 +- frontend/src/context/CartContext.tsx | 86 ++++++ frontend/src/types/cart.ts | 14 + 6 files changed, 413 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/entity/cart/Cart.tsx create mode 100644 frontend/src/context/CartContext.tsx create mode 100644 frontend/src/types/cart.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 29b9f53..f634d34 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'; @@ -25,6 +27,7 @@ function ThemedApp() { } /> } /> } /> + } /> } /> } /> @@ -39,7 +42,9 @@ function App() { return ( - + + + ); diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index 5f35e12..57bd2c9 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 { cart } = useCart(); const [adminMenuOpen, setAdminMenuOpen] = useState(false); return ( @@ -85,6 +87,30 @@ export default function Navigation() {
+ + + + + {cart.totalItems > 0 && ( + + {cart.totalItems} + + )} + +
+ + + ); + } + + return ( +
+
+

+ Shopping Cart +

+ + {checkoutError && ( +
+ {checkoutError} +
+ )} + +
+ {cart.items.map((item) => { + const itemPrice = item.discount ? item.price * (1 - item.discount) : item.price; + const itemTotal = itemPrice * item.quantity; + + return ( +
+ {item.name} +
+

+ {item.name} +

+
+ {item.discount ? ( + <> + + ${item.price.toFixed(2)} + + + ${itemPrice.toFixed(2)} + + + ) : ( + ${itemPrice.toFixed(2)} + )} +
+
+
+
+ + + {item.quantity} + + +
+
+ ${itemTotal.toFixed(2)} +
+ +
+
+ ); + })} +
+ +
+
+ + Total Items: + + + {cart.totalItems} + +
+
+ + Total: + + + ${cart.totalPrice.toFixed(2)} + +
+
+ + +
+
+
+
+ ); +} diff --git a/frontend/src/components/entity/product/Products.tsx b/frontend/src/components/entity/product/Products.tsx index bb7790e..6161383 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) => @@ -45,12 +47,23 @@ 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..2db3c1b --- /dev/null +++ b/frontend/src/context/CartContext.tsx @@ -0,0 +1,86 @@ +import { createContext, ReactNode, useContext, useEffect, useState } from 'react'; +import { CartItem, Cart } from '../types/cart'; + +interface CartContextType { + cart: Cart; + addToCart: (item: Omit, quantity: number) => void; + removeFromCart: (productId: number) => void; + updateQuantity: (productId: number, quantity: number) => void; + clearCart: () => void; +} + +const CartContext = createContext(undefined); + +const CART_STORAGE_KEY = 'octocat_supply_cart'; + +const calculateTotals = (items: CartItem[]): { totalItems: number; totalPrice: number } => { + const totalItems = items.reduce((sum, item) => sum + item.quantity, 0); + const totalPrice = items.reduce((sum, item) => { + const itemPrice = item.discount ? item.price * (1 - item.discount) : item.price; + return sum + itemPrice * item.quantity; + }, 0); + return { totalItems, totalPrice }; +}; + +export function CartProvider({ children }: { children: ReactNode }) { + const [items, setItems] = useState(() => { + const stored = localStorage.getItem(CART_STORAGE_KEY); + return stored ? JSON.parse(stored) : []; + }); + + useEffect(() => { + localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(items)); + }, [items]); + + const { totalItems, totalPrice } = calculateTotals(items); + + const cart: Cart = { + items, + totalItems, + totalPrice, + }; + + 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 + quantity } : i, + ); + } + return [...prevItems, { ...item, quantity }]; + }); + }; + + 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([]); + }; + + return ( + + {children} + + ); +} + +export function useCart() { + const context = useContext(CartContext); + if (!context) { + throw new Error('useCart must be used within a CartProvider'); + } + return context; +} diff --git a/frontend/src/types/cart.ts b/frontend/src/types/cart.ts new file mode 100644 index 0000000..cc8cb34 --- /dev/null +++ b/frontend/src/types/cart.ts @@ -0,0 +1,14 @@ +export interface CartItem { + productId: number; + name: string; + price: number; + quantity: number; + imgName: string; + discount?: number; +} + +export interface Cart { + items: CartItem[]; + totalItems: number; + totalPrice: number; +} From 7939ef3011092f54b94ecf3c95b5f378a7754862 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:37:40 +0000 Subject: [PATCH 3/3] Fix code review issues: add error handling for localStorage and extract magic number Co-authored-by: webmaxru <1560278+webmaxru@users.noreply.github.com> --- frontend/src/components/entity/cart/Cart.tsx | 5 ++++- frontend/src/context/CartContext.tsx | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/entity/cart/Cart.tsx b/frontend/src/components/entity/cart/Cart.tsx index 428635a..a0dcfe1 100644 --- a/frontend/src/components/entity/cart/Cart.tsx +++ b/frontend/src/components/entity/cart/Cart.tsx @@ -5,6 +5,9 @@ import { useState } from 'react'; import axios from 'axios'; import { api } from '../../../api/config'; +// Demo: using first branch for all orders +const DEFAULT_BRANCH_ID = 1; + export default function Cart() { const { cart, updateQuantity, removeFromCart, clearCart } = useCart(); const { darkMode } = useTheme(); @@ -28,7 +31,7 @@ export default function Cart() { try { // Create order with first branch (demo purposes) const orderResponse = await axios.post(`${api.baseURL}${api.endpoints.orders}`, { - branchId: 1, + branchId: DEFAULT_BRANCH_ID, orderDate: new Date().toISOString(), name: `Order ${new Date().toLocaleDateString()}`, description: 'Shopping cart order', diff --git a/frontend/src/context/CartContext.tsx b/frontend/src/context/CartContext.tsx index 2db3c1b..45d1ce9 100644 --- a/frontend/src/context/CartContext.tsx +++ b/frontend/src/context/CartContext.tsx @@ -24,8 +24,13 @@ const calculateTotals = (items: CartItem[]): { totalItems: number; totalPrice: n export function CartProvider({ children }: { children: ReactNode }) { const [items, setItems] = useState(() => { - const stored = localStorage.getItem(CART_STORAGE_KEY); - return stored ? JSON.parse(stored) : []; + try { + const stored = localStorage.getItem(CART_STORAGE_KEY); + return stored ? JSON.parse(stored) : []; + } catch (error) { + console.error('Failed to parse cart data from localStorage:', error); + return []; + } }); useEffect(() => {