From c8098e315fd1b17b27eb13fc0497a330365393b3 Mon Sep 17 00:00:00 2001 From: Pablets Date: Thu, 4 Feb 2021 02:31:47 -0300 Subject: [PATCH 1/2] - Added product remove from stock after success pay. - Added Public / protected route to update only product stock after purchase. - Fixed Product list not updating after purchase. - Deleted CART_CLEAR_ITEMS completely in order to correctly update the stock from each of the products (in case they are more than 1 in the same cart.) --- backend/controllers/productController.js | 134 +++++++++------- backend/models/orderModel.js | 9 +- backend/routes/productRoutes.js | 3 + frontend/package-lock.json | 12 +- frontend/package.json | 2 +- frontend/src/actions/orderActions.js | 6 - frontend/src/actions/productActions.js | 41 +++++ frontend/src/constants/cartConstants.js | 1 - frontend/src/constants/productConstants.js | 5 + frontend/src/reducers/cartReducers.js | 26 ++-- frontend/src/reducers/productReducers.js | 99 +++++++----- frontend/src/screens/OrderScreen.js | 168 ++++++++++++++------- frontend/src/screens/ProductEditScreen.js | 158 +++++++++---------- frontend/src/screens/ProductScreen.js | 133 +++++++++------- frontend/src/store.js | 2 + package-lock.json | 6 +- package.json | 11 +- 17 files changed, 487 insertions(+), 329 deletions(-) diff --git a/backend/controllers/productController.js b/backend/controllers/productController.js index ac29dbfe0..9c2bc292b 100644 --- a/backend/controllers/productController.js +++ b/backend/controllers/productController.js @@ -1,12 +1,12 @@ -import asyncHandler from 'express-async-handler' -import Product from '../models/productModel.js' +import asyncHandler from 'express-async-handler'; +import Product from '../models/productModel.js'; // @desc Fetch all products // @route GET /api/products // @access Public const getProducts = asyncHandler(async (req, res) => { - const pageSize = 10 - const page = Number(req.query.pageNumber) || 1 + const pageSize = 10; + const page = Number(req.query.pageNumber) || 1; const keyword = req.query.keyword ? { @@ -15,44 +15,44 @@ const getProducts = asyncHandler(async (req, res) => { $options: 'i', }, } - : {} + : {}; - const count = await Product.countDocuments({ ...keyword }) + const count = await Product.countDocuments({ ...keyword }); const products = await Product.find({ ...keyword }) .limit(pageSize) - .skip(pageSize * (page - 1)) + .skip(pageSize * (page - 1)); - res.json({ products, page, pages: Math.ceil(count / pageSize) }) -}) + res.json({ products, page, pages: Math.ceil(count / pageSize) }); +}); // @desc Fetch single product // @route GET /api/products/:id // @access Public const getProductById = asyncHandler(async (req, res) => { - const product = await Product.findById(req.params.id) + const product = await Product.findById(req.params.id); if (product) { - res.json(product) + res.json(product); } else { - res.status(404) - throw new Error('Product not found') + res.status(404); + throw new Error('Product not found'); } -}) +}); // @desc Delete a product // @route DELETE /api/products/:id // @access Private/Admin const deleteProduct = asyncHandler(async (req, res) => { - const product = await Product.findById(req.params.id) + const product = await Product.findById(req.params.id); if (product) { - await product.remove() - res.json({ message: 'Product removed' }) + await product.remove(); + res.json({ message: 'Product removed' }); } else { - res.status(404) - throw new Error('Product not found') + res.status(404); + throw new Error('Product not found'); } -}) +}); // @desc Create a product // @route POST /api/products @@ -68,11 +68,11 @@ const createProduct = asyncHandler(async (req, res) => { countInStock: 0, numReviews: 0, description: 'Sample description', - }) + }); - const createdProduct = await product.save() - res.status(201).json(createdProduct) -}) + const createdProduct = await product.save(); + res.status(201).json(createdProduct); +}); // @desc Update a product // @route PUT /api/products/:id @@ -86,43 +86,62 @@ const updateProduct = asyncHandler(async (req, res) => { brand, category, countInStock, - } = req.body + } = req.body; - const product = await Product.findById(req.params.id) + const product = await Product.findById(req.params.id); if (product) { - product.name = name - product.price = price - product.description = description - product.image = image - product.brand = brand - product.category = category - product.countInStock = countInStock - - const updatedProduct = await product.save() - res.json(updatedProduct) + product.name = name; + product.price = price; + product.description = description; + product.image = image; + product.brand = brand; + product.category = category; + product.countInStock = countInStock; + + const updatedProduct = await product.save(); + res.json(updatedProduct); } else { - res.status(404) - throw new Error('Product not found') + res.status(404); + throw new Error('Product not found'); } -}) +}); + +// @desc Update a product +// @route PUT /api/products/:id +// @access Private/Admin +const updateProductStock = asyncHandler(async (req, res) => { + const { countInStock } = req.body; + + const product = await Product.findById(req.params.id); + + if (product) { + product.countInStock = countInStock; + + const updatedProductStock = await product.save(); + res.json(updatedProductStock); + } else { + res.status(404); + throw new Error('Product not found'); + } +}); // @desc Create new review // @route POST /api/products/:id/reviews // @access Private const createProductReview = asyncHandler(async (req, res) => { - const { rating, comment } = req.body + const { rating, comment } = req.body; - const product = await Product.findById(req.params.id) + const product = await Product.findById(req.params.id); if (product) { const alreadyReviewed = product.reviews.find( (r) => r.user.toString() === req.user._id.toString() - ) + ); if (alreadyReviewed) { - res.status(400) - throw new Error('Product already reviewed') + res.status(400); + throw new Error('Product already reviewed'); } const review = { @@ -130,32 +149,32 @@ const createProductReview = asyncHandler(async (req, res) => { rating: Number(rating), comment, user: req.user._id, - } + }; - product.reviews.push(review) + product.reviews.push(review); - product.numReviews = product.reviews.length + product.numReviews = product.reviews.length; product.rating = product.reviews.reduce((acc, item) => item.rating + acc, 0) / - product.reviews.length + product.reviews.length; - await product.save() - res.status(201).json({ message: 'Review added' }) + await product.save(); + res.status(201).json({ message: 'Review added' }); } else { - res.status(404) - throw new Error('Product not found') + res.status(404); + throw new Error('Product not found'); } -}) +}); // @desc Get top rated products // @route GET /api/products/top // @access Public const getTopProducts = asyncHandler(async (req, res) => { - const products = await Product.find({}).sort({ rating: -1 }).limit(3) + const products = await Product.find({}).sort({ rating: -1 }).limit(3); - res.json(products) -}) + res.json(products); +}); export { getProducts, @@ -163,6 +182,7 @@ export { deleteProduct, createProduct, updateProduct, + updateProductStock, createProductReview, getTopProducts, -} +}; diff --git a/backend/models/orderModel.js b/backend/models/orderModel.js index 4d9cc68d1..eed3efb01 100644 --- a/backend/models/orderModel.js +++ b/backend/models/orderModel.js @@ -1,4 +1,4 @@ -import mongoose from 'mongoose' +import mongoose from 'mongoose'; const orderSchema = mongoose.Schema( { @@ -13,6 +13,7 @@ const orderSchema = mongoose.Schema( qty: { type: Number, required: true }, image: { type: String, required: true }, price: { type: Number, required: true }, + countInStock: { type: Number, required: true }, product: { type: mongoose.Schema.Types.ObjectId, required: true, @@ -71,8 +72,8 @@ const orderSchema = mongoose.Schema( { timestamps: true, } -) +); -const Order = mongoose.model('Order', orderSchema) +const Order = mongoose.model('Order', orderSchema); -export default Order +export default Order; diff --git a/backend/routes/productRoutes.js b/backend/routes/productRoutes.js index 801db1cf2..e0c19eac5 100644 --- a/backend/routes/productRoutes.js +++ b/backend/routes/productRoutes.js @@ -6,6 +6,7 @@ import { deleteProduct, createProduct, updateProduct, + updateProductStock, createProductReview, getTopProducts, } from '../controllers/productController.js' @@ -19,5 +20,7 @@ router .get(getProductById) .delete(protect, admin, deleteProduct) .put(protect, admin, updateProduct) +router.route('/:id/updatestock').put(protect, updateProductStock) +router.route('/:id/stock').put(protect, updateProductStock); export default router diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2f70d29f5..131e71db3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2565,9 +2565,9 @@ "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" }, "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { "follow-redirects": "^1.10.0" } @@ -6768,9 +6768,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "7.3.3", diff --git a/frontend/package.json b/frontend/package.json index 428344856..d03b99390 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", - "axios": "^0.20.0", + "axios": "^0.21.1", "react": "^16.13.1", "react-bootstrap": "^1.3.0", "react-dom": "^16.13.1", diff --git a/frontend/src/actions/orderActions.js b/frontend/src/actions/orderActions.js index bee1f9e3a..7c1de915d 100644 --- a/frontend/src/actions/orderActions.js +++ b/frontend/src/actions/orderActions.js @@ -1,5 +1,4 @@ import axios from 'axios' -import { CART_CLEAR_ITEMS } from '../constants/cartConstants' import { ORDER_CREATE_REQUEST, ORDER_CREATE_SUCCESS, @@ -45,11 +44,6 @@ export const createOrder = (order) => async (dispatch, getState) => { type: ORDER_CREATE_SUCCESS, payload: data, }) - dispatch({ - type: CART_CLEAR_ITEMS, - payload: data, - }) - localStorage.removeItem('cartItems') } catch (error) { const message = error.response && error.response.data.message diff --git a/frontend/src/actions/productActions.js b/frontend/src/actions/productActions.js index ad8f8173b..f9415c6a7 100644 --- a/frontend/src/actions/productActions.js +++ b/frontend/src/actions/productActions.js @@ -21,6 +21,9 @@ import { PRODUCT_TOP_REQUEST, PRODUCT_TOP_SUCCESS, PRODUCT_TOP_FAIL, + PRODUCT_UPDATE_STOCK_REQUEST, + PRODUCT_UPDATE_STOCK_SUCCESS, + PRODUCT_UPDATE_STOCK_FAIL, } from '../constants/productConstants' import { logout } from './userActions' @@ -186,6 +189,44 @@ export const updateProduct = (product) => async (dispatch, getState) => { } } +export const updateProductStock = (product) => async (dispatch, getState) => { + try { + dispatch({ + type: PRODUCT_UPDATE_STOCK_REQUEST, + }); + + const { + userLogin: { userInfo }, + } = getState(); + + const config = { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${userInfo.token}`, + }, + }; + + const { data } = await axios.put( + `/api/products/${product._id}/stock`, + product, + config + ); + + dispatch({ + type: PRODUCT_UPDATE_STOCK_SUCCESS, + payload: data, + }); + } catch (error) { + dispatch({ + type: PRODUCT_UPDATE_STOCK_FAIL, + payload: + error.response && error.response.data.message + ? error.response.data.message + : error.message, + }); + } +}; + export const createProductReview = (productId, review) => async ( dispatch, getState diff --git a/frontend/src/constants/cartConstants.js b/frontend/src/constants/cartConstants.js index d6cef833e..8d4cdc0b4 100644 --- a/frontend/src/constants/cartConstants.js +++ b/frontend/src/constants/cartConstants.js @@ -1,5 +1,4 @@ export const CART_ADD_ITEM = 'CART_ADD_ITEM' -export const CART_CLEAR_ITEMS = 'CART_RESET' export const CART_REMOVE_ITEM = 'CART_REMOVE_ITEM' export const CART_SAVE_SHIPPING_ADDRESS = 'CART_SAVE_SHIPPING_ADDRESS' export const CART_SAVE_PAYMENT_METHOD = 'CART_SAVE_PAYMENT_METHOD' diff --git a/frontend/src/constants/productConstants.js b/frontend/src/constants/productConstants.js index bdac67981..3cdefce72 100644 --- a/frontend/src/constants/productConstants.js +++ b/frontend/src/constants/productConstants.js @@ -20,6 +20,11 @@ export const PRODUCT_UPDATE_SUCCESS = 'PRODUCT_UPDATE_SUCCESS' export const PRODUCT_UPDATE_FAIL = 'PRODUCT_UPDATE_FAIL' export const PRODUCT_UPDATE_RESET = 'PRODUCT_UPDATE_RESET' +export const PRODUCT_UPDATE_STOCK_REQUEST = 'PRODUCT_UPDATE_STOCK_REQUEST' +export const PRODUCT_UPDATE_STOCK_SUCCESS = 'PRODUCT_UPDATE_STOCK_SUCCESS' +export const PRODUCT_UPDATE_STOCK_FAIL = 'PRODUCT_UPDATE_STOCK_FAIL' +export const PRODUCT_UPDATE_STOCK_RESET = 'PRODUCT_UPDATE_STOCK_RESET' + export const PRODUCT_CREATE_REVIEW_REQUEST = 'PRODUCT_CREATE_REVIEW_REQUEST' export const PRODUCT_CREATE_REVIEW_SUCCESS = 'PRODUCT_CREATE_REVIEW_SUCCESS' export const PRODUCT_CREATE_REVIEW_FAIL = 'PRODUCT_CREATE_REVIEW_FAIL' diff --git a/frontend/src/reducers/cartReducers.js b/frontend/src/reducers/cartReducers.js index 64815a1c3..516306148 100644 --- a/frontend/src/reducers/cartReducers.js +++ b/frontend/src/reducers/cartReducers.js @@ -3,8 +3,7 @@ import { CART_REMOVE_ITEM, CART_SAVE_SHIPPING_ADDRESS, CART_SAVE_PAYMENT_METHOD, - CART_CLEAR_ITEMS, -} from '../constants/cartConstants' +} from '../constants/cartConstants'; export const cartReducer = ( state = { cartItems: [], shippingAddress: {} }, @@ -12,9 +11,9 @@ export const cartReducer = ( ) => { switch (action.type) { case CART_ADD_ITEM: - const item = action.payload + const item = action.payload; - const existItem = state.cartItems.find((x) => x.product === item.product) + const existItem = state.cartItems.find((x) => x.product === item.product); if (existItem) { return { @@ -22,34 +21,29 @@ export const cartReducer = ( cartItems: state.cartItems.map((x) => x.product === existItem.product ? item : x ), - } + }; } else { return { ...state, cartItems: [...state.cartItems, item], - } + }; } case CART_REMOVE_ITEM: return { ...state, cartItems: state.cartItems.filter((x) => x.product !== action.payload), - } + }; case CART_SAVE_SHIPPING_ADDRESS: return { ...state, shippingAddress: action.payload, - } + }; case CART_SAVE_PAYMENT_METHOD: return { ...state, paymentMethod: action.payload, - } - case CART_CLEAR_ITEMS: - return { - ...state, - cartItems: [], - } + }; default: - return state + return state; } -} +}; diff --git a/frontend/src/reducers/productReducers.js b/frontend/src/reducers/productReducers.js index 24a5bb595..0b0c5d045 100644 --- a/frontend/src/reducers/productReducers.js +++ b/frontend/src/reducers/productReducers.js @@ -23,25 +23,29 @@ import { PRODUCT_TOP_REQUEST, PRODUCT_TOP_SUCCESS, PRODUCT_TOP_FAIL, -} from '../constants/productConstants' + PRODUCT_UPDATE_STOCK_REQUEST, + PRODUCT_UPDATE_STOCK_SUCCESS, + PRODUCT_UPDATE_STOCK_FAIL, + PRODUCT_UPDATE_STOCK_RESET, +} from '../constants/productConstants'; export const productListReducer = (state = { products: [] }, action) => { switch (action.type) { case PRODUCT_LIST_REQUEST: - return { loading: true, products: [] } + return { loading: true, products: [] }; case PRODUCT_LIST_SUCCESS: return { loading: false, products: action.payload.products, pages: action.payload.pages, page: action.payload.page, - } + }; case PRODUCT_LIST_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; default: - return state + return state; } -} +}; export const productDetailsReducer = ( state = { product: { reviews: [] } }, @@ -49,83 +53,98 @@ export const productDetailsReducer = ( ) => { switch (action.type) { case PRODUCT_DETAILS_REQUEST: - return { ...state, loading: true } + return { ...state, loading: true }; case PRODUCT_DETAILS_SUCCESS: - return { loading: false, product: action.payload } + return { loading: false, product: action.payload }; case PRODUCT_DETAILS_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; default: - return state + return state; } -} +}; export const productDeleteReducer = (state = {}, action) => { switch (action.type) { case PRODUCT_DELETE_REQUEST: - return { loading: true } + return { loading: true }; case PRODUCT_DELETE_SUCCESS: - return { loading: false, success: true } + return { loading: false, success: true }; case PRODUCT_DELETE_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; default: - return state + return state; } -} +}; export const productCreateReducer = (state = {}, action) => { switch (action.type) { case PRODUCT_CREATE_REQUEST: - return { loading: true } + return { loading: true }; case PRODUCT_CREATE_SUCCESS: - return { loading: false, success: true, product: action.payload } + return { loading: false, success: true, product: action.payload }; case PRODUCT_CREATE_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; case PRODUCT_CREATE_RESET: - return {} + return {}; default: - return state + return state; } -} +}; -export const productUpdateReducer = (state = { product: {} }, action) => { +export const productUpdateStockReducer = (state = { product: {} }, action) => { switch (action.type) { case PRODUCT_UPDATE_REQUEST: - return { loading: true } + return { loading: true }; case PRODUCT_UPDATE_SUCCESS: - return { loading: false, success: true, product: action.payload } + return { loading: false, success: true, product: action.payload }; case PRODUCT_UPDATE_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; case PRODUCT_UPDATE_RESET: - return { product: {} } + return { product: {} }; + default: + return state; + } +}; + +export const productUpdateReducer = (state = { product: {} }, action) => { + switch (action.type) { + case PRODUCT_UPDATE_STOCK_REQUEST: + return { loading: true }; + case PRODUCT_UPDATE_STOCK_SUCCESS: + return { loading: false, success: true, product: action.payload }; + case PRODUCT_UPDATE_STOCK_FAIL: + return { loading: false, error: action.payload }; + case PRODUCT_UPDATE_STOCK_RESET: + return { product: {} }; default: - return state + return state; } -} +}; export const productReviewCreateReducer = (state = {}, action) => { switch (action.type) { case PRODUCT_CREATE_REVIEW_REQUEST: - return { loading: true } + return { loading: true }; case PRODUCT_CREATE_REVIEW_SUCCESS: - return { loading: false, success: true } + return { loading: false, success: true }; case PRODUCT_CREATE_REVIEW_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; case PRODUCT_CREATE_REVIEW_RESET: - return {} + return {}; default: - return state + return state; } -} +}; export const productTopRatedReducer = (state = { products: [] }, action) => { switch (action.type) { case PRODUCT_TOP_REQUEST: - return { loading: true, products: [] } + return { loading: true, products: [] }; case PRODUCT_TOP_SUCCESS: - return { loading: false, products: action.payload } + return { loading: false, products: action.payload }; case PRODUCT_TOP_FAIL: - return { loading: false, error: action.payload } + return { loading: false, error: action.payload }; default: - return state + return state; } -} +}; diff --git a/frontend/src/screens/OrderScreen.js b/frontend/src/screens/OrderScreen.js index 1b8c87ed8..8bc97c623 100644 --- a/frontend/src/screens/OrderScreen.js +++ b/frontend/src/screens/OrderScreen.js @@ -1,100 +1,152 @@ -import React, { useState, useEffect } from 'react' -import axios from 'axios' -import { PayPalButton } from 'react-paypal-button-v2' -import { Link } from 'react-router-dom' -import { Row, Col, ListGroup, Image, Card, Button } from 'react-bootstrap' -import { useDispatch, useSelector } from 'react-redux' -import Message from '../components/Message' -import Loader from '../components/Loader' +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { PayPalButton } from 'react-paypal-button-v2'; +import { Link } from 'react-router-dom'; +import { Row, Col, ListGroup, Image, Card, Button } from 'react-bootstrap'; +import { useDispatch, useSelector } from 'react-redux'; +import Message from '../components/Message'; +import Loader from '../components/Loader'; import { getOrderDetails, payOrder, deliverOrder, -} from '../actions/orderActions' +} from '../actions/orderActions'; import { ORDER_PAY_RESET, ORDER_DELIVER_RESET, -} from '../constants/orderConstants' + ORDER_LIST_MY_RESET, +} from '../constants/orderConstants'; +import { PRODUCT_UPDATE_STOCK_RESET } from '../constants/productConstants'; +import { removeFromCart } from '../actions/cartActions'; +import { updateProductStock } from '../actions/productActions'; const OrderScreen = ({ match, history }) => { - const orderId = match.params.id + const orderId = match.params.id; - const [sdkReady, setSdkReady] = useState(false) + const [sdkReady, setSdkReady] = useState(false); - const dispatch = useDispatch() + const dispatch = useDispatch(); - const orderDetails = useSelector((state) => state.orderDetails) - const { order, loading, error } = orderDetails + const cart = useSelector((state) => state.cart); + const { cartItems } = cart; - const orderPay = useSelector((state) => state.orderPay) - const { loading: loadingPay, success: successPay } = orderPay + // console.log(cartItems); - const orderDeliver = useSelector((state) => state.orderDeliver) - const { loading: loadingDeliver, success: successDeliver } = orderDeliver + const [countInStock, setCountInStock] = useState(0); - const userLogin = useSelector((state) => state.userLogin) - const { userInfo } = userLogin + const productUpdateStock = useSelector((state) => state.productUpdateStock); + const { success: successStockUpdate } = productUpdateStock; + + const orderDetails = useSelector((state) => state.orderDetails); + const { order, loading, error } = orderDetails; + + const orderPay = useSelector((state) => state.orderPay); + const { loading: loadingPay, success: successPay } = orderPay; + + const orderDeliver = useSelector((state) => state.orderDeliver); + const { loading: loadingDeliver, success: successDeliver } = orderDeliver; + + const userLogin = useSelector((state) => state.userLogin); + const { userInfo } = userLogin; if (!loading) { // Calculate prices const addDecimals = (num) => { - return (Math.round(num * 100) / 100).toFixed(2) - } + return (Math.round(num * 100) / 100).toFixed(2); + }; order.itemsPrice = addDecimals( order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0) - ) + ); } useEffect(() => { if (!userInfo) { - history.push('/login') + history.push('/login'); } const addPayPalScript = async () => { - const { data: clientId } = await axios.get('/api/config/paypal') - const script = document.createElement('script') - script.type = 'text/javascript' - script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}` - script.async = true + const { data: clientId } = await axios.get('/api/config/paypal'); + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`; + script.async = true; script.onload = () => { - setSdkReady(true) - } - document.body.appendChild(script) - } + setSdkReady(true); + }; + document.body.appendChild(script); + }; if (!order || successPay || successDeliver || order._id !== orderId) { - dispatch({ type: ORDER_PAY_RESET }) - dispatch({ type: ORDER_DELIVER_RESET }) - dispatch(getOrderDetails(orderId)) + dispatch({ type: ORDER_PAY_RESET }); + dispatch({ type: ORDER_DELIVER_RESET }); + dispatch(getOrderDetails(orderId)); } else if (!order.isPaid) { if (!window.paypal) { - addPayPalScript() + addPayPalScript(); } else { - setSdkReady(true) + setSdkReady(true); } } - }, [dispatch, orderId, successPay, successDeliver, order]) + + if (successStockUpdate) { + console.log('PRODUCT_UPDATE_STOCK_RESET'); + dispatch({ type: PRODUCT_UPDATE_STOCK_RESET }); + } + + if (order && successPay) { + console.log('sale del loop'); + order.orderItems.forEach((item, i) => { + console.log( + `countinstock: ${cartItems[i].countInStock} + - + qty: ${item.qty} + Equals: ${cartItems[i].countInStock - item.qty}` + ); + const updatedStock = cartItems[i].countInStock - item.qty; + setCountInStock(cartItems[i].countInStock - item.qty); + dispatch( + updateProductStock({ + _id: item.product, + countInStock: updatedStock, + }) + ); + dispatch(removeFromCart(item.product)); + }); + dispatch({ type: ORDER_LIST_MY_RESET }); + } + }, [ + history, + userInfo, + dispatch, + orderId, + successPay, + successDeliver, + order, + cartItems, + countInStock, + successStockUpdate, + ]); const successPaymentHandler = (paymentResult) => { - console.log(paymentResult) - dispatch(payOrder(orderId, paymentResult)) - } + console.log(paymentResult); + dispatch(payOrder(orderId, paymentResult)); + }; const deliverHandler = () => { - dispatch(deliverOrder(order)) - } + dispatch(deliverOrder(order)); + }; return loading ? ( ) : error ? ( - {error} + {error} ) : ( <>

