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..62c4927
--- /dev/null
+++ b/frontend/src/components/Cart.tsx
@@ -0,0 +1,113 @@
+import { useCart } from "../context/CartContext";
+import { useTheme } from "../context/ThemeContext";
+import { useNavigate } from "react-router-dom";
+
+export default function Cart() {
+ const cart = useCart();
+ const { darkMode } = useTheme();
+ const navigate = useNavigate();
+
+ const cartItems = cart.items;
+ const total = cart.totalPrice;
+
+ const handleRemove = (id: number) => {
+ cart.removeFromCart(id);
+ };
+
+ const handleUpdateQty = (id: number, qty: number) => {
+ cart.updateQuantity(id, qty);
+ };
+
+ const handleClearAll = () => {
+ cart.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..ac47c54
--- /dev/null
+++ b/frontend/src/components/Checkout.tsx
@@ -0,0 +1,101 @@
+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 cart = useCart();
+ const { darkMode } = useTheme();
+ const navigate = useNavigate();
+ const [orderPlaced, setOrderPlaced] = useState(false);
+
+ const cartItems = cart.items;
+ const total = cart.totalPrice;
+
+ const handlePlaceOrder = () => {
+ setOrderPlaced(true);
+ setTimeout(() => {
+ cart.clearCart();
+ navigate("/");
+ }, 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;
+}