diff --git a/components/Product/AddCommentToProduct.js b/components/Product/AddCommentToProduct.js
new file mode 100644
index 0000000..599ec2a
--- /dev/null
+++ b/components/Product/AddCommentToProduct.js
@@ -0,0 +1,89 @@
+import React, { useState } from 'react'
+import axios from 'axios';
+import { Button, Comment, Form, Header, Message } from 'semantic-ui-react';
+import formatDate from '../../utils/formatDate';
+import catchErrors from '../../utils/catchErrors';
+import baseUrl from '../../utils/baseUrl';
+import cookie from 'js-cookie';
+import { useRouter } from 'next/router';
+
+export default function AddCommentToProduct({ user, product, handleNewComment }) {
+ const [comment, setComment] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [success, setSuccess] = useState(false);
+ const [error, setError] = React.useState("");
+
+ const router = useRouter();
+
+ function handleChange(event) {
+ const { value } = event.target;
+ setComment(value);
+ }
+
+ async function handleSubmit(event) {
+ event.preventDefault();
+ if (!user) {
+ router.push('/login');
+ return;
+ }
+
+ try {
+ setLoading(true);
+ setError("");
+ const url = `${baseUrl}/api/product`;
+ const token = cookie.get('token');
+ const payload = { comment, productId: product._id };
+ const headers = { Authorization: token };
+ const response = await axios.put(url, payload, { headers });
+ // { totalComments, product }
+ setComment('');
+ handleNewComment(response.data);
+ setSuccess(true);
+ } catch (error) {
+ catchErrors(error, setError);
+ setSuccess(false);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ return (
+ <>
+
+
+ {
+ user ?
+
+ :
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/components/Product/CommentPagination.js b/components/Product/CommentPagination.js
new file mode 100644
index 0000000..e59095a
--- /dev/null
+++ b/components/Product/CommentPagination.js
@@ -0,0 +1,22 @@
+import { useRouter } from "next/router";
+import { Container, Pagination } from "semantic-ui-react";
+import { redirectUser } from '../../utils/auth'
+
+function CommentPagination({ productId, totalPages }, ctx) {
+ const router = useRouter();
+ return (
+
+ {
+ data.activePage === 1
+ ? redirectUser(ctx, `/product?_id=${productId}`)
+ : redirectUser(ctx, `/product?_id=${productId}&page=${data.activePage}`)
+ }}
+ />
+
+ );
+}
+
+export default CommentPagination;
\ No newline at end of file
diff --git a/models/Product.js b/models/Product.js
index ec4da56..d3f4a12 100755
--- a/models/Product.js
+++ b/models/Product.js
@@ -3,6 +3,21 @@ import shortid from 'shortid';
const { String, Number, ObjectId } = mongoose.Schema.Types;
+const CommentSchema = new mongoose.Schema({
+ user: {
+ type: ObjectId,
+ ref: 'User',
+ },
+ content: {
+ type: String,
+ required: true,
+ },
+ updated_at: {
+ type: mongoose.Schema.Types.Date,
+ required: true,
+ },
+});
+
const ProductSchema = new mongoose.Schema({
name: {
type: String,
@@ -25,6 +40,7 @@ const ProductSchema = new mongoose.Schema({
type: String,
required: true,
},
+ comments: [CommentSchema],
category: {
type: String,
//required: true
diff --git a/pages/api/product.js b/pages/api/product.js
index 4b77f33..73a0691 100755
--- a/pages/api/product.js
+++ b/pages/api/product.js
@@ -1,10 +1,15 @@
import Product from '../../models/Product';
import Cart from '../../models/Cart';
+import User from '../../models/User';
import connectDb from '../../utils/connectDb';
+import jwt from 'jsonwebtoken';
+import mongoose from 'mongoose';
import Rating from '../../models/Rating';
connectDb();
+const COMMENTS_PER_PAGE = 5;
+
export default async (req, res) => {
switch (req.method) {
case 'GET':
@@ -13,6 +18,9 @@ export default async (req, res) => {
case 'POST':
await handlePostRequest(req, res);
break;
+ case 'PUT':
+ await handlePutRequest(req, res);
+ break;
case 'DELETE':
await handleDeleteRequest(req, res);
break;
@@ -23,12 +31,24 @@ export default async (req, res) => {
};
async function handleGetRequest(req, res) {
- const { _id } = req.query;
- const product = await Product.findOne({ _id }).populate({
- path: 'ratings',
- model: Rating,
- });
- res.status(200).json(product);
+ const { _id, page } = req.query;
+ const startIndex =
+ page && !Number.isNaN(Number(page)) && page > 0 ? (page - 1) * COMMENTS_PER_PAGE : 0;
+ const product = await Product.findOne({ _id })
+ .populate({ path: 'comments.user', model: User })
+ .populate({
+ path: 'ratings',
+ model: Rating,
+ })
+ .slice('comments', startIndex, startIndex + COMMENTS_PER_PAGE);
+
+ // Get comments count
+ const [{ comments: count }] = await Product.aggregate()
+ .match({ _id: mongoose.Types.ObjectId(_id) })
+ .project({
+ comments: { $cond: [{ $ifNull: ['$comments', false] }, { $size: '$comments' }, 0] },
+ });
+ res.status(200).json({ totalComments: Math.ceil(count / COMMENTS_PER_PAGE), product });
}
async function handlePostRequest(req, res) {
@@ -50,6 +70,53 @@ async function handlePostRequest(req, res) {
}
}
+async function handlePutRequest(req, res) {
+ // Check if the user is authorized
+ if (!('authorization' in req.headers)) {
+ return res.status(401).send('No authorization token');
+ }
+ // Get the required fields & check if they exist
+ const { comment, productId } = req.body;
+ if (!comment || !productId) {
+ return res.status(422).send('Comment and productId are required');
+ }
+ try {
+ // Verify the token
+ const { userId } = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
+ // Find the user
+ const user = await User.findOne({ _id: userId });
+ // If the user exists
+ if (user) {
+ // Create the comment
+ const newComment = { user: userId, content: comment, updated_at: Date.now() };
+ // Add the comment to the Product & get new Product
+ const updatedProduct = await Product.findOneAndUpdate(
+ { _id: productId },
+ { $push: { comments: { $each: [newComment], $position: 0 } } },
+ { new: true },
+ )
+ .populate({ path: 'comments.user', model: User })
+ .slice('comments', COMMENTS_PER_PAGE);
+
+ // Get comments count
+ const [{ comments: count }] = await Product.aggregate()
+ .match({ _id: mongoose.Types.ObjectId(productId) })
+ .project({
+ comments: { $cond: [{ $ifNull: ['$comments', false] }, { $size: '$comments' }, 0] },
+ });
+
+ // Return comments count and the updated product
+ res
+ .status(200)
+ .json({ totalComments: Math.ceil(count / COMMENTS_PER_PAGE), product: updatedProduct });
+ } else {
+ res.status(404).send('User not found');
+ }
+ } catch (error) {
+ res.status(403).send(`Please login to add comments!`);
+ }
+}
+
async function handleDeleteRequest(req, res) {
const { _id } = req.query;
@@ -58,10 +125,7 @@ async function handleDeleteRequest(req, res) {
await Product.findOneAndDelete({ _id });
// 2) Remove from all carts, referenced as "product"
- await Cart.updateMany(
- { 'products.product': _id },
- { $pull: { products: { product: _id } } },
- );
+ await Cart.updateMany({ 'products.product': _id }, { $pull: { products: { product: _id } } });
res.status(204).json({});
} catch (error) {
console.error(error);
diff --git a/pages/product.js b/pages/product.js
index da98249..592c22a 100755
--- a/pages/product.js
+++ b/pages/product.js
@@ -1,10 +1,20 @@
+import React, { useEffect, useState } from 'react';
import axios from 'axios';
import ProductSummary from '../components/Product/ProductSummary';
import ProductAttributes from '../components/Product/ProductAttributes';
+import AddCommentToProduct from '../components/Product/AddCommentToProduct';
+import CommentPagination from '../components/Product/CommentPagination';
import baseUrl from '../utils/baseUrl';
-import { useEffect } from 'react';
-function Product({ product, user }) {
+function Product({ product, user, totalComments }) {
+ const [displayedProduct, setDisplayedProduct] = useState(product);
+ const [displayedTotalComments, setDisplayedTotalComments] = useState(totalComments);
+
+ function handleNewComment({ totalComments: newTotalComments, product: newProduct }) {
+ setDisplayedProduct(newProduct);
+ setDisplayedTotalComments(newTotalComments);
+ }
+
useEffect(() => {
async function incrementProductView() {
const url = `${baseUrl}/api/view`;
@@ -12,21 +22,32 @@ function Product({ product, user }) {
await axios.post(url, payload);
}
incrementProductView();
- }, [product]);
+
+ setDisplayedProduct(product);
+ setDisplayedTotalComments(totalComments);
+ }, [product, totalComments]);
return (
<>
-
-
+
+
+
+ {displayedTotalComments > 0 && (
+
+ )}
>
);
}
-Product.getInitialProps = async ({ query: { _id } }) => {
+Product.getInitialProps = async ({ query: { _id, page } }) => {
const url = `${baseUrl}/api/product`;
- const payload = { params: { _id } };
+ const payload = { params: { _id, page } };
const response = await axios.get(url, payload);
- return { product: response.data };
+ return response.data;
};
export default Product;