diff --git a/src/pages/Weather.jsx b/src/pages/Weather.jsx index 18c3b43..3f10a80 100644 --- a/src/pages/Weather.jsx +++ b/src/pages/Weather.jsx @@ -2,27 +2,29 @@ * WEATHER DASHBOARD TODOs * ----------------------- * Easy: - * - [ ] Add °C / °F toggle - * - [ ] Show weather icon (current + forecast) - * - [ ] Show feels-like temperature & wind speed - * - [ ] Add loading skeleton instead of plain text - * - [ ] Style forecast cards with condition color badges + * - [x] Extract API call into /src/services/weather.js and add caching + * - [ ] Add °C / °F toggle + * - [ ] Show weather icon (current + forecast) + * - [ ] Show feels-like temperature & wind speed + * - [ ] Add loading skeleton instead of plain text + * - [ ] Style forecast cards with condition color badges * Medium: - * - [ ] Dynamic background / gradient based on condition (sunny, rain, snow) - * - [ ] Input debounced search (on stop typing) - * - [ ] Persist last searched city (localStorage) - * - [ ] Add error retry button component - * - [ ] Add favorites list (pin cities) + * - [ ] Dynamic background / gradient based on condition (sunny, rain, snow) + * - [ ] Input debounced search (on stop typing) + * - [ ] Persist last searched city (localStorage) + * - [ ] Add error retry button component + * - [ ] Add favorites list (pin cities) * Advanced: - * - [ ] Hourly forecast visualization (line / area chart) - * - [ ] Animate background transitions - * - [ ] Add geolocation: auto-detect user city (with permission) - * - [ ] Extract API call into /src/services/weather.js and add caching + * - [ ] Hourly forecast visualization (line / area chart) + * - [ ] Animate background transitions + * - [ ] Add geolocation: auto-detect user city (with permission) */ + import { useEffect, useState } from 'react'; import Loading from '../components/Loading.jsx'; import ErrorMessage from '../components/ErrorMessage.jsx'; import Card from '../components/Card.jsx'; +import { getWeatherData, clearWeatherCache, getCacheStats } from '../services/weather.js'; export default function Weather() { const [city, setCity] = useState('London'); @@ -33,15 +35,14 @@ export default function Weather() { useEffect(() => { fetchWeather(city); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); async function fetchWeather(c) { try { - setLoading(true); setError(null); - const res = await fetch(`https://wttr.in/${encodeURIComponent(c)}?format=j1`); - if (!res.ok) throw new Error('Failed to fetch'); - const json = await res.json(); + setLoading(true); + setError(null); + const json = await getWeatherData(c); // Using the service instead of direct fetch setData(json); } catch (e) { setError(e); @@ -50,6 +51,19 @@ export default function Weather() { } } + // Helper function to clear cache and refetch (for testing) + const handleClearCache = () => { + clearWeatherCache(); + fetchWeather(city); + }; + + // Helper function to show cache stats (for development) + const handleShowCacheStats = () => { + const stats = getCacheStats(); + console.log('Cache Statistics:', stats); + alert(`Cache has ${stats.size} entries. Check console for details.`); + }; + const current = data?.current_condition?.[0]; const forecast = data?.weather?.slice(0,3) || []; @@ -57,40 +71,56 @@ export default function Weather() { const displayTemp = (c) => unit === 'C' ? c : Math.round((c * 9/5) + 32); return ( -
-

Weather Dashboard

-
{ e.preventDefault(); fetchWeather(city); }} className="inline-form"> - setCity(e.target.value)} placeholder="Enter city" /> - -
- - {/* Toggle button */} -
- +
+
+

🌤️ Weather Dashboard

+
{e.preventDefault(); fetchWeather(city)}}> + setCity(e.target.value)} + placeholder="Enter city name..." + /> + +
+ + {/* Development tools - you can remove these later */} +
+ + + +
{loading && } - + {error && fetchWeather(city)} />} - {current && ( - -

Temperature: {displayTemp(Number(current.temp_C))}°{unit}

-

Humidity: {current.humidity}%

-

Desc: {current.weatherDesc?.[0]?.value}

-
- )} - -
- {forecast.map(day => ( - -

Avg Temp: {displayTemp(Number(day.avgtempC))}°{unit}

-

Sunrise: {day.astronomy?.[0]?.sunrise}

+ {data && !loading && ( +
+ {/* Current Weather */} + +

{data.nearest_area?.[0]?.areaName?.[0]?.value || city}

+

Temperature: {displayTemp(Number(current.temp_C))}°{unit}

+

Humidity: {current.humidity}%

+

Desc: {current.weatherDesc?.[0]?.value}

- ))} -
+ + {/* 3-Day Forecast */} + {forecast.map((day, i) => ( + +

Avg Temp: {displayTemp(Number(day.avgtempC))}°{unit}

+

Sunrise: {day.astronomy?.[0]?.sunrise}

+

Sunset: {day.astronomy?.[0]?.sunset}

+
+ ))} +
+ )}
); } - diff --git a/src/services/weather.js b/src/services/weather.js new file mode 100644 index 0000000..eee7c40 --- /dev/null +++ b/src/services/weather.js @@ -0,0 +1,63 @@ +// src/services/weather.js + +// Simple in-memory cache +const weatherCache = new Map(); +const CACHE_DURATION = 10 * 60 * 1000; // 10 minutes in milliseconds + +/** + * Fetches weather data for a given city with caching + * @param {string} city - The city name to fetch weather for + * @returns {Promise} Weather data from wttr.in API + */ +export const getWeatherData = async (city) => { + const cacheKey = city.toLowerCase(); + const now = Date.now(); + + // Check if we have cached data that's still valid + if (weatherCache.has(cacheKey)) { + const cachedData = weatherCache.get(cacheKey); + if (now - cachedData.timestamp < CACHE_DURATION) { + return cachedData.data; + } else { + // Remove expired cache entry + weatherCache.delete(cacheKey); + } + } + + try { + const res = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`); + + if (!res.ok) { + throw new Error('Failed to fetch'); + } + + const json = await res.json(); + + // Cache the successful response + weatherCache.set(cacheKey, { + data: json, + timestamp: now + }); + + return json; + } catch (error) { + throw error; // Re-throw to maintain existing error handling + } +}; + +/** + * Clears the weather cache + */ +export const clearWeatherCache = () => { + weatherCache.clear(); +}; + +/** + * Gets cache statistics for debugging + */ +export const getCacheStats = () => { + return { + size: weatherCache.size, + entries: Array.from(weatherCache.keys()) + }; +};