-
아마존 E-commerce 클론 -16) placeorder Screen 만들기NODE.JS 2021. 5. 28. 03:36
우선 screen안에 PlaceOrderScreen.js를 만든다.
import React from 'react' import { useSelector } from 'react-redux' import CheckoutSteps from '../components/CheckoutSteps' function PlaceOrderScreen(props) { const cart = useSelector(state=>state.cart); if(!cart.paymentMethod){ props.history.push('/payment'); } return ( <div> <CheckoutSteps step1 step2 step3 step4></CheckoutSteps> <div className="row top"> <div className="col-2"> <ul> <li> <div className="card card-body"> <h2>Shipping</h2> <p> <strong>Name:</strong>{cart.shippingAddress.fullName}<br/> <strong>Address:</strong>{cart.shippingAddress.address},{cart.shippingAddress.city},{cart.shippingAddress.postalCode},{cart.shippingAddress.country} </p> </div> </li> <li> <div className="card card-body"> <h2>Payment</h2> <p> <strong>Method:</strong>{cart.paymentMethod}<br/> </p> </div> </li> <li> <div className="card card-body"> <h2>Order Items</h2> </div> </li> </ul> </div> <div className="col-1"> </div> </div> </div> ) } export default PlaceOrderScreen
여기서 안에있는 주문된 상품을 보여주는 것은 카트에서 카트내의 아이템을 보여주는 것과 비슷하다.
가져와서 필요없는 부분은 삭제하고 아이템양과 총가격을 보여주는 부분만 추가하자.
import React from 'react' import { useSelector } from 'react-redux' import CheckoutSteps from '../components/CheckoutSteps' import {Link} from 'react-router-dom'; function PlaceOrderScreen(props) { const cart = useSelector(state=>state.cart); if(!cart.paymentMethod){ props.history.push('/payment'); } return ( <div> <CheckoutSteps step1 step2 step3 step4></CheckoutSteps> <div className="row top"> <div className="col-2"> <ul> <li> <div className="card card-body"> <h2>Shipping</h2> <p> <strong>Name:</strong>{cart.shippingAddress.fullName}<br/> <strong>Address:</strong>{cart.shippingAddress.address},{cart.shippingAddress.city},{cart.shippingAddress.postalCode},{cart.shippingAddress.country} </p> </div> </li> <li> <div className="card card-body"> <h2>Payment</h2> <p> <strong>Method:</strong>{cart.paymentMethod}<br/> </p> </div> </li> <li> <div className="card card-body"> <h2>Order Items</h2> <ul> {cart.cartItems.map(item=>( <li key = {item.product}> <div className="row"> <div> <img src={item.image} alt={item.name} className="small"/> </div> <div className="min-30"> <Link to={`/product/${item.product}`}>{item.name}</Link> </div> <div> {item.qty} x ${item.price} = ${item.qty * item.price} </div> </div> </li> )) } </ul> </div> </li> </ul> </div> <div className="col-1"> </div> </div> </div> ) } export default PlaceOrderScreen
app.js에 추가하자.
import PlaceOrderScreen from './screens/PlaceOrderScreen'; <Route path="/placeorder" component={PlaceOrderScreen} exact></Route>
이제 스크린에 총량과 계산 버튼을 선택하는 것을 만들자.
여기서 toPrice 함수는 5.123->"5.12"->5.12의 과정으로 만드는 것이다.
const toPrice = (num)=>Number(num.toFixed(2)); cart.itemsPrice = toPrice(cart.cartItems.reduce((a,c)=> a+c.qty*c.price,0)) cart.shippingPrice = cart.itemsPrice >100 ? toPrice(0):toPrice(10); cart.taxPrice = toPrice(0.15*cart.itemsPrice); cart.totalPrice = cart.itemsPrice + cart.shippingPrice+cart.taxPrice; const placeOrderHandler= () =>{ } <div className="col-1"> <div className="card card-body"> <ul> <li> <h2>Order Summary</h2> </li> <li> <div className="row"> <div>Items</div> <div>${cart.itemsPrice}</div> </div> </li> <li> <div className="row"> <div>Shipping</div> <div>${cart.shippingPrice}</div> </div> </li> <li> <div className="row"> <div>Tax</div> <div>${cart.taxPrice}</div> </div> </li> <li> <div className="row"> <div> <strong>Order Total</strong> </div> <div> <strong>${cart.totalPrice}</strong> </div> </div> </li> <li> <button className="primary block" type="button" onClick={placeOrderHandler} disabled={cart.cartItems.length===0}>Place Order</button> </li> </ul> </div> </div>
모든 가격을 소숫점 2자리 까지 나오도록 설정해보자.
<ul> <li> <h2>Order Summary</h2> </li> <li> <div className="row"> <div>Items</div> <div>${cart.itemsPrice.toFixed(2)}</div> </div> </li> <li> <div className="row"> <div>Shipping</div> <div>${cart.shippingPrice.toFixed(2)}</div> </div> </li> <li> <div className="row"> <div>Tax</div> <div>${cart.taxPrice.toFixed(2)}</div> </div> </li> <li> <div className="row"> <div> <strong>Order Total</strong> </div> <div> <strong>${cart.totalPrice.toFixed(2)}</strong> </div> </div> </li>
이제 placeorder의 백엔드 부분을 작성하자.
db에 주문을 올릴 것이다.
backend의 order모델을 만들자.
import mongoose from 'mongoose'; const orderSchema = new mongoose.Schema({ orderItems:[{ name:{ type:String, required:true }, qty:{ type:Number, required:true }, image:{ type:String, required:true }, price:{ type:Number, required:true }, product:{ type:mongoose.Schema.Types.ObjectId, ref:'Product', required:true }, }], shippingAddress:{ fullName:{ type:String, required:true }, address:{ type:String, required:true }, city:{ type:String, required:true }, postalCode:{ type:String, required:true }, country:{ type:String, required:true } }, paymentMethod:{ type:String, required:true }, itemsPrice:{ type:Number, required:true }, shippingPrice:{ type:Number, required:true }, taxPrice:{ type:Number, required:true }, totalPrice:{ type:Number, required:true }, user:{ type:mongoose.Schema.Types.ObjectId, ref:'User', required:true }, isPaid:{ type:Boolean, default:false }, paidAt:{ type:Date }, isDelivered:{ type:Boolean, default:false }, deliveredAt:{ type:Date } }, {timestamps:true}) const Order = mongoose.model('Order', orderSchema); export default Order;
이제 라우터를 만들자.
import express from 'express'; import expressAsyncHandler from 'express-async-handler'; import Order from '../models/orderModel'; const orderRouter = express.Router(); orderRouter.post('/', expressAsyncHandler(async(req, res) => { if(req.body.orderItems.length===0){ res.status(400).send({message:'Cart is empty'}) }else{ const order = new Order({ orderItems: req.body.orderItems, shippingAddress: req.body.shippingAddress, paymentMethod: req.body.paymentMethod, itemsPrice: req.body.itemsPrice, shippingPrice: req.body.shippingPrice, taxPrice: req.body.taxPrice, totalPrice: req.body.totalPrice, }) } })) export default orderRouter;
라우터에서 유저의 정보를 가져와야 하는데, 이는 미들웨어를 이용해서 isAuth정보를 가져오는 것으로 할 것이다.
utils.js로 간다.
여기서 slice(7, authorization.length)의 의미는 {bearer xxx}에서 'bearer '를 제외한 다음의 토큰만 가져와서 저장하겠다는 것이다.
export const isAuth = (req, res, next)=>{ const authorization = req.headers.authorization; if(authorization){ const token = authorization.slice(7, authorization.length); jwt.verify(token, process.env.JWT_SECRET || 'somethingsecret', (err, decode)=>{ if(err){ req.status(401).send({message:'Invalid Token'}) }else{ req.user = decode; next(); } }) }else{ req.status(401).send({message:'No Token'}) } }
다음과 같이 라우터에 미들웨어를 추가해서 작성해준다.
import express from 'express'; import expressAsyncHandler from 'express-async-handler'; import Order from '../models/orderModel.js'; import { isAuth } from '../utils.js'; const orderRouter = express.Router(); orderRouter.post('/', isAuth, expressAsyncHandler(async(req, res) => { if(req.body.orderItems.length===0){ res.status(400).send({message:'Cart is empty'}) }else{ const order = new Order({ orderItems: req.body.orderItems, shippingaddress: req.body.shippingaddress, paymentMethod: req.body.paymentMethod, itemPrice: req.body.itemPrice, shippingPrice: req.body.shippingPrice, totalPrice: req.body.totalPrice, user:req.user._id }) const createdOrder = await order.save(); res.status(201).send({message:'New Order reated', order:createdOrder}) } })) export default orderRouter;
server.js에 order 주소도 추가해준다.
import orderRouter from './routes/orderRouter.js'; app.use('/api/orders', orderRouter);
이제 placeorder버튼을 활성화 시키자.
placeOrderScreen.js로 가자.
import { useDispatch, useSelector } from 'react-redux' const dispatch = useDispatch() const placeOrderHandler= () =>{ dispatch(createOrder({...cart, orderItems:cart.cartItems})) }
constants를 만들자
orderConstants.js
export const ORDER_CREATE_REQUEST= 'ORDER_CREATE_REQUEST' export const ORDER_CREATE_SUCCESS= 'ORDER_CREATE_SUCCESS' export const ORDER_CREATE_FAIL= 'ORDER_CREATE_FAIL' export const ORDER_CREATE_RESET= 'ORDER_CREATE_RESET'
actions을 만들자.
orderActions.js
여기서 우리는 리덕스의 userSignin안의 userInfo를 가지고 올것이다 (getState())
그리고 헤더안에서 Authorization 을 'Bearer '+ token으로 설정한다.
import axios from "axios" import { ORDER_CREATE_FAIL, ORDER_CREATE_REQUEST, ORDER_CREATE_SUCCESS } from "../constants/orderConstants" import { CART_EMPTY } from "../constants/cartConstants"; export const createOrder = (order)=> async (dispatch, getState)=>{ dispatch({ type:ORDER_CREATE_REQUEST, payload: order }) try{ const {userSignin : {userInfo}} = getState(); const {data} = await axios.post('/api/orders', order, { headers:{ Authorization:`Bearer ${userInfo.token}` } }) dispatch({ type:ORDER_CREATE_SUCCESS, payload:data.order }) dispatch({ type:CART_EMPTY }) localStorage.removeItem("cartItems"); }catch(error){ dispatch({ type:ORDER_CREATE_FAIL, payload:error.response && error.response.data.message ? error.response.data.message : error.message }) } }
여기서 order를 생성하고 나면 카트를 비워줄것이기 때문에 cart_empty상수를 cartConstants안에 생성하고 리듀서액션을 만든다.
export const CART_EMPTY = 'CART_EMPTY'
case CART_EMPTY: return {...state, cartItems:[]} default: return state; }
OrderReducers.js를 만들자.
import { ORDER_CREATE_FAIL, ORDER_CREATE_REQUEST, ORDER_CREATE_RESET, ORDER_CREATE_SUCCESS } from "../constants/orderConstants"; export const orderCreateReducer = (state = {}, action) =>{ switch(action.type){ case ORDER_CREATE_REQUEST: return {loading:true} case ORDER_CREATE_SUCCESS: return {loading:false, success:true, order:action.payload} case ORDER_CREATE_FAIL: return {loading:false, error:action.payload} case ORDER_CREATE_RESET: return {}; default: return state } }
store.js에 추가해준다.
const reducer = combineReducers({ productList: productListReducer, productDetails : productDetailsReducer, cart:cartReducer, userSignin: userSigninReducer, userRegister: userRegisterReducer, orderCreate:orderCreateReducer })
placeOrder스크린에 임포트도 해준다.
import { createOrder } from '../actions/orderActions'; import { ORDER_CREATE_RESET } from '../constants/orderConstants'; import LoadingBox from '../components/LoadingBox' import MessageBox from '../components/MessageBox' const orderCreate = useSelector(state => state.orderCreate); const {loading, success, error, order} = orderCreate; const dispatch = useDispatch() const placeOrderHandler= () =>{ dispatch(createOrder({...cart, orderItems:cart.cartItems})) } useEffect(() => { if(success){ props.history.push(`/order/${order._id}`) dispatch({ type:ORDER_CREATE_RESET }) } }, [success, dispatch, order, props.history]) <div className="row"> <div> <strong>Order Total</strong> </div> <div> <strong>${cart.totalPrice.toFixed(2)}</strong> </div> </div> </li> <li> <button className="primary block" type="button" onClick={placeOrderHandler} disabled={cart.cartItems.length===0}>Place Order</button> </li> { loading && <LoadingBox ></LoadingBox> } { error && <MessageBox variant = 'danger'>{error}</MessageBox> } </ul> </div>
주문하고나서 카트를 확인하면 빈칸임을 확인할 수 있다.
리덕스에서도 주문이 성공하면
카트가 비워지고
orderCreate도 리셋된다.
'NODE.JS' 카테고리의 다른 글
아마존 E-commerce 클론 -18) payment Button 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -17) order Screen 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -15) payment Screen 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -14) shipping Screen 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -13) registerScreen 만들기 (0) 2021.05.27