Skip to content

Commit 434be8c

Browse files
committed
Video-47-Review-Products
1 parent 487bb14 commit 434be8c

File tree

6 files changed

+178
-3
lines changed

6 files changed

+178
-3
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,8 @@ $ npm start
512512
7. Update index.js to render aside 9.
513513
8. call getCategories
514514
9. create getCategories in api.js
515+
47. Review Products
516+
1. create review model
517+
2. create review form
518+
3. create review api
519+
4. style review form

backend/models/productModel.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
import mongoose from 'mongoose';
22

3+
const reviewSchema = new mongoose.Schema(
4+
{
5+
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
6+
name: { type: String, required: true },
7+
rating: {
8+
type: Number,
9+
required: true,
10+
default: 0,
11+
min: 0,
12+
max: 5,
13+
},
14+
comment: { type: String, required: true },
15+
},
16+
{ timestamps: true }
17+
);
18+
319
const productSchema = new mongoose.Schema(
420
{
521
name: { type: String, required: true },
@@ -11,6 +27,7 @@ const productSchema = new mongoose.Schema(
1127
countInStock: { type: Number, default: 0, required: true },
1228
rating: { type: Number, default: 0.0, required: true },
1329
numReviews: { type: Number, default: 0, required: true },
30+
reviews: [reviewSchema],
1431
},
1532
{ timestamps: true }
1633
);

backend/routers/productRouter.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,32 @@ productRouter.delete(
9090
})
9191
);
9292

93+
productRouter.post(
94+
'/:id/reviews',
95+
isAuth,
96+
expressAsyncHandler(async (req, res) => {
97+
const product = await Product.findById(req.params.id);
98+
if (product) {
99+
const review = {
100+
rating: req.body.rating,
101+
comment: req.body.comment,
102+
user: req.user._id,
103+
name: req.user.name,
104+
};
105+
product.reviews.push(review);
106+
product.rating =
107+
product.reviews.reduce((a, c) => c.rating + a, 0) /
108+
product.reviews.length;
109+
product.numReviews = product.reviews.length;
110+
const updatedProduct = await product.save();
111+
res.status(201).send({
112+
message: 'Comment Created.',
113+
data: updatedProduct.reviews[updatedProduct.reviews.length - 1],
114+
});
115+
} else {
116+
throw Error('Product does not exist.');
117+
}
118+
})
119+
);
120+
93121
export default productRouter;

frontend/src/api.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ export const createProduct = async () => {
6161
return { error: err.response.data.message || err.message };
6262
}
6363
};
64+
65+
export const createReview = async (productId, review) => {
66+
try {
67+
const { token } = getUserInfo();
68+
const response = await axios({
69+
url: `${apiUrl}/api/products/${productId}/reviews`,
70+
method: 'POST',
71+
headers: {
72+
'Content-Type': 'application/json',
73+
Authorization: `Bearer ${token}`,
74+
},
75+
data: review,
76+
});
77+
if (response.statusText !== 'Created') {
78+
throw new Error(response.data.message);
79+
}
80+
return response.data;
81+
} catch (err) {
82+
return { error: err.response.data.message || err.message };
83+
}
84+
};
85+
6486
export const deleteProduct = async (productId) => {
6587
try {
6688
const { token } = getUserInfo();

frontend/src/srceens/ProductScreen.js

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
1-
import { parseRequestUrl, showLoading, hideLoading } from '../utils';
2-
import { getProduct } from '../api';
1+
import {
2+
parseRequestUrl,
3+
showLoading,
4+
hideLoading,
5+
showMessage,
6+
rerender,
7+
} from '../utils';
8+
import { createReview, getProduct } from '../api';
39
import Rating from '../components/Rating';
10+
import { getUserInfo } from '../localStorage';
411

512
const ProductScreen = {
613
after_render: () => {
714
const request = parseRequestUrl();
815
document.getElementById('add-button').addEventListener('click', () => {
916
document.location.hash = `/cart/${request.id}`;
1017
});
18+
19+
if (document.getElementById('review-form')) {
20+
document
21+
.getElementById('review-form')
22+
.addEventListener('submit', async (e) => {
23+
e.preventDefault();
24+
showLoading();
25+
const data = await createReview(request.id, {
26+
comment: document.getElementById('comment').value,
27+
rating: document.getElementById('rating').value,
28+
});
29+
hideLoading();
30+
if (data.error) {
31+
showMessage(data.error);
32+
} else {
33+
showMessage('Review Added Successfully', () => {
34+
rerender(ProductScreen);
35+
});
36+
}
37+
});
38+
}
1139
},
1240
render: async () => {
1341
const request = parseRequestUrl();
@@ -17,6 +45,7 @@ const ProductScreen = {
1745
return `<div>${product.error}</div>`;
1846
}
1947
hideLoading();
48+
const userInfo = getUserInfo();
2049
return `
2150
<div class="content">
2251
<div class="back-to-result">
@@ -66,6 +95,68 @@ const ProductScreen = {
6695
</ul>
6796
</div>
6897
</div>
98+
<div class="content">
99+
<h2>Reviews</h2>
100+
${product.reviews.length === 0 ? `<div>There is no review.</div>` : ''}
101+
<ul class="review">
102+
${product.reviews
103+
.map(
104+
(review) =>
105+
`<li>
106+
<div><b>${review.name}</b></div>
107+
<div class="rating-container">
108+
${Rating.render({
109+
value: review.rating,
110+
})}
111+
<div>
112+
${review.createdAt.substring(0, 10)}
113+
</div>
114+
</div>
115+
<div>
116+
${review.comment}
117+
</div>
118+
</li>`
119+
)
120+
.join('\n')}
121+
122+
<li>
123+
124+
${
125+
userInfo.name
126+
? `
127+
<div class="form-container">
128+
<form id="review-form">
129+
<ul class="form-items">
130+
<li> <h3>Write a customer reviews</h3></li>
131+
<li>
132+
<label for="rating">Rating</label>
133+
<select required name="rating" id="rating">
134+
<option value="">Select</option>
135+
<option value="1">1 = Poor</option>
136+
<option value="2">2 = Fair</option>
137+
<option value="3">3 = Good</option>
138+
<option value="4">4 = Very Good</option>
139+
<option value="5">5 = Excellent</option>
140+
</select>
141+
</li>
142+
<li>
143+
<label for="comment">Comment</label>
144+
<textarea required name="comment" id="comment" ></textarea>
145+
</li>
146+
<li>
147+
<button type="submit" class="primary">Submit</button>
148+
</li>
149+
</ul>
150+
</form>
151+
</div>`
152+
: ` <div>
153+
Please <a href="/#/signin">Signin</a> to write a review.
154+
</div>`
155+
}
156+
</li>
157+
</ul>
158+
159+
</div>
69160
</div>`;
70161
},
71162
};

frontend/style.css

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ button {
2020
cursor: pointer;
2121
}
2222
input,
23-
button {
23+
button,
24+
select,
25+
textarea {
2426
font: 1.6rem helvetica;
2527
padding: 1rem;
2628
border: 0.1rem #808080 solid;
@@ -549,3 +551,13 @@ ul.categories {
549551
color: #ffffff;
550552
cursor: pointer;
551553
}
554+
/* review */
555+
556+
.review {
557+
list-style-type: none;
558+
padding: 0;
559+
}
560+
.review li {
561+
margin-bottom: 1rem;
562+
padding-bottom: 1rem;
563+
}

0 commit comments

Comments
 (0)