import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import Navbar from './Navbar';
import Footer from './footer';
import './Explore.css';
const Explore = () => {
const navigate = useNavigate();
const [publicPosts, setPublicPosts] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isSearching, setIsSearching] = useState(false);
const [showPostDetailModal, setShowPostDetailModal] = useState(false);
const [selectedPost, setSelectedPost] = useState(null);
const [users, setUsers] = useState({});
const [likedPosts, setLikedPosts] = useState([]);
const [newComment, setNewComment] = useState('');
const [pins, setPins] = useState([]);
// Fetch all public posts and pins
useEffect(() => {
const fetchPublicPosts = async () => {
setIsLoading(true);
try {
// Fetch public posts
const postsResponse = await fetch('https://jp-backend-kc80.onrender.com/api/userposts');
const postsData = await postsResponse.json();
const filteredPosts = Array.isArray(postsData) ? postsData.filter(post => post.public) : [];
setPublicPosts(filteredPosts);
// Fetch all pins
const pinsResponse = await fetch('https://jp-backend-kc80.onrender.com/api/pins');
const pinsData = await pinsResponse.json();
setPins(Array.isArray(pinsData) ? pinsData : []);
// Fetch user data for each post's author
const uniqueUsernames = [...new Set(filteredPosts.map(post => post.username))];
const usersData = {};
for (const username of uniqueUsernames) {
const userResponse = await fetch(`https://jp-backend-kc80.onrender.com/api/users?username=${username}`);
const userData = await userResponse.json();
if (userData.length > 0) {
usersData[username] = userData[0];
}
}
setUsers(usersData);
// Load liked posts from localStorage
const storedLikes = localStorage.getItem('likedPosts');
if (storedLikes) {
setLikedPosts(JSON.parse(storedLikes));
}
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setIsLoading(false);
}
};
fetchPublicPosts();
}, []);
// Handle search for users
const handleSearch = async (e) => {
e.preventDefault();
if (!searchQuery.trim()) return;
setIsSearching(true);
try {
const response = await fetch('https://jp-backend-kc80.onrender.com/api/getuser');
const users = await response.json();
// Filter users based on search query (case insensitive)
const filteredUsers = Array.isArray(users)
? users.filter(user =>
user.username.toLowerCase().includes(searchQuery.toLowerCase()))
: [];
setSearchResults(filteredUsers);
} catch (error) {
console.error('Error searching users:', error);
} finally {
setIsSearching(false);
}
};
// Handle post click
const handlePostClick = (post) => {
setSelectedPost(post);
setShowPostDetailModal(true);
};
// Handle like post
const handleLikePost = async (postId) => {
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
const newLikedPosts = [...likedPosts, postId];
setLikedPosts(newLikedPosts);
localStorage.setItem('likedPosts', JSON.stringify(newLikedPosts));
// Update the selected post's like count
if (selectedPost && selectedPost.id === postId) {
setSelectedPost(prev => ({
...prev,
likes: result.likes
}));
}
// Update the post in the main posts list
setPublicPosts(prev =>
prev.map(post =>
post.id === postId
? { ...post, likes: result.likes }
: post
)
);
} catch (error) {
console.error('Error liking post:', error);
}
};
// Handle add comment
const handleAddComment = async (e) => {
e.preventDefault();
if (!newComment.trim() || !selectedPost || !localStorage.getItem('username')) return;
const storedUsername = localStorage.getItem('username');
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: storedUsername,
comment: newComment
}),
});
if (!response.ok) throw new Error('Failed to add comment');
// Create the new comment entry
const newCommentEntry = {
[localStorage.getItem('username')]: newComment
};
// Update the selected post's comments
setSelectedPost(prev => ({
...prev,
comments: {
...(prev.comments || {}),
...newCommentEntry
}
}));
// Also update the post in the main posts list
setPublicPosts(prev =>
prev.map(post =>
post.id === selectedPost.id
? {
...post,
comments: {
...(post.comments || {}),
...newCommentEntry
}
}
: post
)
);
setNewComment('');
} catch (error) {
console.error('Error adding comment:', error);
}
};
// Navigate to user's profile
const navigateToProfile = (username) => {
navigate(`/profile/${username}`);
};
return (
<div className="explore-page">
<Navbar />
<div className="explore-container">
{/* Search Bar */}
<div className="search-container">
<form onSubmit={handleSearch}>
<input
type="text"
placeholder="Search users..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<button type="submit" disabled={isSearching}>
{isSearching ? 'Searching...' : 'Search'}
</button>
</form>
</div>
{/* Search Results */}
{searchResults.length > 0 && (
<div className="search-results">
<h3>Search Results</h3>
<div className="users-grid">
{searchResults.map(user => (
<div
key={user.id}
className="user-card"
onClick={() => navigateToProfile(user.username)}
>
<img
src={user.profile_picture || 'https://journeypoint-bucket.s3.us-east-2.amazonaws.com/uploads/default_profile_pic.jpg'}
alt={user.username}
className="user-avatar"
/>
<span className="username">{user.username}</span>
</div>
))}
</div>
</div>
)}
{/* Public Posts Grid */}
<h2 className="section-title">Explore Public Posts</h2>
{isLoading ? (
<div className="loading">Loading posts...</div>
) : publicPosts.length > 0 ? (
<div className="posts-grid">
{publicPosts.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">
<p>No public posts available.</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-info2" onClick={() => navigateToProfile(selectedPost.username)}>
<img
src={users[selectedPost.username]?.profile_picture || 'https://journeypoint-bucket.s3.us-east-2.amazonaws.com/uploads/default_profile_pic.jpg'}
alt="Profile"
className="post-user-avatar"
/>
<span className="post-username">{selectedPost.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>
{selectedPost.comments && Object.keys(selectedPost.comments).length > 0 ? (
<div className="comments-list">
{Object.entries(selectedPost.comments).map(([username, comment]) => (
<div key={`${username}-${comment}`} className="comment">
<strong>{username}:</strong> {comment}
</div>
))}
</div>
) : (
<p>No comments yet</p>
)}
</div>
{/* Add comment form */}
{localStorage.getItem('username') && (
<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>
)}
<Footer />
</div>
);
};
export default Explore;