JourneyPoint / journeypoint / frontend / src / Components / Explore.js
Explore.js
Raw
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;