basic features

This commit is contained in:
Mike 2024-10-17 12:34:45 -04:00
parent da6d76e598
commit 6babd2bee0
8 changed files with 146 additions and 51 deletions

View file

@ -1,51 +1,106 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import styles from "./css/cart.module.css"; import styles from "./css/cart.module.css";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import { currencyFormat } from "./utils/currency";
const Cart = () => { const Cart = () => {
const [cart, setCart, items, SetItems] = useOutletContext(); const [cart, setCart] = useOutletContext();
const cartItems = Object.keys(cart); const cartKeys = Object.keys(cart);
return ( return (
<> <div>
<div> <h2>Cart</h2>
{cart ? ( <Bag cartKeys={cartKeys} cart={cart} setCart={setCart} />
<div> </div>
<h2>Cart</h2>
{cartItems.map((item, index) => (
<CartItem item={cart[item]} key={index} />
))}
</div>
) : (
<h2>Your cart is empty</h2>
)}
</div>
</>
); );
}; };
function CartItem({ item }) { function Bag({ cartKeys, cart, setCart }) {
return (
<>
{cartKeys.length > 0 ? (
<div>
{cartKeys.map((key, index) => (
<CartItem
item={cart[key]}
cart={cart}
setCart={setCart}
key={index}
/>
))}
</div>
) : (
<h2>Your cart is empty</h2>
)}
</>
);
}
function CartItem({ item, cart, setCart }) {
const { title, price, image, qty } = item; const { title, price, image, qty } = item;
return ( return (
<div className={styles.container}> <div className={styles.container}>
<img src={image} alt={title} /> <img src={image} alt={title} />
<h3>{title}</h3> <div>
<p>Qty: {qty}</p> <h3>{title}</h3>
<p>Price: {price}</p> <p>Price: ${currencyFormat(price)}</p>
<p>Total: {price * qty}</p> <p>Qty: {qty}</p>
<p>{item.id}</p> </div>
<div>
<button onClick={() => decreaseQty(item, cart, setCart)}>
Decrease
</button>
<p>{qty}</p>
<button onClick={() => increaseQty(item, cart, setCart)}>
Increase
</button>
</div>
<div>
<p>Total: {currencyFormat(qty * price)}</p>
<button onClick={() => removeFromCart(item, cart, setCart)}>
Delete
</button>
</div>
</div> </div>
); );
} }
function increaseQty(item, cart, setCart) {
if (cart[item.id]) {
let obj = { ...cart };
obj[item.id].qty += 1;
setCart(obj);
}
}
function decreaseQty(item, cart, setCart) {
if (cart[item.id]) {
let obj = { ...cart };
if (obj[item.id].qty > 1) obj[item.id].qty -= 1;
setCart(obj);
}
}
function removeFromCart(item, cart, setCart) {
if (cart[item.id]) {
let obj = { ...cart };
delete obj[item.id];
setCart(obj);
}
}
CartItem.propTypes = { CartItem.propTypes = {
item: PropTypes.object, item: PropTypes.object,
cart: PropTypes.object,
setCart: PropTypes.func,
}; };
Cart.propTypes = { Bag.propTypes = {
cart: PropTypes.array, cartKeys: PropTypes.array,
cart: PropTypes.object,
setCart: PropTypes.func,
}; };
export default Cart; export default Cart;

View file

