diff --git a/package.json b/package.json index c4805b3..d527454 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.27.0" + "react-router-dom": "^6.27.0", + "leaflet": "^1.9.4", + "react-leaflet": "^4.2.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", diff --git a/src/components/IssMap.jsx b/src/components/IssMap.jsx new file mode 100644 index 0000000..e908ea0 --- /dev/null +++ b/src/components/IssMap.jsx @@ -0,0 +1,57 @@ +import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; +import L from 'leaflet'; +import { useEffect, useRef } from 'react'; +import 'leaflet/dist/leaflet.css'; + +// Default Leaflet icon fix (since CRA/Vite bundling sometimes needs explicit paths) +const icon = new L.Icon({ + iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', + iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', + shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41] +}); + +/** + * IssMap component + * Props: + * - latitude (string or number) + * - longitude (string or number) + */ +export default function IssMap({ latitude, longitude }) { + const lat = parseFloat(latitude); + const lon = parseFloat(longitude); + const mapRef = useRef(null); + + useEffect(() => { + if (mapRef.current) { + mapRef.current.setView([lat, lon]); + } + }, [lat, lon]); + + if (Number.isNaN(lat) || Number.isNaN(lon)) return null; + + return ( +
+ + + + + ISS Position
Lat: {lat.toFixed(2)} Lon: {lon.toFixed(2)} +
+
+
+
+ ); +} diff --git a/src/pages/Space.jsx b/src/pages/Space.jsx index 6d38afd..01b38e9 100644 --- a/src/pages/Space.jsx +++ b/src/pages/Space.jsx @@ -17,18 +17,28 @@ * - [ ] Track path trail (polyline) on map over session * - [ ] Extract map component & custom hook (useIssPosition) */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import Loading from '../components/Loading.jsx'; import ErrorMessage from '../components/ErrorMessage.jsx'; import Card from '../components/Card.jsx'; +import IssMap from '../components/IssMap.jsx'; export default function Space() { const [iss, setIss] = useState(null); const [crew, setCrew] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); + const [lastUpdated, setLastUpdated] = useState(null); + const intervalRef = useRef(null); - useEffect(() => { fetchData(); }, []); + useEffect(() => { + fetchData(); + // Poll every 5s for updated ISS position only + intervalRef.current = setInterval(() => { + refreshIssOnly(); + }, 5000); + return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; + }, []); async function fetchData() { try { @@ -42,9 +52,23 @@ export default function Space() { const crewJson = await crewRes.json(); setIss(issJson); setCrew(crewJson.people || []); + setLastUpdated(new Date()); } catch (e) { setError(e); } finally { setLoading(false); } } + async function refreshIssOnly() { + try { + const res = await fetch('http://api.open-notify.org/iss-now.json'); + if (!res.ok) throw new Error('Failed to refresh ISS'); + const issJson = await res.json(); + setIss(issJson); + setLastUpdated(new Date()); + } catch (e) { + // don't clobber existing data, but surface error + setError(e); + } + } +//leaflet map component return (

Space & Astronomy

@@ -54,7 +78,8 @@ export default function Space() {

Latitude: {iss.iss_position.latitude}

Longitude: {iss.iss_position.longitude}

- {/* TODO: Render map (Leaflet) with marker for ISS position */} + {lastUpdated &&

Last updated: {lastUpdated.toLocaleTimeString()}

} +
)}