import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { motion } from 'framer-motion';
import Navbar from './Navbar';
import Footer from './footer';
import './ProfilePage.css';
const UserProfile = () => {
const navigate = useNavigate();
const { username } = useParams();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [isImageLoaded, setIsImageLoaded] = useState(false);
const [newComment, setNewComment] = useState('');
const [userData, setUserData] = useState({
username: '',
bio: 'No bio yet',
profilePicture: '',
posts: [],
followers: [],
following: [],
postsCount: 0,
});
const [loggedInUserData, setLoggedInUserData] = useState({
username: '',
following: []
});
const [showPostDetailModal, setShowPostDetailModal] = useState(false);
const [selectedPost, setSelectedPost] = useState(null);
const [likedPosts, setLikedPosts] = useState([]);
const [isFollowing, setIsFollowing] = useState(false);
const [isUpdatingFollow, setIsUpdatingFollow] = useState(false);
const [pins, setPins] = useState([]);
const [showFollowModal, setShowFollowModal] = useState(false);
const [followListType, setFollowListType] = useState(''); // 'followers' or 'following'
const [followList, setFollowList] = useState([]);
const [isLoadingFollowList, setIsLoadingFollowList] = useState(false);
// Default profile picture URL
const defaultProfilePic = 'https://journeypoint-bucket.s3.us-east-2.amazonaws.com/uploads/default_profile_pic.jpg';
// Check login status and fetch data
useEffect(() => {
const storedUsername = localStorage.getItem('username');
const loginStatus = localStorage.getItem('login');
if (loginStatus === 'true' && storedUsername) {
setIsLoggedIn(true);
setLoggedInUserData(prev => ({
...prev,
username: storedUsername
}));
fetchLoggedInUserData(storedUsername);
fetchUserData(username);
fetchUserPosts(username);
fetchUserPins(username);
} else {
navigate('/login');
}
}, [navigate, username]);
useEffect(() => {
if (loggedInUserData.username && userData.username) {
fetchUserPosts(userData.username);
}
}, [isFollowing, loggedInUserData.username, userData.username]);
// Check if logged in user is following this profile
useEffect(() => {
if (loggedInUserData.username && userData.followers) {
setIsFollowing(userData.followers.includes(loggedInUserData.username));
}
}, [loggedInUserData.username, userData.followers]);
// Fetch user pins
const fetchUserPins = async (username) => {
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/pins?username=${username}`);
const data = await response.json();
setPins(Array.isArray(data) ? data : []);
} catch (error) {
console.error('Error fetching pins:', error);
}
};
// Fetch user profile data
const fetchUserData = async (username) => {
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/getuser?username=${username}`);
const user = await response.json();
if (user.length > 0) {
setUserData(prev => ({
...prev,
username: user[0].username,
bio: user[0].bio || 'No bio yet',
profilePicture: user[0].profile_picture,
followers: user[0].followers || [],
following: user[0].following || [],
}));
}
} catch (error) {
console.error('Error fetching user data:', error);
}
};
// Fetch logged in user data
const fetchLoggedInUserData = async (username) => {
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/getuser?username=${username}`);
const user = await response.json();
if (user.length > 0) {
setLoggedInUserData(prev => ({
...prev,
following: user[0].following || []
}));
}
} catch (error) {
console.error('Error fetching logged in user data:', error);
}
};
const fetchUserPosts = async (username) => {
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/userposts?username=${username}`);
const data = await response.json();
// Check if viewing own profile or is a follower
const isOwnProfile = loggedInUserData.username === username;
const isFollower = userData.followers.includes(loggedInUserData.username);
// Filter posts based on visibility
const filteredPosts = Array.isArray(data)
? data.filter(post => isOwnProfile || isFollower || post.public)
: [];
setUserData(prev => ({
...prev,
posts: filteredPosts,
postsCount: filteredPosts.length
}));
} catch (error) {
console.error('Error fetching user posts:', error);
}
};
const showFollowList = async (type) => {
setFollowListType(type);
setIsLoadingFollowList(true);
setShowFollowModal(true);
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/getuser?username=${username}`);
const user = await response.json();
if (user.length > 0) {
const usernames = type === 'followers' ? user[0].followers : user[0].following;
// Fetch details for each user in the list
const usersData = await Promise.all(
usernames.map(async (username) => {
const userResponse = await fetch(`https://jp-backend-kc80.onrender.com/api/getuser?username=${username}`);
const user = await userResponse.json();
return user[0];
})
);
setFollowList(usersData.filter(user => user)); // Filter out any undefined users
}
} catch (error) {
console.error('Error fetching follow list:', error);
} finally {
setIsLoadingFollowList(false);
}
};
// Handle follow/unfollow action
const handleFollow = async () => {
if (!loggedInUserData.username || !username || isUpdatingFollow) return;
setIsUpdatingFollow(true);
try {
if (isFollowing) {
// Unfollow - remove logged in user from profile's followers and remove profile from logged in user's following
const formData = new FormData();
formData.append("unfollow_username", username);
const response1 = await fetch(`https://jp-backend-kc80.onrender.com/api/userupdate/${loggedInUserData.username}/unfollow`, {
method: 'PATCH',
body: formData,
});
const result1 = await response1.json();
if (!response1.ok) throw new Error(result1.message || 'Failed to remove follower');
// Update local state based on successful responses
setUserData(prev => ({
...prev,
followers: prev.followers.filter(follower => follower !== loggedInUserData.username)
}));
setLoggedInUserData(prev => ({
...prev,
following: prev.following.filter(following => following !== username)
}));
} else {
// Follow - add logged in user to profile's followers and add profile to logged in user's following
const formData = new FormData();
formData.append("add_follower", loggedInUserData.username);
const response1 = await fetch(`https://jp-backend-kc80.onrender.com/api/userupdate/${userData.username}`, {
method: 'PATCH',
body: formData,
});
const result1 = await response1.json();
if (!response1.ok) throw new Error(result1.message || 'Failed to add follower' + loggedInUserData.username + userData.username + result1.add_follower);
const formData2 = new FormData();
formData2.append("add_following", username);
const response2 = await fetch(`https://jp-backend-kc80.onrender.com/api/userupdate/${loggedInUserData.username}`, {
method: 'PATCH',
body: formData2,
});
const result2 = await response2.json();
if (!response2.ok) throw new Error(result2.message || 'Failed to add following');
// Update local state based on successful responses
setUserData(prev => ({
...prev,
followers: [...prev.followers, loggedInUserData.username]
}));
setLoggedInUserData(prev => ({
...prev,
following: [...prev.following, username]
}));
}
// Toggle follow state after successful updates
setIsFollowing(!isFollowing);
} catch (error) {
console.error('Error updating follow status:', error);
alert(error.message || 'Failed to update follow status. Please try again.');
} finally {
setIsUpdatingFollow(false);
}
};
const handleAddComment = async (e) => {
e.preventDefault();
if (!newComment.trim() || !selectedPost || !loggedInUserData.username) return;
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/userposts/${selectedPost.id}/comment`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: loggedInUserData.username,
comment: newComment
}),
});
if (!response.ok) throw new Error('Failed to add comment');
// Create the new comment entry
const newCommentEntry = {
[loggedInUserData.username]: newComment
};
// Update the selected post
setSelectedPost(prev => ({
...prev,
comments: {
...(prev.comments || {}),
...newCommentEntry
}
}));
// Update the post in the main posts list
setUserData(prev => ({
...prev,
posts: prev.posts.map(post =>
post.id === selectedPost.id
? {
...post,
comments: {
...(post.comments || {}),
...newCommentEntry
}
}
: post
)
}));
setNewComment('');
} catch (error) {
console.error('Error adding comment:', error);
alert('Failed to add comment. Please try again.');
}
};
const handleLikePost = async (postId) => {
// Check if user already liked this post
if (likedPosts.includes(postId)) {
return;
}
try {
const response = await fetch(`https://jp-backend-kc80.onrender.com/api/userposts/${postId}/like`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to like post');
const result = await response.json();
// Update liked posts state
setLikedPosts(prev => [...prev, postId]);
// Update the selected post's like count
setSelectedPost(prev => ({
...prev,
likes: result.likes
}));
// Also update the post in the main posts list
setUserData(prev => ({
...prev,
posts: prev.posts.map(post =>
post.id === postId
? { ...post, likes: result.likes }
: post
)
}));
} catch (error) {
console.error('Error liking post:', error);
alert('Failed to like post. Please try again.');
}
};
const canViewPost = (post) => {
return (
loggedInUserData.username === userData.username || // Own post
isFollowing || // Is a follower
post.public // Public post
);
};
// Then update your handlePostClick:
const handlePostClick = (post) => {
if (!canViewPost(post)) {
alert("You must follow this user to view their private posts");
return;
}
setSelectedPost(post);
setShowPostDetailModal(true);
};
return (
<div>
<Navbar />
<div className="profile-container">
{/* Profile Header Section */}
<div className="profile-header">
<div className="profile-pic-container" style={{ visibility: isImageLoaded ? 'visible' : 'hidden' }}>
<img
src={userData.profilePicture || defaultProfilePic}
alt="Profile"
className="profile-picture"
onLoad={() => setIsImageLoaded(true)}
onError={(e) => {
e.target.onerror = null;
e.target.src = defaultProfilePic;
setIsImageLoaded(true);
}}
/>
</div>
<div className="profile-info">
<div className="username">{userData.username}</div>
<div className="stats">
<span><strong>{userData.postsCount}</strong> posts</span>
<span
className="clickable-stat"
onClick={() => showFollowList('followers')}
>
<strong>{userData.followers.length}</strong> followers
</span>
<span
className="clickable-stat"
onClick={() => showFollowList('following')}
>
<strong>{userData.following.length}</strong> following
</span>
</div>
<div className="bio">{userData.bio}</div>
<div className="profile-buttons">
{loggedInUserData.username !== username && (
<button
className={`follow-btn ${isFollowing ? 'following' : ''}`}
onClick={handleFollow}
disabled={isUpdatingFollow}
>
{isUpdatingFollow ? '...' : (isFollowing ? 'Following' : 'Follow')}
</button>
)}
</div>
</div>
</div>
{/* Posts Grid Section */}
{userData.posts.length > 0 ? (
<div className="posts-grid">
{userData.posts.map((post, index) => (
<div key={index} className="post-item" onClick={() => handlePostClick(post)}>
<img src={post.image_url} alt={`Post ${index}`} />
<div className="post-overlay">
<span className="post-likes">❤️ {post.likes || 0}</span>
<span className="post-comments">💬 {Object.keys(post.comments || {}).length}</span>
</div>
</div>
))}
</div>
) : (
<div className="no-posts">
{loggedInUserData.username === username ? (
<>
<h3>Share Photos</h3>
<p>When you share photos, they will appear on your profile.</p>
</>
) : isFollowing ? (
<p>{userData.username} hasn't posted anything yet.</p>
) : (
<p>{userData.username} only shares posts with followers.</p>
)}
</div>
)}
</div>
{/* Post Detail Modal */}
{showPostDetailModal && selectedPost && (
<div className="popup-overlay" onClick={() => setShowPostDetailModal(false)}>
<motion.div
className="popup-content post-detail-modal"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={(e) => e.stopPropagation()}
>
<div className="post-detail-container">
<div className="post-image-container">
<img
src={selectedPost.image_url}
alt="Post"
className="post-detail-image"
/>
</div>
<div className="post-details">
<div className="post-user-info">
<img
src={userData.profilePicture || defaultProfilePic}
alt="Profile"
className="post-user-avatar"
/>
<span className="post-username">{userData.username}</span>
</div>
<div className="post-description">
<p>{selectedPost.description}</p>
</div>
<div className="post-tags">
<strong>Tags:</strong> {selectedPost.tags || 'No tags'}
</div>
{/* Add this section for the pin location */}
{selectedPost.pin_id && (
<div className="post-pin-info">
<strong>Location:</strong>
{pins.find(pin => pin.id === selectedPost.pin_id)?.pin_name || 'Unknown location'}
<button
className="go-to-pin-btn"
onClick={() => {
const pin = pins.find(p => p.id === selectedPost.pin_id);
if (pin) {
navigate('/visit', {
state: {
pinLocation: { lat: pin.y, lng: pin.x }
}
});
}
}}
>
Go to Pin
</button>
</div>
)}
<div className="post-stats">
<button
className={`like-button ${likedPosts.includes(selectedPost.id) ? 'liked' : ''}`}
onClick={() => handleLikePost(selectedPost.id)}
disabled={likedPosts.includes(selectedPost.id)}
>
{likedPosts.includes(selectedPost.id) ? (
'❤️ Liked'
) : (
`❤️ ${selectedPost.likes || 0} likes`
)}
</button>
<span>💬 {Object.keys(selectedPost.comments || {}).length} comments</span>
</div>
{/* Comments section */}
<div className="post-comments-section">
<h4>Comments</h4>
{Object.keys(selectedPost.comments || {}).length > 0 ? (
<div className="comments-list">
{Object.entries(selectedPost.comments || {}).map(([username, comment]) => (
<div key={username} className="comment">
<strong>{username}:</strong> {comment}
</div>
))}
</div>
) : (
<p>No comments yet</p>
)}
</div>
{isLoggedIn && (
<div className="add-comment">
<form onSubmit={handleAddComment}>
<input
type="text"
placeholder="Add a comment..."
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
/>
<button type="submit">Post</button>
</form>
</div>
)}
<div className="post-date">
Posted on: {new Date(selectedPost.created_at).toLocaleDateString()}
</div>
</div>
</div>
</motion.div>
</div>
)}
{showFollowModal && (
<div className="popup-overlay" onClick={() => setShowFollowModal(false)}>
<motion.div
className="popup-content follow-list-modal"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={(e) => e.stopPropagation()}
>
<div className="follow-modal-header">
<h3>{followListType === 'followers' ? 'Followers' : 'Following'}</h3>
<button
className="close-modal-btn"
onClick={() => setShowFollowModal(false)}
>
×
</button>
</div>
{isLoadingFollowList ? (
<div className="loading">Loading...</div>
) : followList.length > 0 ? (
<div className="follow-list">
{followList.map((user) => (
<div
key={user.username}
className="follow-item"
onClick={() => {
setShowFollowModal(false);
navigate(`/profile/${user.username}`);
}}
>
<img
src={user.profile_picture || defaultProfilePic}
alt={user.username}
className="follow-avatar"
onError={(e) => {
e.target.onerror = null;
e.target.src = defaultProfilePic;
}}
/>
<span className="follow-username">{user.username}</span>
</div>
))}
</div>
) : (
<div className="empty-list">
No {followListType === 'followers' ? 'followers' : 'following'} yet
</div>
)}
</motion.div>
</div>
)}
<Footer />
</div>
);
};
export default UserProfile;