import React, { useState, useRef, useEffect } from "react"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; import pinIconImage from "../images/pin.png"; import Navbar from "./Navbar"; import { useLocation, useNavigate } from 'react-router-dom'; // Utility function to introduce a delay const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const Pin = () => { // Address search state const [address, setAddress] = useState(""); const [suggestions, setSuggestions] = useState([]); const [selectedLocation, setSelectedLocation] = useState(null); const [fullAddress, setFullAddress] = useState(""); // Pin state 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 }); const mapRef = useRef(null); const mapInstance = useRef(null); const markersRef = useRef([]); const location = useLocation(); const navigate = useNavigate(); // Custom pin icon const pinIcon = new L.Icon({ iconUrl: pinIconImage, iconSize: [24, 40], iconAnchor: [15, 40], popupAnchor: [0, -40], }); // Initialize map and fetch user data useEffect(() => { // Check login status const storedUsername = localStorage.getItem('username'); const storedLoginStatus = localStorage.getItem('login'); if (storedUsername && storedLoginStatus === 'true') { setUsername(storedUsername); setIsLoggedIn(true); fetchPins(storedUsername); } else { fetchPins(null); } // Initialize map if (!mapInstance.current) { const initialCenter = location.state?.pinLocation || [43.7, -79.42]; const initialZoom = location.state?.pinLocation ? 14 : 10; const map = L.map(mapRef.current, { center: initialCenter, zoom: initialZoom, }); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors", }).addTo(map); mapInstance.current = map; } // Cleanup markers on unmount return () => { markersRef.current.forEach(marker => { if (marker && mapInstance.current) { mapInstance.current.removeLayer(marker); } }); }; }, []); // Handle incoming pin location from state useEffect(() => { if (location.state?.pinLocation && mapInstance.current) { const { lat, lng } = location.state.pinLocation; mapInstance.current.flyTo([lat, lng], 14); // Clear the state after handling it to prevent re-centering navigate('/visit', { replace: true, state: {} }); } }, [location.state, navigate]); // Handle map click for pin placement useEffect(() => { if (!mapInstance.current) return; const handleMapClick = (e) => { if (pinMode) { const { lat, lng } = e.latlng; setCurrentPinLocation({ lat, lng }); setPinDetails(prev => ({ ...prev, x: lng, y: lat })); setShowPinForm(true); } }; // Add click event listener mapInstance.current.on('click', handleMapClick); // Cleanup return () => { if (mapInstance.current) { mapInstance.current.off('click', handleMapClick); } }; }, [pinMode]); // Update pins on the map when pins state changes useEffect(() => { if (!mapInstance.current) return; // Clear existing markers markersRef.current.forEach(marker => { if (marker) { mapInstance.current.removeLayer(marker); } }); markersRef.current = []; // Add new markers pins.forEach((pin) => { const marker = L.marker([pin.y, pin.x], { icon: pinIcon }) .addTo(mapInstance.current) .bindPopup(` ${pin.pin_name || "Unnamed Pin"}
${pin.description || "No description available"}
By: ${pin.username}
Category: ${pin.category || "Unknown"} ${pin.public ? '
Status: Public
' : '
Status: Private (only visible to you)
'} `); markersRef.current.push(marker); }); }, [pins]); // 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) ); setPins(filteredPins); }) .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 save pin with details const savePinWithDetails = () => { if (!currentPinLocation || !username) return; const newPin = { ...pinDetails, username: username, x: currentPinLocation.lng, y: currentPinLocation.lat }; // Send to backend 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); setPins((prevPins) => [...prevPins, newPin]); setShowPinForm(false); setPinDetails({ pin_name: '', description: '', category: '', public: false, x: 0, y: 0 }); fetchPins(username); }) .catch((error) => { console.error("Error saving pin:", error); alert("Failed to save pin. Please try again."); }); }; // Handle input change for address search const handleInputChangeAddress = async (e) => { const value = e.target.value; setAddress(value); if (value.length >= 1) { try { await delay(2000); const response = await fetch( `https://photon.komoot.io/api/?q=${value}&limit=5` ); const data = await response.json(); setSuggestions(data.features); } catch (error) { console.error("Error fetching data from Photon:", error); } } else { setSuggestions([]); } }; // Handle the user selecting a suggestion const handleSuggestionClick = (suggestion) => { const [lon, lat] = suggestion.geometry.coordinates; setSelectedLocation({ lat, lon }); setSuggestions([]); const addressString = [ suggestion.properties.name, suggestion.properties.city, suggestion.properties.postcode, suggestion.properties.country, ] .filter((part) => part) .join(", "); setFullAddress(addressString); setAddress(addressString); }; // Handle the user clicking the "Visit" button const handleVisitClick = () => { if (selectedLocation && mapInstance.current) { const map = mapInstance.current; map.flyTo([selectedLocation.lat, selectedLocation.lon], 14); } }; return (
{/* Floating pin button */}
{suggestions.length > 0 && address.trim().length >= 1 && (
    {suggestions.map((suggestion, index) => { const formattedAddress = [ suggestion.properties.name, suggestion.properties.city, suggestion.properties.postcode, suggestion.properties.country, ] .filter((part) => part) .join(", "); return (
  • handleSuggestionClick(suggestion)} > {formattedAddress}
  • ); })}
)}
{/* Pin Details Form Modal */} {showPinForm && (

Add Pin Details