JourneyPoint / journeypoint / frontend / src / Components / Home.js
Home.js
Raw
import React from 'react'
import "leaflet/dist/leaflet.css"
import { MapContainer, TileLayer, Marker, Popup, useMapEvents } from "react-leaflet"
import Navbar from "./Navbar"
import L from "leaflet";
import pin from "../images/pin.png";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

const Home = () => {
  const [pins, setPins] = useState([]);
  const [username, setUsername] = useState('');
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [pinMode, setPinMode] = useState(false);
  const [showPinForm, setShowPinForm] = useState(false);
  const [currentPinLocation, setCurrentPinLocation] = useState(null);
  const [pinDetails, setPinDetails] = useState({
    pin_name: '',
    description: '',
    category: '',
    public: false,
    x: 0,
    y: 0
  });

  // Fetch username and login status from localStorage
  useEffect(() => {
    const storedUsername = localStorage.getItem('username');
    const storedLoginStatus = localStorage.getItem('login');

    if (storedUsername && storedLoginStatus === 'true') {
      setUsername(storedUsername);
      setIsLoggedIn(true);
      fetchPins(storedUsername);
    } else {
      fetchPins(null);
    }
  }, []);

  // Fetch pins from server
  const fetchPins = (username) => {
    fetch(`https://jp-backend-kc80.onrender.com/api/pins`)
      .then(response => response.json())
      .then(data => {
        // Filter pins: show public ones OR private ones belonging to the logged-in user
        const filteredPins = data.filter(pin =>
          pin.public || (username && pin.username === username)
        );

        const formattedPins = filteredPins.map(pin => ({
          ...pin,
          lat: pin.y,
          lng: pin.x
        }));
        setPins(formattedPins);
      })
      .catch(error => console.error("Error fetching pins:", error));
  };

  // Handle form input changes
  const handleInputChange = (e) => {
    const { name, value, type, checked } = e.target;
    setPinDetails(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  // Function to handle pin placement
  const MapClickHandler = () => {
    useMapEvents({
      click: (e) => {
        const { lat, lng } = e.latlng;
        setCurrentPinLocation({ lat, lng });
        setPinDetails(prev => ({
          ...prev,
          x: lng,
          y: lat
        }));
        setShowPinForm(true);
      },
    });
    return null;
  };

  // Function to save pin with details
  const savePinWithDetails = () => {
    if (!currentPinLocation || !username) return;

    const newPin = {
      ...pinDetails,
      username: username,
      x: currentPinLocation.lng,
      y: currentPinLocation.lat,
      lat: currentPinLocation.lat,
      lng: currentPinLocation.lng
    };

    // Send to backend using same URL format as GET
    fetch(`https://jp-backend-kc80.onrender.com/api/pins?username=${username}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        pin_name: pinDetails.pin_name,
        username: username,
        x: currentPinLocation.lng,
        y: currentPinLocation.lat,
        description: pinDetails.description,
        category: pinDetails.category,
        public: pinDetails.public
      }),
    })
      .then((response) => response.json())
      .then((data) => {
        console.log("Pin saved:", data);
        // Update local state only after successful save
        setPins((prevPins) => [...prevPins, newPin]);
        setShowPinForm(false);
        setPinDetails({
          pin_name: '',
          description: '',
          category: '',
          public: false,
          x: 0,
          y: 0
        });
        // Refresh pins from server
        fetchPins(username);
      })
      .catch((error) => {
        console.error("Error saving pin:", error);
        alert("Failed to save pin. Please try again.");
      });
  };

  // Custom pin icon
  const pinIcon = new L.Icon({
    iconUrl: pin,
    iconSize: [24, 40],
    iconAnchor: [15, 40],
    popupAnchor: [0, -40],
  });

  return (
    <div className="home-container">
      <link rel="stylesheet" href="../App.css"></link>
      <Navbar />

      <div className="container-fluid p-4">
        <div className="row">
          <div className="col">
            <MapContainer center={[43.7, -79.42]} zoom={10} id='map'  className="border rounded shadow-sm">
              <TileLayer
                attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              />
              {pinMode && <MapClickHandler />}

              {pins.map((pin, index) => (
                <Marker key={index} position={[pin.lat, pin.lng]} icon={pinIcon}>
                  <Popup>
                    <strong>{pin.pin_name || "Unnamed Pin"}</strong>
                    <br />
                    {pin.description || "No description available"}
                    <br />
                    <em>By: {pin.username}</em>
                    <br />
                    <small>Category: {pin.category || "Unknown"}</small>
                    {pin.public ? (
                      <div><small>Status: Public</small></div>
                    ) : (
                      <div><small>Status: Private (only visible to you)</small></div>
                    )}
                  </Popup>
                </Marker>
              ))}
            </MapContainer>
          </div>
          <div className="col text-center">
            {/* Dynamic Heading */}
            <h1 className="display-1 mb-4">
              {username ? `Welcome back, ${username}!` : "Welcome to JourneyPoint!"}
            </h1>

            <div className="row mt-5">
              {/* First Section */}
              <h2 className='display-5'>
                {username ? "Ready for your next trip?" : "Explore New Destinations"}
              </h2>
              {username ? "" : "Discover new places by exploring pins from travelers around the world. Get inspiration for your next adventure by seeing where others have been and reading their experiences. Whether you're planning a trip or just dreaming of one, JourneyPoint helps you find your next destination!"}

              {/* Second Section */}
              <div className="row mt-5">
                <h2 className='display-5'>
                  {username ? "" : "Share Your Experiences"}
                </h2>
                {username ? "" : "Share your travel stories and experiences with the JourneyPoint community. Pin your favorite locations, add descriptions, and connect with fellow travelers who have visited the same spots. Engage in real-time conversations and inspire others with your adventures!"}
              </div>

              <div className="row mt-5">
                <h2 className='display-5'>
                  {username ? "" : "Completely Free"}
                </h2>
                {username ? "" : ""}
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Floating pin button */}
      <button
        className={`pin-button ${pinMode ? 'active' : ''}`}
        onClick={() => setPinMode(!pinMode)}
        disabled={!isLoggedIn}
        title={!isLoggedIn ? "Please log in to add pins" : ""}
      >
        <img src={pin} alt="Pin" width="24" height="24" />
      </button>

      {/* Pin Details Form Modal */}
      {showPinForm && (
        <div className="pin-form-modal">
          <div className="pin-form-content">
            <h3>Add Pin Details</h3>
            <div className="form-group">
              <label>Pin Name:</label>
              <input
                type="text"
                name="pin_name"
                value={pinDetails.pin_name}
                onChange={handleInputChange}
                required
              />
            </div>
            <div className="form-group">
              <label>Description:</label>
              <textarea
                name="description"
                value={pinDetails.description}
                onChange={handleInputChange}
              />
            </div>
            <div className="form-group">
              <label>Category:</label>
              <select
                name="category"
                value={pinDetails.category}
                onChange={handleInputChange}
                required
              >
                <option value="">Select a category</option>
                <option value="Landmark">Landmark</option>
                <option value="Restaurant">Restaurant</option>
                <option value="Nature">Nature</option>
                <option value="Event">Event</option>
                <option value="Other">Other</option>
              </select>
            </div>
            <div className="form-group checkbox">
              <label>
                <input
                  type="checkbox"
                  name="public"
                  checked={pinDetails.public}
                  onChange={handleInputChange}
                />
                Public Pin
              </label>
            </div>
            <div className="form-group">
              <label>Coordinates:</label>
              <p>X (Longitude): {pinDetails.x.toFixed(5)}</p>
              <p>Y (Latitude): {pinDetails.y.toFixed(5)}</p>
            </div>
            <div className="form-actions">
              <button onClick={() => setShowPinForm(false)}>Cancel</button>
              <button onClick={savePinWithDetails}>Save Pin</button>
            </div>
          </div>
        </div>
      )}

      {/* Styles */}
      <style>
        {`
          #map {
            position: relative;
            border: 5px solid #000000 !important;
            border-radius: 20px;
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
            overflow: hidden; /* Ensures rounded corners work properly */
          }

          .pin-button {
            position: absolute;
            bottom: 50px;
            left: 45px;
            background-color: ${pinMode ? '#154475' : '#007bff'};
            color: white;
            border: none;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: background-color 0.3s;
          }
          .pin-button:hover {
            background-color: ${pinMode ? '#0d2a47' : '#0056b3'};
          }
          .pin-button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
          }
          .pin-button img {
            width: 20px;
            height: 35px;
          }
          .pin-form-modal {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0,0,0,0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 2000;
          }
          .pin-form-content {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            width: 400px;
            max-width: 90%;
          }
          .form-group {
            margin-bottom: 15px;
          }
          .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
          }
          .form-group input,
          .form-group textarea,
          .form-group select {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
          }
          .form-group textarea {
            height: 80px;
          }
          .form-group.checkbox {
            display: flex;
            align-items: center;
          }
          .form-group.checkbox input {
            width: auto;
            margin-right: 10px;
          }
          .form-actions {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 20px;
          }
          .form-actions button {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
          }
          .form-actions button:last-child {
            background-color: #007bff;
            color: white;
          }
          .form-actions button:last-child:hover {
            background-color: #0056b3;
          }
        `}
      </style>
    </div>
  );
}

export default Home;