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:
2025-07-31 17:12:00 +02:00
parent 480b5188ba
commit 21963eda1b
9 changed files with 87 additions and 22 deletions

View File

@@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"bootstrap-icons": "^1.13.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jscookie": "^1.1.0", "jscookie": "^1.1.0",
"react": "^19.1.0", "react": "^19.1.0",
@@ -2064,6 +2065,22 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",

View File

@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"bootstrap-icons": "^1.13.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jscookie": "^1.1.0", "jscookie": "^1.1.0",
"react": "^19.1.0", "react": "^19.1.0",

View File

@@ -1,7 +1,7 @@
import "./App.css"; import "./App.css";
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
import Layout from "./Layout/Layout.tsx"; import Layout from "./Layout/Layout.tsx";
import WeatherCard from "./components/WeatherCard"; import WeatherCard from "./components/WeatherForm.tsx";
function App() { function App() {
return ( return (

View 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

View 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

View File

@@ -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;

View File

@@ -1,10 +1,60 @@
import React from "react"; 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 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 ( return (
<> <div className="flex justify-center items-center min-h-[80vh]">
<p>Weather</p> <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>
); );
}; };

View File

@@ -21,7 +21,7 @@ const WeatherCard: React.FC = () => {
event.preventDefault(); event.preventDefault();
setLoading(true); setLoading(true);
toast toast
.promise(fetchWeather(city, getAPIKey()), { .promise(fetchWeather(city, getAPIKey(), "metric"), {
pending: "Fetching weather data...", pending: "Fetching weather data...",
success: "Weather data loaded successfully!", success: "Weather data loaded successfully!",
error: "Error loading weather data! (Check console for details)", error: "Error loading weather data! (Check console for details)",

View File

@@ -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 // Get location data
const location = await fetch( const location = await fetch(
`http://api.openweathermap.org/geo/1.0/direct?q=${city}&appid=${apiKey}` `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 // Get weather data
const weather = await fetch( 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()); ).then((response) => response.json());
localStorage.setItem("weather", JSON.stringify(weather)); localStorage.setItem("weather", JSON.stringify(weather));
}; };