Order {order._id}

- +

Shipping

@@ -111,11 +163,11 @@ const OrderScreen = ({ match, history }) => { {order.shippingAddress.country}

{order.isDelivered ? ( - + Delivered on {order.deliveredAt} ) : ( - Not Delivered + Not Delivered )}
@@ -126,9 +178,9 @@ const OrderScreen = ({ match, history }) => { {order.paymentMethod}

{order.isPaid ? ( - Paid on {order.paidAt} + Paid on {order.paidAt} ) : ( - Not Paid + Not Paid )} @@ -137,7 +189,7 @@ const OrderScreen = ({ match, history }) => { {order.orderItems.length === 0 ? ( Order is empty ) : ( - + {order.orderItems.map((item, index) => ( @@ -167,7 +219,7 @@ const OrderScreen = ({ match, history }) => { - +

Order Summary

@@ -215,8 +267,8 @@ const OrderScreen = ({ match, history }) => { !order.isDelivered && ( )} - ) -} + ); +}; -export default ProductEditScreen +export default ProductEditScreen; diff --git a/frontend/src/screens/ProductScreen.js b/frontend/src/screens/ProductScreen.js index f7375a61d..a1829ea9d 100644 --- a/frontend/src/screens/ProductScreen.js +++ b/frontend/src/screens/ProductScreen.js @@ -1,71 +1,90 @@ -import React, { useState, useEffect } from 'react' -import { Link } from 'react-router-dom' -import { useDispatch, useSelector } from 'react-redux' -import { Row, Col, Image, ListGroup, Card, Button, Form } from 'react-bootstrap' -import Rating from '../components/Rating' -import Message from '../components/Message' -import Loader from '../components/Loader' -import Meta from '../components/Meta' +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { + Row, + Col, + Image, + ListGroup, + Card, + Button, + Form, +} from 'react-bootstrap'; +import Rating from '../components/Rating'; +import Message from '../components/Message'; +import Loader from '../components/Loader'; +import Meta from '../components/Meta'; import { listProductDetails, createProductReview, -} from '../actions/productActions' -import { PRODUCT_CREATE_REVIEW_RESET } from '../constants/productConstants' +} from '../actions/productActions'; +import { + PRODUCT_CREATE_REVIEW_RESET, + PRODUCT_UPDATE_STOCK_RESET, +} from '../constants/productConstants'; const ProductScreen = ({ history, match }) => { - const [qty, setQty] = useState(1) - const [rating, setRating] = useState(0) - const [comment, setComment] = useState('') + const [qty, setQty] = useState(1); + const [rating, setRating] = useState(0); + const [comment, setComment] = useState(''); + + const dispatch = useDispatch(); - const dispatch = useDispatch() + const productUpdateStock = useSelector((state) => state.productUpdateStock); + const { success: successStockUpdate } = productUpdateStock; - const productDetails = useSelector((state) => state.productDetails) - const { loading, error, product } = productDetails + const productDetails = useSelector((state) => state.productDetails); + const { loading, error, product } = productDetails; - const userLogin = useSelector((state) => state.userLogin) - const { userInfo } = userLogin + const userLogin = useSelector((state) => state.userLogin); + const { userInfo } = userLogin; - const productReviewCreate = useSelector((state) => state.productReviewCreate) + const productReviewCreate = useSelector((state) => state.productReviewCreate); const { success: successProductReview, loading: loadingProductReview, error: errorProductReview, - } = productReviewCreate + } = productReviewCreate; useEffect(() => { + if (successStockUpdate) { + dispatch({ type: PRODUCT_UPDATE_STOCK_RESET }); + } + if (successProductReview) { - setRating(0) - setComment('') + setRating(0); + setComment(''); } if (!product._id || product._id !== match.params.id) { - dispatch(listProductDetails(match.params.id)) - dispatch({ type: PRODUCT_CREATE_REVIEW_RESET }) + dispatch({ type: PRODUCT_CREATE_REVIEW_RESET }); } - }, [dispatch, match, successProductReview]) + dispatch(listProductDetails(match.params.id)); + }, [dispatch, match, successProductReview, product._id, + successStockUpdate,]); const addToCartHandler = () => { - history.push(`/cart/${match.params.id}?qty=${qty}`) - } + history.push(`/cart/${match.params.id}?qty=${qty}`); + }; const submitHandler = (e) => { - e.preventDefault() + e.preventDefault(); dispatch( createProductReview(match.params.id, { rating, comment, }) - ) - } + ); + }; return ( <> - + Go Back {loading ? ( ) : error ? ( - {error} + {error} ) : ( <> @@ -74,7 +93,7 @@ const ProductScreen = ({ history, match }) => { {product.name} - +

{product.name}

@@ -92,7 +111,7 @@ const ProductScreen = ({ history, match }) => { - + Price: @@ -117,7 +136,7 @@ const ProductScreen = ({ history, match }) => { Qty setQty(e.target.value)} > @@ -137,8 +156,8 @@ const ProductScreen = ({ history, match }) => { ) : ( - Please sign in to write a review{' '} + Please sign in to write a review{' '} )} @@ -218,7 +237,7 @@ const ProductScreen = ({ history, match }) => { )} - ) -} + ); +}; -export default ProductScreen +export default ProductScreen; diff --git a/frontend/src/store.js b/frontend/src/store.js index 80b27484e..9dadae48d 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -7,6 +7,7 @@ import { productDeleteReducer, productCreateReducer, productUpdateReducer, + productUpdateStockReducer, productReviewCreateReducer, productTopRatedReducer, } from './reducers/productReducers' @@ -35,6 +36,7 @@ const reducer = combineReducers({ productDelete: productDeleteReducer, productCreate: productCreateReducer, productUpdate: productUpdateReducer, + productUpdateStock:productUpdateStockReducer, productReviewCreate: productReviewCreateReducer, productTopRated: productTopRatedReducer, cart: cartReducer, diff --git a/package-lock.json b/package-lock.json index c4131961f..43fbc029a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -916,9 +916,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "ipaddr.js": { diff --git a/package.json b/package.json index 536e5600e..3c498d7f2 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,14 @@ "devDependencies": { "concurrently": "^5.3.0", "nodemon": "^2.0.4" - } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Pablets/proshop_mern.git" + }, + "keywords": [], + "bugs": { + "url": "https://github.com/Pablets/proshop_mern/issues" + }, + "homepage": "https://github.com/Pablets/proshop_mern#readme" } From 76ea248e0d8b03763ccf9e3c25f2059d1012a750 Mon Sep 17 00:00:00 2001 From: Pablets Date: Sun, 7 Feb 2021 00:29:51 -0300 Subject: [PATCH 2/2] Fixed code format issue, now aligned with main repo. --- backend/controllers/productController.js | 130 +++++++++--------- backend/models/orderModel.js | 8 +- backend/routes/productRoutes.js | 2 +- frontend/package.json | 2 +- frontend/src/actions/productActions.js | 14 +- frontend/src/reducers/cartReducers.js | 20 +-- frontend/src/reducers/productReducers.js | 90 ++++++------ frontend/src/screens/OrderScreen.js | 152 ++++++++++----------- frontend/src/screens/ProductEditScreen.js | 158 +++++++++++----------- frontend/src/screens/ProductScreen.js | 129 ++++++++---------- frontend/src/store.js | 2 +- 11 files changed, 349 insertions(+), 358 deletions(-) diff --git a/backend/controllers/productController.js b/backend/controllers/productController.js index 9c2bc292b..c96607dd4 100644 --- a/backend/controllers/productController.js +++ b/backend/controllers/productController.js @@ -1,12 +1,12 @@ -import asyncHandler from 'express-async-handler'; -import Product from '../models/productModel.js'; +import asyncHandler from 'express-async-handler' +import Product from '../models/productModel.js' // @desc Fetch all products // @route GET /api/products // @access Public const getProducts = asyncHandler(async (req, res) => { - const pageSize = 10; - const page = Number(req.query.pageNumber) || 1; + const pageSize = 10 + const page = Number(req.query.pageNumber) || 1 const keyword = req.query.keyword ? { @@ -15,44 +15,44 @@ const getProducts = asyncHandler(async (req, res) => { $options: 'i', }, } - : {}; + : {} - const count = await Product.countDocuments({ ...keyword }); + const count = await Product.countDocuments({ ...keyword }) const products = await Product.find({ ...keyword }) .limit(pageSize) - .skip(pageSize * (page - 1)); + .skip(pageSize * (page - 1)) - res.json({ products, page, pages: Math.ceil(count / pageSize) }); -}); + res.json({ products, page, pages: Math.ceil(count / pageSize) }) +}) // @desc Fetch single product // @route GET /api/products/:id // @access Public const getProductById = asyncHandler(async (req, res) => { - const product = await Product.findById(req.params.id); + const product = await Product.findById(req.params.id) if (product) { - res.json(product); + res.json(product) } else { - res.status(404); - throw new Error('Product not found'); + res.status(404) + throw new Error('Product not found') } -}); +}) // @desc Delete a product // @route DELETE /api/products/:id // @access Private/Admin const deleteProduct = asyncHandler(async (req, res) => { - const product = await Product.findById(req.params.id); + const product = await Product.findById(req.params.id) if (product) { - await product.remove(); - res.json({ message: 'Product removed' }); + await product.remove() + res.json({ message: 'Product removed' }) } else { - res.status(404); - throw new Error('Product not found'); + res.status(404) + throw new Error('Product not found') } -}); +}) // @desc Create a product // @route POST /api/products @@ -68,11 +68,11 @@ const createProduct = asyncHandler(async (req, res) => { countInStock: 0, numReviews: 0, description: 'Sample description', - }); + }) - const createdProduct = await product.save(); - res.status(201).json(createdProduct); -}); + const createdProduct = await product.save() + res.status(201).json(createdProduct) +}) // @desc Update a product // @route PUT /api/products/:id @@ -86,62 +86,62 @@ const updateProduct = asyncHandler(async (req, res) => { brand, category, countInStock, - } = req.body; + } = req.body - const product = await Product.findById(req.params.id); + const product = await Product.findById(req.params.id) if (product) { - product.name = name; - product.price = price; - product.description = description; - product.image = image; - product.brand = brand; - product.category = category; - product.countInStock = countInStock; - - const updatedProduct = await product.save(); - res.json(updatedProduct); + product.name = name + product.price = price + product.description = description + product.image = image + product.brand = brand + product.category = category + product.countInStock = countInStock + + const updatedProduct = await product.save() + res.json(updatedProduct) } else { - res.status(404); - throw new Error('Product not found'); + res.status(404) + throw new Error('Product not found') } -}); +}) // @desc Update a product // @route PUT /api/products/:id // @access Private/Admin const updateProductStock = asyncHandler(async (req, res) => { - const { countInStock } = req.body; + const { countInStock } = req.body - const product = await Product.findById(req.params.id); + const product = await Product.findById(req.params.id) if (product) { - product.countInStock = countInStock; + product.countInStock = countInStock - const updatedProductStock = await product.save(); - res.json(updatedProductStock); + const updatedProductStock = await product.save() + res.json(updatedProductStock) } else { - res.status(404); - throw new Error('Product not found'); + res.status(404) + throw new Error('Product not found') } -}); +}) // @desc Create new review // @route POST /api/products/:id/reviews // @access Private const createProductReview = asyncHandler(async (req, res) => { - const { rating, comment } = req.body; + const { rating, comment } = req.body - const product = await Product.findById(req.params.id); + const product = await Product.findById(req.params.id) if (product) { const alreadyReviewed = product.reviews.find( (r) => r.user.toString() === req.user._id.toString() - ); + ) if (alreadyReviewed) { - res.status(400); - throw new Error('Product already reviewed'); + res.status(400) + throw new Error('Product already reviewed') } const review = { @@ -149,32 +149,32 @@ const createProductReview = asyncHandler(async (req, res) => { rating: Number(rating), comment, user: req.user._id, - }; + } - product.reviews.push(review); + product.reviews.push(review) - product.numReviews = product.reviews.length; + product.numReviews = product.reviews.length product.rating = product.reviews.reduce((acc, item) => item.rating + acc, 0) / - product.reviews.length; + product.reviews.length - await product.save(); - res.status(201).json({ message: 'Review added' }); + await product.save() + res.status(201).json({ message: 'Review added' }) } else { - res.status(404); - throw new Error('Product not found'); + res.status(404) + throw new Error('Product not found') } -}); +}) // @desc Get top rated products // @route GET /api/products/top // @access Public const getTopProducts = asyncHandler(async (req, res) => { - const products = await Product.find({}).sort({ rating: -1 }).limit(3); + const products = await Product.find({}).sort({ rating: -1 }).limit(3) - res.json(products); -}); + res.json(products) +}) export { getProducts, @@ -185,4 +185,4 @@ export { updateProductStock, createProductReview, getTopProducts, -}; +} diff --git a/backend/models/orderModel.js b/backend/models/orderModel.js index eed3efb01..ecfd01371 100644 --- a/backend/models/orderModel.js +++ b/backend/models/orderModel.js @@ -1,4 +1,4 @@ -import mongoose from 'mongoose'; +import mongoose from 'mongoose' const orderSchema = mongoose.Schema( { @@ -72,8 +72,8 @@ const orderSchema = mongoose.Schema( { timestamps: true, } -); +) -const Order = mongoose.model('Order', orderSchema); +const Order = mongoose.model('Order', orderSchema) -export default Order; +export default Order diff --git a/backend/routes/productRoutes.js b/backend/routes/productRoutes.js index e0c19eac5..16efc6f61 100644 --- a/backend/routes/productRoutes.js +++ b/backend/routes/productRoutes.js @@ -21,6 +21,6 @@ router .delete(protect, admin, deleteProduct) .put(protect, admin, updateProduct) router.route('/:id/updatestock').put(protect, updateProductStock) -router.route('/:id/stock').put(protect, updateProductStock); +router.route('/:id/stock').put(protect, updateProductStock) export default router diff --git a/frontend/package.json b/frontend/package.json index d03b99390..428344856 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", - "axios": "^0.21.1", + "axios": "^0.20.0", "react": "^16.13.1", "react-bootstrap": "^1.3.0", "react-dom": "^16.13.1", diff --git a/frontend/src/actions/productActions.js b/frontend/src/actions/productActions.js index f9415c6a7..cfa928bca 100644 --- a/frontend/src/actions/productActions.js +++ b/frontend/src/actions/productActions.js @@ -193,29 +193,29 @@ export const updateProductStock = (product) => async (dispatch, getState) => { try { dispatch({ type: PRODUCT_UPDATE_STOCK_REQUEST, - }); + }) const { userLogin: { userInfo }, - } = getState(); + } = getState() const config = { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${userInfo.token}`, }, - }; + } const { data } = await axios.put( `/api/products/${product._id}/stock`, product, config - ); + ) dispatch({ type: PRODUCT_UPDATE_STOCK_SUCCESS, payload: data, - }); + }) } catch (error) { dispatch({ type: PRODUCT_UPDATE_STOCK_FAIL, @@ -223,9 +223,9 @@ export const updateProductStock = (product) => async (dispatch, getState) => { error.response && error.response.data.message ? error.response.data.message : error.message, - }); + }) } -}; +} export const createProductReview = (productId, review) => async ( dispatch, diff --git a/frontend/src/reducers/cartReducers.js b/frontend/src/reducers/cartReducers.js index 516306148..78dc505bc 100644 --- a/frontend/src/reducers/cartReducers.js +++ b/frontend/src/reducers/cartReducers.js @@ -3,7 +3,7 @@ import { CART_REMOVE_ITEM, CART_SAVE_SHIPPING_ADDRESS, CART_SAVE_PAYMENT_METHOD, -} from '../constants/cartConstants'; +} from '../constants/cartConstants' export const cartReducer = ( state = { cartItems: [], shippingAddress: {} }, @@ -11,9 +11,9 @@ export const cartReducer = ( ) => { switch (action.type) { case CART_ADD_ITEM: - const item = action.payload; + const item = action.payload - const existItem = state.cartItems.find((x) => x.product === item.product); + const existItem = state.cartItems.find((x) => x.product === item.product) if (existItem) { return { @@ -21,29 +21,29 @@ export const cartReducer = ( cartItems: state.cartItems.map((x) => x.product === existItem.product ? item : x ), - }; + } } else { return { ...state, cartItems: [...state.cartItems, item], - }; + } } case CART_REMOVE_ITEM: return { ...state, cartItems: state.cartItems.filter((x) => x.product !== action.payload), - }; + } case CART_SAVE_SHIPPING_ADDRESS: return { ...state, shippingAddress: action.payload, - }; + } case CART_SAVE_PAYMENT_METHOD: return { ...state, paymentMethod: action.payload, - }; + } default: - return state; + return state } -}; +} diff --git a/frontend/src/reducers/productReducers.js b/frontend/src/reducers/productReducers.js index 0b0c5d045..fb6672afb 100644 --- a/frontend/src/reducers/productReducers.js +++ b/frontend/src/reducers/productReducers.js @@ -27,25 +27,25 @@ import { PRODUCT_UPDATE_STOCK_SUCCESS, PRODUCT_UPDATE_STOCK_FAIL, PRODUCT_UPDATE_STOCK_RESET, -} from '../constants/productConstants'; +} from '../constants/productConstants' export const productListReducer = (state = { products: [] }, action) => { switch (action.type) { case PRODUCT_LIST_REQUEST: - return { loading: true, products: [] }; + return { loading: true, products: [] } case PRODUCT_LIST_SUCCESS: return { loading: false, products: action.payload.products, pages: action.payload.pages, page: action.payload.page, - }; + } case PRODUCT_LIST_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } default: - return state; + return state } -}; +} export const productDetailsReducer = ( state = { product: { reviews: [] } }, @@ -53,98 +53,98 @@ export const productDetailsReducer = ( ) => { switch (action.type) { case PRODUCT_DETAILS_REQUEST: - return { ...state, loading: true }; + return { ...state, loading: true } case PRODUCT_DETAILS_SUCCESS: - return { loading: false, product: action.payload }; + return { loading: false, product: action.payload } case PRODUCT_DETAILS_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } default: - return state; + return state } -}; +} export const productDeleteReducer = (state = {}, action) => { switch (action.type) { case PRODUCT_DELETE_REQUEST: - return { loading: true }; + return { loading: true } case PRODUCT_DELETE_SUCCESS: - return { loading: false, success: true }; + return { loading: false, success: true } case PRODUCT_DELETE_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } default: - return state; + return state } -}; +} export const productCreateReducer = (state = {}, action) => { switch (action.type) { case PRODUCT_CREATE_REQUEST: - return { loading: true }; + return { loading: true } case PRODUCT_CREATE_SUCCESS: - return { loading: false, success: true, product: action.payload }; + return { loading: false, success: true, product: action.payload } case PRODUCT_CREATE_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } case PRODUCT_CREATE_RESET: - return {}; + return {} default: - return state; + return state } -}; +} export const productUpdateStockReducer = (state = { product: {} }, action) => { switch (action.type) { case PRODUCT_UPDATE_REQUEST: - return { loading: true }; + return { loading: true } case PRODUCT_UPDATE_SUCCESS: - return { loading: false, success: true, product: action.payload }; + return { loading: false, success: true, product: action.payload } case PRODUCT_UPDATE_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } case PRODUCT_UPDATE_RESET: - return { product: {} }; + return { product: {} } default: - return state; + return state } -}; +} export const productUpdateReducer = (state = { product: {} }, action) => { switch (action.type) { case PRODUCT_UPDATE_STOCK_REQUEST: - return { loading: true }; + return { loading: true } case PRODUCT_UPDATE_STOCK_SUCCESS: - return { loading: false, success: true, product: action.payload }; + return { loading: false, success: true, product: action.payload } case PRODUCT_UPDATE_STOCK_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } case PRODUCT_UPDATE_STOCK_RESET: - return { product: {} }; + return { product: {} } default: - return state; + return state } -}; +} export const productReviewCreateReducer = (state = {}, action) => { switch (action.type) { case PRODUCT_CREATE_REVIEW_REQUEST: - return { loading: true }; + return { loading: true } case PRODUCT_CREATE_REVIEW_SUCCESS: - return { loading: false, success: true }; + return { loading: false, success: true } case PRODUCT_CREATE_REVIEW_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } case PRODUCT_CREATE_REVIEW_RESET: - return {}; + return {} default: - return state; + return state } -}; +} export const productTopRatedReducer = (state = { products: [] }, action) => { switch (action.type) { case PRODUCT_TOP_REQUEST: - return { loading: true, products: [] }; + return { loading: true, products: [] } case PRODUCT_TOP_SUCCESS: - return { loading: false, products: action.payload }; + return { loading: false, products: action.payload } case PRODUCT_TOP_FAIL: - return { loading: false, error: action.payload }; + return { loading: false, error: action.payload } default: - return state; + return state } -}; +} diff --git a/frontend/src/screens/OrderScreen.js b/frontend/src/screens/OrderScreen.js index 8bc97c623..7d31b6836 100644 --- a/frontend/src/screens/OrderScreen.js +++ b/frontend/src/screens/OrderScreen.js @@ -1,119 +1,119 @@ -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import { PayPalButton } from 'react-paypal-button-v2'; -import { Link } from 'react-router-dom'; -import { Row, Col, ListGroup, Image, Card, Button } from 'react-bootstrap'; -import { useDispatch, useSelector } from 'react-redux'; -import Message from '../components/Message'; -import Loader from '../components/Loader'; +import React, { useState, useEffect } from 'react' +import axios from 'axios' +import { PayPalButton } from 'react-paypal-button-v2' +import { Link } from 'react-router-dom' +import { Row, Col, ListGroup, Image, Card, Button } from 'react-bootstrap' +import { useDispatch, useSelector } from 'react-redux' +import Message from '../components/Message' +import Loader from '../components/Loader' import { getOrderDetails, payOrder, deliverOrder, -} from '../actions/orderActions'; +} from '../actions/orderActions' import { ORDER_PAY_RESET, ORDER_DELIVER_RESET, ORDER_LIST_MY_RESET, -} from '../constants/orderConstants'; -import { PRODUCT_UPDATE_STOCK_RESET } from '../constants/productConstants'; -import { removeFromCart } from '../actions/cartActions'; -import { updateProductStock } from '../actions/productActions'; +} from '../constants/orderConstants' +import { PRODUCT_UPDATE_STOCK_RESET } from '../constants/productConstants' +import { removeFromCart } from '../actions/cartActions' +import { updateProductStock } from '../actions/productActions' const OrderScreen = ({ match, history }) => { - const orderId = match.params.id; + const orderId = match.params.id - const [sdkReady, setSdkReady] = useState(false); + const [sdkReady, setSdkReady] = useState(false) - const dispatch = useDispatch(); + const dispatch = useDispatch() - const cart = useSelector((state) => state.cart); - const { cartItems } = cart; + const cart = useSelector((state) => state.cart) + const { cartItems } = cart // console.log(cartItems); - const [countInStock, setCountInStock] = useState(0); + const [countInStock, setCountInStock] = useState(0) - const productUpdateStock = useSelector((state) => state.productUpdateStock); - const { success: successStockUpdate } = productUpdateStock; + const productUpdateStock = useSelector((state) => state.productUpdateStock) + const { success: successStockUpdate } = productUpdateStock - const orderDetails = useSelector((state) => state.orderDetails); - const { order, loading, error } = orderDetails; + const orderDetails = useSelector((state) => state.orderDetails) + const { order, loading, error } = orderDetails - const orderPay = useSelector((state) => state.orderPay); - const { loading: loadingPay, success: successPay } = orderPay; + const orderPay = useSelector((state) => state.orderPay) + const { loading: loadingPay, success: successPay } = orderPay - const orderDeliver = useSelector((state) => state.orderDeliver); - const { loading: loadingDeliver, success: successDeliver } = orderDeliver; + const orderDeliver = useSelector((state) => state.orderDeliver) + const { loading: loadingDeliver, success: successDeliver } = orderDeliver - const userLogin = useSelector((state) => state.userLogin); - const { userInfo } = userLogin; + const userLogin = useSelector((state) => state.userLogin) + const { userInfo } = userLogin if (!loading) { // Calculate prices const addDecimals = (num) => { - return (Math.round(num * 100) / 100).toFixed(2); - }; + return (Math.round(num * 100) / 100).toFixed(2) + } order.itemsPrice = addDecimals( order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0) - ); + ) } useEffect(() => { if (!userInfo) { - history.push('/login'); + history.push('/login') } const addPayPalScript = async () => { - const { data: clientId } = await axios.get('/api/config/paypal'); - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`; - script.async = true; + const { data: clientId } = await axios.get('/api/config/paypal') + const script = document.createElement('script') + script.type = 'text/javascript' + script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}` + script.async = true script.onload = () => { - setSdkReady(true); - }; - document.body.appendChild(script); - }; + setSdkReady(true) + } + document.body.appendChild(script) + } if (!order || successPay || successDeliver || order._id !== orderId) { - dispatch({ type: ORDER_PAY_RESET }); - dispatch({ type: ORDER_DELIVER_RESET }); - dispatch(getOrderDetails(orderId)); + dispatch({ type: ORDER_PAY_RESET }) + dispatch({ type: ORDER_DELIVER_RESET }) + dispatch(getOrderDetails(orderId)) } else if (!order.isPaid) { if (!window.paypal) { - addPayPalScript(); + addPayPalScript() } else { - setSdkReady(true); + setSdkReady(true) } } if (successStockUpdate) { - console.log('PRODUCT_UPDATE_STOCK_RESET'); - dispatch({ type: PRODUCT_UPDATE_STOCK_RESET }); + console.log('PRODUCT_UPDATE_STOCK_RESET') + dispatch({ type: PRODUCT_UPDATE_STOCK_RESET }) } if (order && successPay) { - console.log('sale del loop'); + console.log('sale del loop') order.orderItems.forEach((item, i) => { console.log( `countinstock: ${cartItems[i].countInStock} - qty: ${item.qty} Equals: ${cartItems[i].countInStock - item.qty}` - ); - const updatedStock = cartItems[i].countInStock - item.qty; - setCountInStock(cartItems[i].countInStock - item.qty); + ) + const updatedStock = cartItems[i].countInStock - item.qty + setCountInStock(cartItems[i].countInStock - item.qty) dispatch( updateProductStock({ _id: item.product, countInStock: updatedStock, }) - ); - dispatch(removeFromCart(item.product)); - }); - dispatch({ type: ORDER_LIST_MY_RESET }); + ) + dispatch(removeFromCart(item.product)) + }) + dispatch({ type: ORDER_LIST_MY_RESET }) } }, [ history, @@ -126,27 +126,27 @@ const OrderScreen = ({ match, history }) => { cartItems, countInStock, successStockUpdate, - ]); + ]) const successPaymentHandler = (paymentResult) => { - console.log(paymentResult); - dispatch(payOrder(orderId, paymentResult)); - }; + console.log(paymentResult) + dispatch(payOrder(orderId, paymentResult)) + } const deliverHandler = () => { - dispatch(deliverOrder(order)); - }; + dispatch(deliverOrder(order)) + } return loading ? ( ) : error ? ( - {error} + {error} ) : ( <>

Order {order._id}

- +

Shipping

@@ -163,11 +163,11 @@ const OrderScreen = ({ match, history }) => { {order.shippingAddress.country}

{order.isDelivered ? ( - + Delivered on {order.deliveredAt} ) : ( - Not Delivered + Not Delivered )}
@@ -178,9 +178,9 @@ const OrderScreen = ({ match, history }) => { {order.paymentMethod}

{order.isPaid ? ( - Paid on {order.paidAt} + Paid on {order.paidAt} ) : ( - Not Paid + Not Paid )}
@@ -189,7 +189,7 @@ const OrderScreen = ({ match, history }) => { {order.orderItems.length === 0 ? ( Order is empty ) : ( - + {order.orderItems.map((item, index) => ( @@ -219,7 +219,7 @@ const OrderScreen = ({ match, history }) => { - +

Order Summary

@@ -267,8 +267,8 @@ const OrderScreen = ({ match, history }) => { !order.isDelivered && ( )} - ); -}; + ) +} -export default ProductEditScreen; +export default ProductEditScreen diff --git a/frontend/src/screens/ProductScreen.js b/frontend/src/screens/ProductScreen.js index a1829ea9d..18c1c146f 100644 --- a/frontend/src/screens/ProductScreen.js +++ b/frontend/src/screens/ProductScreen.js @@ -1,90 +1,81 @@ -import React, { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; -import { - Row, - Col, - Image, - ListGroup, - Card, - Button, - Form, -} from 'react-bootstrap'; -import Rating from '../components/Rating'; -import Message from '../components/Message'; -import Loader from '../components/Loader'; -import Meta from '../components/Meta'; +import React, { useState, useEffect } from 'react' +import { Link } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' +import { Row, Col, Image, ListGroup, Card, Button, Form } from 'react-bootstrap' +import Rating from '../components/Rating' +import Message from '../components/Message' +import Loader from '../components/Loader' +import Meta from '../components/Meta' import { listProductDetails, createProductReview, -} from '../actions/productActions'; +} from '../actions/productActions' import { PRODUCT_CREATE_REVIEW_RESET, PRODUCT_UPDATE_STOCK_RESET, -} from '../constants/productConstants'; +} from '../constants/productConstants' const ProductScreen = ({ history, match }) => { - const [qty, setQty] = useState(1); - const [rating, setRating] = useState(0); - const [comment, setComment] = useState(''); + const [qty, setQty] = useState(1) + const [rating, setRating] = useState(0) + const [comment, setComment] = useState('') - const dispatch = useDispatch(); + const dispatch = useDispatch() - const productUpdateStock = useSelector((state) => state.productUpdateStock); - const { success: successStockUpdate } = productUpdateStock; + const productUpdateStock = useSelector((state) => state.productUpdateStock) + const { success: successStockUpdate } = productUpdateStock - const productDetails = useSelector((state) => state.productDetails); - const { loading, error, product } = productDetails; + const productDetails = useSelector((state) => state.productDetails) + const { loading, error, product } = productDetails - const userLogin = useSelector((state) => state.userLogin); - const { userInfo } = userLogin; + const userLogin = useSelector((state) => state.userLogin) + const { userInfo } = userLogin - const productReviewCreate = useSelector((state) => state.productReviewCreate); + const productReviewCreate = useSelector((state) => state.productReviewCreate) const { success: successProductReview, loading: loadingProductReview, error: errorProductReview, - } = productReviewCreate; + } = productReviewCreate useEffect(() => { if (successStockUpdate) { - dispatch({ type: PRODUCT_UPDATE_STOCK_RESET }); + dispatch({ type: PRODUCT_UPDATE_STOCK_RESET }) } if (successProductReview) { - setRating(0); - setComment(''); + setRating(0) + setComment('') } if (!product._id || product._id !== match.params.id) { - dispatch({ type: PRODUCT_CREATE_REVIEW_RESET }); + dispatch({ type: PRODUCT_CREATE_REVIEW_RESET }) } - dispatch(listProductDetails(match.params.id)); - }, [dispatch, match, successProductReview, product._id, - successStockUpdate,]); + dispatch(listProductDetails(match.params.id)) + }, [dispatch, match, successProductReview, product._id, successStockUpdate]) const addToCartHandler = () => { - history.push(`/cart/${match.params.id}?qty=${qty}`); - }; + history.push(`/cart/${match.params.id}?qty=${qty}`) + } const submitHandler = (e) => { - e.preventDefault(); + e.preventDefault() dispatch( createProductReview(match.params.id, { rating, comment, }) - ); - }; + ) + } return ( <> - + Go Back {loading ? ( ) : error ? ( - {error} + {error} ) : ( <> @@ -93,7 +84,7 @@ const ProductScreen = ({ history, match }) => { {product.name} - +

{product.name}

@@ -111,7 +102,7 @@ const ProductScreen = ({ history, match }) => { - + Price: @@ -136,7 +127,7 @@ const ProductScreen = ({ history, match }) => { Qty setQty(e.target.value)} > @@ -156,8 +147,8 @@ const ProductScreen = ({ history, match }) => { ) : ( - Please sign in to write a review{' '} + Please sign in to write a review{' '} )} @@ -237,7 +228,7 @@ const ProductScreen = ({ history, match }) => { )} - ); -}; + ) +} -export default ProductScreen; +export default ProductScreen diff --git a/frontend/src/store.js b/frontend/src/store.js index 9dadae48d..3d8d60e0a 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -36,7 +36,7 @@ const reducer = combineReducers({ productDelete: productDeleteReducer, productCreate: productCreateReducer, productUpdate: productUpdateReducer, - productUpdateStock:productUpdateStockReducer, + productUpdateStock: productUpdateStockReducer, productReviewCreate: productReviewCreateReducer, productTopRated: productTopRatedReducer, cart: cartReducer,