## Milestone 1.
### BE
* cart.controller.js
```javascript=
cartController.deleteCartItem = async (req, res) => {
try {
const { id } = req.params;
const { userId } = req;
const cart = await Cart.findOne({ userId });
cart.items = cart.items.filter((item) => !item._id.equals(id));
await cart.save();
res.status(200).json({ status: 200, cartItemQty: cart.items.length });
} catch (error) {
return res.status(400).json({ status: "fail", error: error.message });
}
};
```
* cart.api.js
```javascript=
router.delete(
"/:id",
authController.authenticate,
cartController.deleteCartItem
);
```
### FE
* cartSlice.js 전체코드 (milestone 1,2,3전체코드)
```javascript=
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import api from "../../utils/api";
import { showToastMessage } from "../common/uiSlice";
const initialState = {
loading: false,
error: "",
cartList: [],
selectedItem: {},
cartItemCount: 0,
totalPrice: 0,
};
// Async thunk actions
export const addToCart = createAsyncThunk(
"cart/addToCart",
async ({ id, size }, { rejectWithValue, dispatch }) => {
try {
const response = await api.post("/cart", { size, productId: id, qty: 1 });
if (response.status !== 200) throw new Error(response.error);
dispatch(
showToastMessage({
message: "카트에 아이템이 추가됐습니다!",
status: "success",
})
);
return response.data.cartItemQty;
} catch (error) {
dispatch(showToastMessage({ message: error.error, status: "error" }));
return rejectWithValue(error.error);
}
}
);
export const getCartList = createAsyncThunk(
"cart/getCartList",
async (_, { rejectWithValue, dispatch }) => {
try {
const response = await api.get("/cart");
if (response.status !== 200) throw new Error(response.error);
return response.data.data;
} catch (error) {
dispatch(showToastMessage({ message: error, status: "error" }));
return rejectWithValue(error);
}
}
);
export const deleteCartItem = createAsyncThunk(
"cart/deleteCartItem",
async (id, { rejectWithValue, dispatch }) => {
try {
const response = await api.delete(`/cart/${id}`);
if (response.status !== 200) throw new Error(response.error);
dispatch(getCartList());
return response.data.cartItemQty;
} catch (error) {
dispatch(showToastMessage({ message: error, status: "error" }));
return rejectWithValue(error);
}
}
);
export const updateQty = createAsyncThunk(
"cart/updateQty",
async ({ id, value }, { rejectWithValue }) => {
try {
const response = await api.put(`/cart/${id}`, { qty: value });
if (response.status !== 200) throw new Error(response.error);
return response.data.data;
} catch (error) {
return rejectWithValue(error);
}
}
);
export const getCartQty = createAsyncThunk(
"cart/getCartQty",
async (_, { rejectWithValue, dispatch }) => {
try {
const response = await api.get("/cart/qty");
if (response.status !== 200) throw new Error(response.error);
return response.data.qty;
} catch (error) {
dispatch(showToastMessage({ message: error, status: "error" }));
return rejectWithValue(error);
}
}
);
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
initialCart: (state) => {
state.cartItemCount = 0;
},
// You can still add reducers here for non-async actions if necessary
},
extraReducers: (builder) => {
builder
.addCase(addToCart.pending, (state) => {
state.loading = true;
})
.addCase(addToCart.fulfilled, (state, action) => {
state.loading = false;
state.cartItemCount = action.payload;
})
.addCase(addToCart.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
})
.addCase(getCartList.fulfilled, (state, action) => {
state.loading = false;
state.cartList = action.payload;
state.totalPrice = action.payload.reduce(
(total, item) => total + item.productId.price * item.qty,
0
);
})
.addCase(updateQty.fulfilled, (state, action) => {
state.loading = false;
state.cartList = action.payload;
state.totalPrice = action.payload.reduce(
(total, item) => total + item.productId.price * item.qty,
0
);
})
.addCase(deleteCartItem.fulfilled, (state, action) => {
state.cartItemCount = action.payload;
})
.addCase(getCartQty.fulfilled, (state, action) => {
state.cartItemCount = action.payload;
});
},
});
export default cartSlice.reducer;
export const { initialCart } = cartSlice.actions;
```
* CartProductCard.js (Milestone 1,2,3전체 코드)
```javascript=
import React from "react";
import { faTrash } from "@fortawesome/free-solid-svg-icons";
import { Row, Col, Form } from "react-bootstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useDispatch } from "react-redux";
import { currencyFormat } from "../../../utils/number";
import { updateQty, deleteCartItem } from "../../../features/cart/cartSlice";
const CartProductCard = ({ item }) => {
const dispatch = useDispatch();
const handleQtyChange = (id, value) => {
dispatch(updateQty({ id, value }));
};
const deleteCart = (id) => {
dispatch(deleteCartItem(id));
};
return (
<div className="product-card-cart">
<Row>
<Col md={2} xs={12}>
<img src={item.productId.image} width={112} alt="product" />
</Col>
<Col md={10} xs={12}>
<div className="display-flex space-between">
<h3>{item.productId.name}</h3>
<button className="trash-button">
<FontAwesomeIcon
icon={faTrash}
width={24}
onClick={() => deleteCart(item._id)}
/>
</button>
</div>
<div>
<strong>₩ {currencyFormat(item.productId.price)}</strong>
</div>
<div>Size: {item.size}</div>
<div>Total: ₩ {currencyFormat(item.productId.price * item.qty)}</div>
<div>
Quantity:
<Form.Select
onChange={(event) =>
handleQtyChange(item._id, event.target.value)
}
required
defaultValue={item.qty}
className="qty-dropdown"
>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
<option value={5}>5</option>
<option value={6}>6</option>
<option value={7}>7</option>
<option value={8}>8</option>
<option value={9}>9</option>
<option value={10}>10</option>
</Form.Select>
</div>
</Col>
</Row>
</div>
);
};
export default CartProductCard;
```
* OrderReciept.js (Milestone 1,2,3전체코드)
```javascript=
import React from "react";
import { Button } from "react-bootstrap";
import { useNavigate } from "react-router";
import { useLocation } from "react-router-dom";
import { currencyFormat } from "../utils/number";
const OrderReceipt = ({ cartList, totalPrice }) => {
const location = useLocation();
const navigate = useNavigate();
return (
<div className="receipt-container">
<h3 className="receipt-title">주문 내역</h3>
<ul className="receipt-list">
{cartList.length > 0 &&
cartList.map((item) => (
<li key={item._id}>
<div className="display-flex space-between">
<div>{item.productId.name}</div>
<div>₩ {currencyFormat(item.productId.price * item.qty)}</div>
</div>
</li>
))}
</ul>
<div className="display-flex space-between receipt-title">
<div>
<strong>Total:</strong>
</div>
<div>
<strong>₩ {currencyFormat(totalPrice)}</strong>
</div>
</div>
{location.pathname.includes("/cart") && cartList.length > 0 && (
<Button
variant="dark"
className="payment-button"
onClick={() => navigate("/payment")}
>
결제 계속하기
</Button>
)}
<div>
가능한 결제 수단 귀하가 결제 단계에 도달할 때까지 가격 및 배송료는
확인되지 않습니다.
<div>
30일의 반품 가능 기간, 반품 수수료 및 미수취시 발생하는 추가 배송 요금
읽어보기 반품 및 환불
</div>
</div>
</div>
);
};
export default OrderReceipt;
```
## Milestone 2.
### BE
* cart.controller.js
```javascript=
cartController.editCartItem = async (req, res) => {
try {
const { userId } = req;
const { id } = req.params;
const { qty } = req.body;
const cart = await Cart.findOne({ userId }).populate({
path: "items",
populate: {
path: "productId",
model: "Product",
},
});
if (!cart) throw new Error("There is no cart for this user");
const index = cart.items.findIndex((item) => item._id.equals(id));
if (index === -1) throw new Error("Can not find item");
cart.items[index].qty = qty;
await cart.save();
res.status(200).json({ status: 200, data: cart.items });
} catch (error) {
return res.status(400).json({ status: "fail", error: error.message });
}
};
```
* cart.api.js
```javascript=
router.put("/:id", authController.authenticate, cartController.editCartItem);
```
* FE코드는 milestone1 에 cartSlice.js 참고
## Milestone 3.
### BE
* cart.controller.js
```javascript=
cartController.getCartQty = async (req, res) => {
try {
const { userId } = req;
const cart = await Cart.findOne({ userId: userId });
if (!cart) throw new Error("There is no cart!");
res.status(200).json({ status: 200, qty: cart.items.length });
} catch (error) {
return res.status(400).json({ status: "fail", error: error.message });
}
};
```
* cart.api.js
```javascript=
router.get("/qty", authController.authenticate, cartController.getCartQty);
```
### FE
* Applayout.js
```javascript=
useEffect(() => {
if (user) {
dispatch(getCartQty());
}
}, [user, dispatch]);
```
* FE코드는 milestone1 에 cartSlice.js 참고