Skip to content

Commit

Permalink
Added reaction button on blog post and updates on backend too
Browse files Browse the repository at this point in the history
  • Loading branch information
Sawan-Kushwah committed Nov 4, 2024
1 parent d81621d commit 7583859
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 14 deletions.
34 changes: 33 additions & 1 deletion backend/controllers/addBlogController.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const upload = multer({ storage: storage });
// Function to save a new blog post
export async function saveBlog(req, res) {
try {
const { title, category, summary, excerpt, tags, publish, featuredImage } = req.body;
const { title, category, summary, excerpt, tags, publish, likes, featuredImage } = req.body;

// If an image is uploaded, use its path
let imagePath = req.file ? req.file.path : null;
Expand Down Expand Up @@ -56,6 +56,7 @@ export async function saveBlog(req, res) {
excerpt,
tags,
publish,
likes,
featuredImage: imagePath // Use the determined image path
});

Expand Down Expand Up @@ -104,4 +105,35 @@ export async function getBlog(req, res) {
}
}



export async function updateLikes(req, res) {
const { postId, liked } = req.body; // Destructure liked from the body
console.log(postId + " " + liked);

try {
// Find the post by ID
const post = await BlogPost.findById(postId);
console.log(post);

if (!post) {
return res.status(404).json({ message: "Post not found" });
}

// Update the likes count based on the "liked" value
post.likes = liked ? post.likes + 1 : Math.max(post.likes - 1, 0); // Update post.likes, not BlogPost.likes

// Save the updated post
await post.save();

// Respond with the new likes count
return res.status(200).json({ likesCount: post.likes }); // Use post.likes
} catch (error) {
console.error("Error updating likes:", error);
return res.status(500).json({ message: "Failed to update likes" });
}
}



export { upload };
4 changes: 4 additions & 0 deletions backend/models/addBlog.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ const blogPostSchema = new mongoose.Schema({
type: String,
default: null
},
likes: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: Date.now
Expand Down
3 changes: 2 additions & 1 deletion backend/routes/addBlogRoutes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import express from "express";
import { getAllBlog, getBlog, saveBlog, upload } from "../controllers/addBlogController.js";
import { getAllBlog, getBlog, saveBlog, upload, updateLikes } from "../controllers/addBlogController.js";
const router = express.Router();

router.post("/saveBlog", upload.single('featuredImage'), saveBlog);
router.get("/getAllBlog", getAllBlog);
router.patch("/updateLikes", updateLikes);
router.get("/getBlog/:id", getBlog);

export default router;
1 change: 1 addition & 0 deletions frontend/src/pages/AddBlog.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function renderAddBlog(container) {
formData.append('excerpt', excerpt);
formData.append('tags', tags);
formData.append('publish', publish);
formData.append('likes', 0);
if (featuredImage) {
formData.append('featuredImage', featuredImage); // Ensure key matches backend
}
Expand Down
79 changes: 76 additions & 3 deletions frontend/src/pages/Blogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ function setupCategoryFilter(blogPosts) {
}

function renderBlogPosts(posts) {
return posts.map(post => renderBlogPost(post.id, post.title, post.excerpt, post.date, post.tags, post.featuredImage, post.publish)).join('');
return posts.map(post => renderBlogPost(post._id, post.title, post.excerpt, post.createdAt, post.tags, post.featuredImage, post.publish, post.likes)).join('');
}

function renderBlogPost(id, title, excerpt, date, tags, imageUrl, publish) {
function renderBlogPost(id, title, excerpt, date, tags, imageUrl, publish, likes) {
if (!publish) return '';
let imagePath = "";
if (imageUrl) {
Expand All @@ -106,18 +106,91 @@ function renderBlogPost(id, title, excerpt, date, tags, imageUrl, publish) {
<div class="flex items-center text-sm text-gray-500 dark:text-gray-400">
<time datetime="${date}">${formatDate(date)}</time>
<span class="ml-4 font-medium text-gray-900 dark:text-white">${tags}</span>
<a href="/readmore" class="flex items-center ml-4 text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-500 cursor-pointer">
<a href="/readmore/${id}" class="flex items-center ml-4 text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-500 cursor-pointer">
Read More
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
</div>
<div class="mt-4 flex items-center">
<button
onclick="handleReaction('${id}')"
class="flex items-center text-gray-600 dark:text-gray-300 hover:text-red-500 dark:hover:text-red-400 cursor-pointer transition-all duration-200 ease-in-out"
>
<svg
id="heart-icon-${id}"
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 mr-2 transition-transform duration-200 ease-in-out transform"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 21c-1.104-.002-2.113-.365-3.022-1.023C6.568 18.482 4 15.673 4 12.528c0-2.58 1.812-4.528 4.25-4.528 1.1 0 2.161.458 2.884 1.178a4.053 4.053 0 0 1 2.884-1.178c2.438 0 4.25 1.948 4.25 4.528 0 3.145-2.568 5.954-4.978 7.449-.909.658-1.918 1.021-3.022 1.023z"
/>
</svg>
<span id="reaction-count-${id}" class="text-sm font-semibold mt-[6px]">${likes}</span>
<span class="ml-1 text-sm mt-[6px]">Likes</span>
</button>
</div>
</div>
</article>
`;
}

window.handleReaction = async function (postId) {
const reactionCountElement = document.getElementById(`reaction-count-${postId}`);
const heartIcon = document.getElementById(`heart-icon-${postId}`);
let currentCount = parseInt(reactionCountElement.innerText, 10);

// Check if the heart is currently filled
const isFilled = heartIcon.getAttribute("fill") === "currentColor";

// Toggle the heart fill color and update count with animation
const newCount = isFilled ? currentCount - 1 : currentCount + 1;
heartIcon.classList.toggle("text-red-500", !isFilled);
heartIcon.setAttribute("fill", !isFilled ? "currentColor" : "none");
reactionCountElement.innerText = newCount;

// Add a quick scale animation for visual feedback
heartIcon.classList.add("scale");
setTimeout(() => heartIcon.classList.remove("scale"), 150);

// Send updated like count to the server
try {
const response = await fetch(`http://localhost:5000/api/addBlog/updateLikes`, {
method: "PATCH", // Change POST to PATCH
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ postId, liked: !isFilled }), // Ensure liked reflects the new state
});

if (!response.ok) {
throw new Error("Server error");
}

const data = await response.json();
// Update the like count based on the server response
reactionCountElement.innerText = data.likesCount; // Ensure the server returns likesCount
} catch (error) {
console.error("Failed to update like on server:", error);

// Revert the UI changes if the server update fails
heartIcon.classList.toggle("text-red-500", isFilled);
heartIcon.setAttribute("fill", isFilled ? "currentColor" : "none");
reactionCountElement.innerText = currentCount; // Revert to previous count
}
};





