245 lines
6.5 KiB
JavaScript
245 lines
6.5 KiB
JavaScript
"use client";
|
||
|
||
import { getWeatherIcon } from "@/utils/weather.js";
|
||
import Image from "next/image";
|
||
import { useEffect, useState } from "react";
|
||
import styles from "./todo.module.css";
|
||
|
||
export default function WeatherPage() {
|
||
const [weather, setWeather] = useState(null);
|
||
const [time, setTime] = useState(new Date().toLocaleTimeString());
|
||
const [date, setDate] = useState(new Date().toLocaleDateString());
|
||
const [weatherIcon, setWeatherIcon] = useState("/weather/sunnyday.png");
|
||
|
||
useEffect(() => {
|
||
async function fetchWeather() {
|
||
try {
|
||
const res = await fetch(
|
||
`https://api.pirateweather.net/forecast/${process.env.NEXT_PUBLIC_WEATHER_API}/42.3967,-88.1837?units=us&exclude=minutely,hourly,flags&icon=pirate`,
|
||
{ cache: "no-store" },
|
||
);
|
||
const data = await res.json();
|
||
setWeather(data);
|
||
} catch (err) {
|
||
console.error("Failed to fetch weather:", err);
|
||
}
|
||
}
|
||
|
||
// only fetch weather every 5 minutes on the 0th second
|
||
const fetchWeatherInterval = () => {
|
||
const now = new Date();
|
||
|
||
if (now.getSeconds() === 0 && now.getMinutes() % 5 === 0) {
|
||
fetchWeather();
|
||
}
|
||
};
|
||
|
||
fetchWeather();
|
||
|
||
const interval = setInterval(fetchWeatherInterval, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setTime(new Date().toLocaleTimeString());
|
||
}, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setDate(new Date().toLocaleDateString());
|
||
}, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
const weatherTemp = Math.floor(weather?.currently?.temperature) || "--";
|
||
|
||
const weatherFeelsLike =
|
||
Math.floor(weather?.currently?.apparentTemperature) || "--";
|
||
|
||
const weatherHigh =
|
||
Math.floor(weather?.daily?.data[0]?.temperatureHigh) || "--";
|
||
const weatherLow =
|
||
Math.floor(weather?.daily?.data[0]?.temperatureLow) || "--";
|
||
|
||
const percentChance =
|
||
Math.floor(weather?.currently?.precipProbability * 100) || "0";
|
||
|
||
const lastUpdatedTime = weather
|
||
? new Date(weather.currently.time * 1000).toLocaleTimeString([], {
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
})
|
||
: "--";
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setWeatherIcon(
|
||
`/weather/${getWeatherIcon(weather?.currently?.icon)}.png`,
|
||
);
|
||
}, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, [weather]);
|
||
|
||
const weatherAlerts = weather?.alerts || [];
|
||
|
||
const futureForecasts = weather?.daily?.data.slice(1, 4) || [];
|
||
|
||
return (
|
||
<div className={styles.todoContainer}>
|
||
<div className={styles.todoHeader}>
|
||
<span className={styles.left}>
|
||
<span className={styles.title}>Raceway Car Wash</span>
|
||
<span className={styles.location}>Fox Lake</span>
|
||
</span>
|
||
<span className={styles.right}>
|
||
<span className={styles.time}>{time || "00:00:00 AM"}</span>
|
||
<span className={styles.date}>{date}</span>
|
||
</span>
|
||
</div>
|
||
<span className={styles.container}>
|
||
<span className={styles.listContainer}>
|
||
<span className={styles.lists}>Automated List</span>
|
||
<span className={styles.lists}>Manual List</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherContainer}>
|
||
<span className={styles.weatherContainerInner}>
|
||
<span className={styles.weatherHeader}>
|
||
⛅ Hyperlocal Weather
|
||
</span>
|
||
|
||
{weatherAlerts.length > 0 && (
|
||
<span className={styles.weatherAlerts}>
|
||
{weatherAlerts.map((alert, index) => (
|
||
<span
|
||
key={index}
|
||
className={styles.weatherAlert}
|
||
>
|
||
⚠️ {alert.title}
|
||
<span
|
||
className={
|
||
styles.weatherAlertExpires
|
||
}
|
||
>
|
||
Expires:{" "}
|
||
{new Date(
|
||
alert.expires * 1000,
|
||
).toLocaleString()}
|
||
</span>
|
||
</span>
|
||
))}
|
||
</span>
|
||
)}
|
||
|
||
<span className={styles.weatherTop}>
|
||
<span className={styles.weatherTopLeft}>
|
||
<span className={styles.weatherCondition}>
|
||
{weather?.currently?.summary}
|
||
</span>
|
||
<span className={styles.weatherTemp}>
|
||
{weatherTemp}°
|
||
</span>
|
||
<span className={styles.weatherFeelsLike}>
|
||
Feels Like {weatherFeelsLike}°
|
||
</span>
|
||
<span className={styles.weatherHighLow}>
|
||
High: {weatherHigh}° · Low:{" "}
|
||
{weatherLow}°
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherTopRight}>
|
||
<Image
|
||
alt="Weather Icon"
|
||
src={weatherIcon}
|
||
width={175}
|
||
height={175}
|
||
loading="eager"
|
||
/>
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherDetails}>
|
||
<span className={styles.detail}>
|
||
{weather?.currently.windSpeed || "--"}
|
||
<span className={styles.detailUnit}>mph</span>
|
||
<span className={styles.detailText}>
|
||
Wind Speed
|
||
</span>
|
||
</span>
|
||
<span className={styles.detail}>
|
||
{percentChance}%
|
||
<span className={styles.detailText}>
|
||
Precipitation
|
||
</span>
|
||
</span>
|
||
<span className={styles.detail}>
|
||
{Math.floor(
|
||
weather?.currently?.humidity * 100,
|
||
) || "--"}
|
||
%
|
||
<span className={styles.detailText}>
|
||
Humidity
|
||
</span>
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherFutureForecasts}>
|
||
{futureForecasts.map((forecast, index) => (
|
||
<span
|
||
key={index}
|
||
className={styles.futureForecast}
|
||
>
|
||
<span className={styles.futureForecastDay}>
|
||
{new Date(
|
||
forecast.time * 1000,
|
||
).toLocaleDateString([], {
|
||
weekday: "short",
|
||
})}
|
||
</span>
|
||
<span
|
||
className={
|
||
styles.futureForecastIconContainer
|
||
}
|
||
>
|
||
<Image
|
||
alt="Weather Icon"
|
||
src={`/weather/${getWeatherIcon(forecast.icon)}.png`}
|
||
className={
|
||
styles.futureForecastIcon
|
||
}
|
||
width={40}
|
||
height={40}
|
||
unoptimized
|
||
/>
|
||
</span>
|
||
<span className={styles.futurePrecipChance}>
|
||
{Math.floor(
|
||
forecast.precipProbability * 100,
|
||
)}
|
||
%
|
||
</span>
|
||
<span className={styles.futureForecastTemp}>
|
||
{Math.floor(forecast.temperatureHigh)}°
|
||
/ {Math.floor(forecast.temperatureLow)}°
|
||
</span>
|
||
</span>
|
||
))}
|
||
</span>
|
||
|
||
<span className={styles.weatherFooter}>
|
||
Last Updated {lastUpdatedTime}
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|