diff --git a/components/observatory/map-component.tsx b/components/observatory/map-component.tsx new file mode 100644 index 00000000..3b3b377f --- /dev/null +++ b/components/observatory/map-component.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { useEffect, useState } from "react"; +import L from "leaflet"; +import { MapContainer, TileLayer, Marker, Popup, ZoomControl } from "react-leaflet"; + +// Import leaflet CSS +import "leaflet/dist/leaflet.css"; + +// Add declaration for Leaflet's Icon.Default +declare module 'leaflet' { + namespace Icon { + interface Default { + _getIconUrl?: string; + } + } +} + +interface CountryData { + name: string; + count: number; + position: [number, number]; // latitude, longitude +} + +interface MapComponentProps { + countries: CountryData[]; + onCountrySelect: (country: string) => void; + selectedCountry: string | null; +} + +// Fix Leaflet default icon path issues +function fixLeafletIcons() { + // Delete to prevent duplicate icon definitions + delete L.Icon.Default.prototype._getIconUrl; + + // Set paths to the images + L.Icon.Default.mergeOptions({ + iconRetinaUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png", + iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png", + shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png", + }); +} + +export function MapComponent({ countries, onCountrySelect, selectedCountry }: MapComponentProps) { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + // Fix Leaflet icons on client side + fixLeafletIcons(); + setIsClient(true); + }, []); + + // Get custom icon based on count + const getMarkerIcon = (count: number, isSelected: boolean) => { + const size = Math.min(Math.max(20, count * 5), 40); + + return L.divIcon({ + html: `
${count}
`, + className: '', + iconSize: [size, size], + iconAnchor: [size/2, size/2] + }); + }; + + if (!isClient) { + return ( +
+

Loading map...

+
+ ); + } + + return ( + + + + + {countries.map((country) => ( + onCountrySelect(country.name) + }} + > + +
+ {country.name} +
{country.count} news articles
+
+
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/components/observatory/observatory-map.tsx b/components/observatory/observatory-map.tsx index d73a5e46..d89ed88e 100644 --- a/components/observatory/observatory-map.tsx +++ b/components/observatory/observatory-map.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; interface CountryData { @@ -90,27 +90,17 @@ const COUNTRY_COORDINATES: Record = { 'United Nations': [40.7, -74.0], // UN HQ in New York }; -// Dynamic imports for Leaflet components to avoid SSR issues -// We use "any" type here to avoid TypeScript errors with dynamic imports -const MapContainer: any = dynamic( - () => import('react-leaflet').then(mod => mod.MapContainer), - { ssr: false } -); -const TileLayer: any = dynamic( - () => import('react-leaflet').then(mod => mod.TileLayer), - { ssr: false } -); -const Marker: any = dynamic( - () => import('react-leaflet').then(mod => mod.Marker), - { ssr: false } -); -const Popup: any = dynamic( - () => import('react-leaflet').then(mod => mod.Popup), - { ssr: false } -); -const ZoomControl: any = dynamic( - () => import('react-leaflet').then(mod => mod.ZoomControl), - { ssr: false } +// Create a client-only map component to avoid SSR issues +const MapComponent = dynamic( + () => import('./map-component').then((mod) => mod.MapComponent), + { + ssr: false, + loading: () => ( +
+

Loading map...

+
+ ), + } ); export function ObservatoryMap({ @@ -118,51 +108,6 @@ export function ObservatoryMap({ onCountrySelect, selectedCountry }: ObservatoryMapProps) { - const [isMounted, setIsMounted] = useState(false); - const [mapKey, setMapKey] = useState(Date.now()); // Unique key for map container - - // We'll need the Leaflet CSS - useEffect(() => { - // Import Leaflet CSS only on the client - const loadLeafletStyles = async () => { - try { - await import('leaflet/dist/leaflet.css'); - } catch (e) { - console.error('Failed to load Leaflet CSS', e); - } - }; - - loadLeafletStyles(); - - // Add marker icons to prevent missing icons issue - if (typeof window !== 'undefined') { - // Fix Leaflet's icon paths - delete window._leaflet_L; - - try { - const L = require('leaflet'); - - // Set default icon paths - delete L.Icon.Default.prototype._getIconUrl; - L.Icon.Default.mergeOptions({ - iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', - iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', - shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', - }); - } catch (e) { - console.error('Failed to load Leaflet', e); - } - } - - setIsMounted(true); - - // Return cleanup function - return () => { - // Generate a new key if the component is unmounted and remounted - setMapKey(Date.now()); - }; - }, []); - // Prepare countries with coordinates const countriesWithCoordinates = countries.filter(country => { return COUNTRY_COORDINATES[country.name] !== undefined; @@ -174,79 +119,13 @@ export function ObservatoryMap({ // Sort countries by count (higher count = shows on top) const sortedCountries = [...countriesWithCoordinates].sort((a, b) => b.count - a.count); - // Get custom icon based on count - const getMarkerIcon = (count: number, isSelected: boolean) => { - if (typeof window === 'undefined') return null; - - try { - const L = require('leaflet'); - const size = Math.min(Math.max(20, count * 5), 40); - - return L.divIcon({ - html: `
${count}
`, - className: '', - iconSize: [size, size], - iconAnchor: [size/2, size/2] - }); - } catch (e) { - console.error('Failed to create icon', e); - return null; - } - }; - - if (!isMounted) { - return ( -
-

Loading map...

-
- ); - } - return (
- - - - - {sortedCountries.map((country) => ( - onCountrySelect(country.name) - }} - > - -
- {country.name} -
{country.count} news articles
-
-
-
- ))} -
+
); } \ No newline at end of file diff --git a/components/observatory/observatory-view.tsx b/components/observatory/observatory-view.tsx index 333b52b5..8f82a777 100644 --- a/components/observatory/observatory-view.tsx +++ b/components/observatory/observatory-view.tsx @@ -23,6 +23,12 @@ export function ObservatoryView() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedCountry, setSelectedCountry] = useState(null); + const [isBrowser, setIsBrowser] = useState(false); + + // Check if we're in the browser + useEffect(() => { + setIsBrowser(true); + }, []); // Fetch news data const fetchNews = async () => { @@ -186,7 +192,7 @@ export function ObservatoryView() {
- {!loading && ( + {!loading && isBrowser && ( ({