Skip to content

Commit 41f8513

Browse files
committed
Added geolocation to auto-detect
user city
1 parent 4242a93 commit 41f8513

File tree

2 files changed

+125
-26
lines changed

2 files changed

+125
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ dist
33
.DS_Store
44
*.log
55
package-lock.json
6+
.env

src/pages/Weather.jsx

Lines changed: 124 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,78 +19,176 @@
1919
* - [ ] Add geolocation: auto-detect user city (with permission)
2020
* - [ ] Extract API call into /src/services/weather.js and add caching
2121
*/
22-
import { useEffect, useState } from 'react';
23-
import Loading from '../components/Loading.jsx';
24-
import ErrorMessage from '../components/ErrorMessage.jsx';
25-
import Card from '../components/Card.jsx';
22+
import { useEffect, useState } from "react";
23+
import Loading from "../components/Loading.jsx";
24+
import ErrorMessage from "../components/ErrorMessage.jsx";
25+
import Card from "../components/Card.jsx";
2626

2727
export default function Weather() {
28-
const [city, setCity] = useState('London');
28+
const [city, setCity] = useState("");
2929
const [data, setData] = useState(null);
3030
const [loading, setLoading] = useState(false);
3131
const [error, setError] = useState(null);
32-
const [unit, setUnit] = useState('C'); // °C by default
32+
const [unit, setUnit] = useState("C"); // °C by default
33+
const [isLocAllowed, setIsLocAllowed] = useState(null);
34+
const [isRequestingLoc, setIsRequestingLoc] = useState(false);
3335

3436
useEffect(() => {
35-
fetchWeather(city);
36-
// eslint-disable-next-line react-hooks/exhaustive-deps
37+
const storedCity = localStorage.getItem("current_city");
38+
if (storedCity) {
39+
setIsLocAllowed(true);
40+
setCity(JSON.parse(storedCity));
41+
} else if (navigator.geolocation) {
42+
requestLocation();
43+
} else {
44+
setIsLocAllowed(false);
45+
setError(
46+
"Your browser does not support location detection. Please enter city manually."
47+
);
48+
setCity("London");
49+
}
3750
}, []);
3851

52+
useEffect(() => {
53+
if (city) {
54+
fetchWeather(city);
55+
}
56+
}, [city]);
57+
58+
async function getCurrentCity(lat, lon) {
59+
const APIkey = import.meta.env.VITE_WEATHER_API_KEY;
60+
try {
61+
const res = await fetch(
62+
`https://api.openweathermap.org/geo/1.0/reverse?lat=${lat}&lon=${lon}&appid=${APIkey}`
63+
);
64+
const data = await res.json();
65+
if (data && data.length > 0 && data[0].name) {
66+
setCity(data[0].name);
67+
setError(null);
68+
setIsLocAllowed(true);
69+
localStorage.setItem("current_city", JSON.stringify(data[0].name));
70+
} else {
71+
setCity("London");
72+
setError("Could not detect city from location.");
73+
setIsLocAllowed(false);
74+
}
75+
} catch (err) {
76+
console.log(err);
77+
setCity("London");
78+
setError(err.message);
79+
setIsLocAllowed(false);
80+
}
81+
}
82+
83+
function requestLocation() {
84+
setIsRequestingLoc(true);
85+
navigator.geolocation.getCurrentPosition(
86+
async function onSuccess(position) {
87+
await getCurrentCity(
88+
position.coords.latitude,
89+
position.coords.longitude
90+
);
91+
setIsRequestingLoc(false);
92+
},
93+
94+
function onError(err) {
95+
console.log("Error", err);
96+
setIsLocAllowed(false);
97+
setError(
98+
"Location is blocked. Please enable location in your browser settings to detect automatically."
99+
);
100+
setCity("London");
101+
setIsRequestingLoc(false);
102+
}
103+
);
104+
}
39105
async function fetchWeather(c) {
40106
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');
107+
setLoading(true);
108+
setError(null);
109+
const res = await fetch(
110+
`https://wttr.in/${encodeURIComponent(c)}?format=j1`
111+
);
112+
if (!res.ok) throw new Error("Failed to fetch");
44113
const json = await res.json();
114+
45115
setData(json);
46116
} catch (e) {
47-
setError(e);
117+
setError(e.message);
48118
} finally {
49119
setLoading(false);
50120
}
51121
}
52122

53123
const current = data?.current_condition?.[0];
54-
const forecast = data?.weather?.slice(0,3) || [];
124+
const forecast = data?.weather?.slice(0, 3) || [];
55125

56126
// Helper to convert °C to °F
57-
const displayTemp = (c) => unit === 'C' ? c : Math.round((c * 9/5) + 32);
127+
const displayTemp = (c) => (unit === "C" ? c : Math.round((c * 9) / 5 + 32));
58128

59129
return (
60130
<div>
61131
<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>
132+
<form
133+
onSubmit={(e) => {
134+
e.preventDefault();
135+
fetchWeather(city);
136+
}}
137+
className="inline-form"
138+
>
139+
<input
140+
value={city}
141+
onChange={(e) => setCity(e.target.value)}
142+
placeholder="Enter city"
143+
/>
144+
<button type="submit" disabled={isRequestingLoc}>
145+
Fetch
146+
</button>
147+
<button
148+
type="button"
149+
disabled={isRequestingLoc}
150+
onClick={() => requestLocation()}
151+
>
152+
{isLocAllowed
153+
? isRequestingLoc
154+
? "Updating..."
155+
: "Update location"
156+
: isRequestingLoc
157+
? "Detecting..."
158+
: "Detect my location"}
159+
</button>
65160
</form>
66161

67162
{/* Toggle button */}
68-
<div style={{ margin: '10px 0' }}>
69-
<button onClick={() => setUnit(unit === 'C' ? 'F' : 'C')}>
70-
Switch to °{unit === 'C' ? 'F' : 'C'}
163+
<div style={{ margin: "10px 0" }}>
164+
<button onClick={() => setUnit(unit === "C" ? "F" : "C")}>
165+
Switch to °{unit === "C" ? "F" : "C"}
71166
</button>
72167
</div>
73168

74169
{loading && <Loading />}
75-
<ErrorMessage error={error} />
170+
{error && <ErrorMessage error={error} />}
76171

77172
{current && (
78-
<Card title={`Current in ${city}`}>
79-
<p>Temperature: {displayTemp(Number(current.temp_C))}°{unit}</p>
173+
<Card title={`Current in ${city}`}>
174+
<p>
175+
Temperature: {displayTemp(Number(current.temp_C))}°{unit}
176+
</p>
80177
<p>Humidity: {current.humidity}%</p>
81178
<p>Desc: {current.weatherDesc?.[0]?.value}</p>
82179
</Card>
83180
)}
84181

85182
<div className="grid">
86-
{forecast.map(day => (
183+
{forecast.map((day) => (
87184
<Card key={day.date} title={day.date}>
88-
<p>Avg Temp: {displayTemp(Number(day.avgtempC))}°{unit}</p>
185+
<p>
186+
Avg Temp: {displayTemp(Number(day.avgtempC))}°{unit}
187+
</p>
89188
<p>Sunrise: {day.astronomy?.[0]?.sunrise}</p>
90189
</Card>
91190
))}
92191
</div>
93192
</div>
94193
);
95194
}
96-

0 commit comments

Comments
 (0)