Skip to content

Commit

Permalink
Merge pull request #19 from larkinds/cart-page
Browse files Browse the repository at this point in the history
Cart page
  • Loading branch information
thelmaboamah authored Sep 11, 2023
2 parents c035909 + 908444d commit f20dc0c
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 0 deletions.
3 changes: 3 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export default function App() {
return <div></div>;
}

20 changes: 20 additions & 0 deletions client/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type ButtonProps<T> = {
action: (args?: T) => void;
children: React.ReactNode;
className?: string;
};

export default function Button<T>({
action,
children,
className,
}: ButtonProps<T>) {
function handleClick() {
action();
}
return (
<button className={className} onClick={handleClick}>
{children}
</button>
);
}
39 changes: 39 additions & 0 deletions client/src/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect, useRef } from "react";

type ModalProps = {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
containerClassNames?: string;
buttonClassNames?: string;
};

export default function Modal({
onClose,
isOpen,
containerClassNames,
buttonClassNames,
children,
}: ModalProps) {
const modalRef = useRef<HTMLDialogElement | null>(null);

useEffect(() => {
const { current: el } = modalRef;

if (!el) return;

if (isOpen) {
el.showModal();
} else {
el.close();
}
}, [isOpen]);
return (
<dialog ref={modalRef} className={containerClassNames}>
<button onClick={onClose} className={buttonClassNames}>
X
</button>
{children}
</dialog>
);
}
4 changes: 4 additions & 0 deletions client/src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Button from "./Button";
import Modal from "./Modal";

export { Button, Modal };
142 changes: 142 additions & 0 deletions client/src/pages/cart/Cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useState } from "react";
import { Button, Modal } from "../../components";
import styles from "./cart.module.css";

type Item = {
id: number;
image: string;
flavor: string;
price: number;
quantity: number;
};

const itemList: Item[] = [
{
id: 1,
image:
"https://media.istockphoto.com/id/980474978/vector/strawberry-ice-cream-cone-flat-design-dessert-icon.jpg?s=612x612&w=0&k=20&c=kY7enczOhemyXVu5Jp2pmVbv5SfQPj03zcqb27fJv4I=",
flavor: "strawberry",
price: 0,
quantity: 1,
},
{
id: 2,
image:
"https://img.freepik.com/free-vector/chocolate-ice-creame-waffle-cone-sticker_1308-68693.jpg?w=2000",
flavor: "chocolate",
price: 0,
quantity: 3,
},
{
id: 3,
image:
"https://thumbs.dreamstime.com/b/ice-cream-cone-vector-cartoon-illustration-vanilla-213405239.jpg",
flavor: "vanilla",
price: 0,
quantity: 2,
},
];

type CartProps = {
isOpen: boolean;
onClose: () => void;
};

export default function Cart({ isOpen, onClose }: CartProps) {
const [items, setItems] = useState(itemList);

function handleDeleteItem(id: Item["id"]) {
setItems((items) => items.filter((item) => item.id !== id));
}

function handleUpdateQuantity(id: Item["id"], newQuantity: Item["quantity"]) {
const updatedItems = items.map((item) => {
if (item.id === id) {
return { ...item, quantity: newQuantity };
}
return item;
});

setItems(updatedItems);
}

return (
<Modal
isOpen={isOpen}
onClose={onClose}
containerClassNames={styles.modal}
buttonClassNames={styles.modalCloseBtn}
>
{/* Currently getting data from a constant but will eventually either need to make this dynamic */}
{items.map((item) => (
<CartItem key={item.id}>
<ItemDetails item={item} />
<div className={styles.actionBtns}>
<RemoveBtn itemId={item.id} onDeleteItem={handleDeleteItem} />
<Quantity item={item} onUpdate={handleUpdateQuantity} />
</div>
</CartItem>
))}
</Modal>
);
}

function CartItem({ children }: { children: React.ReactNode }) {
return <div className={styles.cartItem}>{children}</div>;
}

function RemoveBtn({
onDeleteItem,
itemId,
}: {
onDeleteItem: (id: Item["id"]) => void;
itemId: Item["id"];
}) {
return (
<Button className={styles.removeBtn} action={() => onDeleteItem(itemId)}>
Remove
</Button>
);
}

function ItemDetails({ item }: { item: Item }) {
return (
<div className={styles.details}>
<div className={styles.flavor}>
<img src={item.image} alt={item.flavor} />
<span> {item.flavor} </span>
</div>
<p> ${item.price} </p>
</div>
);
}

function Quantity({
item,
onUpdate,
}: {
item: Item;
onUpdate: (id: Item["id"], newQuantity: Item["quantity"]) => void;
}) {
function handleIncrement() {
onUpdate(item.id, item.quantity + 1);
}

function handleDecrement() {
if (item.quantity > 0) {
onUpdate(item.id, item.quantity - 1);
}
}

return (
<div className={styles.quantity}>
<button className={styles.minus} onClick={handleDecrement}>
-
</button>
<span> {item.quantity} </span>
<button className={styles.plus} onClick={handleIncrement}>
+
</button>
</div>
);
}
10 changes: 10 additions & 0 deletions client/src/pages/cart/CartBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styles from "./cart.module.css";

// TODO: This eventually needs to be part of a header component
export default function CartBtn({ handleClick }: { handleClick: () => void }) {
return (
<button className={styles.cartBtn} onClick={handleClick}>
🛒
</button>
);
}
14 changes: 14 additions & 0 deletions client/src/pages/cart/CartPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useState } from "react";
import Cart from "./Cart";
import CartBtn from "./CartBtn";

export default function CartPage() {
const [isCartOpen, setIsCartOpen] = useState(false);

return (
<>
<CartBtn handleClick={() => setIsCartOpen(true)} />
<Cart isOpen={isCartOpen} onClose={() => setIsCartOpen(false)} />
</>
);
}
97 changes: 97 additions & 0 deletions client/src/pages/cart/cart.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
.modal {
height: 100vh;
width: 100%;
left: inherit;
}

.modalCloseBtn {
border: none;
background-color: inherit;
position: absolute;
right: 1rem;
margin-bottom: 1rem;
cursor: pointer;
}

.cartBtn {
position: absolute;
top: 0;
right: 10px;
background: transparent;
border: none;
font-size: 2rem;
cursor: pointer;
outline: none;
padding: 0.5rem;
}

.cartBtn:hover {
background: #f5f5f5;
}

.cartItem {
margin: 30px 0;
}

.details {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-size: 1.25rem;
}

.details p {
margin: 0 15px 0 0;
}

.flavor {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}

.flavor img {
width: 65px;
height: 65px;
object-fit: contain;
}

.actionBtns {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}

.actionBtns button {
background: transparent;
border: none;
font-size: 1rem;
cursor: pointer;
outline: none;
}

.removeBtn {
margin-left: 60px;
}

.removeBtn:hover {
background: #f5f5f5;
}

.quantity span {
border: 1px solid #333;
border-radius: 50%;
}

.plus {
padding-right: 0;
}

@media (min-width: 768px) {
.modal {
width: 300px;
}
}

0 comments on commit f20dc0c

Please sign in to comment.