Skip to content

Commit 4379b0c

Browse files
authored
Video 24 rate review products (#59)
* update readme * Video-24-Rate-Review-Products
1 parent c9a6cb8 commit 4379b0c

18 files changed

Lines changed: 884 additions & 467 deletions

File tree

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,75 @@ Admin should be able to define products and update the count in stock whenever t
114114
In this part, we implement the checkout wizard including sign in, shipping info, payment method, and place order.
115115
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l8w3g9mc3ccijt70wpf3.png)
116116

117+
## Only On Udemy
118+
119+
Following parts are on my udemy course. [Get it by 90% discount](https://www.udemy.com/course/build-ecommerce-website-like-amazon-react-node-mongodb/?couponCode=BASIR1)
120+
121+
### Part 18- Order Details Screen
122+
123+
It shows all details about an order includeing shipping, payments and order items. Also it is possible for admin to manage orders like set them as delivered.
124+
125+
### Part 19- Connect to PayPal
126+
127+
This parts create PaypalButton component to show paypal payment button on the screen.
128+
when users click on it, they will be redirected to paypal website to make the payment.
129+
after payment users will be redirected to details page of the order.
130+
131+
### Part 20- Manage Order Screen
132+
133+
This is an admin page to manage list of orders. Admin can delete an order or set it as delivered.
134+
135+
### Part 21- User Profile Screen
136+
137+
When user click on thier name on the header menu, this page appears. It consists of two sections. First an profile update form and second order history.
138+
139+
### Part 22- Filter and Sort Products
140+
141+
In the home page, right after header, there is a filter bar to filter products based on their name and description. also it is possible to sort product based on prices and arrivals.
142+
143+
### Part 23- Deploy Website on Heroku
144+
145+
This section explains all steps to publish the ecommerce website on heroku. first you need to create a cloud mongodb and the make an account on heroku.
146+
147+
### Part 24- Rate and Review Products
148+
149+
This part shows list of reviews by users for each products. also it provides a form to enter rating and review for every single product. also it update the avg rating of each product by user ratings.
150+
151+
1. index.html
152+
2. link fontawesome
153+
3. Rating.js
154+
4. create stars based on props.value
155+
5. show text based on props.text
156+
6. index.css
157+
7. style rating, span color gold and last span to gray, link text to blue
158+
8. HomeScreen.js
159+
9. use Rating component
160+
10. ProductScreen.js
161+
11. use Rating component, wrap it in anchor#reviews
162+
12. list reviews after product details
163+
13. create new review form to get rating and reviews
164+
14. index.css
165+
15. style reviews
166+
16. ProductScreen.js
167+
17. implement submitHandler
168+
18. productActions.js
169+
19. create saveProductReview(productId, review)
170+
20. productConstants.js
171+
21. create product review constants
172+
22. productReducers.js
173+
23. create productReviewSaveReducer
174+
24. store.js
175+
25. add productReviewSaveReducer
176+
26. backend
177+
27. productRoute.js
178+
28. router.post('/:id/reviews')
179+
29. save review in product.reviews
180+
30. update avg rating
181+
182+
### Part 25- Upload images
183+
184+
Admin shoud be able to uploads photos from their computer. This section teaches this feature.
185+
117186
## Summary
118187

119188
In this tutorial, we have made an eCommerce website like Amazon. Feel free to change this project based on your needs and add it to your portfolio.

backend/models/productModel.js

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

3+
const reviewSchema = new mongoose.Schema(
4+
{
5+
name: { type: String, required: true },
6+
rating: { type: Number, default: 0 },
7+
comment: { type: String, required: true },
8+
},
9+
{
10+
timestamps: true,
11+
}
12+
);
313
const prodctSchema = new mongoose.Schema({
414
name: { type: String, required: true },
515
image: { type: String, required: true },
@@ -10,8 +20,9 @@ const prodctSchema = new mongoose.Schema({
1020
description: { type: String, required: true },
1121
rating: { type: Number, default: 0, required: true },
1222
numReviews: { type: Number, default: 0, required: true },
23+
reviews: [reviewSchema],
1324
});
1425

15-
const productModel = mongoose.model("Product", prodctSchema);
26+
const productModel = mongoose.model('Product', prodctSchema);
1627

17-
export default productModel;
28+
export default productModel;

backend/routes/productRoute.js

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,58 @@ import { isAuth, isAdmin } from '../util';
44

55
const router = express.Router();
66

7-
router.get("/", async (req, res) => {
7+
router.get('/', async (req, res) => {
88
const category = req.query.category ? { category: req.query.category } : {};
9-
const searchKeyword = req.query.searchKeyword ? {
10-
name: {
11-
$regex: req.query.searchKeyword,
12-
$options: 'i'
13-
}
14-
} : {};
15-
const sortOrder = req.query.sortOrder ?
16-
(req.query.sortOrder === 'lowest' ? { price: 1 } : { price: -1 })
17-
:
18-
{ _id: -1 };
19-
const products = await Product.find({ ...category, ...searchKeyword }).sort(sortOrder);
9+
const searchKeyword = req.query.searchKeyword
10+
? {
11+
name: {
12+
$regex: req.query.searchKeyword,
13+
$options: 'i',
14+
},
15+
}
16+
: {};
17+
const sortOrder = req.query.sortOrder
18+
? req.query.sortOrder === 'lowest'
19+
? { price: 1 }
20+
: { price: -1 }
21+
: { _id: -1 };
22+
const products = await Product.find({ ...category, ...searchKeyword }).sort(
23+
sortOrder
24+
);
2025
res.send(products);
2126
});
2227

23-
router.get("/:id", async (req, res) => {
28+
router.get('/:id', async (req, res) => {
2429
const product = await Product.findOne({ _id: req.params.id });
2530
if (product) {
2631
res.send(product);
2732
} else {
28-
res.status(404).send({ message: "Product Not Found." });
33+
res.status(404).send({ message: 'Product Not Found.' });
2934
}
3035
});
31-
32-
router.put("/:id", isAuth, isAdmin, async (req, res) => {
36+
router.post('/:id/reviews', isAuth, async (req, res) => {
37+
const product = await Product.findById(req.params.id);
38+
if (product) {
39+
const review = {
40+
name: req.body.name,
41+
rating: Number(req.body.rating),
42+
comment: req.body.comment,
43+
};
44+
product.reviews.push(review);
45+
product.numReviews = product.reviews.length;
46+
product.rating =
47+
product.reviews.reduce((a, c) => c.rating + a, 0) /
48+
product.reviews.length;
49+
const updatedProduct = await product.save();
50+
res.status(201).send({
51+
data: updatedProduct.reviews[updatedProduct.reviews.length - 1],
52+
message: 'Review saved successfully.',
53+
});
54+
} else {
55+
res.status(404).send({ message: 'Product Not Found' });
56+
}
57+
});
58+
router.put('/:id', isAuth, isAdmin, async (req, res) => {
3359
const productId = req.params.id;
3460
const product = await Product.findById(productId);
3561
if (product) {
@@ -42,25 +68,25 @@ router.put("/:id", isAuth, isAdmin, async (req, res) => {
4268
product.description = req.body.description;
4369
const updatedProduct = await product.save();
4470
if (updatedProduct) {
45-
return res.status(200).send({ message: 'Product Updated', data: updatedProduct });
71+
return res
72+
.status(200)
73+
.send({ message: 'Product Updated', data: updatedProduct });
4674
}
4775
}
4876
return res.status(500).send({ message: ' Error in Updating Product.' });
49-
5077
});
5178

52-
router.delete("/:id", isAuth, isAdmin, async (req, res) => {
79+
router.delete('/:id', isAuth, isAdmin, async (req, res) => {
5380
const deletedProduct = await Product.findById(req.params.id);
5481
if (deletedProduct) {
5582
await deletedProduct.remove();
56-
res.send({ message: "Product Deleted" });
83+
res.send({ message: 'Product Deleted' });
5784
} else {
58-
res.send("Error in Deletion.");
85+
res.send('Error in Deletion.');
5986
}
6087
});
6188

62-
63-
router.post("/", isAuth, isAdmin, async (req, res) => {
89+
router.post('/', isAuth, isAdmin, async (req, res) => {
6490
const product = new Product({
6591
name: req.body.name,
6692
price: req.body.price,
@@ -74,10 +100,11 @@ router.post("/", isAuth, isAdmin, async (req, res) => {
74100
});
75101
const newProduct = await product.save();
76102
if (newProduct) {
77-
return res.status(201).send({ message: 'New Product Created', data: newProduct });
103+
return res
104+
.status(201)
105+
.send({ message: 'New Product Created', data: newProduct });
78106
}
79107
return res.status(500).send({ message: ' Error in Creating Product.' });
80-
})
81-
108+
});
82109

83-
export default router;
110+
export default router;

backend/routes/userRoute.js

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,36 @@ router.put('/:id', isAuth, async (req, res) => {
1717
name: updatedUser.name,
1818
email: updatedUser.email,
1919
isAdmin: updatedUser.isAdmin,
20-
token: getToken(updatedUser)
20+
token: getToken(updatedUser),
2121
});
2222
} else {
23-
res.status(404).send({ msg: 'User Not Found' });
23+
res.status(404).send({ message: 'User Not Found' });
2424
}
25-
2625
});
2726

2827
router.post('/signin', async (req, res) => {
29-
3028
const signinUser = await User.findOne({
3129
email: req.body.email,
32-
password: req.body.password
30+
password: req.body.password,
3331
});
3432
if (signinUser) {
3533
res.send({
3634
_id: signinUser.id,
3735
name: signinUser.name,
3836
email: signinUser.email,
3937
isAdmin: signinUser.isAdmin,
40-
token: getToken(signinUser)
38+
token: getToken(signinUser),
4139
});
42-
4340
} else {
44-
res.status(401).send({ msg: 'Invalid Email or Password.' });
41+
res.status(401).send({ message: 'Invalid Email or Password.' });
4542
}
46-
4743
});
4844

4945
router.post('/register', async (req, res) => {
5046
const user = new User({
5147
name: req.body.name,
5248
email: req.body.email,
53-
password: req.body.password
49+
password: req.body.password,
5450
});
5551
const newUser = await user.save();
5652
if (newUser) {
@@ -59,27 +55,26 @@ router.post('/register', async (req, res) => {
5955
name: newUser.name,
6056
email: newUser.email,
6157
isAdmin: newUser.isAdmin,
62-
token: getToken(newUser)
63-
})
58+
token: getToken(newUser),
59+
});
6460
} else {
65-
res.status(401).send({ msg: 'Invalid User Data.' });
61+
res.status(401).send({ message: 'Invalid User Data.' });
6662
}
63+
});
6764

68-
})
69-
70-
router.get("/createadmin", async (req, res) => {
65+
router.get('/createadmin', async (req, res) => {
7166
try {
7267
const user = new User({
7368
name: 'Basir',
7469
email: 'basir.jafarzadeh@gmail.com',
7570
password: '1234',
76-
isAdmin: true
71+
isAdmin: true,
7772
});
7873
const newUser = await user.save();
7974
res.send(newUser);
8075
} catch (error) {
81-
res.send({ msg: error.message });
76+
res.send({ message: error.message });
8277
}
8378
});
8479

85-
export default router;
80+
export default router;

backend/util.js

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import jwt from 'jsonwebtoken';
22
import config from './config';
33
const getToken = (user) => {
4-
return jwt.sign({
5-
_id: user._id,
6-
name: user.name,
7-
email: user.email,
8-
isAdmin: user.isAdmin,
9-
10-
}, config.JWT_SECRET, {
11-
expiresIn: '48h'
12-
})
13-
}
4+
return jwt.sign(
5+
{
6+
_id: user._id,
7+
name: user.name,
8+
email: user.email,
9+
isAdmin: user.isAdmin,
10+
},
11+
config.JWT_SECRET,
12+
{
13+
expiresIn: '48h',
14+
}
15+
);
16+
};
1417

1518
const isAuth = (req, res, next) => {
1619
const token = req.headers.authorization;
@@ -19,25 +22,23 @@ const isAuth = (req, res, next) => {
1922
const onlyToken = token.slice(7, token.length);
2023
jwt.verify(onlyToken, config.JWT_SECRET, (err, decode) => {
2124
if (err) {
22-
return res.status(401).send({ msg: 'Invalid Token' });
25+
return res.status(401).send({ message: 'Invalid Token' });
2326
}
2427
req.user = decode;
2528
next();
26-
return
29+
return;
2730
});
2831
} else {
29-
return res.status(401).send({ msg: "Token is not supplied." });
32+
return res.status(401).send({ message: 'Token is not supplied.' });
3033
}
31-
}
34+
};
3235

3336
const isAdmin = (req, res, next) => {
34-
console.log(req.user)
37+
console.log(req.user);
3538
if (req.user && req.user.isAdmin) {
3639
return next();
3740
}
38-
return res.status(401).send({ msg: 'Admin Token is not valid.' })
39-
}
41+
return res.status(401).send({ message: 'Admin Token is not valid.' });
42+
};
4043

41-
export {
42-
getToken, isAuth, isAdmin
43-
}
44+
export { getToken, isAuth, isAdmin };

frontend/public/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
work correctly both with client-side routing and a non-root public URL.
2525
Learn how to configure a non-root public URL by running `npm run build`.
2626
-->
27+
<link
28+
rel="stylesheet"
29+
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
30+
/>
2731
<title>React App</title>
2832
</head>
2933
<body>

0 commit comments

Comments
 (0)