@ -10,7 +10,7 @@ export default function Store() {
return ( return (
<div> <div>
<h1>Smig.Tech Coaching Store</h1> <h1>Smig.Tech Store</h1>
<ProductCollection <ProductCollection
loading={loading} loading={loading}
items={items} items={items}
@ -28,7 +28,9 @@ function useFakeStoreAPIData(items, setItems, loading, setLoading) {
return; return;
} }
fetch("https://fakestoreapi.com/products?limit=5", { mode: "cors" }) fetch("https://fakestoreapi.com/products/category/electronics", {
mode: "cors",
})
.then((response) => { .then((response) => {
if (response.status >= 400) { if (response.status >= 400) {
throw new Error("unable to fetch items"); throw new Error("unable to fetch items");
@ -40,7 +42,7 @@ function useFakeStoreAPIData(items, setItems, loading, setLoading) {
response.forEach((item) => { response.forEach((item) => {
arr.push({ arr.push({
title: item.title, title: item.title,
price: item.price, price: item.price * 100,
image: item.image, image: item.image,
id: crypto.randomUUID(), id: crypto.randomUUID(),
}); });

View file

@ -8,7 +8,7 @@
.card { .card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: calc(100% / 4); max-width: calc(100% / 5);
justify-content: end; justify-content: end;
/*flex: 1;*/ /*flex: 1;*/
padding: 1.5rem; padding: 1.5rem;

View file

@ -1,47 +1,62 @@
import { useParams, useOutletContext } from "react-router-dom"; import { useParams, useOutletContext, Link } from "react-router-dom";
import Products from "./products"; import Products from "./products";
import PropTypes from "prop-types";
import styles from "./productCollection.module.css"; import styles from "./productDetails.module.css";
export default function ProductDetails() { export default function ProductDetails() {
const [cart, setCart, items, setItems] = useOutletContext(); const [cart, setCart, items] = useOutletContext();
const { id } = useParams(); const { id } = useParams();
const item = items.filter((item) => item.id === id)[0];
if (!items) return <HandleInvalidItem />;
const item = items.find((item) => item.id === id);
return ( return (
<div className={styles.container}> <div className={styles.container}>
{item ? ( {item ? (
<div className={styles.card}> <div className={styles.card}>
<Link to="/store">Back</Link>
<Products item={item} cart={cart} setCart={setCart} /> <Products item={item} cart={cart} setCart={setCart} />
<button <button
onClick={() => { onClick={() => {
addToCart(item, cart, setCart); cart[item.id]
? removeFromCart(item, cart, setCart)
: addToCart(item, cart, setCart);
}} }}
> >
Add to Cart {cart[item.id] ? "Remove from Cart" : "Add to Cart"}
</button> </button>
<Link to="/bag">View Cart</Link>
</div> </div>
) : null} ) : (
<HandleInvalidItem />
)}
</div>
);
}
function HandleInvalidItem() {
return (
<div className={styles.container}>
<div className={styles.card}>
<h1>Product Does Not Exist!</h1>
<Link to="/store">Return to Store</Link>
</div>
</div> </div>
); );
} }
function addToCart(item, cart, setCart) { function addToCart(item, cart, setCart) {
let obj = { ...cart }; let obj = { ...cart };
if (obj[item.id]) { obj[item.id] = item;
obj[item.id].qty += 1; obj[item.id].qty = 1;
} else {
obj[item.id] = item;
obj[item.id].qty = 1;
}
setCart(obj); setCart(obj);
} }
ProductDetails.propTypes = { function removeFromCart(item, cart, setCart) {
item: PropTypes.object, if (cart[item.id]) {
cart: PropTypes.object, let obj = { ...cart };
setCart: PropTypes.func, delete obj[item.id];
}; setCart(obj);
}
}

View file

@ -0,0 +1,16 @@
.container {
display: flex;
gap: 2rem;
justify-content: center;
flex-wrap: wrap;
align-items: stretch;
}
.card {
display: flex;
flex-direction: column;
max-width: calc(100% / 5);
justify-content: end;
/*flex: 1;*/
padding: 1.5rem;
margin: 1.5rem;
}

View file

@ -2,13 +2,15 @@ import PropTypes from "prop-types";
import styles from "./products.module.css"; import styles from "./products.module.css";
import { currencyFormat } from "../utils/currency";
export default function Products({ item, cart, setCart }) { export default function Products({ item, cart, setCart }) {
return ( return (
<div className={styles.card}> <>
<img src={item.image} alt={item.title} className={styles.img} /> <img src={item.image} alt={item.title} className={styles.img} />
<p>{item.title}</p> <p>{item.title}</p>
<p>${item.price}</p> <p>${currencyFormat(item.price)}</p>
</div> </>
); );
} }

View file

@ -2,4 +2,6 @@
width: 100%; width: 100%;
height: auto; height: auto;
vertical-align: middle; vertical-align: middle;
object-fit: cover;
float: left;
} }

View file

@ -0,0 +1,3 @@
export function currencyFormat(str) {
return (str / 100).toFixed(2);
}