feat: enhance weather fetching experience with loading state and notifications

This commit is contained in:
2025-07-29 23:40:24 +02:00
parent bf8c270171
commit dcfbbb2f22
7 changed files with 47 additions and 12 deletions

View File

@@ -13,8 +13,14 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
<ToastContainer <ToastContainer
position="top-right" position="top-right"
autoClose={3000} autoClose={3000}
hideProgressBar hideProgressBar={false}
theme="colored" newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss={false}
draggable
pauseOnHover
theme="dark"
/> />
<main className="flex-1 container mx-auto px-4 py-8">{children}</main> <main className="flex-1 container mx-auto px-4 py-8">{children}</main>
</div> </div>

View File

@@ -1,11 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import React from "react";
import { changeAPIcookie } from "../utils/changeAPIcookie"; import { changeAPIcookie } from "../utils/changeAPIcookie";
interface Props { interface Props {
currentAPIKey: string; currentAPIKey: string;
} }
function ChangeAPI({ currentAPIKey }: Props) { const ChangeAPI: React.FC<Props> = ({ currentAPIKey }) => {
const [apiKey, setApiKey] = useState(currentAPIKey); const [apiKey, setApiKey] = useState(currentAPIKey);
return ( return (
@@ -14,6 +15,13 @@ function ChangeAPI({ currentAPIKey }: Props) {
<p className="mb-6 text-gray-600"> <p className="mb-6 text-gray-600">
Update your API key to fetch weather data. Update your API key to fetch weather data.
</p> </p>
<p className="mb-6 text-gray-600">
We are using{" "}
<a href="https://openweathermap.org/api">
<strong>OpenWeatherMap</strong>
</a>{" "}
API for fetching weather data.
</p>
<form className="flex flex-col gap-4"> <form className="flex flex-col gap-4">
<label htmlFor="apiKey" className="font-medium text-gray-700"> <label htmlFor="apiKey" className="font-medium text-gray-700">
API Key: API Key:
@@ -38,6 +46,6 @@ function ChangeAPI({ currentAPIKey }: Props) {
</form> </form>
</div> </div>
); );
} };
export default ChangeAPI; export default ChangeAPI;

View File

@@ -21,7 +21,7 @@ const Header: React.FC = () => {
</button> </button>
</header> </header>
{apiCard && ( {apiCard && (
<div className="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-gray-800 bg-opacity-40 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-lg p-8 w-full max-w-md relative"> <div className="bg-white rounded-xl shadow-lg p-8 w-full max-w-md relative">
<button <button
className="absolute top-4 right-4 text-gray-400 hover:text-blue-600 text-xl" className="absolute top-4 right-4 text-gray-400 hover:text-blue-600 text-xl"

View File

@@ -0,0 +1,15 @@
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

@@ -2,18 +2,27 @@ import React from "react";
import { useState } from "react"; import { useState } from "react";
import { fetchWeather } from "../utils/apiFunc"; import { fetchWeather } from "../utils/apiFunc";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import IsLoading from "./IsLoading";
import { toast } from "react-toastify";
const WeatherCard: React.FC = () => { const WeatherCard: React.FC = () => {
const [city, setCity] = useState(""); const [city, setCity] = useState("");
const [loading, setLoading] = useState(false);
const getAPIKey = () => Cookies.get("apiKey") || ""; const getAPIKey = () => Cookies.get("apiKey") || "";
const handleCityChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleCityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setCity(event.target.value); setCity(event.target.value);
}; };
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
fetchWeather(city, getAPIKey()); setLoading(true);
toast.promise(fetchWeather(city, getAPIKey()), {
pending: "Fetching weather data...",
success: "Weather data loaded successfully!",
error: "Error loading weather data!",
});
setLoading(false);
}; };
return ( return (
@@ -33,6 +42,7 @@ const WeatherCard: React.FC = () => {
/> />
<button type="submit">Get Weather</button> <button type="submit">Get Weather</button>
</form> </form>
{loading && <IsLoading message="Loading weather data..." />}
</div> </div>
); );
}; };

View File

@@ -1,12 +1,9 @@
import { myToast } from "./toastify";
export const fetchWeather = async (city: string, apiKey: string) => { export const fetchWeather = async (city: string, apiKey: string) => {
// 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}`
).then((response) => { ).then((response) => {
if (response.status === 401) { if (response.status === 401) {
myToast("Request Failed! Check API key and city name.", "error");
throw new Error("Network response was not ok"); throw new Error("Network response was not ok");
} else if (response.ok) { } else if (response.ok) {
return response.json(); return response.json();
@@ -20,5 +17,4 @@ export const fetchWeather = async (city: string, apiKey: string) => {
`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}`
).then((response) => response.json()); ).then((response) => response.json());
console.log(weather); console.log(weather);
myToast("Successfully fetched weather data", "success");
}; };

View File

@@ -5,7 +5,7 @@ export type ToastType = "success" | "error" | "info" | "warning";
export const myToast = (message: string, msgType: ToastType) => { export const myToast = (message: string, msgType: ToastType) => {
let config: ToastOptions = { let config: ToastOptions = {
position: "top-right", position: "top-right",
autoClose: 5000, autoClose: 3000,
hideProgressBar: false, hideProgressBar: false,
closeOnClick: true, closeOnClick: true,
pauseOnHover: true, pauseOnHover: true,