feat: implement WeatherForm component and integrate weather fetching functionality.
Also added a weather data card where the design is displayed
This commit is contained in:
17
frontend/package-lock.json
generated
17
frontend/package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jscookie": "^1.1.0",
|
||||
"react": "^19.1.0",
|
||||
@@ -2064,6 +2065,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bootstrap-icons": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
|
||||
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
|
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jscookie": "^1.1.0",
|
||||
"react": "^19.1.0",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "./App.css";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import Layout from "./Layout/Layout.tsx";
|
||||
import WeatherCard from "./components/WeatherCard";
|
||||
import WeatherCard from "./components/WeatherForm.tsx";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
3
frontend/src/assets/icons/sunrise-fill.svg
Normal file
3
frontend/src/assets/icons/sunrise-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sunrise-fill" viewBox="0 0 16 16">
|
||||
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l1.5 1.5a.5.5 0 0 1-.708.708L8.5 2.707V4.5a.5.5 0 0 1-1 0V2.707l-.646.647a.5.5 0 1 1-.708-.708zM2.343 4.343a.5.5 0 0 1 .707 0l1.414 1.414a.5.5 0 0 1-.707.707L2.343 5.05a.5.5 0 0 1 0-.707m11.314 0a.5.5 0 0 1 0 .707l-1.414 1.414a.5.5 0 1 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0M11.709 11.5a4 4 0 1 0-7.418 0H.5a.5.5 0 0 0 0 1h15a.5.5 0 0 0 0-1h-3.79zM0 10a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2A.5.5 0 0 1 0 10m13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 649 B |
3
frontend/src/assets/icons/sunset-fill.svg
Normal file
3
frontend/src/assets/icons/sunset-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sunset-fill" viewBox="0 0 16 16">
|
||||
<path d="M7.646 4.854a.5.5 0 0 0 .708 0l1.5-1.5a.5.5 0 0 0-.708-.708l-.646.647V1.5a.5.5 0 0 0-1 0v1.793l-.646-.647a.5.5 0 1 0-.708.708zm-5.303-.51a.5.5 0 0 1 .707 0l1.414 1.413a.5.5 0 0 1-.707.707L2.343 5.05a.5.5 0 0 1 0-.707zm11.314 0a.5.5 0 0 1 0 .706l-1.414 1.414a.5.5 0 1 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zM11.709 11.5a4 4 0 1 0-7.418 0H.5a.5.5 0 0 0 0 1h15a.5.5 0 0 0 0-1h-3.79zM0 10a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2A.5.5 0 0 1 0 10m13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 650 B |
@@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const IsLoading: React.FC<Props> = ({ message }) => {
|
||||
return (
|
||||
<div>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IsLoading;
|
@@ -1,10 +1,60 @@
|
||||
import React from "react";
|
||||
import sunriseIcon from "../assets/icons/sunrise-fill.svg";
|
||||
import sunsetIcon from "../assets/icons/sunset-fill.svg";
|
||||
|
||||
const WeatherData: React.FC = () => {
|
||||
const weatherRaw = localStorage.getItem("weather");
|
||||
const weatherData = JSON.parse(weatherRaw || "{}");
|
||||
|
||||
// OpenWeatherMap Icon-URL
|
||||
const iconCode = weatherData?.weather?.[0]?.icon;
|
||||
const iconUrl = iconCode
|
||||
? `https://openweathermap.org/img/wn/${iconCode}@4x.png`
|
||||
: "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Weather</p>
|
||||
</>
|
||||
<div className="flex justify-center items-center min-h-[80vh]">
|
||||
<div className="bg-white/80 p-8 rounded-xl shadow-lg min-w-[300px] text-center">
|
||||
<h2 className="text-2xl font-semibold mb-2">{weatherData?.name}</h2>
|
||||
<p className="text-4xl font-bold mb-2">{weatherData?.main?.temp} °C</p>
|
||||
<p className="text-lg mb-4 capitalize">
|
||||
{weatherData?.weather?.[0]?.description}
|
||||
</p>
|
||||
{iconUrl && (
|
||||
<img
|
||||
src={iconUrl}
|
||||
alt={weatherData?.weather?.[0]?.description}
|
||||
className="mx-auto mb-4 w-32 h-32"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col items-center gap-2 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={sunriseIcon} alt="Sunrise Icon" className="w-6 h-6" />
|
||||
<span className="text-base">
|
||||
Sunrise:{" "}
|
||||
{weatherData?.sys?.sunrise
|
||||
? new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString(
|
||||
"de-DE",
|
||||
{ hour: "2-digit", minute: "2-digit" }
|
||||
)
|
||||
: "--:--"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={sunsetIcon} alt="Sunset Icon" className="w-6 h-6" />
|
||||
<span className="text-base">
|
||||
Sunset:{" "}
|
||||
{weatherData?.sys?.sunset
|
||||
? new Date(weatherData.sys.sunset * 1000).toLocaleTimeString(
|
||||
"de-DE",
|
||||
{ hour: "2-digit", minute: "2-digit" }
|
||||
)
|
||||
: "--:--"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -21,7 +21,7 @@ const WeatherCard: React.FC = () => {
|
||||
event.preventDefault();
|
||||
setLoading(true);
|
||||
toast
|
||||
.promise(fetchWeather(city, getAPIKey()), {
|
||||
.promise(fetchWeather(city, getAPIKey(), "metric"), {
|
||||
pending: "Fetching weather data...",
|
||||
success: "Weather data loaded successfully!",
|
||||
error: "Error loading weather data! (Check console for details)",
|
@@ -1,4 +1,10 @@
|
||||
export const fetchWeather = async (city: string, apiKey: string) => {
|
||||
export type units = "metric" | "imperial";
|
||||
|
||||
export const fetchWeather = async (
|
||||
city: string,
|
||||
apiKey: string,
|
||||
units: units
|
||||
) => {
|
||||
// Get location data
|
||||
const location = await fetch(
|
||||
`http://api.openweathermap.org/geo/1.0/direct?q=${city}&appid=${apiKey}`
|
||||
@@ -18,7 +24,7 @@ export const fetchWeather = async (city: string, apiKey: string) => {
|
||||
|
||||
// Get weather data
|
||||
const weather = await fetch(
|
||||
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}`
|
||||
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=${units}`
|
||||
).then((response) => response.json());
|
||||
localStorage.setItem("weather", JSON.stringify(weather));
|
||||
};
|
||||
|
Reference in New Issue
Block a user