Open In App

Product Review Platform using MEAN Stack

Last Updated : 02 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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.

preview1

Product Review Platform using MEAN

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):

Screenshot-2024-03-17-012946

PROJECT STRUCTURE IMAGE FOR BACKEND

Example: Create the required files as seen on the project structure and add the following codes.

Node
// 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
        });
    }
};
Node
// 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"
        });
    }
};
Node
// 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
        });
    }
};
Node
// 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);
Node
// 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);
Node
// 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);
Node
// 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;
Node
// 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;
Node
// 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;
Node
// 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):

frontendProjStructure

PROJECT STRUCTURE GIF FOR FRONTEND

Example: Create the required files as seen in folder structure and add the codes.

HTML
<!-- 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>
HTML
<!-- 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>
HTML
<!-- 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>
HTML
<!-- star.component.html -->

<span
    class="star"
    (click)="rateStar(i)"
    [ngClass]="{ filled: i < filledStars }"
    *ngFor="let star of stars; let i = index"
    >&#9733;</span
>
HTML
<!-- 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>
CSS
/* 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;
}
CSS
/* 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;
}
CSS
/* 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;
}
CSS
/* 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%;
}
CSS
/* 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;
}
JavaScript
// 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();
    }
}
JavaScript
// 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;
                    }
                );
        }
    }
}
JavaScript
// 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';
            }
        );
    }
}
JavaScript
// 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);
        }
    }
}
JavaSc’ript
// 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 = 'GeeksForGeeks 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(['/']);
    }
}
JavaScript
// 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();
    }
}
JavaScript
// 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);
    }
}
JavaScript
// 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,
        });
    }
}
JavaScript
// 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,
            });
    }
}
JavaScript
// 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}`);
    }
}
JavaScript
// 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 { }
JavaScript
// 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

Output:

output-(6)

OUTPUT GIF FOR THE PROJECT



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads