Product Review Platform using MEAN Stack
In today’s digital age, online reviews play an important role in shaping consumer decisions. Whether it’s choosing a restaurant, purchasing a gadget, or booking a hotel, people often rely on the experiences and opinions shared by others. In this article, we’ll explore how to create a dynamic and user-friendly product review platform using the MEAN stack – MongoDB, Express.js, Angular, and Node.js.
Output Preview: Let us have a look at how the final output will look like.
Prerequisites:
Approach to create Product Review Platform:
Backend:
- Set up a new node.js project
- Create server.js file to setup the server using express using cors as the middleware
- Create app instance
- Create routes folder and setup routes for servicing API requests
- Create controllers folder to set up the methods for declared routes
- Create models folder to define the database schemas
- Set up mongo db in your system and connect to it in server.js
- Create a database product-review and three collections
- Products
- Reviews
- Users
- Implement the logic for following operations in respective controllers –
- Create, Update, Delete Products
- Create, Update, Delete Reviews
- Register new user, Login existing user
- Test your API endpoints using postman
Frontend:
- Create a new Angular project
- Create four components and define HTML, CSS and typescript files for all of them
- Product Component
- Review Component
- User Component
- Star Component
- Create a service to set up communication between server API endpoints and angular HttpClient
- Test you application in browser
Steps to create the Backend:
Step 1: Create the main folder for project which will contain files for both frontend and backend.
mkdir product-review
Step 2: Initialize the node js project
npm init -y
Step 3: Install the required dependencies
npm install express cors nodemon mongoose jsonwebtoken bcryptjs
The updated dependencies in package.json file of backend will look like:
{
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.2.1",
"nodemon": "^3.1.0"
}
Project Structure (Backend):
Example: Create the required files as seen on the project structure and add the following codes.
// authController.js
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
exports.register = async (req, res) => {
try {
const { username, email, password } = req.body;
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
message: "User Already Exist",
success: false
});
}
user = new User({
username: username,
email: email,
password: password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const token = generateJwtToken(user.id);
res.status(201).json({
success: true,
token: token,
message: "User registered successfully"
});
}
catch (error) {
res.status(500).json({
message: "Server error! New user registration failed",
success: false
});
}
};
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({
message: "Invalid credentials",
success: false
});
}
const isMatched = await bcrypt.compare(password, user.password);
if (!isMatched) {
return res.status(400).json({
message: "Invalid credentials",
success: false
});
}
const token = generateJwtToken(user.id);
res.status(200).json({
success: true,
message: "User logged in successfully",
token: token
});
}
catch (error) {
return res.status(500).json({
success: false,
message: "Internal Server Error, Login unsuccessful"
});
}
};
function generateJwtToken(userID) {
const payload = {
user: {
id: userID
}
};
return jwt.sign(payload, 'jwtSecret', { expiresIn: 3600 });
}
exports.getUserDetailsFronUserId = async (req, res) => {
try {
const { id } = req.params;
const user = await User.findById(id);
return res.status(200).json(user);
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
// productController.js
const Product = require('../models/Product');
const jwt = require('jsonwebtoken');
const secretKey = 'jwtSecret';
exports.getProductDetaisFromId = async (req, res) => {
try {
const { id } = req.params;
const product = await Product.findById(id);
return res.status(200).json(product);
}
catch (error) {
return res.status(500).json({
success: false,
message: error.message
});
}
};
exports.getAllProducts = async (req, res) => {
let userId;
try {
jwt.verify(req.headers['authorization'].substring(7), secretKey, (error, decodedToken) => {
if (error) {
res.status(401).json({
success: false,
message: error.message
});
}
else {
userId = decodedToken.user.id;
}
});
const products = await Product.find();
res.status(200).json(products);
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.getProductById = async (req, res) => {
try {
const id = req.params.id;
const product = await Product.findById(id);
res.status(200).json(product);
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.createProduct = async (req, res) => {
try {
let product = {};
const { productName, productDescription, productCategory, productPrice, productImageUrl } = req.body;
if (req.headers['authorization'] && req.headers['authorization'].startsWith('Bearer ')) {
jwt.verify(req.headers['authorization'].substring(7), secretKey, (error, decodedToken) => {
if (error) {
res.status(401).json({
success: false,
message: error.message
});
}
else {
product = new Product({
user: decodedToken.user.id,
productName,
productDescription,
productCategory,
productPrice,
productImageUrl,
numberOfReviews: 0,
reviews: []
});
}
});
await product.save();
res.status(200).json({
success: true,
product: product
});
}
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.updateProduct = async (req, res) => {
try {
const { id } = req.params;
const { productName, productDescription, productCategory, productPrice, productImageUrl, numberOfReviews, reviews } = req.body;
const product = await Product.findByIdAndUpdate(id, {
productName,
productDescription,
productCategory,
productPrice,
productImageUrl,
numberOfReviews,
reviews
}, { new: true });
res.status(201).json({
success: true,
product: product
});
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.deleteProduct = async (req, res) => {
try {
const { id } = req.params;
await Product.findByIdAndDelete(id);
res.status(200).json({
success: true,
message: "Product Deleted Successfully"
});
}
catch (error) {
res.status(500).json({
success: false,
message: "Error while deleting product"
});
}
};
// reviewController.js
const Review = require('../models/Review');
const jwt = require('jsonwebtoken');
const secretKey = 'jwtSecret';
exports.getAllReviews = async (req, res) => {
let userId;
let productId;
try {
jwt.verify(req.headers['authorization'].substring(7), secretKey, (error, decodedToken) => {
if (error) {
res.status(401).json({
success: false,
message: error.message
});
}
else {
userId = decodedToken.user.id;
productId = req.headers['productid'];
}
});
const reviews = await Review.find({ product: productId });
res.status(200).json(reviews);
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.getReviewById = async (req, res) => {
try {
const id = req.params.id;
const review = await Review.findById(id);
res.status(200).json(review);
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.addReview = async (req, res) => {
try {
let review = {};
const { productId, rating, comment } = req.body;
if (req.headers['authorization'] && req.headers['authorization'].startsWith('Bearer ')) {
jwt.verify(req.headers['authorization'].substring(7), secretKey, (error, decodedToken) => {
if (error) {
res.status(401).json({
success: false,
message: "Error while decoding token"
});
}
else {
review = new Review({
user: decodedToken.user.id,
product: productId,
rating: rating,
comment: comment
});
}
});
await review.save();
res.status(200).json(review);
}
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.editReview = async (req, res) => {
try {
let review = {};
const { id } = req.params;
const { productId, rating, comment } = req.body;
if (req.headers['authorization'] && req.headers['authorization'].startsWith('Bearer ')) {
jwt.verify(req.headers['authorization'].substring(7), secretKey, async (error, decodedToken) => {
if (error) {
res.status(401).json({
success: false,
message: "Error while decoding token"
});
}
else {
review = await Review.findByIdAndUpdate(id,
{
user: decodedToken.user.id,
product: productId,
rating: rating,
comment: comment
}, { new: true });
}
});
res.status(201).json(review);
}
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
exports.deleteReview = async (req, res) => {
try {
const { id } = req.params;
await Review.findByIdAndDelete(id);
res.status(200).json({
success: true,
message: "Review deleted successfully"
});
}
catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
};
// productModel.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: true
},
productName: {
type: String,
required: [
true, "Please enter Product name"
]
},
productDescription: {
type: String,
required: [
true, "Please enter product description"
]
},
productCategory: {
type: String,
required: [
true, "Please enter product category"
]
},
productPrice: {
type: Number,
required: [
true, "Please enter product price"
],
maxLength: [8, "Price cannnot exceed length of 8"]
},
productImageUrl: {
type: String,
required: true
},
numberOfReviews: {
type: Number,
dedfault: 0
},
reviews: [
{
review: {
type: mongoose.Schema.ObjectId,
ref: "Review"
}
}
],
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Product', productSchema);
// reviewModel.js
const mongoose = require('mongoose');
const reviewSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: true
},
product: {
type: mongoose.Schema.ObjectId,
ref: "Product",
required: true
},
rating: {
type: Number,
required: true
},
comment: {
type: String,
required: true
}
});
module.exports = mongoose.model("Review", reviewSchema);
// userModel.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true,
uniques: true
},
password: {
type: String,
required: true
}
});
module.exports = mongoose.model('User', userSchema);
// productRoutes.js
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
router.get('/getProductInfo/:id', productController.getProductDetaisFromId);
router.get('/getAllProducts', productController.getAllProducts);
router.get('/getProductById/:id', productController.getProductById);
router.post('/createProduct', productController.createProduct);
router.put('/updateProduct/:id', productController.updateProduct);
router.delete('/deleteProduct/:id', productController.deleteProduct);
module.exports = router;
// reviewRoutes.js
const express = require('express');
const router = express.Router();
const reviewController = require('../controllers/reviewController');
router.get('/getAllReviews', reviewController.getAllReviews);
router.get('/getReviewById/:id', reviewController.getReviewById);
router.post('/addReview', reviewController.addReview);
router.put('/editReview/:id', reviewController.editReview);
router.delete('/deleteReview/:id', reviewController.deleteReview);
module.exports = router;
// authRoutes.js
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
router.post('/register', authController.register);
router.post('/login', authController.login);
router.get('/:id', authController.getUserDetailsFronUserId);
module.exports = router;
// server.js
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const authRoutes = require('../backend/routes/authRoutes');
const productRoutes = require('../backend/routes/productRoutes');
const reviewRoutes = require('../backend/routes/reviewRoutes');
const app = express();
app.use(cors());
app.use(express.json());
mongoose.connect('mongodb://localhost:27017/product-review-forum', {
family: 4
})
.then (() => console.log("Mongo DB connected"))
.catch(err => console.log(err));
app.use('/api/auth', authRoutes);
app.use('/api/product', productRoutes);
app.use('/api/review', reviewRoutes);
const PORT = 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
To start the backend run the following command:
nodemon server.js
Step 4: Install the angular CLI
npm install -g @angular/cli
Step 5: Create a new angular project using the following command
ng new frontend
Step 6: Create components in angular for different functionality
ng generate component <component-name>
ng generate component product
ng generate component review
ng generate component star
ng generate component user
Step 7: Create service for communication between backend and frontend
ng generate service <service-name>
Step 8: Create model
ng generate interface <model-name>
The updated dependencies in package.json file of frontend will look like:
"dependencies": {
"@angular/animations": "^17.2.0",
"@angular/common": "^17.2.0",
"@angular/compiler": "^17.2.0",
"@angular/core": "^17.2.0",
"@angular/forms": "^17.2.0",
"@angular/platform-browser": "^17.2.0",
"@angular/platform-browser-dynamic": "^17.2.0",
"@angular/platform-server": "^17.2.0",
"@angular/router": "^17.2.0",
"@angular/ssr": "^17.2.3",
"@auth0/angular-jwt": "^5.2.0",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}
Project Structure (Frontend):
Example: Create the required files as seen in folder structure and add the codes.
<!-- product.component.html -->
<div class="container">
<a class="link-button" (click)="showAddFormFunction()">
Add New Product</a>
<h2>Products</h2>
<!-- Search bar -->
<div class="search-bar">
<input type="text" [(ngModel)]="searchQuery"
placeholder="Search by name" />
<button (click)="applyFilter()"
class="link-button-search">Search</button>
<button (click)="clearFilter()"
class="link-button-clear">Clear</button>
</div>
<div class="search-bar">
<input type="text" [(ngModel)]="searchQueryCategory"
placeholder="Search by category" />
<button (click)="applyFilter()"
class="link-button-search">Search</button>
<button (click)="clearFilterCategory()"
class="link-button-clear">
Clear
</button>
</div>
<div *ngIf="displayedProductList.length > 0;
else noProducts" class="table-container">
<table class="activity-table">
<thead>
<tr>
<th>Product Name</th>
<th>Product Description</th>
<th>Product Category</th>
<th>Product Price</th>
<th>Product Image</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of displayedProductList">
<td>{{ product.productName }}</td>
<td>{{ product.productDescription }}</td>
<td>{{ product.productCategory }}</td>
<td>{{ product.productPrice }}</td>
<td><img src="{{ product.productImageUrl }}" /></td>
<td>
<button class="btn"
(click)="getProductById(product._id)">
Get Product
</button>
<button class="btn"
(click)="populateUpdateForm(product)"
*ngIf="product.user === getUserId()">
Update
</button>
<button class="btn delete-btn"
(click)="confirmDelete(product._id)"
*ngIf="product.user === getUserId()">
Delete
</button>
<button class="btn"
(click)="getReviews(product)">
Get Reviews
</button>
</td>
</tr>
</tbody>
</table>
</div>
<ng-template #noProducts>
<div class="no-activities-container">
<p>No Products available</p>
</div>
</ng-template>
<div class="update-form-container"
*ngIf="showAddForm && !showUpdateForm">
<button class="close-button"
(click)="closeAddForm()">X</button>
<h2>Add Product</h2>
<form class="update-form"
(ngSubmit)="createProduct()">
<div>
<label for="productName">Product Name:</label>
<input type="text" id="productName" name="productName"
[(ngModel)]="productCreated.productName"
required />
</div>
<div>
<label for="productDescription">
Product Description:
</label>
<input type="text" id="productDescription"
name="productDescription"
[(ngModel)]="productCreated.productDescription"
required />
</div>
<div>
<label for="productCategory">Product Category:</label>
<input type="text"
id="productCategory"
name="productCategory"
[(ngModel)]="productCreated.productCategory"
required />
</div>
<div>
<label for="productPrice">Product Price:</label>
<input type="number"
id="productPrice"
name="productPrice"
[(ngModel)]="productCreated.productPrice"
required />
</div>
<div>
<label for="productImageUrl">Product Image Url:</label>
<input type="text"
id="productImageUrl"
name="v"
[(ngModel)]="productCreated.productImageUrl"
required />
<div *ngIf="productCreated && productCreated.productImageUrl">
<img src="{{ productCreated.productImageUrl }}" />
</div>
</div>
<div>
<button type="submit">Add Product</button>
<button type="button"
(click)="resetForm()">Clear</button>
</div>
</form>
</div>
<div *ngIf="showUpdateForm" class="update-form-container">
<button class="close-button"
(click)="cancelUpdate()">X</button>
<h2>Update Product</h2>
<form class="update-form"
(ngSubmit)="updateProduct(productUpdated._id)">
<div>
<label for="updateProductName">Product Name:</label>
<input type="text" id="updateProductName"
name="updateProductName"
[(ngModel)]="productUpdated.productName" required />
</div>
<div>
<label for="updateProductDescription">Product Description:</label>
<input type="text" id="updateProductDescription"
name="updateProductDescription"
[(ngModel)]="productUpdated.productDescription" required />
</div>
<div>
<label for="updateProductCategory">Product Category:</label>
<input type="text" id="updateProductCategory"
name="updateProductCategory"
[(ngModel)]="productUpdated.productCategory" required />
</div>
<div>
<label for="updateProductPrice">Steps:</label>
<input type="number" id="updateProductPrice"
name="updateProductPrice"
[(ngModel)]="productUpdated.productPrice" required />
</div>
<div>
<label for="updateProductImageUrl">Product Image Url:</label>
<input type="text" id="updateProductImageUrl"
name="updateProductImageUrl"
[(ngModel)]="productUpdated.productImageUrl" required />
<div><img src="{{ productUpdated.productImageUrl }}" /></div>
</div>
<div>
<button type="submit">Update Product</button>
<button type="button"
(click)="cancelUpdate()">Cancel</button>
</div>
</form>
</div>
<div *ngIf="!showUpdateForm && productById._id"
class="view-activity-container">
<button class="close-button" (click)="closeView()">X</button>
<h2>View Product</h2>
<form class="view-activity-form">
<div>
<label for="productName">Product Name:</label>
<input type="text" id="productName"
name="productName" [(ngModel)]="productById.productName" readonly />
</div>
<div>
<label for="productDescription">Product Description:</label>
<input type="text" id="productDescription" name="productDescription"
[(ngModel)]="productById.productDescription" readonly />
</div>
<div>
<label for="productCategory">Product Category:</label>
<input type="text" id="productCategory"
name="productCategory" [(ngModel)]="productById.productCategory"
readonly />
</div>
<div>
<label for="productPrice">Product Price:</label>
<input type="number" id="productPrice"
name="productPrice" [(ngModel)]="productById.productPrice"
readonly />
</div>
<div>
<label for="productImageUrl">Product Image:</label>
<div><img src="{{ productById.productImageUrl }}" /></div>
</div>
<div>
<a href="/getAllProducts" class="link-button"
(click)="closeView()">Get All Products</a>
</div>
</form>
</div>
</div>
<!-- review.component.html -->
<div class="container">
<a class="link-button" (click)="showAddFormFunction()">Add New Review</a>
<h2>Reviews</h2>
<div *ngIf="displayedReviewList.length > 0;
else noReviews" class="table-container">
<h3>User Name - {{ username }}</h3>
<h3>Email ID - {{ email }}</h3>
<h3>Product Name - {{ product.productName }}</h3>
<h3>Product Category - {{ product.productCategory }}</h3>
<div>
<h3>Product Image -</h3>
<img src="{{ product.productImageUrl }}" />
</div>
<table class="activity-table">
<thead>
<tr>
<th>Rating</th>
<th>Comment</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let review of displayedReviewList">
<td>
{{ review.rating }}
<div>
<span>
<app-star [rating]="review.rating"
[isDisabled]="true"></app-star>
</span>
</div>
</td>
<td>{{ review.comment }}</td>
<td>
<button class="btn" (click)="populateForm(review)"
*ngIf="review.user === getUserId()">
Update
</button>
<button class="btn delete-btn"
(click)="confirmDelete(review._id)"
*ngIf="review.user === getUserId()">
Delete
</button>
<h4 *ngIf="review.user !== getUserId()">
No Update / Delete Actions Allowed
</h4>
</td>
</tr>
</tbody>
</table>
</div>
<ng-template #noReviews>
<div class="no-activities-container">
<p>No reviews available</p>
</div>
</ng-template>
<a class="link-button" href="/getAllProducts">
Go to Product List</a>
<div *ngIf="showReviewEditForm"
class="update-form-container">
<button class="close-button"
(click)="cancelEdit()">X</button>
<h2>Update Review</h2>
<form class="update-form"
(ngSubmit)="editReview(reviewUpdated._id)">
<div>
<label for="username">User Name:</label>
<input type="text" id="username"
name="username" [(ngModel)]="username" readonly />
</div>
<div>
<label for="email">Email:</label>
<input type="text" id="email" name="email"
[(ngModel)]="email" readonly />
</div>
<div>
<label for="productName">Product Name:</label>
<input type="text" id="productName"
name="productName" [(ngModel)]="product.productName" readonly />
</div>
<div>
<label for="productCategory">Product Category:</label>
<input type="text" id="productCategory"
name="productCategory" [(ngModel)]="product.productCategory"
readonly />
</div>
<div>
<label>Product Image:</label>
<img src="{{ product.productImageUrl }}" />
</div>
<div>
<label>Rating:</label>
<div>
<span>
<app-star [rating]="reviewUpdated.rating"
(ratingChanged)="onRatingChanged($event)"></app-star>
</span>
</div>
</div>
<div>
<label for="comment">Comment:</label>
<input type="text" id="comment" name="comment"
[(ngModel)]="reviewUpdated.comment" required />
</div>
<div>
<button type="submit">Update Review</button>
<button type="button" (click)="cancelEdit()">Cancel</button>
</div>
</form>
</div>
<div class="update-form-container"
*ngIf="showReviewAddForm && !showReviewEditForm">
<button class="close-button"
(click)="closeAddForm()">X</button>
<h2>Add Review</h2>
<form class="update-form"
(ngSubmit)="addReview(product.id)">
<div>
<label for="username">User Name:</label>
<input type="text" id="username"
name="username" [(ngModel)]="username" readonly />
</div>
<div>
<label for="email">Email:</label>
<input type="text" id="email" name="email"
[(ngModel)]="email" readonly />
</div>
<div>
<label for="productName">Product Name:</label>
<input type="text" id="productName"
name="productName" [(ngModel)]="product.productName" readonly />
</div>
<div>
<label for="productCategory">Product Category:</label>
<input type="text" id="productCategory"
name="productCategory" [(ngModel)]="product.productCategory"
readonly />
</div>
<div>
<label>Product Image:</label>
<div><img src="{{ product.productImageUrl }}" /></div>
</div>
<div>
<label for="rating">Rating:</label>
<div>
<span>
<app-star [rating]="reviewDataAdded.rating"
(ratingChanged)="onRatingChanged($event)"></app-star>
</span>
</div>
</div>
<div>
<label for="comment">Comment:</label>
<input type="text" id="comment" name="comment"
[(ngModel)]="reviewDataAdded.comment" required />
</div>
<div>
<button type="submit">Add Review</button>
<button type="button" (click)="resetForm()">Clear</button>
</div>
</form>
</div>
</div>
<!-- user.component.html -->
<div class="error-message" *ngIf="errorMessage">
{{ errorMessage }}
</div>
<div class="container" *ngIf="loginActive">
<h2>Login</h2>
<form (ngSubmit)="login()">
<div class="form-group">
<label for="email">Email:</label>
<input
type="email"
class="form-control"
id="email"
name="email"
[(ngModel)]="email"
required
/>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input
type="password"
class="form-control"
id="password"
name="password"
[(ngModel)]="password"
required
/>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
<div class="container" *ngIf="registerActive">
<h2>Register</h2>
<form (submit)="register()">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
id="username"
class="form-control"
[(ngModel)]="username"
name="username"
required
/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
class="form-control"
[(ngModel)]="email"
name="email"
required
/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
class="form-control"
[(ngModel)]="password"
name="password"
required
/>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
<!-- star.component.html -->
<span
class="star"
(click)="rateStar(i)"
[ngClass]="{ filled: i < filledStars }"
*ngFor="let star of stars; let i = index"
>★</span
>
<!-- app.component.html -->
<main class="main">
<div class="content">
<div class="left-side">
<h1>{{ title }}</h1>
<div>
<ul>
<li><a (click)="login()"
*ngIf="!isLoggedIn">Login</a></li>
<li><a (click)="register()"
*ngIf="!isLoggedIn">Register</a></li>
<li><a (click)="logout()"
*ngIf="isLoggedIn">Logout</a></li>
</ul>
</div>
</div>
</div>
</main>
<router-outlet></router-outlet>
/* product.component.css */
.container {
max-width: 100%;
margin: 0 auto;
padding: 20px;
}
.search-bar {
display: flex;
margin-bottom: 10px;
}
.search-bar input {
flex: 1;
margin-right: 10px;
}
.activity-table {
width: 100%;
border-collapse: collapse;
}
.activity-table th,
.activity-table td {
padding: 10px;
border: 1px solid #ccc;
text-align: center;
}
.activity-table th {
background-color: #f0f0f0;
}
.update-form-container,
.view-activity-container {
margin-top: 20px;
padding: 20px;
border: 1px solid #ccc;
}
.update-form,
.view-activity-form {
display: flex;
flex-direction: column;
}
.update-form input,
.view-activity-form input {
margin-bottom: 10px;
}
.btn {
padding: 8px 16px;
margin-right: 10px;
margin-bottom: 1.5vmax;
cursor: pointer;
background-color: #0056b3;
color: #fff;
border: none;
border-radius: 4px;
}
.delete-btn {
background-color: #dc3545;
}
.link-button {
display: inline-block;
padding: 8px 16px;
background-color: #1b599a;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
}
.link-button-search {
display: inline-block;
padding: 8px 16px;
background-color: green;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
border: none;
margin-right: 1vmax;
}
.link-button-clear {
display: inline-block;
padding: 8px 16px;
background-color: #dc3545;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
border: none;
}
.no-activities-container {
margin-top: 20px;
padding: 10px;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 4px;
}
.no-activities-container p {
text-align: center;
margin: 0;
}
.update-form-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
background-color: rgba(255, 255, 255, 1);
padding: 3.7vmax;
border-radius: 5px;
}
.update-form-container h2 {
text-align: center;
font-size: 2rem;
}
.update-form-container .close-btn {
position: absolute;
top: 5px;
right: 5px;
font-size: 18px;
color: #555;
cursor: pointer;
}
.update-form-container .close-btn:hover {
color: #333;
}
.update-form label {
display: block;
margin-bottom: 5px;
}
.update-form input[type="text"],
.update-form input[type="number"],
.update-form input[type="date"] {
width: calc(100% - 12px);
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.update-form button[type="submit"],
.update-form button[type="button"] {
width: 10%;
padding: 10px;
background-color: green;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
}
.update-form button[type="button"] {
background-color: #dc3545;
}
.view-activity-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
background-color: rgba(255, 255, 255, 1);
padding: 3.7vmax;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.view-activity-container h2 {
text-align: center;
font-size: 2rem;
}
.view-activity-form label {
display: block;
margin-bottom: 5px;
}
.view-activity-form input[type="text"],
.view-activity-form input[type="number"],
.view-activity-form input[type="date"] {
width: calc(100% - 12px);
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.view-activity-form button[type="submit"],
.view-activity-form a {
width: 10%;
padding: 10px;
background-color: #0056b3;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
text-align: center;
display: inline-block;
text-decoration: none;
}
.close-button {
position: absolute;
top: 10px;
right: 10px;
padding: 5px 10px;
background-color: #ccc;
border: none;
border-radius: 5px;
cursor: pointer;
}
.close-button:hover {
background-color: #aaa;
}
img {
border-radius: 50%;
width: 2.5vmax;
height: 2.5vmax;
}
/* review.component.css */
.container {
max-width: 100%;
margin: 0 auto;
padding: 20px;
}
.search-bar {
display: flex;
margin-bottom: 10px;
}
.search-bar input {
flex: 1;
margin-right: 10px;
}
.activity-table {
width: 100%;
border-collapse: collapse;
}
.activity-table th,
.activity-table td {
padding: 10px;
border: 1px solid #ccc;
text-align: center;
}
.activity-table th {
background-color: #f0f0f0;
}
.update-form-container,
.view-activity-container {
margin-top: 20px;
padding: 20px;
border: 1px solid #ccc;
}
.update-form,
.view-activity-form {
display: flex;
flex-direction: column;
}
.update-form input,
.view-activity-form input {
margin-bottom: 10px;
}
.btn {
padding: 8px 16px;
margin-right: 10px;
margin-bottom: 1.5vmax;
cursor: pointer;
background-color: #0056b3;
color: #fff;
border: none;
border-radius: 4px;
}
.delete-btn {
background-color: #dc3545;
}
.link-button {
display: inline-block;
padding: 8px 16px;
background-color: #1b599a;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
margin-top: 2vmax;
}
.link-button-search {
display: inline-block;
padding: 8px 16px;
background-color: green;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
border: none;
margin-right: 1vmax;
}
.link-button-clear {
display: inline-block;
padding: 8px 16px;
background-color: #dc3545;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
border: none;
}
.no-activities-container {
margin-top: 20px;
padding: 10px;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 4px;
}
.no-activities-container p {
text-align: center;
margin: 0;
}
/* Update Review */
.update-form-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
background-color: rgba(255, 255, 255, 1);
padding: 3.7vmax;
border-radius: 5px;
}
.update-form-container h2 {
text-align: center;
font-size: 2rem;
}
.update-form-container .close-btn {
position: absolute;
top: 5px;
right: 5px;
font-size: 18px;
color: #555;
cursor: pointer;
}
.update-form-container .close-btn:hover {
color: #333;
}
.update-form label {
display: block;
margin-bottom: 5px;
}
.update-form input[type="text"],
.update-form input[type="number"],
.update-form input[type="date"] {
width: calc(100% - 12px);
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.update-form button[type="submit"],
.update-form button[type="button"] {
width: 20%;
padding: 10px;
background-color: green;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
}
.update-form button[type="button"] {
background-color: #dc3545;
}
/* View Review */
.view-activity-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
background-color: rgba(255, 255, 255, 1);
padding: 3.7vmax;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.view-activity-container h2 {
text-align: center;
font-size: 2rem;
}
.view-activity-form label {
display: block;
margin-bottom: 5px;
}
.view-activity-form input[type="text"],
.view-activity-form input[type="number"],
.view-activity-form input[type="date"] {
width: calc(100% - 12px);
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.view-activity-form button[type="submit"],
.view-activity-form a {
width: 10%;
padding: 10px;
background-color: #0056b3;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
text-align: center;
display: inline-block;
text-decoration: none;
}
.close-button {
position: absolute;
top: 10px;
right: 10px;
padding: 5px 10px;
background-color: #ccc;
border: none;
border-radius: 5px;
cursor: pointer;
}
.close-button:hover {
background-color: #aaa;
}
img {
border-radius: 50%;
width: 2.5vmax;
height: 2.5vmax;
}
/* user.component.css */
.container {
width: 50%;
margin: 2rem auto;
padding: 1.5vmax;
padding-right: 2.5vmax;
border: 1px solid #ccc;
border-radius: 5px;
}
h2 {
text-align: center;
margin-bottom: 20px;
font-size: 2rem;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 97%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
button[type="submit"] {
width: 20%;
padding: 1.1vmax;
background-color: #0056b3;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
font-size: 1rem;
align-self: center;
margin-top: 1vmax;
}
.container {
width: 50%;
margin: 2rem auto;
padding: 1.5vmax;
padding-right: 3.5vmax;
border: 1px solid #ccc;
border-radius: 5px;
}
h2 {
text-align: center;
margin-bottom: 20px;
font-size: 2rem;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="email"],
input[type="password"] {
width: 99%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
button[type="submit"] {
width: 20%;
padding: 1.1vmax;
background-color: #0056b3;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
font-size: 1rem;
align-self: center;
margin-top: 1vmax;
}
.error-message {
color: #FF0000;
background-color: #FFEFEF;
padding: 10px;
border: 1px solid #FF0000;
border-radius: 5px;
margin-bottom: 10px;
}
/* star.component.css */
.star {
font-size: 24px;
cursor: pointer;
display: inline;
}
.filled {
color: gold;
}
.star.half-filled {
position: relative;
}
.star.half-filled::before {
content: '\2605';
color: gold;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
width: 50%;
}
/* app.component.css */
.main {
display: flex;
flex-direction: column;
align-items: center;
background-color: darkslategrey;
color: white;
}
.content {
width: 100%;
max-width: 1200px;
padding: 20px;
}
.left-side {
display: flex;
justify-content: space-between;
align-items: center;
}
.left-side h1 {
margin: 0;
margin-left: 3%;
}
.left-side ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.left-side li {
display: inline;
margin-right: 20px;
}
.left-side li a {
text-decoration: none;
color: white;
font-weight: bold;
font-size: 1.5rem;
}
.left-side li a:hover {
color: lightgray;
}
a {
cursor: pointer;
}
// product.component.ts
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ProductService } from '../../services/product.service';
import { Product } from '../../models/product';
import { UserService } from '../../services/user.service';
import { Router } from '@angular/router';
import { SharedServiceService } from '../../services/shared-service.service';
@Component({
selector: 'app-product',
standalone: true,
imports: [FormsModule, CommonModule],
templateUrl: './product.component.html',
styleUrl: './product.component.css',
})
export class ProductComponent implements OnInit {
isLoggedIn: boolean = false;
productList: any[] = [];
displayedProductList: any[] = [];
productById: any = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
productCreated: any = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
productUpdated: any = {};
showUpdateForm: boolean = false;
showAddForm: boolean = false;
errorMessage: string = '';
searchQueryCategory: string = '';
searchQuery: string = '';
constructor(
private productService: ProductService,
private userService: UserService,
private router: Router,
private sharedService: SharedServiceService
) { }
ngOnInit(): void {
if (typeof localStorage !== 'undefined'
&& localStorage.getItem('token')) {
this.getAllProducts();
}
}
ifLoggedIn(): boolean {
this.userService.isAuthenticated()
.subscribe((isAuthenticated: boolean) => {
this.isLoggedIn = isAuthenticated;
});
return this.isLoggedIn;
}
getUserId(): string | null {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
const tokenPayload = JSON.parse(atob(token.split('.')[1]));
console.log(tokenPayload);
return tokenPayload.user.id;
}
}
return null;
}
getAllProducts(): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
this.productService
.getAllProducts(token)
.subscribe((productList: any) => {
this.productList = productList;
this.displayedProductList = [...this.productList];
});
}
}
}
getProductById(id: string): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
this.showUpdateForm = false;
this.showAddForm = false;
this.productService
.getProductById(id, token)
.subscribe((productById) => {
this.productById = productById;
});
}
}
}
createProduct(): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
this.showAddForm = true;
this.showUpdateForm = false;
this.productService
.createProduct(this.productCreated, token)
.subscribe((productCreated) => {
this.productCreated = productCreated;
this.closeAddForm();
this.getAllProducts();
this.router.navigate(['/getAllProducts']);
});
}
}
}
resetForm(): void {
this.productCreated = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
}
closeAddForm(): void {
this.productCreated = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
this.showAddForm = false;
}
showAddFormFunction(): void {
this.showAddForm = true;
}
closeView(): void {
this.productById = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
}
populateUpdateForm(product: Product) {
this.productUpdated = { ...product };
this.showUpdateForm = true;
}
updateProduct(id: string): Product {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
this.productService.updateProduct(this.productUpdated, token).subscribe(
(productUpdated: any) => {
const index = this.displayedProductList.findIndex(
(p) => p._id === id
);
if (index !== -1) {
this.productList[index] = productUpdated;
this.displayedProductList[index] = productUpdated;
this.getAllProducts();
this.showUpdateForm = false;
this.router.navigate(['/getAllProducts']);
}
this.cancelUpdate();
},
(error) => {
this.errorMessage = 'Error Updating Product';
}
);
}
}
return this.productUpdated;
}
cancelUpdate(): void {
this.showUpdateForm = false;
this.productUpdated = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
this.productById = {
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
numberOfReviews: 0,
reviews: [],
};
}
confirmDelete(productId: string): void {
const confirmDelete = window.confirm(
'Are you sure you want to delete this product?'
);
if (confirmDelete) {
this.deleteProduct(productId);
}
}
deleteProduct(id: string): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
this.productService.deleteProduct(id, token).subscribe(
() => {
this.productList = this.productList.filter(
(product: any) => product._id !== id
);
this.displayedProductList = [...this.productList];
},
(error) => {
this.errorMessage = 'Error Deleting product';
}
);
}
}
}
getReviews(product: any): void {
this.sharedService.triggerProductIdEvent(product);
this.router.navigate(['/getAllReviews']);
}
applyFilter(): void {
if (!this.searchQuery?.trim() && !this.searchQueryCategory?.trim()) {
this.displayedProductList = [...this.productList];
} else if (this.searchQueryCategory?.trim() && !this.searchQuery?.trim()) {
const queryCategory = this.searchQueryCategory
? this.searchQueryCategory.toLowerCase().trim()
: this.searchQueryCategory;
this.displayedProductList = this.productList.filter((product) =>
product.productCategory.toString().toLowerCase().includes(queryCategory)
);
} else if (!this.searchQueryCategory?.trim() && this.searchQuery?.trim()) {
const query = this.searchQuery
? this.searchQuery.toLowerCase().trim()
: this.searchQuery;
this.displayedProductList = this.productList.filter((product) =>
product.productName.toLowerCase().includes(query)
);
} else {
const query = this.searchQuery
? this.searchQuery.toLowerCase().trim()
: this.searchQuery;
const queryCategory = this.searchQueryCategory
? this.searchQueryCategory.toLowerCase().trim()
: this.searchQueryCategory;
this.displayedProductList = this.productList.filter(
(product) =>
product.productName.toLowerCase().includes(query) &&
product.productCategory
.toString()
.toLowerCase()
.includes(queryCategory)
);
}
}
clearFilter(): void {
this.searchQuery = '';
this.applyFilter();
}
clearFilterCategory(): void {
this.searchQueryCategory = '';
this.applyFilter();
}
}
// review.component.ts
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { UserService } from '../../services/user.service';
import { ReviewService } from '../../services/review.service';
import { Review } from '../../models/review';
import { Router } from '@angular/router';
import { SharedServiceService } from '../../services/shared-service.service';
import { ObjectService } from '../../services/object.service';
import { ProductService } from '../../services/product.service';
import { StarComponent } from '../star/star.component';
@Component({
selector: 'app-review',
standalone: true,
imports: [FormsModule, CommonModule, StarComponent],
templateUrl: './review.component.html',
styleUrl: './review.component.css',
})
export class ReviewComponent implements OnInit {
reviewDataAdded: Review = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
reviewById: Review = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
reviewList: any[] = [];
showReviewAddForm: boolean = false;
displayedReviewList: any[] = [];
isLoggedIn: boolean = false;
reviewUpdated: any = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
showReviewEditForm: boolean = false;
errorMessage: string = '';
productId: any = '';
product: any = {
id: '',
userId: '',
productName: '',
productDescription: '',
productCategory: '',
productPrice: 0,
productImageUrl: '',
};
username: string = '';
email: string = '';
stars: number[] = [1, 2, 3, 4, 5];
constructor(
private reviewService: ReviewService,
private userService: UserService,
private router: Router,
private sharedService: SharedServiceService,
private objectService: ObjectService
) { }
ngOnInit() {
this.sharedService.productIdEvent.subscribe((product: any) => {
this.productId = product._id;
if (localStorage.getItem('productId')) {
localStorage.removeItem('productId');
}
localStorage.setItem('productId', this.productId);
this.product = product;
});
this.getAllReviews();
}
getUserFromId(id: string): void {
this.objectService.getObjectById(id).subscribe((userData: any) => {
this.username = userData.username;
this.email = userData.email;
});
}
getProductFromId(id: string): any {
this.objectService
.getProductObjectById(id)
.subscribe((productData: any) => {
this.product = {
id: productData._id,
userId: productData.user,
productName: productData.productName,
productDescription: productData.productDescription,
productCategory: productData.productCategory,
productPrice: productData.productPrice,
productImageUrl: productData.productImageUrl,
};
this.getUserFromId(this.product.userId);
});
return this.product;
}
ifLoggedIn(): boolean {
this.userService.isAuthenticated()
.subscribe((isAuthenticated: boolean) => {
this.isLoggedIn = isAuthenticated;
});
return this.isLoggedIn;
}
getAllReviews(): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
const productId = localStorage.getItem('productId');
if (token && productId) {
this.getProductFromId(productId);
this.reviewService
.getAllReviews(productId, token)
.subscribe((reviewList) => {
this.reviewList = reviewList;
this.displayedReviewList = [...this.reviewList];
});
}
}
}
getReviewById(id: string): void {
const token = localStorage.getItem('token');
this.showReviewAddForm = false;
this.showReviewEditForm = false;
if (token) {
this.reviewService
.getReviewById(id, this.productId, token).subscribe(
(review) => {
this.reviewById = review;
},
(error) => {
console.error('Error in getting review:', error);
}
);
}
}
addReview(productId: string): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
this.reviewDataAdded.productId = productId;
this.reviewService
.addReview(this.reviewDataAdded, this.productId, token)
.subscribe((reviewDataAdded) => {
this.reviewDataAdded = reviewDataAdded;
this.showReviewAddForm = false;
this.router.navigate(['/getAllReviews']);
this.getAllReviews();
});
}
}
}
resetForm(): void {
this.reviewDataAdded = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
}
closeAddForm(): void {
this.reviewDataAdded = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
this.showReviewAddForm = false;
}
showAddFormFunction(): void {
if (typeof localStorage !== 'undefined') {
this.productId = localStorage.getItem('productId');
this.getProductFromId(this.productId);
}
this.showReviewAddForm = true;
}
updateRating(event: any): void {
console.log(event.target.value);
this.onRatingChanged(event.target.value);
}
getUserId(): string | null {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
const tokenPayload = JSON.parse(atob(token.split('.')[1]));
return tokenPayload.user.id;
}
}
return null;
}
onRatingChanged(newRating: number): void {
if (this.reviewDataAdded) {
this.reviewDataAdded.rating = newRating;
}
if (this.reviewUpdated) {
this.reviewUpdated.rating = newRating;
}
}
populateForm(review: Review): void {
this.reviewUpdated = { ...review };
this.showReviewEditForm = true;
}
editReview(reviewId: string): void {
if (typeof localStorage !== 'undefined') {
const token = localStorage.getItem('token');
const productId = localStorage.getItem('productId');
if (token && productId) {
this.reviewUpdated.productId = productId;
this.reviewService
.editReview(this.reviewUpdated, productId, token)
.subscribe(
(reviewUpdated: any) => {
const index = this.displayedReviewList.findIndex(
(r) => reviewId === r._id
);
if (index !== -1) {
this.router.navigate(['/getAllReviews']);
this.reviewList[index] = reviewUpdated;
this.displayedReviewList[index] = reviewUpdated;
this.getAllReviews();
this.showReviewEditForm = false;
}
this.cancelEdit();
},
(error) => {
this.errorMessage = error;
}
);
}
}
}
cancelEdit(): void {
this.showReviewEditForm = false;
this.reviewUpdated = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
this.reviewDataAdded = {
userId: '',
productId: '',
rating: 0,
comment: '',
};
}
confirmDelete(reviewId: string): void {
const confirmDelete = window.confirm(
'Are you sure you want to delete this review?'
);
if (confirmDelete) {
this.deleteReview(reviewId);
}
}
deleteReview(reviewId: string): void {
const token = localStorage.getItem('token');
this.showReviewEditForm = false;
if (token) {
this.reviewService
.deleteReview(reviewId, this.productId, token)
.subscribe(
() => {
this.reviewList = this.reviewList.filter(
(review: any) => review._id !== reviewId
);
this.displayedReviewList = [...this.reviewList];
},
(error) => {
this.errorMessage = error;
}
);
}
}
}
// user.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from '../../services/user.service';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { SharedServiceService }
from '../../services/shared-service.service';
@Component({
selector: 'app-user',
standalone: true,
imports: [FormsModule, CommonModule],
templateUrl: './user.component.html',
styleUrl: './user.component.css',
})
export class UserComponent implements OnInit {
username!: string;
email!: string;
password!: string;
credentials: any = {};
successMessage: string = '';
errorMessage: string = '';
loginActive: boolean = true;
registerActive: boolean = false;
constructor(
private userService: UserService,
private router: Router,
private sharedService: SharedServiceService
) { }
ngOnInit(): void {
this.sharedService.loginEvent.subscribe(() => {
this.loginActive = true;
this.registerActive = false;
this.username = '';
this.email = '';
this.password = '';
this.successMessage = '';
this.errorMessage = '';
});
this.sharedService.registerEvent.subscribe(() => {
this.registerActive = true;
this.loginActive = false;
this.username = '';
this.email = '';
this.password = '';
this.successMessage = '';
this.errorMessage = '';
});
}
login(): void {
const credentials = {
email: this.email,
password: this.password,
};
this.userService.login(credentials).subscribe(
(response: any) => {
const token = response.token;
localStorage.setItem('token', token);
this.userService.setAuthenticationStatus(true);
this.userService.emitLoggedInEvent();
this.loginActive = false;
this.registerActive = false;
this.router.navigate(['/getAllProducts']);
this.successMessage = response.message;
},
(error) => {
console.error('Error logging in:', error);
this.errorMessage =
`Login unsuccessfull ! Please reload
or try in incognito tab`;
}
);
}
register(): void {
const userData = {
username: this.username,
email: this.email,
password: this.password,
};
this.userService.register(userData).subscribe(
(response: any) => {
this.successMessage = response.message;
this.loginActive = true;
this.registerActive = false;
},
(error: any) => {
this.errorMessage = 'User not registered successfully';
}
);
}
}
// star.component.ts
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-star',
standalone: true,
imports: [CommonModule],
templateUrl: './star.component.html',
styleUrl: './star.component.css',
})
export class StarComponent implements OnInit {
@Input() rating: number = 0;
@Output() ratingChanged: EventEmitter<number>
= new EventEmitter<number>();
@Input() isDisabled: boolean = false;
filledStars: number = 0;
stars: number[] = [];
constructor() { }
ngOnInit(): void {
this.stars = [0, 1, 2, 3, 4];
this.filledStars = Math.floor(this.rating);
}
rateStar(index: number): void {
if (!this.isDisabled) {
this.filledStars = index + 1;
this.ratingChanged.emit(this.filledStars);
}
}
}
// app.component.ts
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterOutlet } from '@angular/router';
import { UserService } from './services/user.service';
import { SharedServiceService } from './services/shared-service.service';
import { StarComponent } from './components/star/star.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, FormsModule, CommonModule, StarComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {
title = 'w3wiki Product Review Platform';
isLoggedIn: boolean = false;
constructor(
private router: Router,
private userService: UserService,
private sharedService: SharedServiceService
) { }
ngOnInit(): void {
this.userService.loggedInEvent.subscribe((data: any) => {
this.isLoggedIn = true;
});
if (typeof localStorage !== 'undefined'
&& localStorage.getItem('token')) {
this.isLoggedIn = true;
}
}
login(): void {
this.sharedService.triggerLoginEvent();
this.router.navigate(['/']);
}
register(): void {
this.sharedService.triggerRegisterEvent();
this.router.navigate(['/']);
}
logout(): void {
this.userService.setAuthenticationStatus(false);
this.isLoggedIn = false;
localStorage.removeItem('token');
localStorage.removeItem('productId');
this.router.navigate(['/']);
}
}
// user.service.ts
import { HttpClient } from '@angular/common/http';
import { EventEmitter, Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class UserService {
private baseUrl = 'http://localhost:5000/api/auth';
constructor(private httpClient: HttpClient) { }
register(userData: any): Observable<any> {
return this.httpClient
.post(`${this.baseUrl}/register`, userData);
}
login(credentials: any): Observable<any> {
return this.httpClient
.post(`${this.baseUrl}/login`, credentials);
}
private isAuthenticatedSubject =
new BehaviorSubject<boolean>(false);
isAuthenticated(): Observable<boolean> {
return this.isAuthenticatedSubject.asObservable();
}
setAuthenticationStatus(isAuthenticated: boolean): void {
this.isAuthenticatedSubject
.next(isAuthenticated);
}
loggedInEvent: EventEmitter<any> = new EventEmitter();
emitLoggedInEvent() {
this.loggedInEvent.emit();
}
}
// shared-service.service.ts
import { EventEmitter, Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class SharedServiceService {
loginEvent: EventEmitter<void> = new EventEmitter<void>();
registerEvent: EventEmitter<void> = new EventEmitter<void>();
productIdEvent: EventEmitter<any> = new EventEmitter<any>();
constructor() { }
triggerLoginEvent(): void {
this.loginEvent.emit();
}
triggerRegisterEvent(): void {
this.registerEvent.emit();
}
triggerProductIdEvent(product: any): void {
this.productIdEvent.emit(product);
}
}
// review.service.ts
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Review } from '../models/review';
@Injectable({
providedIn: 'root',
})
export class ReviewService {
private baseUrl = 'http://localhost:5000/api/review';
constructor(private httpClient: HttpClient) { }
getAllReviews(productId: string, token: string) {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
productId: `${productId}`,
});
return this.httpClient.get<Review[]>(`${this.baseUrl}/getAllReviews`, {
headers,
});
}
getReviewById(id: string, productId: string, token: string) {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
productId: `${productId}`,
});
return this.httpClient.get<Review>(`${this.baseUrl}/getReviewById/${id}`, {
headers,
});
}
addReview(reviewData: Review, productId: string, token: string) {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
productId: `${productId}`,
});
return this.httpClient.post<Review>(
`${this.baseUrl}/addReview`,
reviewData,
{ headers }
);
}
editReview(reviewData: any, productId: string, token: string) {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
productId: `${productId}`,
});
return this.httpClient.put<Review>(
`${this.baseUrl}/editReview/${reviewData._id}`,
reviewData,
{ headers }
);
}
deleteReview(id: string, productId: string, token: string) {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
productId: `${productId}`,
});
return this.httpClient.delete<void>(`${this.baseUrl}/deleteReview/${id}`, {
headers,
});
}
}
// product.service.ts
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ProductService {
private baseUrl = 'http://localhost:5000/api/product';
constructor(private httpClient: HttpClient) { }
getAllProducts(token: string): Observable<Product[]> {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
});
return this.httpClient.get<Product[]>
(`${this.baseUrl}/getAllProducts`, {
headers,
});
}
getProductById(id: string, token: string):
Observable<Product> {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
});
return this.httpClient.get<Product>(
`${this.baseUrl}/getProductById/${id}`,
{ headers }
);
}
createProduct(product: Product, token: string):
Observable<Product> {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
});
return this.httpClient.post<Product>(
`${this.baseUrl}/createProduct`,
product,
{ headers }
);
}
updateProduct(product: any, token: string):
Observable<Product> {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
});
return this.httpClient.put<Product>(
`${this.baseUrl}/updateProduct/${product._id}`,
product,
{ headers }
);
}
deleteProduct(id: string, token: string):
Observable<void> {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
});
return this.httpClient.delete<void>
(`${this.baseUrl}/deleteProduct/${id}`, {
headers,
});
}
}
// object.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ObjectService {
private baseUrl = 'http://localhost:5000/api/auth';
constructor(private http: HttpClient) { }
getObjectById(id: string): Observable<any> {
return this.http.get<any>
(`${this.baseUrl}/${id}`);
}
private baseApiUrl =
'http://localhost:5000/api/product/getProductInfo';
getProductObjectById(id: string): Observable<any> {
return this.http.get<any>
(`${this.baseApiUrl}/${id}`);
}
}
// app.module.ts
import { InjectionToken, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
import { RouterModule } from '@angular/router';
import { routes } from './app.routes';
import { UserComponent } from './components/user/user.component';
import { ProductComponent } from './components/product/product.component';
import { ReviewComponent } from './components/review/review.component';
import { StarComponent } from './components/star/star.component';
@NgModule({
declarations: [
AppComponent,
UserComponent,
ProductComponent,
ReviewComponent,
StarComponent,
],
imports: [BrowserModule, FormsModule,
RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [
{ provide: JWT_OPTIONS, useValue: JWT_OPTIONS },
JwtHelperService,
],
bootstrap: [AppComponent],
})
export class AppModule { }
// app.routes.ts
import { Router, Routes } from '@angular/router';
import { UserComponent } from './components/user/user.component';
import { ProductComponent } from './components/product/product.component';
import { ReviewComponent } from './components/review/review.component';
export const routes: Routes = [
{ path: '', component: UserComponent },
{ path: 'getAllProducts', component: ProductComponent },
{ path: 'getProductById/:id', component: ProductComponent },
{ path: 'createProduct', component: ProductComponent },
{ path: 'updateProduct/:id', component: ProductComponent },
{ path: 'deleteProduct/:id', component: ProductComponent },
{ path: 'getAllReviews', component: ReviewComponent },
{ path: 'getReviewById', component: ReviewComponent },
{ path: 'addReview', component: ReviewComponent },
{ path: 'editReview/:id', component: ReviewComponent },
{ path: 'deleteReview/:id', component: ReviewComponent },
{ path: '**', redirectTo: '/' }
];
To start the frontend run the following command.
ng serve
Contact Us