22 * WEATHER DASHBOARD TODOs
33 * -----------------------
44 * Easy:
5- * - [ ] Add °C / °F toggle
6- * - [ ] Show weather icon (current + forecast)
7- * - [ ] Show feels-like temperature & wind speed
8- * - [ ] Add loading skeleton instead of plain text
9- * - [ ] Style forecast cards with condition color badges
5+ * - [x] Extract API call into /src/services/weather.js and add caching
6+ * - [ ] Add °C / °F toggle
7+ * - [ ] Show weather icon (current + forecast)
8+ * - [ ] Show feels-like temperature & wind speed
9+ * - [ ] Add loading skeleton instead of plain text
10+ * - [ ] Style forecast cards with condition color badges
1011 * Medium:
11- * - [ ] Dynamic background / gradient based on condition (sunny, rain, snow)
12- * - [ ] Input debounced search (on stop typing)
13- * - [ ] Persist last searched city (localStorage)
14- * - [ ] Add error retry button component
15- * - [ ] Add favorites list (pin cities)
12+ * - [ ] Dynamic background / gradient based on condition (sunny, rain, snow)
13+ * - [ ] Input debounced search (on stop typing)
14+ * - [ ] Persist last searched city (localStorage)
15+ * - [ ] Add error retry button component
16+ * - [ ] Add favorites list (pin cities)
1617 * Advanced:
17- * - [ ] Hourly forecast visualization (line / area chart)
18- * - [ ] Animate background transitions
19- * - [ ] Add geolocation: auto-detect user city (with permission)
20- * - [ ] Extract API call into /src/services/weather.js and add caching
18+ * - [ ] Hourly forecast visualization (line / area chart)
19+ * - [ ] Animate background transitions
20+ * - [ ] Add geolocation: auto-detect user city (with permission)
2121 */
22+
2223import { useEffect , useState } from 'react' ;
2324import Loading from '../components/Loading.jsx' ;
2425import ErrorMessage from '../components/ErrorMessage.jsx' ;
2526import Card from '../components/Card.jsx' ;
27+ import { getWeatherData , clearWeatherCache , getCacheStats } from '../services/weather.js' ;
2628
2729export default function Weather ( ) {
2830 const [ city , setCity ] = useState ( 'London' ) ;
@@ -33,15 +35,14 @@ export default function Weather() {
3335
3436 useEffect ( ( ) => {
3537 fetchWeather ( city ) ;
36- // eslint-disable-next-line react-hooks/exhaustive-deps
38+ // eslint-disable-next-line react-hooks/exhaustive-deps
3739 } , [ ] ) ;
3840
3941 async function fetchWeather ( c ) {
4042 try {
41- setLoading ( true ) ; setError ( null ) ;
42- const res = await fetch ( `https://wttr.in/${ encodeURIComponent ( c ) } ?format=j1` ) ;
43- if ( ! res . ok ) throw new Error ( 'Failed to fetch' ) ;
44- const json = await res . json ( ) ;
43+ setLoading ( true ) ;
44+ setError ( null ) ;
45+ const json = await getWeatherData ( c ) ; // Using the service instead of direct fetch
4546 setData ( json ) ;
4647 } catch ( e ) {
4748 setError ( e ) ;
@@ -50,47 +51,76 @@ export default function Weather() {
5051 }
5152 }
5253
54+ // Helper function to clear cache and refetch (for testing)
55+ const handleClearCache = ( ) => {
56+ clearWeatherCache ( ) ;
57+ fetchWeather ( city ) ;
58+ } ;
59+
60+ // Helper function to show cache stats (for development)
61+ const handleShowCacheStats = ( ) => {
62+ const stats = getCacheStats ( ) ;
63+ console . log ( 'Cache Statistics:' , stats ) ;
64+ alert ( `Cache has ${ stats . size } entries. Check console for details.` ) ;
65+ } ;
66+
5367 const current = data ?. current_condition ?. [ 0 ] ;
5468 const forecast = data ?. weather ?. slice ( 0 , 3 ) || [ ] ;
5569
5670 // Helper to convert °C to °F
5771 const displayTemp = ( c ) => unit === 'C' ? c : Math . round ( ( c * 9 / 5 ) + 32 ) ;
5872
5973 return (
60- < div >
61- < h2 > Weather Dashboard</ h2 >
62- < form onSubmit = { e => { e . preventDefault ( ) ; fetchWeather ( city ) ; } } className = "inline-form" >
63- < input value = { city } onChange = { e => setCity ( e . target . value ) } placeholder = "Enter city" />
64- < button type = "submit" > Fetch</ button >
65- </ form >
66-
67- { /* Toggle button */ }
68- < div style = { { margin : '10px 0' } } >
69- < button onClick = { ( ) => setUnit ( unit === 'C' ? 'F' : 'C' ) } >
70- Switch to °{ unit === 'C' ? 'F' : 'C' }
71- </ button >
74+ < div className = "dashboard-page" >
75+ < div className = "dashboard-header" >
76+ < h1 > 🌤️ Weather Dashboard</ h1 >
77+ < form onSubmit = { ( e ) => { e . preventDefault ( ) ; fetchWeather ( city ) } } >
78+ < input
79+ type = "text"
80+ value = { city }
81+ onChange = { ( e ) => setCity ( e . target . value ) }
82+ placeholder = "Enter city name..."
83+ />
84+ < button type = "submit" > Get Weather</ button >
85+ </ form >
86+
87+ { /* Development tools - you can remove these later */ }
88+ < div style = { { marginTop : '10px' , display : 'flex' , gap : '10px' } } >
89+ < button onClick = { handleClearCache } style = { { fontSize : '12px' } } >
90+ Clear Cache
91+ </ button >
92+ < button onClick = { handleShowCacheStats } style = { { fontSize : '12px' } } >
93+ Cache Stats
94+ </ button >
95+ < button onClick = { ( ) => setUnit ( unit === 'C' ? 'F' : 'C' ) } style = { { fontSize : '12px' } } >
96+ Switch to °{ unit === 'C' ? 'F' : 'C' }
97+ </ button >
98+ </ div >
7299 </ div >
73100
74101 { loading && < Loading /> }
75- < ErrorMessage error = { error } />
102+ { error && < ErrorMessage message = { error . message } onRetry = { ( ) => fetchWeather ( city ) } /> }
76103
77- { current && (
78- < Card title = { `Current in ${ city } ` } >
79- < p > Temperature: { displayTemp ( Number ( current . temp_C ) ) } °{ unit } </ p >
80- < p > Humidity: { current . humidity } %</ p >
81- < p > Desc: { current . weatherDesc ?. [ 0 ] ?. value } </ p >
82- </ Card >
83- ) }
84-
85- < div className = "grid" >
86- { forecast . map ( day => (
87- < Card key = { day . date } title = { day . date } >
88- < p > Avg Temp: { displayTemp ( Number ( day . avgtempC ) ) } °{ unit } </ p >
89- < p > Sunrise: { day . astronomy ?. [ 0 ] ?. sunrise } </ p >
104+ { data && ! loading && (
105+ < div className = "dashboard-grid" >
106+ { /* Current Weather */ }
107+ < Card title = "Current Weather" size = "large" >
108+ < h2 > { data . nearest_area ?. [ 0 ] ?. areaName ?. [ 0 ] ?. value || city } </ h2 >
109+ < p > < strong > Temperature:</ strong > { displayTemp ( Number ( current . temp_C ) ) } °{ unit } </ p >
110+ < p > < strong > Humidity:</ strong > { current . humidity } %</ p >
111+ < p > < strong > Desc:</ strong > { current . weatherDesc ?. [ 0 ] ?. value } </ p >
90112 </ Card >
91- ) ) }
92- </ div >
113+
114+ { /* 3-Day Forecast */ }
115+ { forecast . map ( ( day , i ) => (
116+ < Card key = { i } title = { i === 0 ? 'Today' : `Day ${ i + 1 } ` } >
117+ < p > < strong > Avg Temp:</ strong > { displayTemp ( Number ( day . avgtempC ) ) } °{ unit } </ p >
118+ < p > < strong > Sunrise:</ strong > { day . astronomy ?. [ 0 ] ?. sunrise } </ p >
119+ < p > < strong > Sunset:</ strong > { day . astronomy ?. [ 0 ] ?. sunset } </ p >
120+ </ Card >
121+ ) ) }
122+ </ div >
123+ ) }
93124 </ div >
94125 ) ;
95126}
96-
0 commit comments