You are currently logged in as {currentUser.firstName}.
13 |You will be redirected to PayPal to complete your payment.
16 |Thank you for your order, {currentUser.firstName}.
15 | 16 |LIMITED OFFER
14 |USE DISCOUNT
16 | 17 || UserID | 20 |Full Name | 21 |Phone | 23 |Address | 24 ||
|---|---|---|---|---|
| {user.userID} | 30 |{user.firstName} {user.lastName} | 31 |{user.email} | 32 |{user.phone} | 33 |{user.postalCode} {user.city} {user.address} | 34 |
{product.brand}
26 |{formatPrice(product.defaultPrice)}
28 | 29 |{currentUser?.firstName} {currentUser?.lastName}
24 |{currentUser?.address}
25 |{currentUser?.postalCode} {currentUser?.city}
26 |Denmark
27 | { !discount > 0 && 28 |{statusObj.statusString}
; 14 | }; 15 | }; 16 | 17 | export const useStock = () => { 18 | return (stock) => { 19 | const stockMap = { 20 | true: { statusString: 'In stock', className: 'green' }, 21 | false: { statusString: 'Out of stock', className: 'red' }, 22 | }; 23 | const stockObj = stockMap[stock] ?? { statusString: '', className: '' }; 24 | return{stockObj.statusString}
; 25 | }; 26 | }; 27 | 28 | export default function useToggle(initialValue = false) { 29 | const [toggled, setToggled] = useState(initialValue); 30 | 31 | function toggle() { 32 | setToggled(!toggled); 33 | } 34 | 35 | function isToggled() { 36 | return toggled; 37 | } 38 | 39 | return { toggle, isToggled }; 40 | } 41 | 42 | export function formatPrice(price) { 43 | return price.toLocaleString('da-DK', { style: 'currency', currency: 'DKK' }); 44 | } 45 | 46 | export function formatDate(date) { 47 | const options = { year: 'numeric', month: 'long', day: 'numeric' }; 48 | return new Date(date).toLocaleDateString('us-US', options); 49 | } 50 | 51 | export function formatDateTime(dateTime) { 52 | const options = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }; 53 | return new Date(dateTime).toLocaleDateString('us-US', options); 54 | } -------------------------------------------------------------------------------- /frontend/src/store/actions/productActions.js: -------------------------------------------------------------------------------- 1 | 2 | import { createAsyncThunk } from '@reduxjs/toolkit'; 3 | import productApi from '../../utils/api/productApi'; 4 | import sizeApi from '../../utils/api/sizeApi'; 5 | 6 | export const fetchProducts = createAsyncThunk( 7 | 'products/fetchProducts', 8 | async () => { 9 | const products = await productApi.getProducts(); 10 | const productSizes = await sizeApi.getProductSizes(); 11 | const productSizesMap = productSizes.reduce((map, size) => { 12 | if (!map[size.productID]) { 13 | map[size.productID] = []; 14 | } 15 | map[size.productID].push(size); 16 | return map; 17 | }, {}); 18 | const productsWithSizes = products.map(product => { 19 | const { productID } = product; 20 | const sizes = productSizesMap[productID] || []; 21 | const minPrice = Math.min(...sizes.map(({ price }) => price)); 22 | const inStock = sizes.length > 0; 23 | return { ...product, sizes, defaultPrice: minPrice, inStock }; 24 | }); 25 | return productsWithSizes; 26 | } 27 | ); 28 | 29 | 30 | export const fetchProductById = createAsyncThunk('products/fetchProductById', async (productId) => { 31 | const product = await productApi.getProduct(productId); 32 | return product; 33 | }); 34 | 35 | export const createProduct = createAsyncThunk('products/createProduct', async (product) => { 36 | const createdProduct = await productApi.addProduct(product); 37 | return createdProduct; 38 | }); 39 | 40 | export const updateExistingProduct = createAsyncThunk( 41 | 'products/updateExistingProduct', 42 | async ({ productId, product }) => { 43 | const updatedProduct = await productApi.updateProduct(productId, product); 44 | return updatedProduct; 45 | } 46 | ); 47 | 48 | export const removeProduct = createAsyncThunk('products/removeProduct', async (productId) => { 49 | const deletedProduct = await productApi.deleteProduct(productId); 50 | return deletedProduct; 51 | }); -------------------------------------------------------------------------------- /frontend/src/components/cart/CartItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom'; 3 | import { useCart } from '../../utils/hooks/useCart'; 4 | import { formatPrice } from '../../utils/hooks/useUtil'; 5 | 6 | 7 | function CartItem() { 8 | const { removeFromCart, updateQuantity, items } = useCart(); 9 | 10 | return ( 11 | <> 12 | {items.map((item) => ( 13 |{item.product.brand} {item.product.name}
22 |Brand: {item.product.brand}
23 |Size: {item.size}
24 |Quantity: {item.quantity}
25 | removeFromCart(item.product.id, item.size)}>Remove 26 |{formatPrice(item.price)}
29 || ID | 34 |Order Date | 35 |Status | 36 |UserID | 37 |
|---|---|---|---|
| {order.orderID} | 43 |{formatDate(order.dateTime)} | 44 |{getStatusString(order.status)} | 45 |{order.userID} | 46 |
Subtotal
28 |{subtotal}
29 |Discount
33 |-10%
34 |Delivery
38 |{delivery}
39 |Total
43 |{defaultTotal}
44 |{products[index].brand} {products[index].name}
43 | )} 44 | {productSizes[index] && ( 45 |Size: {productSizes[index].size}
46 | )} 47 |Quantity: {orderItem.quantity}
48 |{formatPrice(productSizes[index].price)}
52 |{currentUser?.firstName} {currentUser?.lastName}
19 |{currentUser?.address}
20 |{currentUser?.postalCode} {currentUser?.city}
21 |Denmark
22 | { isToggled() &&There’s nothing in your bag yet.
18 | ) : ( 19 |Subtotal
31 |{subtotal ? subtotal : 0}
32 |Discount
36 |-10%
37 |Delivery
41 |{delivery ? delivery : "Free"}
42 |Total
46 |{total ? total : 0}
47 |51 | Spend {formatPrice(DELIVERY_THRESHOLD - defaultSubtotal)} more and get free shipping! 52 |
53 | ) : (Your order is eligible for free shipping.
) 54 | } 55 |47 | {selectedSize ? `${formatPrice(selectedSize?.price)}` : `${formatPrice(product.defaultPrice)}`} 48 |
49 |{product.description}
66 |Order #{order.orderID}
Placed {formatDateTime(order.dateTime)}
{orderItem.product.brand} {orderItem.product.name}
72 |Size: {orderItem.productSize.size}
73 |Quantity: {orderItem.quantity}
74 |Price: {formatPrice(orderItem.productSize.price)}
75 |-
59 | setMaxPrice(event.target.value)} /> 60 || Size | 23 |Price | 24 |Quantity | 25 |Edit | 26 |
|---|---|---|---|
| 31 | 41 | | 42 |43 | 44 | setNewSize((prevSize) => ({ 45 | ...prevSize, 46 | price: e.target.value, 47 | })) 48 | }/> 49 | | 50 |51 | 52 | setNewSize((prevSize) => ({ 53 | ...prevSize, 54 | quantity: e.target.value, 55 | })) 56 | }/> 57 | | 58 |59 | 60 | | 61 |
| {size.size} | 65 |66 | handleInputChange(e, index)} 71 | /> 72 | | 73 |74 | handleInputChange(e, index)} 79 | /> 80 | | 81 |82 | 85 | 88 | | 89 |
| ID | 28 |Model | 29 |Stock | 30 |
|---|---|---|
| {product.productID} | 36 |{product.brand} {product.name} | 37 |{getStock(product.inStock)} | 38 |