From f5adb02567764c1bad99814a5fbde865d3a219c1 Mon Sep 17 00:00:00 2001 From: chaaanuwu Date: Sat, 2 May 2026 12:54:43 +0530 Subject: [PATCH 1/2] feat: Implement follow/unfollow functionality and enhance user profile data fetching --- client/src/api/userFollows.api.js | 5 + client/src/pages/Profile.jsx | 111 ++++++++++-------- .../features/profile.controller.js | 61 ++++------ server/controllers/user.controller.js | 12 +- server/routes/features/profile.route.js | 4 +- server/routes/user.route.js | 2 - 6 files changed, 108 insertions(+), 87 deletions(-) diff --git a/client/src/api/userFollows.api.js b/client/src/api/userFollows.api.js index a5881f2..1d189ea 100644 --- a/client/src/api/userFollows.api.js +++ b/client/src/api/userFollows.api.js @@ -3,4 +3,9 @@ import axiosInstance from "./axiosInstance"; export const followUser = async (followingId) => { const res = await axiosInstance.post('/me/follow', { followingId }); return res; +} + +export const unfollowUser = async (followingId) => { + const res = await axiosInstance.post('/me/unfollow', { followingId }); + return res; } \ No newline at end of file diff --git a/client/src/pages/Profile.jsx b/client/src/pages/Profile.jsx index 9bdd00b..2dc0025 100644 --- a/client/src/pages/Profile.jsx +++ b/client/src/pages/Profile.jsx @@ -12,7 +12,7 @@ import Modal from "../components/ui/Modal"; import SearchBar from "../components/ui/SearchBar"; import { getHistoryBanner } from "../api/history.api"; import Dropdown from "../components/ui/Dropdown"; -import { followUser } from "../api/userFollows.api"; +import { followUser, unfollowUser } from "../api/userFollows.api"; import { getAllComments, postComment, updateComment } from "../api/commments.api"; export default function Profile() { @@ -35,7 +35,7 @@ export default function Profile() { useEffect(() => { const fetchProfile = async () => { try { - setProfileData(null); + setLoading(true); const res = await getProfile(userId); setProfileData(res); } catch (err) { @@ -47,16 +47,58 @@ export default function Profile() { fetchProfile(); }, [userId]); - const handleFollow = async () => { + // Check following status + useEffect(() => { + if (user?.user?._id && profileData?.followers) { + const loggedInUserId = user.user._id; + + const isFollowing = profileData.followers.some((record) => { + const followerId = record.followerId?._id || record.followerId || record._id; + return followerId === loggedInUserId; + }); + + setFollowing(isFollowing); + } + }, [profileData, user]); + + const handleFollowToggle = async () => { + // 1. Store the previous state in case the API fails (to rollback) + const wasFollowing = following; + const profileId = profileData.user._id; + try { - const res = await followUser(profileData.user._id); - if (res.status === 200) { - setFollowing(true); + if (wasFollowing) { + // UNFOLLOW LOGIC + const res = await unfollowUser(profileId); + if (res.status === 200) { + setFollowing(false); + setProfileData(prev => ({ + ...prev, + followersCount: Math.max(0, (prev.followersCount || 0) - 1), + // Remove current user from the local followers array + followers: prev.followers.filter(f => + (f.followerId?._id || f.followerId || f._id) !== user.user._id + ) + })); + } + } else { + // FOLLOW LOGIC + const res = await followUser(profileId); + if (res.status === 200) { + setFollowing(true); + setProfileData(prev => ({ + ...prev, + followersCount: (prev.followersCount || 0) + 1, + // Add current user to local followers array to keep useEffect happy + followers: [...prev.followers, { followerId: user.user._id }] + })); + } } } catch (error) { - console.error("Failed to follow user", error); + console.error("Toggle follow failed", error); + // Optional: Alert the user or rollback UI state } - } + }; const handleOpenReplyModal = async (review) => { setComments([]); @@ -77,7 +119,6 @@ export default function Profile() { try { const res = await postComment(selectedReview._id, content); if (res.data.success) { - // Refresh comments locally setComments(prev => [...prev, res.data.comment]); commentInputRef.current.value = ""; commentInputRef.current.style.height = 'auto'; @@ -134,7 +175,6 @@ export default function Profile() { return (
- {/* Cover Section */}
- {/* Profile Info */}
Follow ) : ( - ) @@ -279,10 +320,9 @@ export default function Profile() {
- {/* Comments Modal */} + {/* Discussion Modal */}
-
@@ -299,13 +339,10 @@ export default function Profile() { onClick={() => setIsReplyModalOpen(false)} className="w-10 h-10 md:w-14 md:h-14 flex items-center justify-center bg-stone-100 hover:bg-amber-400 text-stone-900 rounded-full transition-all group active:scale-95 shrink-0" > - - close - + close
- {/* Comments Feed */}
{comments?.length > 0 ? ( comments.map((c) => ( @@ -320,7 +357,6 @@ export default function Profile() { className="w-8 h-8 md:w-12 md:h-12 rounded-xl md:rounded-2xl object-cover shrink-0 shadow-md border-2 border-white ring-1 ring-stone-100" alt="User" /> -
@@ -331,39 +367,28 @@ export default function Profile() { {new Date(c.createdAt).toLocaleDateString()}
- {user?.user?._id === c.userId?._id && ( )}
- -
-

- {c.comment} -

-
+

+ {c.comment} +

)) ) : (
- - forum - -

- Be the first to speak -

+ forum +

Be the first to speak

)}
- {/* Textarea wrapper */}