From 8341e50dc8e526437e45b647f9cfab990e276052 Mon Sep 17 00:00:00 2001 From: "theis.gaedigk" Date: Fri, 1 Aug 2025 01:06:58 +0200 Subject: [PATCH] feat: enhance ChangeAPI and ChangePreferences components; add "API key update" functionality and save preferences feature --- frontend/src/components/ChangeAPI.tsx | 56 +++++--- frontend/src/components/ChangePreferences.tsx | 123 +++++++++++++++++- frontend/src/components/Header.tsx | 32 ++++- frontend/src/components/WeatherData.tsx | 20 ++- frontend/src/components/WeatherForm.tsx | 47 +++++-- frontend/src/utils/apiFunc.ts | 10 +- frontend/src/utils/changeAPIcookie.ts | 3 +- 7 files changed, 250 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/ChangeAPI.tsx b/frontend/src/components/ChangeAPI.tsx index f1347b3..52d4c4b 100644 --- a/frontend/src/components/ChangeAPI.tsx +++ b/frontend/src/components/ChangeAPI.tsx @@ -4,19 +4,25 @@ import { changeAPIcookie } from "../utils/changeAPIcookie"; interface Props { currentAPIKey: string; + onClose: () => void; } -const ChangeAPI: React.FC = ({ currentAPIKey }) => { +const ChangeAPI: React.FC = ({ currentAPIKey, onClose }) => { const [apiKey, setApiKey] = useState(currentAPIKey); + const handleUpdate = () => { + changeAPIcookie(apiKey); + onClose(); + }; + return (

Change API Key

-

+

Update your API key to fetch weather data. -

-
- We are using +

+
+

We are using

= ({ currentAPIKey }) => { > OpenWeatherMap - API for fetching weather data. +

API for fetching weather data.

- setApiKey(e.target.value)} - required - className="border border-blue-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 bg-blue-50 text-blue-900 font-mono" - /> +
+ + + + setApiKey(e.target.value)} + required + className="border border-blue-300 rounded-xl px-6 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 bg-blue-50 text-blue-900 font-mono w-full" + /> +
diff --git a/frontend/src/components/ChangePreferences.tsx b/frontend/src/components/ChangePreferences.tsx index 9cc3559..cf7fec0 100644 --- a/frontend/src/components/ChangePreferences.tsx +++ b/frontend/src/components/ChangePreferences.tsx @@ -1,11 +1,124 @@ -import React from "react"; +import React, { useState } from "react"; +import { myToast } from "../utils/toastify"; + +const getInitialTheme = () => localStorage.getItem("theme") || "light"; +const getInitialUnit = () => localStorage.getItem("unit") || "celsius"; + +interface Props { + onClose: () => void; +} + +const ChangePreferences: React.FC = ({ onClose }) => { + const [unit, setUnit] = useState(getInitialUnit()); + const [theme, setTheme] = useState(getInitialTheme()); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + localStorage.setItem("unit", unit); + localStorage.setItem("theme", theme); + myToast("Preferences saved successfully!", "success"); + onClose(); + }; + + // Theme-Optionen so sortieren, dass die aktuelle Auswahl oben steht + const themeOptions = + theme === "dark" + ? [ + { value: "dark", label: "Dark" }, + { value: "light", label: "Light" }, + ] + : [ + { value: "light", label: "Light" }, + { value: "dark", label: "Dark" }, + ]; -const ChangePreferences: React.FC = () => { return ( -
-

- Change Preferences +
+

+ + + + Preferences

+ + {/* Unit */} +
+ + +
+ {/* Theme */} +
+ + +
+ {/* Submit */} + +
); }; diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 121f814..1b24fb4 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -75,6 +75,31 @@ const Header: React.FC = () => { Preferences + + +

{apiCard && ( @@ -87,7 +112,10 @@ const Header: React.FC = () => { > × - + setApiCard(false)} + />
)} @@ -102,7 +130,7 @@ const Header: React.FC = () => { > × - + setPreferencesCard(false)} /> )} diff --git a/frontend/src/components/WeatherData.tsx b/frontend/src/components/WeatherData.tsx index 417b2a9..6cc5266 100644 --- a/frontend/src/components/WeatherData.tsx +++ b/frontend/src/components/WeatherData.tsx @@ -19,7 +19,25 @@ const WeatherData: React.FC = () => { {weatherData?.name}

- {weatherData?.main?.temp} °C + {weatherData?.main?.temp}{" "} + {localStorage.getItem("unit") === "metric" + ? "°C" + : localStorage.getItem("unit") === "imperial" + ? "°F" + : "K"} +

+

+ {weatherData?.sys?.country && ( + <> + {weatherData.sys.country} + {weatherData.sys.country} + + )}

{weatherData?.weather?.[0]?.description} diff --git a/frontend/src/components/WeatherForm.tsx b/frontend/src/components/WeatherForm.tsx index cbbc0f4..2333db4 100644 --- a/frontend/src/components/WeatherForm.tsx +++ b/frontend/src/components/WeatherForm.tsx @@ -10,8 +10,8 @@ const WeatherCard: React.FC = () => { const [city, setCity] = useState(""); const [loading, setLoading] = useState(false); const [weatherData, setWeatherData] = useState(false); - console.log(loading); // only for better reading because a syntax error would appear const getAPIKey = () => Cookies.get("apiKey") || ""; + const getUnit = () => localStorage.getItem("unit") || "metric"; const handleCityChange = (event: React.ChangeEvent) => { setCity(event.target.value); @@ -21,10 +21,11 @@ const WeatherCard: React.FC = () => { event.preventDefault(); setLoading(true); toast - .promise(fetchWeather(city, getAPIKey(), "metric"), { + .promise(fetchWeather(city, getAPIKey(), getUnit()), { pending: "Fetching weather data...", success: "Weather data loaded successfully!", - error: "Error loading weather data! (Check console for details)", + error: + "Failed to load weather data. Please check your entered city name.", }) .then(() => { if (localStorage.getItem("weather")) { @@ -67,12 +68,40 @@ const WeatherCard: React.FC = () => { required className="border border-blue-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 bg-blue-50 text-blue-900 font-mono" /> - +

+ + {weatherData && ( + + )} +
{weatherData && } diff --git a/frontend/src/utils/apiFunc.ts b/frontend/src/utils/apiFunc.ts index 5ca8aa8..3ebd8c6 100644 --- a/frontend/src/utils/apiFunc.ts +++ b/frontend/src/utils/apiFunc.ts @@ -1,22 +1,20 @@ -export type units = "metric" | "imperial"; +import { myToast } from "./toastify"; export const fetchWeather = async ( city: string, apiKey: string, - units: units + units: string ) => { // Get location data const location = await fetch( `http://api.openweathermap.org/geo/1.0/direct?q=${city}&appid=${apiKey}` ).then((response) => { if (response.status === 401) { - console.error( - "You are not authorized to access this resource. Please check your API key." - ); + myToast("You are not authorized to access this resource. Please check your API key.", "error"); } else if (response.ok) { return response.json(); } else { - console.error("Error fetching location data: ", response.statusText); + myToast("Error fetching location data: " + response.statusText, "error"); } }); const lat = location[0].lat; diff --git a/frontend/src/utils/changeAPIcookie.ts b/frontend/src/utils/changeAPIcookie.ts index 6e682da..6a9995b 100644 --- a/frontend/src/utils/changeAPIcookie.ts +++ b/frontend/src/utils/changeAPIcookie.ts @@ -2,6 +2,7 @@ import Cookies from "js-cookie"; import { myToast } from "./toastify"; export const changeAPIcookie = (newApiKey: string) => { + let apiKey15 = newApiKey.slice(0, 15); Cookies.set("apiKey", newApiKey); - myToast("API key updated successfully!", "success"); + myToast("API key updated successfully!" + " " + "Your new API key: " + apiKey15 + "...", "success"); };