function renderSearchWidget() {
return `
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6" data-aos="fade-up" data-aos-delay="100">
Expand Down
48 changes: 39 additions & 9 deletions frontend/src/styles/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,10 @@ video {
margin-top: 2rem;
}

.mt-\[6px\] {
margin-top: 6px;
}

.block {
display: block;
}
Expand Down Expand Up @@ -1073,6 +1077,10 @@ video {
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}

.fill-current {
fill: currentColor;
}

.object-cover {
-o-object-fit: cover;
object-fit: cover;
Expand Down Expand Up @@ -1221,11 +1229,6 @@ video {
line-height: 1.75rem;
}

.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}

.font-bold {
font-weight: 700;
}
Expand All @@ -1242,10 +1245,6 @@ video {
font-weight: 600;
}

.font-normal {
font-weight: 400;
}

.leading-relaxed {
line-height: 1.625;
}
Expand Down Expand Up @@ -1408,6 +1407,18 @@ video {
transition-duration: 150ms;
}

.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}

.transition-transform {
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}

.duration-150 {
transition-duration: 150ms;
}
Expand All @@ -1416,6 +1427,10 @@ video {
transition-duration: 300ms;
}

.duration-200 {
transition-duration: 200ms;
}

.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
Expand Down Expand Up @@ -1508,6 +1523,16 @@ body.dark-mode {
color: rgb(255 255 255 / var(--tw-text-opacity));
}

.hover\:text-red-600:hover {
--tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity));
}

.hover\:text-red-500:hover {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}

.hover\:underline:hover {
text-decoration-line: underline;
}
Expand Down Expand Up @@ -1772,6 +1797,11 @@ body.dark-mode {
color: rgb(255 255 255 / var(--tw-text-opacity));
}

.dark\:hover\:text-red-400:hover:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(248 113 113 / var(--tw-text-opacity));
}

.dark\:focus\:ring-gray-600:focus:is(.dark *) {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity));
Expand Down

0 comments on commit 7583859

Please sign in to comment.