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()}
}
+
)}