diff --git a/backend/src/app.js b/backend/src/app.js index 8a6be6b..27f1dd2 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -35,7 +35,13 @@ app.use(helmet({ scriptSrc: ["'self'", "'unsafe-inline'", "https://maps.googleapis.com"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], fontSrc: ["'self'", "https://fonts.gstatic.com"], - imgSrc: ["'self'", "data:", "https://maps.googleapis.com", "https://maps.gstatic.com"], + imgSrc: [ + "'self'", + "data:", + "https://maps.googleapis.com", + "https://maps.gstatic.com", + "https://openweathermap.org" + ], connectSrc: ["'self'", "https://api.openweathermap.org"] } } diff --git a/backend/src/routes/weather.js b/backend/src/routes/weather.js index 2e19349..5a8f286 100644 --- a/backend/src/routes/weather.js +++ b/backend/src/routes/weather.js @@ -220,6 +220,7 @@ router.get('/:propertyId/forecast', async (req, res, next) => { windSpeed: [], precipitation: 0, conditions: [], + icons: [], timestamps: [] }; } @@ -229,6 +230,7 @@ router.get('/:propertyId/forecast', async (req, res, next) => { dailyForecast[date].windSpeed.push(item.wind?.speed || 0); dailyForecast[date].precipitation += item.rain?.['3h'] || 0; dailyForecast[date].conditions.push(item.weather[0].description); + dailyForecast[date].icons.push(item.weather[0].icon); dailyForecast[date].timestamps.push(new Date(item.dt * 1000)); }); @@ -246,6 +248,7 @@ router.get('/:propertyId/forecast', async (req, res, next) => { maxWindSpeed: Math.round(Math.max(...windSpeeds)), totalPrecipitation: Math.round(day.precipitation * 100) / 100, conditions: day.conditions[0], // Use first condition of the day + icon: day.icons[0], timestamps: day.timestamps }; }).slice(0, 5); // Limit to 5 days diff --git a/frontend/src/pages/Weather/Weather.js b/frontend/src/pages/Weather/Weather.js index 9621788..d13d282 100644 --- a/frontend/src/pages/Weather/Weather.js +++ b/frontend/src/pages/Weather/Weather.js @@ -3,7 +3,7 @@ import { propertiesAPI, weatherAPI } from '../../services/api'; const Weather = () => { const [properties, setProperties] = useState([]); - const [weatherMap, setWeatherMap] = useState({}); // propertyId -> weather + const [weatherMap, setWeatherMap] = useState({}); // propertyId -> { weather, forecast } const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -20,14 +20,26 @@ const Weather = () => { .filter(p => p.latitude && p.longitude) .map(p => p.id); - const results = await Promise.allSettled(ids.map(id => weatherAPI.getCurrent(id))); + // Current weather + const currentResults = await Promise.allSettled(ids.map(id => weatherAPI.getCurrent(id))); const map = {}; - results.forEach((r, idx) => { + currentResults.forEach((r, idx) => { const id = ids[idx]; if (r.status === 'fulfilled') { - map[id] = r.value.data.data.weather; + map[id] = { weather: r.value.data.data.weather }; } }); + + // 5-day forecast + const forecastResults = await Promise.allSettled(ids.map(id => weatherAPI.getForecast(id))); + forecastResults.forEach((r, idx) => { + const id = ids[idx]; + if (r.status === 'fulfilled') { + map[id] = map[id] || {}; + map[id].forecast = r.value.data.data.forecast; + } + }); + setWeatherMap(map); setError(null); } catch (e) { @@ -58,20 +70,35 @@ const Weather = () => { {!p.latitude || !p.longitude ? (
No coordinates set
- ) : weatherMap[p.id] ? ( + ) : weatherMap[p.id]?.weather ? (
- icon +
-
{weatherMap[p.id].current.temperature}°F
-
{weatherMap[p.id].current.conditions}
+
{weatherMap[p.id].weather.current.temperature}°F
+
{weatherMap[p.id].weather.current.conditions}
- Humidity {weatherMap[p.id].current.humidity}% • Wind {Math.round(weatherMap[p.id].current.windSpeed)} mph + Humidity {weatherMap[p.id].weather.current.humidity}% • Wind {Math.round(weatherMap[p.id].weather.current.windSpeed)} mph
) : (
Loading weather…
)} + + {/* Forecast */} + {weatherMap[p.id]?.forecast?.daily && ( +
+ {weatherMap[p.id].forecast.daily.map((d, i) => ( +
+
+ {new Date(d.date).toLocaleDateString(undefined, { weekday: 'short' })} +
+ +
{d.temperatureHigh}° / {d.temperatureLow}°
+
+ ))} +
+ )} ))}