Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import orderRoutes from './routes/order';
import branchRoutes from './routes/branch';
import headquartersRoutes from './routes/headquarters';
import supplierRoutes from './routes/supplier';
import cartRoutes from './routes/cart';

const app = express();
const port = process.env.PORT || 3000;
Expand Down Expand Up @@ -74,6 +75,7 @@ app.use('/api/orders', orderRoutes);
app.use('/api/branches', branchRoutes);
app.use('/api/headquarters', headquartersRoutes);
app.use('/api/suppliers', supplierRoutes);
app.use('/api/carts', cartRoutes);

app.get('/', (req, res) => {
res.send('Hello, world!');
Expand Down
42 changes: 42 additions & 0 deletions api/src/models/cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @swagger
* components:
* schemas:
* Cart:
* type: object
* required:
* - cartId
* - userId
* - createdAt
* properties:
* cartId:
* type: integer
* description: The unique identifier for the cart
* userId:
* type: string
* description: The ID of the user who owns the cart
* createdAt:
* type: string
* format: date-time
* description: The date and time when the cart was created
* updatedAt:
* type: string
* format: date-time
* description: The date and time when the cart was last updated
* status:
* type: string
* description: The current status of the cart
* enum: [active, checkout, completed, abandoned]
* totalAmount:
* type: number
* format: float
* description: The total amount of all items in the cart
*/
export interface Cart {
cartId: number;
userId: string;
createdAt: string;
updatedAt: string;
status: 'active' | 'checkout' | 'completed' | 'abandoned';
totalAmount: number;
}
46 changes: 46 additions & 0 deletions api/src/models/cartItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @swagger
* components:
* schemas:
* CartItem:
* type: object
* required:
* - cartItemId
* - cartId
* - productId
* - quantity
* - unitPrice
* properties:
* cartItemId:
* type: integer
* description: The unique identifier for the cart item
* cartId:
* type: integer
* description: The ID of the parent cart
* productId:
* type: integer
* description: The ID of the product in the cart
* quantity:
* type: integer
* description: The quantity of products in the cart
* unitPrice:
* type: number
* format: float
* description: The price per unit when added to cart
* addedAt:
* type: string
* format: date-time
* description: The date and time when the item was added to the cart
* notes:
* type: string
* description: Additional notes for the cart item
*/
export interface CartItem {
cartItemId: number;
cartId: number;
productId: number;
quantity: number;
unitPrice: number;
addedAt: string;
notes?: string;
}
56 changes: 56 additions & 0 deletions api/src/routes/cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @swagger
* tags:
* name: Carts
* description: API endpoints for managing shopping carts
*/

import express from 'express';
import { Cart } from '../models/cart';
import { CartItem } from '../models/cartItem';
import { Product } from '../models/product';
import { Order } from '../models/order';
import { OrderDetail } from '../models/orderDetail';
import { carts as seedCarts, cartItems as seedCartItems, products as seedProducts, orders as seedOrders, orderDetails as seedOrderDetails } from '../seedData';

const router = express.Router();

let carts: Cart[] = [...seedCarts];
let cartItems: CartItem[] = [...seedCartItems];
let products: Product[] = [...seedProducts];
let orders: Order[] = [...seedOrders];
let orderDetails: OrderDetail[] = [...seedOrderDetails];

// Create a new cart
router.post('/', (req, res) => {
const { userId } = req.body;
const newCart: Cart = {
cartId: Math.max(0, ...carts.map(c => c.cartId)) + 1,
userId,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
status: 'active',
totalAmount: 0
};
carts.push(newCart);
res.status(201).json(newCart);
});

// Get all carts
router.get('/', (req, res) => {
res.json(carts);
});

// Helper function to update cart total
function updateCartTotal(cartId: number) {
const cart = carts.find(c => c.cartId === cartId);
if (!cart) return;

const items = cartItems.filter(item => item.cartId === cartId);
const total = items.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0);

cart.totalAmount = total;
cart.updatedAt = new Date().toISOString();
}

export default router;
19 changes: 19 additions & 0 deletions api/src/seedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Order } from './models/order';
import { OrderDetail } from './models/orderDetail';
import { Delivery } from './models/delivery';
import { OrderDetailDelivery } from './models/orderDetailDelivery';
import { Cart } from './models/cart';
import { CartItem } from './models/cartItem';

// Suppliers
export const suppliers: Supplier[] = [
Expand Down Expand Up @@ -291,4 +293,21 @@ export const orderDetailDeliveries: OrderDetailDelivery[] = [
quantity: 20,
notes: "Delivery"
}
];

// Carts
export const carts: Cart[] = [
{
cartId: 1,
userId: "user1",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
status: 'active',
totalAmount: 0 // Will be calculated dynamically
}
];

// Cart Items
export const cartItems: CartItem[] = [
// Empty initially - items will be added through the API
];
7 changes: 6 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import About from './components/About';
import Footer from './components/Footer';
import Products from './components/entity/product/Products';
import Login from './components/Login';
import CartPage from './components/cart/CartPage';
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';

