import React, { useState, useEffect, useRef } from 'react';
import { Menu, X, Plus, Search, LogIn, LogOut, Settings, User, MapPin, List, Grid, ChevronDown, ChevronUp } from 'lucide-react';
// Main App Component
export default function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);
const [activeTab, setActiveTab] = useState('map'); // 'map', 'admin'
const [sidebarOpen, setSidebarOpen] = useState(true);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [locations, setLocations] = useState([]);
const [filteredLocations, setFilteredLocations] = useState([]);
const [selectedLocation, setSelectedLocation] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
const [viewMode, setViewMode] = useState('card'); // 'card' or 'list'
const [loading, setLoading] = useState(true);
const [mapCenter, setMapCenter] = useState([40.7128, -74.0060]); // NYC default
const [mapZoom, setMapZoom] = useState(13);
const [categories, setCategories] = useState([]);
const [selectedCategory, setSelectedCategory] = useState('all');
const [newLocation, setNewLocation] = useState(null);
const [showAddForm, setShowAddForm] = useState(false);
const [formData, setFormData] = useState({
title: '',
description: '',
category: '',
image: '',
lat: '',
lng: '',
});
const mapRef = useRef(null);
// Fetch locations from API
useEffect(() => {
// Simulated API call
setTimeout(() => {
const dummyLocations = [
{
_id: '1',
title: 'Central Park',
description: 'A large urban park in Manhattan, New York City.',
category: 'Parks',
image: 'https://images.unsplash.com/photo-1534251369789-5067c8b8602a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
coordinates: [40.7812, -73.9665],
createdAt: '2023-01-15T12:00:00Z',
rating: 4.8,
},
{
_id: '2',
title: 'Empire State Building',
description: 'A 102-story Art Deco skyscraper in Midtown Manhattan.',
category: 'Landmarks',
image: 'https://images.unsplash.com/photo-1555109307-f7d9da25c244?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
coordinates: [40.7484, -73.9857],
createdAt: '2023-01-20T14:30:00Z',
rating: 4.7,
},
{
_id: '3',
title: 'Brooklyn Bridge',
description: 'A hybrid cable-stayed/suspension bridge spanning the East River.',
category: 'Bridges',
image: 'https://images.unsplash.com/photo-1581373449483-37449f962b6c?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
coordinates: [40.7061, -73.9969],
createdAt: '2023-01-25T09:15:00Z',
rating: 4.6,
},
{
_id: '4',
title: 'Times Square',
description: 'A major commercial intersection and tourist destination.',
category: 'Entertainment',
image: 'https://images.unsplash.com/photo-1534430480872-3b5e7416a3a3?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
coordinates: [40.7580, -73.9855],
createdAt: '2023-02-01T18:45:00Z',
rating: 4.5,
},
{
_id: '5',
title: 'The High Line',
description: 'A 1.45-mile-long elevated linear park built on a former railroad spur.',
category: 'Parks',
image: 'https://images.unsplash.com/photo-1587965596700-3875ef9757e3?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
coordinates: [40.7480, -74.0048],
createdAt: '2023-02-05T11:20:00Z',
rating: 4.9,
},
];
const uniqueCategories = [...new Set(dummyLocations.map(loc => loc.category))];
setCategories(uniqueCategories);
setLocations(dummyLocations);
setFilteredLocations(dummyLocations);
setLoading(false);
// Initialize map after data is loaded
initMap();
}, 1000);
}, []);
// Initialize map using vanilla Leaflet instead of react-leaflet
const initMap = () => {
// Skip if not in map view or map already initialized
if (activeTab !== 'map' || !document.getElementById('map')) return;
// Clean up previous map instance if it exists
if (mapRef.current) {
mapRef.current.remove();
}
// Import Leaflet dynamically to avoid SSR issues
import('https://cdn.jsdelivr.net/npm/leaflet@1.9.4/+esm').then((L) => {
// Create map
const map = L.map('map').setView(mapCenter, mapZoom);
mapRef.current = map;
// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Create custom icon
const customIcon = L.icon({
iconUrl: 'https://cdn.mapmarker.io/api/v1/pin?size=50&background=%2348A9A6&icon=fa-map-marker&color=%23FFFFFF',
iconSize: [30, 30],
iconAnchor: [15, 30],
popupAnchor: [0, -30],
});
// Add markers for locations
filteredLocations.forEach(location => {
const marker = L.marker(location.coordinates, { icon: customIcon }).addTo(map);
marker.bindPopup(`
${location.title}
${location.description.substring(0, 50)}...
${location.category}
`);
marker.on('click', () => {
setSelectedLocation(location);
});
});
// Add click handler for admin mode
if (isAdmin && activeTab === 'admin') {
map.on('click', (e) => {
const { lat, lng } = e.latlng;
// Remove previous new location marker if exists
if (newLocation) {
map.eachLayer((layer) => {
if (layer._latlng &&
layer._latlng.lat === newLocation[0] &&
layer._latlng.lng === newLocation[1]) {
map.removeLayer(layer);
}
});
}
// Add new marker
const newMarker = L.marker([lat, lng]).addTo(map);
newMarker.bindPopup(`
New Location
Lat: ${lat.toFixed(6)}, Lng: ${lng.toFixed(6)}
`).openPopup();
setNewLocation([lat, lng]);
setFormData({
...formData,
lat: lat.toFixed(6),
lng: lng.toFixed(6),
});
setShowAddForm(true);
});
}
// Update map when selected location changes
if (selectedLocation) {
map.setView(selectedLocation.coordinates, 15);
}
});
};
// Re-initialize map when filtered locations change
useEffect(() => {
if (activeTab === 'map') {
initMap();
}
}, [filteredLocations, activeTab, isAdmin]);
// Clean up map on unmount
useEffect(() => {
return () => {
if (mapRef.current) {
mapRef.current.remove();
}
};
}, []);
// Filter locations based on search and category
useEffect(() => {
let filtered = [...locations];
if (searchQuery) {
filtered = filtered.filter(loc =>
loc.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
loc.description.toLowerCase().includes(searchQuery.toLowerCase())
);
}
if (selectedCategory !== 'all') {
filtered = filtered.filter(loc => loc.category === selectedCategory);
}
setFilteredLocations(filtered);
}, [searchQuery, selectedCategory, locations]);
// Handle login
const handleLogin = () => {
setIsLoggedIn(true);
setIsAdmin(true); // For demo purposes
};
// Handle logout
const handleLogout = () => {
setIsLoggedIn(false);
setIsAdmin(false);
setActiveTab('map');
};
// Handle form input changes
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
};
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
// Create new location
const newLoc = {
_id: Date.now().toString(),
title: formData.title,
description: formData.description,
category: formData.category || 'Uncategorized',
image: formData.image || 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
coordinates: [parseFloat(formData.lat), parseFloat(formData.lng)],
createdAt: new Date().toISOString(),
rating: 4.0,
};
// Add to locations
setLocations([...locations, newLoc]);
// Reset form
setFormData({
title: '',
description: '',
category: '',
image: '',
lat: '',
lng: '',
});
setShowAddForm(false);
setNewLocation(null);
};
return (
{/* Header */}
{/* Main content */}
{activeTab === 'map' ? (
<>
{/* Map section */}
{/* Map container */}
{/* Add Leaflet CSS */}
{/* Map controls */}
{/* Sidebar */}
{/* Sidebar header */}
Locations
{sidebarOpen && (
<>
{/* Search and filters */}
{/* Locations list */}
{loading ? (
) : filteredLocations.length === 0 ? (
No locations found matching your criteria.
) : (
{filteredLocations.map(location => (
{
setSelectedLocation(location);
if (mapRef.current) {
mapRef.current.setView(location.coordinates, 15);
}
}}
>
{viewMode === 'card' && (
)}
{location.title}
{viewMode === 'list' && (
{location.category}
)}
{viewMode === 'card' && (
{location.category}
)}
{location.description}
{location.coordinates[0].toFixed(2)}, {location.coordinates[1].toFixed(2)}
★ {location.rating}
))}
)}
>
)}
>
) : (
/* Admin Dashboard */
Location Management
{showAddForm ? (
) : (
Click on the map to add a new location, or use the button below to enter coordinates manually.
)}
Existing Locations
| Title |
Category |
Coordinates |
Rating |
Actions |
{locations.map(location => (
|
{location.title}
|
{location.category}
|
{location.coordinates[0].toFixed(4)}, {location.coordinates[1].toFixed(4)}
|
{location.rating}
|
|
))}
)}
);
}