Skip to content

Commit a47a146

Browse files
Merge pull request #34 from AnshuRani7781/main-fea-weather-hours
Feat: add weather icons and improve hourly forecast display in Weather component
2 parents 93cdbe3 + 022d2fb commit a47a146

File tree

1 file changed

+133
-9
lines changed

1 file changed

+133
-9
lines changed

src/pages/Weather.jsx

Lines changed: 133 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,34 @@ export default function Weather() {
8585
// Helper to convert °C to °F
8686
const displayTemp = (c) => (unit === "C" ? c : Math.round((c * 9) / 5 + 32));
8787

88+
// Normalize icon URL returned by wttr.in (they sometimes return // links)
89+
const getIconUrl = (iconArrOrStr) => {
90+
if (!iconArrOrStr) return null;
91+
92+
// If API returned an array like [{ value: "//..." }]
93+
if (Array.isArray(iconArrOrStr)) {
94+
const url = iconArrOrStr?.[0]?.value;
95+
if (!url) return null;
96+
return url.startsWith("//") ? `https:${url}` : url;
97+
}
98+
99+
// If API returned a plain string (rare) or already a URL
100+
if (typeof iconArrOrStr === "string") {
101+
return iconArrOrStr.startsWith("//")
102+
? `https:${iconArrOrStr}`
103+
: iconArrOrStr;
104+
}
105+
106+
return null;
107+
};
108+
109+
// Format wttr.in hourly time values like "0", "300", "1500" -> "00:00", "03:00", "15:00"
110+
const formatWttTime = (t) => {
111+
if (t == null) return "";
112+
const s = String(t).padStart(4, "0");
113+
return `${s.slice(0, 2)}:${s.slice(2)}`;
114+
};
115+
88116
const getBadgeStyle = (condition) => {
89117
if (!condition) return { color: "#E0E0E0", label: "Clear 🌤️" };
90118

@@ -142,9 +170,18 @@ export default function Weather() {
142170
{/* Current Weather */}
143171
<Card title="Current Weather" size="large">
144172
<h2>{data.nearest_area?.[0]?.areaName?.[0]?.value || city}</h2>
145-
<p>
146-
<strong>Temperature:</strong>{" "}
147-
{displayTemp(Number(current.temp_C))}°{unit}
173+
<p style={{ display: "flex", alignItems: "center", gap: "12px" }}>
174+
{current && getIconUrl(current.weatherIconUrl) && (
175+
<img
176+
src={getIconUrl(current.weatherIconUrl)}
177+
alt={current.weatherDesc?.[0]?.value || "weather icon"}
178+
style={{ width: 48, height: 48, objectFit: "contain" }}
179+
/>
180+
)}
181+
<span>
182+
<strong>Temperature:</strong>{" "}
183+
{displayTemp(Number(current.temp_C))}°{unit}
184+
</span>
148185
</p>
149186
<p>
150187
<strong>Humidity:</strong> {current.humidity}%
@@ -163,7 +200,98 @@ export default function Weather() {
163200
return (
164201
<Card key={i} title={i === 0 ? "Today" : `Day ${i + 1}`}>
165202
{/* Badge Section */}
166-
<div
203+
{/* Forecast icon (use first hourly entry icon) */}
204+
{day.hourly?.[0] &&
205+
getIconUrl(day.hourly?.[0]?.weatherIconUrl) && (
206+
<div style={{ marginTop: 8 }}>
207+
<img
208+
src={getIconUrl(day.hourly?.[0]?.weatherIconUrl)}
209+
alt={
210+
day.hourly?.[0]?.weatherDesc?.[0]?.value ||
211+
"forecast icon"
212+
}
213+
style={{ width: 40, height: 40, objectFit: "contain" }}
214+
onError={(e) =>
215+
(e.currentTarget.style.display = "none")
216+
}
217+
/>
218+
</div>
219+
)}
220+
221+
{/* Full-day hourly timeline (0:00 - 23:00) */}
222+
{day.hourly && (
223+
<div
224+
style={{
225+
display: "block",
226+
marginTop: 8,
227+
}}
228+
>
229+
<div
230+
style={{
231+
display: "flex",
232+
gap: 12,
233+
overflowX: "auto",
234+
padding: "8px 4px",
235+
WebkitOverflowScrolling: "touch",
236+
}}
237+
>
238+
{day.hourly.map((h, idx) => {
239+
const icon = getIconUrl(h.weatherIconUrl);
240+
const t = h.time ?? h.Time ?? "";
241+
const temp =
242+
h.tempC ?? h.temp_C ?? h.tempC ?? h.tempF ?? "";
243+
244+
return (
245+
<div
246+
key={idx}
247+
style={{
248+
minWidth: 72,
249+
padding: 6,
250+
borderRadius: 6,
251+
background: "rgba(0,0,0,0.03)",
252+
display: "flex",
253+
flexDirection: "column",
254+
alignItems: "center",
255+
justifyContent: "center",
256+
fontSize: 12,
257+
}}
258+
>
259+
{icon ? (
260+
<img
261+
src={icon}
262+
alt={h.weatherDesc?.[0]?.value || "hour icon"}
263+
style={{
264+
width: 36,
265+
height: 36,
266+
objectFit: "contain",
267+
}}
268+
loading="lazy"
269+
onError={(e) =>
270+
(e.currentTarget.style.display = "none")
271+
}
272+
/>
273+
) : (
274+
<div style={{ fontSize: 18 }}>🌤️</div>
275+
)}
276+
<div style={{ marginTop: 6 }}>
277+
{formatWttTime(t)}
278+
</div>
279+
<div style={{ fontWeight: 700 }}>
280+
{displayTemp(Number(temp))}°{unit}
281+
</div>
282+
</div>
283+
);
284+
})}
285+
</div>
286+
</div>
287+
)}
288+
289+
<div style={
290+
{display:"flex",gap:"8px", marginTop:"17px"}
291+
}>
292+
<strong>Avg Temp:</strong> {displayTemp(Number(day.avgtempC))}
293+
°{unit}
294+
<div
167295
style={{
168296
backgroundColor: badge.color,
169297
borderRadius: "8px",
@@ -177,11 +305,7 @@ export default function Weather() {
177305
>
178306
{badge.label}
179307
</div>
180-
181-
<p>
182-
<strong>Avg Temp:</strong> {displayTemp(Number(day.avgtempC))}
183-
°{unit}
184-
</p>
308+
</div>
185309
<p>
186310
<strong>Sunrise:</strong> {day.astronomy?.[0]?.sunrise}
187311
</p>

0 commit comments

Comments
 (0)