Expand All @@ -23,6 +25,7 @@ function ThemedApp() {
<Route path="/" element={<Welcome />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/login" element={<Login />} />
<Route path="/admin/products" element={<AdminProducts />} />
</Routes>
Expand All @@ -37,7 +40,9 @@ function App() {
return (
<AuthProvider>
<ThemeProvider>
<ThemedApp />
<CartProvider>
<ThemedApp />
</CartProvider>
</ThemeProvider>
</AuthProvider>
);
Expand Down
88 changes: 88 additions & 0 deletions frontend/src/api/cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import axios from 'axios';
import { api } from './config';

export interface Cart {
cartId: number;
userId: string;
createdAt: string;
updatedAt: string;
status: 'active' | 'checkout' | 'completed' | 'abandoned';
totalAmount: number;
}

export interface CartItem {
cartItemId: number;
cartId: number;
productId: number;
quantity: number;
unitPrice: number;
addedAt: string;
notes?: string;
}

export interface CartWithItems {
cart: Cart;
items: CartItem[];
}

export interface Product {
productId: number;
name: string;
description: string;
price: number;
imgName: string;
sku: string;
unit: string;
supplierId: number;
discount?: number;
}

// Create a new cart
export const createCart = async (userId: string): Promise<Cart> => {
const { data } = await axios.post(`${api.baseURL}${api.endpoints.carts}`, { userId });
return data;
};

// Get cart by ID
export const getCart = async (cartId: number): Promise<CartWithItems> => {
const { data } = await axios.get(`${api.baseURL}${api.endpoints.carts}/${cartId}`);
return data;
};

// Add item to cart
export const addItemToCart = async (cartId: number, productId: number, quantity: number, notes?: string): Promise<CartItem> => {
const { data } = await axios.post(`${api.baseURL}${api.endpoints.carts}/${cartId}/items`, {
productId,
quantity,
notes
});
return data;
};

// Update cart item quantity
export const updateCartItem = async (cartId: number, itemId: number, quantity: number): Promise<CartItem> => {
const { data } = await axios.put(`${api.baseURL}${api.endpoints.carts}/${cartId}/items/${itemId}`, {
quantity
});
return data;
};

// Remove item from cart
export const removeCartItem = async (cartId: number, itemId: number): Promise<void> => {
await axios.delete(`${api.baseURL}${api.endpoints.carts}/${cartId}/items/${itemId}`);
};

// Checkout cart
export const checkoutCart = async (cartId: number, branchId: number, notes?: string): Promise<any> => {
const { data } = await axios.post(`${api.baseURL}${api.endpoints.carts}/${cartId}/checkout`, {
branchId,
notes
});
return data;
};

// Get all products
export const fetchProducts = async (): Promise<Product[]> => {
const { data } = await axios.get(`${api.baseURL}${api.endpoints.products}`);
return data;
};
3 changes: 2 additions & 1 deletion frontend/src/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const api = {
headquarters: '/api/headquarters',
deliveries: '/api/deliveries',
orderDetails: '/api/order-details',
orderDetailDeliveries: '/api/order-detail-deliveries'
orderDetailDeliveries: '/api/order-detail-deliveries',
carts: '/api/carts'
}
};
4 changes: 4 additions & 0 deletions frontend/src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Link } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { useTheme } from '../context/ThemeContext';
import { useState } from 'react';
import CartIcon from './cart/CartIcon';

export default function Navigation() {
const { isLoggedIn, isAdmin, logout } = useAuth();
Expand Down Expand Up @@ -68,6 +69,9 @@ export default function Navigation() {
</div>
</div>
<div className="flex items-center space-x-4">
{/* Cart Icon */}
<CartIcon />

<button
onClick={toggleTheme}
className="p-2 rounded-full focus:outline-none transition-colors"
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/components/cart/CartIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useCart } from '../../context/CartContext';
import { useTheme } from '../../context/ThemeContext';
import { Link } from 'react-router-dom';

export default function CartIcon() {
const { cartCount } = useCart();
const { darkMode } = useTheme();

return (
<Link
to="/cart"
className={`relative inline-flex items-center p-2 rounded-full ${darkMode ? 'text-light hover:text-primary' : 'text-gray-700 hover:text-primary'} transition-colors`}
aria-label={`Shopping cart with ${cartCount} items`}
>
{/* Shopping Cart Icon */}
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 3h2l.4 2M7 13h10l4-8H5.4m0 0L7 13m0 0l-1.5 9H19m-7-9V9a2 2 0 11-4 0v4m0-4a2 2 0 11-4 0v0m4 0V6a2 2 0 114 0v3"
/>
</svg>

{/* Cart Count Badge */}
{cartCount > 0 && (
<span
className="absolute -top-1 -right-1 bg-primary text-white text-xs font-bold rounded-full h-5 w-5 flex items-center justify-center min-w-[1.25rem]"
aria-label={`${cartCount} items in cart`}
>
{cartCount > 99 ? '99+' : cartCount}
</span>
)}
</Link>
);
}
Loading