- 장바구니 페이지
장바구니 페이지에 기본적으로 필요한 기능은 '장바구니 담기(생성)'와 '장바구니 보기(읽기)'이다.
장바구니를 보려면 일단 상품을 추가해야하기에 상품 상세 탭에서 '장바구니 담기'를 클릭하면 추가하도록 설계해야한다.
<ItemDetail.js>
<div className="add_Bt">
<button >♡</button>
<button className="add_cart" onClick={addCart}>장바구니 담기</button>
</div>
상세 페이지 코드 중 장바구니 담기 버튼 부분이다.
해당 버튼 태그에 onClick 구문을 통해 클릭시 addcart 함수를 거치도록 하였다.
addcart의 내용은 다음과 같다.
const ItemDetail = () => {
...
const isLoggedIn = sessionStorage.getItem("isLoggedIn") === 'true';
const nickname = sessionStorage.getItem("ss_nickname");
...
const addCart = async () => {
if(isLoggedIn === true){
try {
const response = await axios.post('http://localhost:8000/api/addcart', { nickname: nickname, item_id: itemdetail.id, num: quantity });
console.log(response.data);
alert('장바구니 담기 완료');
} catch (err) {
console.error(err);
}
}
else {
alert('로그인을 먼저 해주세요.');
navigate("/login");
}
}
...
}
클릭 시 가장 먼저 확인하여야할 것은 로그인이 되어있는지 이기 때문에 if문을 통해 확인한다.
성공할 경우 엔드 포인트로 정보를 전송하고, 실패할 경우 로그인 경고를 보여준다.
엔드 포인트로 전달할 정보에는 사용자 구별을 위한 정보, 아이템 정보, 아이템 수량이다.
해당 정보를 전달 받은 서버의 내용은 다음과 같다.
const postCartItem = (req, res) => {
const { nickname, item_id, num } = req.body;
const query = "INSERT INTO kurly.cart (user_nickname, item_id, num) VALUES (?, ?, ?);";
const params = [nickname, item_id, num];
console.log(params);
db.query(query, params, (err, result)=>{
res.send(result);
})
};
전달 받은 정보는 일시적으로 저장되어야 하는 것이 아니기에 데이터베이스에 저장한다.
데이터베이스에는 장바구니 정보를 저장할 cart라는 이름의 새로운 테이블을 생성하고, 그 안에는 유저이름과 상품 번호, 수량을 입력받을 행을 생성한다.
이 테이블은 이후에 상품 테이블과 join하여 사용할 것이다.
이제 담은 상품들을 보여줄 페이지를 만들 차례이다.
<Cart.js>
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchCarts } from "../redux/actions/cartActions";
import '../css/Cart.css';
const Cart = () => {
const [checkbox, setCheckbox] = useState([]);
const nickname = sessionStorage.getItem("ss_nickname");
const [items, setItem] = useState(0);
const [checkAll, setCheckAll] = useState(false);
const [quantity, setQuantity] = useState({});
const [price, setPrice] = useState({});
const [originalPrice, setOriginalPrice] = useState({}); // 원래 가격을 저장할 상태
const dispatch = useDispatch();
const carts = useSelector(state => state.carts.carts);
useEffect(() => {
dispatch(fetchCarts());
}, [dispatch]);
useEffect(() => {
if (carts && Array.isArray(carts)) {
const mycart = carts.filter(cart => cart.user_nickname === nickname);
setItem(mycart.length);
const initialCheckbox = {};
const initialQuantities = {};
const initialPrices = {};
const initialOriginalPrices = {}; // 원래 가격을 초기화
mycart.forEach(cart => {
initialPrices[cart.id] = cart.disc_price;
initialOriginalPrices[cart.id] = cart.disc_price; // 원래 가격 저장
initialQuantities[cart.id] = Number(cart.num);
initialCheckbox[cart.id] = false;
});
setPrice(initialPrices);
setOriginalPrice(initialOriginalPrices); // 원래 가격 설정
setQuantity(initialQuantities);
setCheckbox(initialCheckbox);
}
}, [carts, nickname]);
const CheckAll = () => {
const temp = !checkAll;
setCheckAll(temp);
for (let i = 0; i < checkbox.length; i++) {
setCheckbox(i + 1, temp);
}
};
const CheckOne = (id) => {
setCheckbox(prevState => ({
...prevState,
[id]: !prevState[id]
}));
};
return (
<cart>
<div className="cartP_wrap">
<h2>장바구니</h2>
<div className="cart_wrap">
<div className="cart_main_wrap">
<div className="cart_checkBar">
<input type="checkbox" id="cart0" checked={checkAll} onChange={CheckAll} />
<label htmlFor="cart0"></label>전체선택
<div className="separator"></div>
<button className="selc_delete">선택삭제</button>
</div>
<div className="cart_main">
{items === 0 ? (
<div className="cart_empty">장바구니에 담긴 상품이 없습니다.</div>
) : (
carts.filter(cart => cart.user_nickname === nickname).map((cart) => (
<div className="cart_item" key={cart.id}>
<label>
<input type="checkbox" className="cart_list" checked={checkbox[cart.id] || false} onChange={() => CheckOne(cart.id)} />
<p className="cart_title">{cart.title}</p>
<Quantity id={cart.id} quantity={quantity[cart.id]} setQuantity={setQuantity} price={price[cart.id]} updateTotalPrice={updateTotalPrice} />
<p className="cart_price">{price[cart.id]}</p>
</label>
</div>
))
)}
</div>
</div>
<div className="cart_side_wrap">
</div>
</div>
<div className="order">
<input className="order_bt" type="button" value="주문하기" />
</div>
</div>
</cart>
);
}
export default Cart;
일단 상품 정보를 띄우는 부분이다.
이전에 했던 것처럼 리덕스를 이용해 cart 아이템을 저장하고, 이를 불러왔다.
DB로부터 받은 사용자 정보를 통해 일치하는 장바구니 정보만을 불러오도록 한다.
className = "cart_main" 에서는 조건에 따라 다른 내용을 보여주도록 하였는데, 불러온 내용이 없으면 "장바구니에 담긴 상품이 없습니다"를 출력하고, 내용이 있을 경우에는 정보를 보여주는 부분이다.
이제 받은 정보로부터 수정하게되는 부분은 수량인데, 이 부분은 따로 함수로 빼서 적용하였다.
...
function Quantity({ id, quantity, setQuantity, updateTotalPrice }) {
const cal = (num) => {
if (num < 0 && quantity > 1) {
setQuantity(prevState => {
const newQuantity = prevState[id] + num;
updateTotalPrice(id, newQuantity);
return {
...prevState,
[id]: newQuantity
};
});
} else if (num > 0) {
setQuantity(prevState => {
const newQuantity = prevState[id] + num;
updateTotalPrice(id, newQuantity);
return {
...prevState,
[id]: newQuantity
};
});
}
};
return (
<div className="quan_bt">
<input type="button" value="–" onClick={() => cal(-1)} />
{quantity}
<input type="button" value="+" onClick={() => cal(1)} />
</div>
);
...
초기에 DB를 이용해 설정된 수량 값과 어떤 아이템인지 전달해줄 상품 번호, 그리고 수량에 변화가 생길때마다 가격을 변화시켜줄 함수가 인자로 필요하다.
cal함수를 통해 전달받은 num인자(-/+ 버튼 클릭시)가 -값인지 +값인지에 따라 다르게 작동하도록한다.
둘 모두 num값을 이용해 quantity값을 변경시키며, 그에 맞게 updateTotalPrice(수량에 따른 가격 변화 함수)를 이용해 가격을 변화 시킨다.
마무리로 리턴해줄 코드에 onClick을 통한 cal함수의 호출을 포함한다.
이제 마지막으로 가격 변화 함수이다.
...
const updateTotalPrice = (id, quantity) => {
const disc_price = originalPrice[id]; // 원래 가격을 사용
if (disc_price) {
const tempPrice = disc_price.replace(/[,]/gim, '');
const totalPrice = (tempPrice * quantity).toLocaleString();
setPrice(prevState => ({
...prevState,
[id]: totalPrice
}));
}
};
...
전달 받을 인자는 당연히 상품 번호와 수량이다.
수량에 맞게 가격을 변화시켜야하는데 문제는 상품 기본 가격이 ','와 숫자로 구성된 문자열이라는 점이다.
','가 포함되어 있기에 단순 곱하기 계산을 할 수 없어 이를 제거하여야한다.
따라서 replace함수를 이용해 제거를 진행하였고, 반대로 리턴해줄 때에는 기본 가격과 동일한 형식으로 리턴해주어야 하기에 toLocaleString을 사용하였다. (toLocalString은 천단위마다 숫자사이에 ','를 찍어주도록 하는데에 활용할 수도 있다)
반환 값은 문자열 형태로 반환되기 때문에 그대로 setPrice를 통해 저장하였다.
여기까지 하면 아주 기본적인 장바구니 페이지가 완성된다.
장바구니 페이지를 끝으로 클론코딩 프로젝트는 끝을 내기로 하였다.
원래는 이 외에도 좀 더 디테일한 부분도 제작해보려 했으나, 근무와 병행하는 코딩 공부인지라 시간이 부족다보니 원하는 만큼 완성시키려면 너무 많은 기간이 걸릴거 같았다.
아직 내가 창작해본것도 없고, node외에도 java를 공부해보고 싶기도 하기 때문에 여기에 더이상 매달리지 않고 넘어가기로 하였다.
얼마나 걸릴지 모르지만 일단 생각해둔 아이디어와 이에 필요한 기술이나 기획에 대한 공부를 좀 한 뒤에 다음 프로젝트로 넘어가려한다.
https://memo-code.tistory.com/49
마켓컬리 클론코딩 12 - 메일 인증
메일 인증 기능 마무리한 줄 알았으나 뭔가 찝찝해서 둘러보니 메일 인증 구현한 걸 정리하지 않았다.. 10번 게시물에서 이어지는 글!https://memo-code.tistory.com/45 마켓컬리 클론코딩 10 - 로그인/
memo-code.tistory.com
'기타 > 마켓컬리 클론코딩' 카테고리의 다른 글
[React/Node] 마켓컬리 클론코딩 12 - 메일 인증 (1) | 2024.06.11 |
---|---|
[React/Node] 마켓컬리 클론코딩 10 - 로그인/로그아웃 (0) | 2024.05.16 |
[React/Node] 마켓컬리 클론코딩 9 - 회원가입 페이지 (0) | 2024.04.23 |
[React/Node] 마켓컬리 클론코딩 8 - Redux 추가 (0) | 2024.04.17 |
[React/Node] 마켓컬리 클론코딩 7 - node.js express 연결 (0) | 2024.04.02 |