Skip to content

Commit

Permalink
Merge pull request #19 from HackYourFuture/customer-review
Browse files Browse the repository at this point in the history
Customer review
  • Loading branch information
NoerGitKat committed Feb 2, 2020
2 parents 6dba510 + 0650f44 commit 094f116
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 18 deletions.
89 changes: 89 additions & 0 deletions components/Product/AddCommentToProduct.js
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Comment.Group minimal>
<Form reply onSubmit={handleSubmit} loading={loading} error={Boolean(error)} success={success}>
<Message error header="Oops!" content={error} />
<Message success header="Success" content="Thank you for your precious comment!" />
<Form.TextArea name="content" onChange={handleChange} value={comment} />
{
user ?
<Button type="submit" content='Add Comment' labelPosition='left' icon='edit' primary disabled={loading || !comment.trim()} />
:
<Button color='orange' content="Login to Add Comments" icon='sign in' onClick={() => router.push('/login')} />
}

</Form>
{
product.comments && product.comments.length > 0 &&
<>
<Header as='h3' dividing>
Comments
</Header>
{
product.comments.map(comment => (
<Comment key={`comment_id ${comment._id}`}>
<Comment.Content>
<Comment.Author as='span'>{comment.user.name}</Comment.Author>
<Comment.Metadata>
<span>{formatDate(comment.updated_at)}</span>
</Comment.Metadata>
<Comment.Text>{comment.content}</Comment.Text>
</Comment.Content>
</Comment>
))
}
</>
}
</Comment.Group>
</>
)
}
22 changes: 22 additions & 0 deletions components/Product/CommentPagination.js
Original file line number Diff line number Diff line change
@@ -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 (
<Container textAlign="center" style={{ margin: "2em" }}>
<Pagination
defaultActivePage={1}
totalPages={totalPages}
onPageChange={(event, data) => {
data.activePage === 1
? redirectUser(ctx, `/product?_id=${productId}`)
: redirectUser(ctx, `/product?_id=${productId}&page=${data.activePage}`)
}}
/>
</Container>
);
}

export default CommentPagination;
16 changes: 16 additions & 0 deletions models/Product.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -25,6 +40,7 @@ const ProductSchema = new mongoose.Schema({
type: String,
required: true,
},
comments: [CommentSchema],
category: {
type: String,
//required: true
Expand Down
84 changes: 74 additions & 10 deletions pages/api/product.js
Original file line number Diff line number Diff line change
@@ -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':
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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;

Expand All @@ -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);
Expand Down
37 changes: 29 additions & 8 deletions pages/product.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
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`;
const payload = { productId: product._id };
await axios.post(url, payload);
}
incrementProductView();
}, [product]);

setDisplayedProduct(product);
setDisplayedTotalComments(totalComments);
}, [product, totalComments]);

return (
<>
<ProductSummary user={user} {...product} />
<ProductAttributes user={user} {...product} />
<ProductSummary user={user} {...displayedProduct} />
<ProductAttributes user={user} {...displayedProduct} />
<AddCommentToProduct
user={user}
product={displayedProduct}
handleNewComment={handleNewComment}
/>
{displayedTotalComments > 0 && (
<CommentPagination productId={product._id} totalPages={displayedTotalComments} />
)}
</>
);
}

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;

0 comments on commit 094f116

Please sign in to comment.