Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion backend/controller/reviewController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import Product from "../model/productModel.js";
import User from "../model/userModel.js";
import { sendNotification } from "../services/notificationService.js";

// 1. Import and initialize the sentiment library
import Sentiment from 'sentiment';
const sentiment = new Sentiment();

async function recalculateProductRating(productId) {
const result = await Review.aggregate([
{ $match: { productId: new mongoose.Types.ObjectId(productId) } },
Expand Down Expand Up @@ -45,6 +49,19 @@ export const addReview = async (req, res) => {
return res.status(404).json({ message: "Product not found" });
}

// 2. Perform the sentiment analysis on the comment text
let sentimentScore = 0;
let sentimentLabel = 'Neutral';

if (comment && comment.trim().length > 0) {
const result = sentiment.analyze(comment);
sentimentScore = result.score;

// Assign labels based on the score threshold
if (sentimentScore >= 2) sentimentLabel = 'Positive';
else if (sentimentScore <= -2) sentimentLabel = 'Negative';
}

const existingReview = await Review.findOne({
userId: req.userId,
productId,
Expand All @@ -53,6 +70,10 @@ export const addReview = async (req, res) => {
if (existingReview) {
existingReview.rating = rating;
existingReview.comment = comment;
// 3. Update existing review with new sentiment data
existingReview.sentimentScore = sentimentScore;
existingReview.sentimentLabel = sentimentLabel;

await existingReview.save();

const { avgRating, reviewCount } = await recalculateProductRating(productId);
Expand All @@ -65,12 +86,15 @@ export const addReview = async (req, res) => {
});
}

// 4. Create new review with sentiment data attached
const newReview = new Review({
userId: req.userId,
productId,
name: user.name,
rating,
comment,
sentimentScore,
sentimentLabel
});

await newReview.save();
Expand Down Expand Up @@ -115,4 +139,4 @@ export const getProductReviews = async (req, res) => {
console.log(error);
res.status(500).json({ message: "Server error" });
}
};
};
12 changes: 12 additions & 0 deletions backend/model/reviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ const reviewSchema = new mongoose.Schema(
type: String,
required: true,
},
// Add this inside the reviewSchema definition
sentimentScore: {
type: Number,
required: false,
default: 0 // Scores usually range from negative to positive numbers
},
sentimentLabel: {
type: String,
enum: ['Positive', 'Neutral', 'Negative'],
default: 'Neutral'
}

},
{
timestamps: true,
Expand Down
80 changes: 54 additions & 26 deletions frontend/src/pages/ProductDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'react-toastify/dist/ReactToastify.css';
import { FaChevronLeft, FaChevronRight, FaHeart, FaShare, FaShoppingCart, FaStar } from 'react-icons/fa';
import RelatedProduct from '../components/RelatedProduct';


function ProductDetail() {
const { userData } = useContext(userDataContext);
const { productId } = useParams();
Expand Down Expand Up @@ -120,16 +119,17 @@ function ProductDetail() {
};

const handleAddToWishlist = () => {
if (!productData?._id) return;

if (isWishlisted) {
removeFromWishlist(productData._id);
toast.info('Removed from wishlist');
} else {
addToWishlist(productData._id);
toast.success('Added to wishlist 💖');
}
};
if (!productData?._id) return;

if (isWishlisted) {
removeFromWishlist(productData._id);
toast.info('Removed from wishlist');
} else {
addToWishlist(productData._id);
toast.success('Added to wishlist 💖');
}
};

// share
const handleShare = async () => {
if (navigator.share) {
Expand Down Expand Up @@ -460,16 +460,16 @@ function ProductDetail() {

<div className="flex gap-3">
<button
onClick={handleAddToWishlist}
className={`flex-1 py-3 rounded-lg flex items-center justify-center gap-2 transition-all duration-200
${isWishlisted
? 'bg-rose-600 text-white'
: 'bg-gray-800 text-white hover:bg-gray-700'
}`}
>
<FaHeart className={isWishlisted ? 'text-white' : ''} />
{isWishlisted ? 'Wishlisted' : 'Wishlist'}
</button>
onClick={handleAddToWishlist}
className={`flex-1 py-3 rounded-lg flex items-center justify-center gap-2 transition-all duration-200
${isWishlisted
? 'bg-rose-600 text-white'
: 'bg-gray-800 text-white hover:bg-gray-700'
}`}
>
<FaHeart className={isWishlisted ? 'text-white' : ''} />
{isWishlisted ? 'Wishlisted' : 'Wishlist'}
</button>
<button
type="button"
onClick={handleShare}
Expand Down Expand Up @@ -559,10 +559,23 @@ function ProductDetail() {
Edit Review
</button>
</div>
<div className="flex gap-1 mb-2" aria-hidden="true">
<div className="flex items-center gap-1 mb-2" aria-hidden="true">
{[...Array(userReview.rating)].map((_, i) => (
<FaStar key={i} className="text-yellow-400" />
))}

{/* Integrated Sentiment Badge for Current User's Review */}
{userReview.sentimentLabel === 'Positive' && (
<span className="ml-3 px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 border border-green-200 dark:border-green-800">
Helpful Positive
</span>
)}
{userReview.sentimentLabel === 'Negative' && (
<span className="ml-3 px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400 border border-red-200 dark:border-red-800">
Critical Review
</span>
)}

</div>
<p className="text-slate-700 dark:text-gray-300">
{userReview.comment}
Expand Down Expand Up @@ -651,9 +664,24 @@ function ProductDetail() {
<FaStar key={i} className="text-yellow-400" />
))}
</div>
<span className="text-cyan-400 font-semibold">
{review.name}
</span>
<div className="flex items-center gap-3">
<span className="text-cyan-400 font-semibold">
{review.name}
</span>

{/* Integrated Sentiment Badge for the General Review List */}
{review.sentimentLabel === 'Positive' && (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 border border-green-200 dark:border-green-800">
Helpful Positive
</span>
)}
{review.sentimentLabel === 'Negative' && (
<span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400 border border-red-200 dark:border-red-800">
Critical Review
</span>
)}

</div>
</div>
<p className="text-slate-700 dark:text-gray-300">
{review.comment}
Expand Down Expand Up @@ -697,4 +725,4 @@ function ProductDetail() {
);
}

export default ProductDetail;
export default ProductDetail;