ClimaCell API v4 (#8)
* gets lan/lon from map geo * climacell v4 endpoints * Uses ClimaCell API v4, adds Sunrise-Sunset API
This commit is contained in:
Vendored
+9
-9
File diff suppressed because one or more lines are too long
+57
-20
@@ -48,6 +48,8 @@ export function AppContextProvider({ children }) {
|
||||
const [customLat, setCustomLat] = useState(null);
|
||||
const [customLon, setCustomLon] = useState(null);
|
||||
const [mouseHide, setMouseHide] = useState(false);
|
||||
const [sunriseTime, setSunriseTime] = useState(null);
|
||||
const [sunsetTime, setSunsetTime] = useState(null);
|
||||
|
||||
/**
|
||||
* Save mouse hide state
|
||||
@@ -307,10 +309,10 @@ export function AppContextProvider({ children }) {
|
||||
setHourlyWeatherDataErrMsg(null);
|
||||
const { latitude, longitude } = coords;
|
||||
const fields = [
|
||||
"temp",
|
||||
"precipitation_probability",
|
||||
"precipitation",
|
||||
"wind_speed",
|
||||
"temperature",
|
||||
"precipitationProbability",
|
||||
"precipitationIntensity",
|
||||
"windSpeed",
|
||||
].join("%2c");
|
||||
|
||||
const endTime = new Date(
|
||||
@@ -330,7 +332,7 @@ export function AppContextProvider({ children }) {
|
||||
|
||||
axios
|
||||
.get(
|
||||
`https://api.climacell.co/v3/weather/forecast/hourly?unit_system=si&fields=${fields}&apikey=${weatherApiKey}&lat=${latitude}&lon=${longitude}&end_time=${endTime}`
|
||||
`https://data.climacell.co/v4/timelines?location=${latitude}%2C${longitude}&fields=${fields}×teps=1h&apikey=${weatherApiKey}&endTime=${endTime}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (!res) {
|
||||
@@ -365,10 +367,10 @@ export function AppContextProvider({ children }) {
|
||||
setDailyWeatherDataErrMsg(null);
|
||||
const { latitude, longitude } = coords;
|
||||
const fields = [
|
||||
"temp",
|
||||
"precipitation_probability",
|
||||
"precipitation",
|
||||
"wind_speed",
|
||||
"temperature",
|
||||
"precipitationProbability",
|
||||
"precipitationIntensity",
|
||||
"windSpeed",
|
||||
].join("%2c");
|
||||
|
||||
const endTime = new Date(
|
||||
@@ -387,7 +389,7 @@ export function AppContextProvider({ children }) {
|
||||
}
|
||||
axios
|
||||
.get(
|
||||
`https://api.climacell.co/v3/weather/forecast/daily?unit_system=si&fields=${fields}&apikey=${weatherApiKey}&lat=${latitude}&lon=${longitude}&end_time=${endTime}`
|
||||
`https://data.climacell.co/v4/timelines?location=${latitude}%2C${longitude}&fields=${fields}×teps=1d&apikey=${weatherApiKey}&endTime=${endTime}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (!res) {
|
||||
@@ -407,6 +409,39 @@ export function AppContextProvider({ children }) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateSunriseSunset(coords) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!coords) {
|
||||
setSunriseTime(null);
|
||||
setSunsetTime(null);
|
||||
return reject("No coords");
|
||||
}
|
||||
const { latitude, longitude } = coords;
|
||||
|
||||
axios
|
||||
.get(
|
||||
`https://api.sunrise-sunset.org/json?lat=${latitude}&lng=${longitude}&formatted=0`
|
||||
)
|
||||
.then((res) => {
|
||||
const { results } = res?.data;
|
||||
if (results) {
|
||||
const { sunrise, sunset } = results;
|
||||
setSunriseTime(sunrise);
|
||||
setSunsetTime(sunset);
|
||||
} else {
|
||||
setSunriseTime(null);
|
||||
setSunsetTime(null);
|
||||
}
|
||||
resolve(results);
|
||||
})
|
||||
.catch((err) => {
|
||||
setSunriseTime(null);
|
||||
setSunsetTime(null);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates current weather data
|
||||
*
|
||||
@@ -421,17 +456,15 @@ export function AppContextProvider({ children }) {
|
||||
setCurrentWeatherDataErrMsg(null);
|
||||
const { latitude, longitude } = coords;
|
||||
|
||||
// https://developer.climacell.co/v3/reference#data-layers-weather
|
||||
const fields = [
|
||||
"temp",
|
||||
"temperature",
|
||||
"humidity",
|
||||
"wind_speed",
|
||||
"precipitation",
|
||||
"precipitation_type",
|
||||
"sunrise",
|
||||
"sunset",
|
||||
"cloud_cover",
|
||||
"weather_code",
|
||||
"windSpeed",
|
||||
"precipitationIntensity",
|
||||
"precipitationType",
|
||||
"precipitationProbability",
|
||||
"cloudCover",
|
||||
"weatherCode",
|
||||
].join("%2c");
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!coords) {
|
||||
@@ -443,9 +476,10 @@ export function AppContextProvider({ children }) {
|
||||
setSettingsMenuOpen(true);
|
||||
return reject("Missing weather API key");
|
||||
}
|
||||
|
||||
axios
|
||||
.get(
|
||||
`https://api.climacell.co/v3/weather/realtime?unit_system=si&fields=${fields}&apikey=${weatherApiKey}&lat=${latitude}&lon=${longitude};`
|
||||
`https://data.climacell.co/v4/timelines?location=${latitude}%2C${longitude}&fields=${fields}×teps=current&apikey=${weatherApiKey}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (!res) {
|
||||
@@ -572,6 +606,9 @@ export function AppContextProvider({ children }) {
|
||||
dailyWeatherDataErrMsg,
|
||||
mouseHide,
|
||||
saveMouseHide,
|
||||
updateSunriseSunset,
|
||||
sunriseTime,
|
||||
sunsetTime,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,6 @@ import styles from "./styles.css";
|
||||
import {
|
||||
convertTemp,
|
||||
convertSpeed,
|
||||
convertLength,
|
||||
} from "~/services/conversions";
|
||||
|
||||
import { InlineIcon } from "@iconify/react";
|
||||
@@ -33,29 +32,29 @@ import daySunnyOvercast from "@iconify/icons-wi/day-sunny-overcast";
|
||||
* @returns {JSX.Element} Current weather conditions component
|
||||
*/
|
||||
const CurrentWeather = () => {
|
||||
const { currentWeatherData, tempUnit, speedUnit, lengthUnit } = useContext(
|
||||
const { currentWeatherData, tempUnit, speedUnit, sunriseTime, sunsetTime } = useContext(
|
||||
AppContext
|
||||
);
|
||||
if (currentWeatherData) {
|
||||
const weatherData =
|
||||
currentWeatherData?.data?.timelines?.[0]?.intervals[0]?.values;
|
||||
if (weatherData) {
|
||||
const {
|
||||
cloud_cover: { value: cloudCover },
|
||||
humidity: { value: humidity },
|
||||
precipitation: { value: precipitation },
|
||||
precipitation_type: { value: precipitationType },
|
||||
temp: { value: temp },
|
||||
weather_code: { value: weatherCode },
|
||||
wind_speed: { value: windSpeed },
|
||||
sunrise: { value: sunrise },
|
||||
sunset: { value: sunset },
|
||||
} = currentWeatherData;
|
||||
|
||||
cloudCover,
|
||||
humidity,
|
||||
precipitationType,
|
||||
precipitationProbability,
|
||||
temperature,
|
||||
weatherCode,
|
||||
windSpeed,
|
||||
} = weatherData;
|
||||
const daylight = sunriseTime && sunsetTime ? isDaylight(new Date(sunriseTime), new Date(sunsetTime)) : true;
|
||||
const { icon: weatherIcon, desc: weatherDesc } =
|
||||
parseWeatherCode(weatherCode, isDaylight(sunrise, sunset)) || {};
|
||||
parseWeatherCode(weatherCode, daylight) || {};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.currentTemp}>
|
||||
{convertTemp(temp, tempUnit)}
|
||||
{convertTemp(temperature, tempUnit)}
|
||||
<InlineIcon icon={degreesIcon} />
|
||||
</div>
|
||||
<div className={styles.iconContainer}>
|
||||
@@ -65,6 +64,14 @@ const CurrentWeather = () => {
|
||||
</div>
|
||||
<div className={styles.stats}>
|
||||
<div>
|
||||
<div className={styles.statItem}>
|
||||
<div>
|
||||
<InlineIcon
|
||||
icon={precipitationType === 2 ? snowIcon : rainIcon}
|
||||
/>
|
||||
</div>
|
||||
<div>{precipitationProbability}%</div>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<div>
|
||||
<InlineIcon icon={cloudIcon} />
|
||||
@@ -88,17 +95,6 @@ const CurrentWeather = () => {
|
||||
</div>
|
||||
<div>{parseInt(humidity)}%</div>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<div>
|
||||
<InlineIcon
|
||||
icon={precipitationType === "snow" ? snowIcon : rainIcon}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.textUnit}>
|
||||
<div>{convertLength(precipitation, lengthUnit)}</div>
|
||||
<div>{lengthUnit}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.description}>{weatherDesc || ""}</div>
|
||||
@@ -112,61 +108,69 @@ const CurrentWeather = () => {
|
||||
/**
|
||||
* Parse weather code
|
||||
*
|
||||
* https://docs.climacell.co/reference/data-layers-overview
|
||||
*
|
||||
* @param {String} code
|
||||
* @param {Boolean} [isDay] if it is currently day
|
||||
* @returns {Object} weather description and icon
|
||||
*/
|
||||
const parseWeatherCode = (code, isDay) => {
|
||||
switch (code) {
|
||||
case "freezing_rain_heavy":
|
||||
case 6201:
|
||||
return { desc: "Heavy freezing rain", icon: isDay ? dayRain : nightRain };
|
||||
case "freezing_rain":
|
||||
case 6001:
|
||||
return { desc: "Freezing rain", icon: isDay ? dayRain : nightRain };
|
||||
case "freezing_rain_light":
|
||||
case 6200:
|
||||
return { desc: "Light freezing rain", icon: isDay ? dayRain : nightRain };
|
||||
case "freezing_drizzle":
|
||||
case 6000:
|
||||
return { desc: "Freezing drizzle", icon: rainMix };
|
||||
case "ice_pellets_heavy":
|
||||
case 7101:
|
||||
return { desc: "Heavy ice pellets", icon: rainMix };
|
||||
case "ice_pellets":
|
||||
case 7000:
|
||||
return { desc: "Ice pellets", icon: rainMix };
|
||||
case "ice_pellets_light":
|
||||
case 7102:
|
||||
return { desc: "Light ice pellets", icon: rainMix };
|
||||
case "snow_heavy":
|
||||
case 5101:
|
||||
return { desc: "Heavy snow", icon: snowIcon };
|
||||
case "snow":
|
||||
case 5000:
|
||||
return { desc: "Show", icon: snowIcon };
|
||||
case "snow_light":
|
||||
case 5100:
|
||||
return { desc: "Light snow", icon: snowIcon };
|
||||
case "flurries":
|
||||
case 5001:
|
||||
return { desc: "Flurries", icon: snowIcon };
|
||||
case "tstorm":
|
||||
case 8000:
|
||||
return { desc: "Thunder storm", icon: thunderstormIcon };
|
||||
case "rain_heavy":
|
||||
case 4201:
|
||||
return { desc: "Heavy rain", icon: isDay ? dayRain : nightRain };
|
||||
case "rain":
|
||||
case 4001:
|
||||
return { desc: "Rain", icon: isDay ? dayRain : nightRain };
|
||||
case "rain_light":
|
||||
case 4200:
|
||||
return { desc: "Light rain", icon: isDay ? dayRain : nightRain };
|
||||
case "drizzle":
|
||||
case 4000:
|
||||
return { desc: "Drizzle", icon: rainMix };
|
||||
case "fog_light":
|
||||
case 2100:
|
||||
return { desc: "Light fog", icon: fogIcon };
|
||||
case "fog":
|
||||
case 2000:
|
||||
return { desc: "Fog", icon: fogIcon };
|
||||
case "cloudy":
|
||||
case 1001:
|
||||
return { desc: "Cloudy", icon: cloudyIcon };
|
||||
case "mostly_cloudy":
|
||||
case 1102:
|
||||
return { desc: "Mostly cloudy", icon: cloudyIcon };
|
||||
case "partly_cloudy":
|
||||
case 1101:
|
||||
return {
|
||||
desc: "Partly cloudy",
|
||||
icon: isDay ? daySunnyOvercast : nightAltCloudy,
|
||||
};
|
||||
case "mostly_clear":
|
||||
case 1100:
|
||||
return { desc: "Mostly clear", icon: isDay ? dayCloudy : nightAltCloudy };
|
||||
case "clear":
|
||||
case 1000:
|
||||
return { desc: "Clear", icon: isDay ? daySunny : nightClear };
|
||||
case 3001:
|
||||
return { desc: "Wind", icon: strongWind };
|
||||
case 3000:
|
||||
return { desc: "Light wind", icon: strongWind };
|
||||
case 3002:
|
||||
return { desc: "Strong wind", icon: strongWind };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
grid-area: weather-icon;
|
||||
grid-area: weather-icon;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
@@ -11,12 +11,12 @@ import styles from "./styles.css";
|
||||
* @returns {JSX.Element} Location name
|
||||
*/
|
||||
const LocationName = () => {
|
||||
const { currentWeatherData, reverseGeoApiKey } = useContext(AppContext);
|
||||
const { mapGeo, reverseGeoApiKey } = useContext(AppContext);
|
||||
const [name, setName] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentWeatherData && reverseGeoApiKey) {
|
||||
const { lat, lon } = currentWeatherData;
|
||||
if (mapGeo && reverseGeoApiKey) {
|
||||
const { latitude: lat, longitude: lon } = mapGeo;
|
||||
reverseGeocode({ lat, lon, apiKey: reverseGeoApiKey })
|
||||
.then((res) => {
|
||||
setName(getName(res));
|
||||
@@ -25,11 +25,11 @@ const LocationName = () => {
|
||||
setName(`${lat}, ${lon}`);
|
||||
console.log("err!", err);
|
||||
});
|
||||
} else if (currentWeatherData && !reverseGeoApiKey) {
|
||||
const { lat, lon } = currentWeatherData;
|
||||
} else if (mapGeo && !reverseGeoApiKey) {
|
||||
const { latitude: lat, longitude: lon } = mapGeo;
|
||||
setName(`${lat}, ${lon}`);
|
||||
}
|
||||
}, [currentWeatherData, reverseGeoApiKey]);
|
||||
}, [mapGeo, reverseGeoApiKey]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.container}`}>
|
||||
@@ -48,7 +48,7 @@ const LocationName = () => {
|
||||
* @param {Object} res
|
||||
* @returns {String} Display name
|
||||
*/
|
||||
const getName = (res) => {
|
||||
const getName = (res) => {
|
||||
// eslint-disable-next-line babel/camelcase
|
||||
const { city, country, state, country_code, county, region } = res.address;
|
||||
// eslint-disable-next-line babel/camelcase
|
||||
|
||||
@@ -13,24 +13,20 @@ import styles from "./styles.css";
|
||||
* @returns {JSX.Element} Sunrise / Sunset component
|
||||
*/
|
||||
const SunRiseSet = () => {
|
||||
const { currentWeatherData, clockTime } = useContext(AppContext);
|
||||
if (currentWeatherData) {
|
||||
const {
|
||||
sunrise: { value: sunrise },
|
||||
sunset: { value: sunset },
|
||||
} = currentWeatherData;
|
||||
const { sunriseTime, sunsetTime, clockTime } = useContext(AppContext);
|
||||
if (sunriseTime && sunsetTime) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
<InlineIcon icon={bxsSun} />
|
||||
<span>
|
||||
{format(new Date(sunrise), clockTime === "12" ? "p" : "HH:mm")}
|
||||
{format(new Date(sunriseTime), clockTime === "12" ? "p" : "HH:mm")}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<InlineIcon icon={bxsMoon} />
|
||||
<span>
|
||||
{format(new Date(sunset), clockTime === "12" ? "p" : "HH:mm")}
|
||||
{format(new Date(sunsetTime), clockTime === "12" ? "p" : "HH:mm")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,7 @@ const WeatherInfo = () => {
|
||||
darkMode,
|
||||
setSettingsMenuOpen,
|
||||
currentWeatherData,
|
||||
updateSunriseSunset
|
||||
} = useContext(AppContext);
|
||||
|
||||
const [
|
||||
@@ -77,10 +78,11 @@ const WeatherInfo = () => {
|
||||
const [err, setErr] = useState(null);
|
||||
|
||||
const hourlyWeatherUpdateCb = useCallback(() => {
|
||||
updateSunriseSunset(mapGeo);
|
||||
updateHourlyWeatherData(mapGeo).catch((err) => {
|
||||
console.log("err", err);
|
||||
});
|
||||
}, [updateHourlyWeatherData, mapGeo]);
|
||||
}, [updateHourlyWeatherData, updateSunriseSunset, mapGeo]);
|
||||
|
||||
const dailyWeatherUpdateCb = useCallback(() => {
|
||||
updateDailyWeatherData(mapGeo).catch((err) => {
|
||||
|
||||
@@ -87,16 +87,22 @@ const createChartOptions = ({
|
||||
const chartColors = {
|
||||
blue: "rgba(63, 127, 191, 0.5)",
|
||||
gray: "rgba(127, 127, 127, 0.5)",
|
||||
gray2: "rgba(127, 127, 127, 0.3)",
|
||||
};
|
||||
|
||||
const mapChartData = ({ data, tempUnit, speedUnit, altMode, lengthUnit }) => {
|
||||
const mapChartData = ({
|
||||
data: weatherData,
|
||||
tempUnit,
|
||||
speedUnit,
|
||||
altMode,
|
||||
lengthUnit,
|
||||
}) => {
|
||||
const data = weatherData?.data?.timelines?.[0]?.intervals;
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
labels: data.map((e) => {
|
||||
const date = new Date(e.observation_time.value);
|
||||
const date = new Date(e.startTime);
|
||||
const adjustedTimestamp =
|
||||
date.getTime() + date.getTimezoneOffset() * 60 * 1000;
|
||||
return format(new Date(adjustedTimestamp), "EEEEE");
|
||||
@@ -104,37 +110,30 @@ const mapChartData = ({ data, tempUnit, speedUnit, altMode, lengthUnit }) => {
|
||||
datasets: [
|
||||
{
|
||||
radius: 0,
|
||||
label: altMode ? "High Wind Speed" : "High Temp",
|
||||
label: altMode ? "Wind Speed" : "Temp",
|
||||
data: data.map((e) => {
|
||||
const {
|
||||
values: { windSpeed, temperature },
|
||||
} = e;
|
||||
return altMode
|
||||
? convertSpeed(e.wind_speed[1].max.value, speedUnit)
|
||||
: convertTemp(e.temp[1].max.value, tempUnit);
|
||||
? convertSpeed(windSpeed, speedUnit)
|
||||
: convertTemp(temperature, tempUnit);
|
||||
}),
|
||||
yAxisID: "y-axis-1",
|
||||
borderColor: chartColors.gray,
|
||||
backgroundColor: chartColors.gray,
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
radius: 0,
|
||||
label: altMode ? "Low Wind Speed" : "Low Temp",
|
||||
data: data.map((e) => {
|
||||
return altMode
|
||||
? convertSpeed(e.wind_speed[0].min.value, speedUnit)
|
||||
: convertTemp(e.temp[0].min.value, tempUnit);
|
||||
}),
|
||||
yAxisID: "y-axis-1",
|
||||
borderColor: chartColors.gray2,
|
||||
backgroundColor: chartColors.gray2,
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
radius: 0,
|
||||
label: "Precipitation",
|
||||
data: data.map((e) => {
|
||||
const {
|
||||
values: { precipitationIntensity, precipitationProbability },
|
||||
} = e;
|
||||
return altMode
|
||||
? convertLength(e.precipitation[0].max.value, lengthUnit)
|
||||
: e.precipitation_probability.value;
|
||||
? convertLength(precipitationIntensity, lengthUnit)
|
||||
: precipitationProbability;
|
||||
}),
|
||||
yAxisID: "y-axis-2",
|
||||
borderColor: chartColors.blue,
|
||||
|
||||
@@ -90,25 +90,26 @@ const chartColors = {
|
||||
};
|
||||
|
||||
const mapChartData = ({
|
||||
data,
|
||||
data: weatherData,
|
||||
tempUnit,
|
||||
speedUnit,
|
||||
clockTime,
|
||||
altMode,
|
||||
lengthUnit,
|
||||
}) => {
|
||||
const data = weatherData?.data?.timelines?.[0]?.intervals;
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
labels: data.map((e) => {
|
||||
if (clockTime === "12") {
|
||||
return `${format(new Date(e.observation_time.value), "h")}${format(
|
||||
new Date(e.observation_time.value),
|
||||
return `${format(new Date(e.startTime), "h")}${format(
|
||||
new Date(e.startTime),
|
||||
"aaaaa"
|
||||
)}`;
|
||||
} else {
|
||||
return `${format(new Date(e.observation_time.value), "HH")}`;
|
||||
return `${format(new Date(e.startTime), "HH")}`;
|
||||
}
|
||||
}),
|
||||
datasets: [
|
||||
@@ -116,9 +117,12 @@ const mapChartData = ({
|
||||
radius: 0,
|
||||
label: altMode ? "Wind Speed" : "Temp",
|
||||
data: data.map((e) => {
|
||||
const {
|
||||
values: { windSpeed, temperature },
|
||||
} = e;
|
||||
return altMode
|
||||
? convertSpeed(e.wind_speed.value, speedUnit)
|
||||
: convertTemp(e.temp.value, tempUnit);
|
||||
? convertSpeed(windSpeed, speedUnit)
|
||||
: convertTemp(temperature, tempUnit);
|
||||
}),
|
||||
yAxisID: "y-axis-1",
|
||||
borderColor: chartColors.gray,
|
||||
@@ -129,9 +133,12 @@ const mapChartData = ({
|
||||
radius: 0,
|
||||
label: "Precipitation",
|
||||
data: data.map((e) => {
|
||||
const {
|
||||
values: { precipitationIntensity, precipitationProbability },
|
||||
} = e;
|
||||
return altMode
|
||||
? convertLength(e.precipitation.value, lengthUnit)
|
||||
: e.precipitation_probability.value;
|
||||
? convertLength(precipitationIntensity, lengthUnit)
|
||||
: precipitationProbability;
|
||||
}),
|
||||
yAxisID: "y-axis-2",
|
||||
borderColor: chartColors.blue,
|
||||
@@ -196,7 +203,7 @@ const HourlyChart = () => {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}else if (hourlyWeatherDataErr) {
|
||||
} else if (hourlyWeatherDataErr) {
|
||||
return (
|
||||
<div
|
||||
className={`${darkMode ? styles.dark : styles.light} ${
|
||||
|
||||
Reference in New Issue
Block a user