2025-09-21 11:02:31 -07:00
|
|
|
"use client";
|
2024-10-11 18:00:22 +05:30
|
|
|
|
2025-09-21 11:02:31 -07:00
|
|
|
import cx from "classnames";
|
|
|
|
|
import { format, isWithinInterval } from "date-fns";
|
|
|
|
|
import { useEffect, useState } from "react";
|
2024-10-11 18:00:22 +05:30
|
|
|
|
2025-10-09 19:28:04 +01:00
|
|
|
const SunIcon = ({ size = 40 }: { size?: number }) => (
|
|
|
|
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<circle cx="12" cy="12" r="5" fill="currentColor" />
|
|
|
|
|
<line x1="12" y1="1" x2="12" y2="3" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="12" y1="21" x2="12" y2="23" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="1" y1="12" x2="3" y2="12" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="21" y1="12" x2="23" y2="12" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" stroke="currentColor" strokeWidth="2" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const MoonIcon = ({ size = 40 }: { size?: number }) => (
|
|
|
|
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<path d="M21 12.79A9 9 0 1 1 11.21 3A7 7 0 0 0 21 12.79z" fill="currentColor" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const CloudIcon = ({ size = 24 }: { size?: number }) => (
|
|
|
|
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
|
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" stroke="currentColor" strokeWidth="2" fill="none" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-21 11:02:31 -07:00
|
|
|
type WeatherAtLocation = {
|
2024-10-11 18:00:22 +05:30
|
|
|
latitude: number;
|
|
|
|
|
longitude: number;
|
|
|
|
|
generationtime_ms: number;
|
|
|
|
|
utc_offset_seconds: number;
|
|
|
|
|
timezone: string;
|
|
|
|
|
timezone_abbreviation: string;
|
|
|
|
|
elevation: number;
|
2025-10-09 19:28:04 +01:00
|
|
|
cityName?: string;
|
2024-10-11 18:00:22 +05:30
|
|
|
current_units: {
|
|
|
|
|
time: string;
|
|
|
|
|
interval: string;
|
|
|
|
|
temperature_2m: string;
|
|
|
|
|
};
|
|
|
|
|
current: {
|
|
|
|
|
time: string;
|
|
|
|
|
interval: number;
|
|
|
|
|
temperature_2m: number;
|
|
|
|
|
};
|
|
|
|
|
hourly_units: {
|
|
|
|
|
time: string;
|
|
|
|
|
temperature_2m: string;
|
|
|
|
|
};
|
|
|
|
|
hourly: {
|
|
|
|
|
time: string[];
|
|
|
|
|
temperature_2m: number[];
|
|
|
|
|
};
|
|
|
|
|
daily_units: {
|
|
|
|
|
time: string;
|
|
|
|
|
sunrise: string;
|
|
|
|
|
sunset: string;
|
|
|
|
|
};
|
|
|
|
|
daily: {
|
|
|
|
|
time: string[];
|
|
|
|
|
sunrise: string[];
|
|
|
|
|
sunset: string[];
|
|
|
|
|
};
|
2025-09-21 11:02:31 -07:00
|
|
|
};
|
2024-10-11 18:00:22 +05:30
|
|
|
|
|
|
|
|
const SAMPLE = {
|
2025-09-21 11:02:31 -07:00
|
|
|
latitude: 37.763_283,
|
|
|
|
|
longitude: -122.412_86,
|
|
|
|
|
generationtime_ms: 0.027_894_973_754_882_812,
|
2024-10-11 18:00:22 +05:30
|
|
|
utc_offset_seconds: 0,
|
2025-09-21 11:02:31 -07:00
|
|
|
timezone: "GMT",
|
|
|
|
|
timezone_abbreviation: "GMT",
|
2024-10-11 18:00:22 +05:30
|
|
|
elevation: 18,
|
2025-09-21 11:02:31 -07:00
|
|
|
current_units: { time: "iso8601", interval: "seconds", temperature_2m: "°C" },
|
|
|
|
|
current: { time: "2024-10-07T19:30", interval: 900, temperature_2m: 29.3 },
|
|
|
|
|
hourly_units: { time: "iso8601", temperature_2m: "°C" },
|
2024-10-11 18:00:22 +05:30
|
|
|
hourly: {
|
|
|
|
|
time: [
|
2025-09-21 11:02:31 -07:00
|
|
|
"2024-10-07T00:00",
|
|
|
|
|
"2024-10-07T01:00",
|
|
|
|
|
"2024-10-07T02:00",
|
|
|
|
|
"2024-10-07T03:00",
|
|
|
|
|
"2024-10-07T04:00",
|
|
|
|
|
"2024-10-07T05:00",
|
|
|
|
|
"2024-10-07T06:00",
|
|
|
|
|
"2024-10-07T07:00",
|
|
|
|
|
"2024-10-07T08:00",
|
|
|
|
|
"2024-10-07T09:00",
|
|
|
|
|
"2024-10-07T10:00",
|
|
|
|
|
"2024-10-07T11:00",
|
|
|
|
|
"2024-10-07T12:00",
|
|
|
|
|
"2024-10-07T13:00",
|
|
|
|
|
"2024-10-07T14:00",
|
|
|
|
|
"2024-10-07T15:00",
|
|
|
|
|
"2024-10-07T16:00",
|
|
|
|
|
"2024-10-07T17:00",
|
|
|
|
|
"2024-10-07T18:00",
|
|
|
|
|
"2024-10-07T19:00",
|
|
|
|
|
"2024-10-07T20:00",
|
|
|
|
|
"2024-10-07T21:00",
|
|
|
|
|
"2024-10-07T22:00",
|
|
|
|
|
"2024-10-07T23:00",
|
|
|
|
|
"2024-10-08T00:00",
|
|
|
|
|
"2024-10-08T01:00",
|
|
|
|
|
"2024-10-08T02:00",
|
|
|
|
|
"2024-10-08T03:00",
|
|
|
|
|
"2024-10-08T04:00",
|
|
|
|
|
"2024-10-08T05:00",
|
|
|
|
|
"2024-10-08T06:00",
|
|
|
|
|
"2024-10-08T07:00",
|
|
|
|
|
"2024-10-08T08:00",
|
|
|
|
|
"2024-10-08T09:00",
|
|
|
|
|
"2024-10-08T10:00",
|
|
|
|
|
"2024-10-08T11:00",
|
|
|
|
|
"2024-10-08T12:00",
|
|
|
|
|
"2024-10-08T13:00",
|
|
|
|
|
"2024-10-08T14:00",
|
|
|
|
|
"2024-10-08T15:00",
|
|
|
|
|
"2024-10-08T16:00",
|
|
|
|
|
"2024-10-08T17:00",
|
|
|
|
|
"2024-10-08T18:00",
|
|
|
|
|
"2024-10-08T19:00",
|
|
|
|
|
"2024-10-08T20:00",
|
|
|
|
|
"2024-10-08T21:00",
|
|
|
|
|
"2024-10-08T22:00",
|
|
|
|
|
"2024-10-08T23:00",
|
|
|
|
|
"2024-10-09T00:00",
|
|
|
|
|
"2024-10-09T01:00",
|
|
|
|
|
"2024-10-09T02:00",
|
|
|
|
|
"2024-10-09T03:00",
|
|
|
|
|
"2024-10-09T04:00",
|
|
|
|
|
"2024-10-09T05:00",
|
|
|
|
|
"2024-10-09T06:00",
|
|
|
|
|
"2024-10-09T07:00",
|
|
|
|
|
"2024-10-09T08:00",
|
|
|
|
|
"2024-10-09T09:00",
|
|
|
|
|
"2024-10-09T10:00",
|
|
|
|
|
"2024-10-09T11:00",
|
|
|
|
|
"2024-10-09T12:00",
|
|
|
|
|
"2024-10-09T13:00",
|
|
|
|
|
"2024-10-09T14:00",
|
|
|
|
|
"2024-10-09T15:00",
|
|
|
|
|
"2024-10-09T16:00",
|
|
|
|
|
"2024-10-09T17:00",
|
|
|
|
|
"2024-10-09T18:00",
|
|
|
|
|
"2024-10-09T19:00",
|
|
|
|
|
"2024-10-09T20:00",
|
|
|
|
|
"2024-10-09T21:00",
|
|
|
|
|
"2024-10-09T22:00",
|
|
|
|
|
"2024-10-09T23:00",
|
|
|
|
|
"2024-10-10T00:00",
|
|
|
|
|
"2024-10-10T01:00",
|
|
|
|
|
"2024-10-10T02:00",
|
|
|
|
|
"2024-10-10T03:00",
|
|
|
|
|
"2024-10-10T04:00",
|
|
|
|
|
"2024-10-10T05:00",
|
|
|
|
|
"2024-10-10T06:00",
|
|
|
|
|
"2024-10-10T07:00",
|
|
|
|
|
"2024-10-10T08:00",
|
|
|
|
|
"2024-10-10T09:00",
|
|
|
|
|
"2024-10-10T10:00",
|
|
|
|
|
"2024-10-10T11:00",
|
|
|
|
|
"2024-10-10T12:00",
|
|
|
|
|
"2024-10-10T13:00",
|
|
|
|
|
"2024-10-10T14:00",
|
|
|
|
|
"2024-10-10T15:00",
|
|
|
|
|
"2024-10-10T16:00",
|
|
|
|
|
"2024-10-10T17:00",
|
|
|
|
|
"2024-10-10T18:00",
|
|
|
|
|
"2024-10-10T19:00",
|
|
|
|
|
"2024-10-10T20:00",
|
|
|
|
|
"2024-10-10T21:00",
|
|
|
|
|
"2024-10-10T22:00",
|
|
|
|
|
"2024-10-10T23:00",
|
|
|
|
|
"2024-10-11T00:00",
|
|
|
|
|
"2024-10-11T01:00",
|
|
|
|
|
"2024-10-11T02:00",
|
|
|
|
|
"2024-10-11T03:00",
|
2024-10-11 18:00:22 +05:30
|
|
|
],
|
|
|
|
|
temperature_2m: [
|
|
|
|
|
36.6, 32.8, 29.5, 28.6, 29.2, 28.2, 27.5, 26.6, 26.5, 26, 25, 23.5, 23.9,
|
|
|
|
|
24.2, 22.9, 21, 24, 28.1, 31.4, 33.9, 32.1, 28.9, 26.9, 25.2, 23, 21.1,
|
|
|
|
|
19.6, 18.6, 17.7, 16.8, 16.2, 15.5, 14.9, 14.4, 14.2, 13.7, 13.3, 12.9,
|
|
|
|
|
12.5, 13.5, 15.8, 17.7, 19.6, 21, 21.9, 22.3, 22, 20.7, 18.9, 17.9, 17.3,
|
|
|
|
|
17, 16.7, 16.2, 15.6, 15.2, 15, 15, 15.1, 14.8, 14.8, 14.9, 14.7, 14.8,
|
|
|
|
|
15.3, 16.2, 17.9, 19.6, 20.5, 21.6, 21, 20.7, 19.3, 18.7, 18.4, 17.9,
|
|
|
|
|
17.3, 17, 17, 16.8, 16.4, 16.2, 16, 15.8, 15.7, 15.4, 15.4, 16.1, 16.7,
|
|
|
|
|
17, 18.6, 19, 19.5, 19.4, 18.5, 17.9, 17.5, 16.7, 16.3, 16.1,
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
daily_units: {
|
2025-09-21 11:02:31 -07:00
|
|
|
time: "iso8601",
|
|
|
|
|
sunrise: "iso8601",
|
|
|
|
|
sunset: "iso8601",
|
2024-10-11 18:00:22 +05:30
|
|
|
},
|
|
|
|
|
daily: {
|
|
|
|
|
time: [
|
2025-09-21 11:02:31 -07:00
|
|
|
"2024-10-07",
|
|
|
|
|
"2024-10-08",
|
|
|
|
|
"2024-10-09",
|
|
|
|
|
"2024-10-10",
|
|
|
|
|
"2024-10-11",
|
2024-10-11 18:00:22 +05:30
|
|
|
],
|
|
|
|
|
sunrise: [
|
2025-09-21 11:02:31 -07:00
|
|
|
"2024-10-07T07:15",
|
|
|
|
|
"2024-10-08T07:16",
|
|
|
|
|
"2024-10-09T07:17",
|
|
|
|
|
"2024-10-10T07:18",
|
|
|
|
|
"2024-10-11T07:19",
|
2024-10-11 18:00:22 +05:30
|
|
|
],
|
|
|
|
|
sunset: [
|
2025-09-21 11:02:31 -07:00
|
|
|
"2024-10-07T19:00",
|
|
|
|
|
"2024-10-08T18:58",
|
|
|
|
|
"2024-10-09T18:57",
|
|
|
|
|
"2024-10-10T18:55",
|
|
|
|
|
"2024-10-11T18:54",
|
2024-10-11 18:00:22 +05:30
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-14 23:01:46 +05:30
|
|
|
function n(num: number): number {
|
|
|
|
|
return Math.ceil(num);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 18:00:22 +05:30
|
|
|
export function Weather({
|
|
|
|
|
weatherAtLocation = SAMPLE,
|
|
|
|
|
}: {
|
|
|
|
|
weatherAtLocation?: WeatherAtLocation;
|
|
|
|
|
}) {
|
|
|
|
|
const currentHigh = Math.max(
|
2025-09-21 11:02:31 -07:00
|
|
|
...weatherAtLocation.hourly.temperature_2m.slice(0, 24)
|
2024-10-11 18:00:22 +05:30
|
|
|
);
|
|
|
|
|
const currentLow = Math.min(
|
2025-09-21 11:02:31 -07:00
|
|
|
...weatherAtLocation.hourly.temperature_2m.slice(0, 24)
|
2024-10-11 18:00:22 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const isDay = isWithinInterval(new Date(weatherAtLocation.current.time), {
|
|
|
|
|
start: new Date(weatherAtLocation.daily.sunrise[0]),
|
|
|
|
|
end: new Date(weatherAtLocation.daily.sunset[0]),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const [isMobile, setIsMobile] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
setIsMobile(window.innerWidth < 768);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handleResize();
|
2025-09-21 11:02:31 -07:00
|
|
|
window.addEventListener("resize", handleResize);
|
2024-10-11 18:00:22 +05:30
|
|
|
|
2025-09-21 11:02:31 -07:00
|
|
|
return () => window.removeEventListener("resize", handleResize);
|
2024-10-11 18:00:22 +05:30
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const hoursToShow = isMobile ? 5 : 6;
|
|
|
|
|
|
|
|
|
|
const currentTimeIndex = weatherAtLocation.hourly.time.findIndex(
|
2025-09-21 11:02:31 -07:00
|
|
|
(time) => new Date(time) >= new Date(weatherAtLocation.current.time)
|
2024-10-11 18:00:22 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const displayTimes = weatherAtLocation.hourly.time.slice(
|
|
|
|
|
currentTimeIndex,
|
2025-09-21 11:02:31 -07:00
|
|
|
currentTimeIndex + hoursToShow
|
2024-10-11 18:00:22 +05:30
|
|
|
);
|
|
|
|
|
const displayTemperatures = weatherAtLocation.hourly.temperature_2m.slice(
|
|
|
|
|
currentTimeIndex,
|
2025-09-21 11:02:31 -07:00
|
|
|
currentTimeIndex + hoursToShow
|
2024-10-11 18:00:22 +05:30
|
|
|
);
|
|
|
|
|
|
2025-10-09 19:28:04 +01:00
|
|
|
const location = weatherAtLocation.cityName ||
|
|
|
|
|
`${weatherAtLocation.latitude?.toFixed(1)}°, ${weatherAtLocation.longitude?.toFixed(1)}°`;
|
|
|
|
|
|
2024-10-11 18:00:22 +05:30
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cx(
|
2025-10-09 19:28:04 +01:00
|
|
|
"relative flex w-full flex-col gap-6 rounded-3xl p-6 shadow-lg overflow-hidden backdrop-blur-sm",
|
2024-10-11 18:00:22 +05:30
|
|
|
{
|
2025-10-09 19:28:04 +01:00
|
|
|
"bg-gradient-to-br from-sky-400 via-blue-500 to-blue-600": isDay,
|
2024-10-11 18:00:22 +05:30
|
|
|
},
|
|
|
|
|
{
|
2025-10-09 19:28:04 +01:00
|
|
|
"bg-gradient-to-br from-indigo-900 via-purple-900 to-slate-900": !isDay,
|
2025-09-21 11:02:31 -07:00
|
|
|
}
|
2024-10-11 18:00:22 +05:30
|
|
|
)}
|
|
|
|
|
>
|
2025-10-09 19:28:04 +01:00
|
|
|
<div className="absolute inset-0 bg-white/10 backdrop-blur-sm" />
|
|
|
|
|
|
|
|
|
|
<div className="relative z-10">
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
<div className="text-white/80 text-sm font-medium">
|
|
|
|
|
{location}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-white/60 text-xs">
|
|
|
|
|
{format(new Date(weatherAtLocation.current.time), "MMM d, h:mm a")}
|
2024-10-11 18:00:22 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-09 19:28:04 +01:00
|
|
|
<div className="flex items-center justify-between mb-6">
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<div className={cx("text-white/90", { "text-yellow-200": isDay, "text-blue-200": !isDay })}>
|
|
|
|
|
{isDay ? <SunIcon size={48} /> : <MoonIcon size={48} />}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-white text-5xl font-light">
|
|
|
|
|
{n(weatherAtLocation.current.temperature_2m)}
|
|
|
|
|
<span className="text-2xl text-white/80">
|
|
|
|
|
{weatherAtLocation.current_units.temperature_2m}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2024-10-11 18:00:22 +05:30
|
|
|
|
2025-10-09 19:28:04 +01:00
|
|
|
<div className="text-right">
|
|
|
|
|
<div className="text-white/90 text-sm font-medium">
|
|
|
|
|
H: {n(currentHigh)}°
|
2024-10-11 18:00:22 +05:30
|
|
|
</div>
|
2025-10-09 19:28:04 +01:00
|
|
|
<div className="text-white/70 text-sm">
|
|
|
|
|
L: {n(currentLow)}°
|
2024-10-11 18:00:22 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-10-09 19:28:04 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="bg-white/10 rounded-2xl p-4 backdrop-blur-sm">
|
|
|
|
|
<div className="text-white/80 text-sm mb-3 font-medium">
|
|
|
|
|
Hourly Forecast
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between gap-2">
|
|
|
|
|
{displayTimes.map((time, index) => {
|
|
|
|
|
const hourTime = new Date(time);
|
|
|
|
|
const isCurrentHour = hourTime.getHours() === new Date().getHours();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cx(
|
|
|
|
|
"flex flex-col items-center gap-2 py-2 px-1 rounded-lg min-w-0 flex-1",
|
|
|
|
|
{
|
|
|
|
|
"bg-white/20": isCurrentHour,
|
|
|
|
|
}
|
|
|
|
|
)}
|
|
|
|
|
key={time}
|
|
|
|
|
>
|
|
|
|
|
<div className="text-white/70 text-xs font-medium">
|
|
|
|
|
{index === 0 ? "Now" : format(hourTime, "ha")}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={cx("text-white/60", { "text-yellow-200": isDay, "text-blue-200": !isDay })}>
|
|
|
|
|
<CloudIcon size={20} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="text-white text-sm font-medium">
|
|
|
|
|
{n(displayTemperatures[index])}°
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-between text-white/60 text-xs mt-4">
|
|
|
|
|
<div>Sunrise: {format(new Date(weatherAtLocation.daily.sunrise[0]), "h:mm a")}</div>
|
|
|
|
|
<div>Sunset: {format(new Date(weatherAtLocation.daily.sunset[0]), "h:mm a")}</div>
|
|
|
|
|
</div>
|
2024-10-11 18:00:22 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|