feat: enhance weather fetching experience with loading state and notifications
This commit is contained in:
@@ -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>
|
||||||
|
@@ -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;
|
||||||
|
@@ -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"
|
||||||
|
15
frontend/src/components/IsLoading.tsx
Normal file
15
frontend/src/components/IsLoading.tsx
Normal 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;
|
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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");
|
|
||||||
};
|
};
|
||||||
|
@@ -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,
|
||||||
|
Reference in New Issue
Block a user