85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import { CloudRain, Sun, Cloud, Thermometer } from 'lucide-react';
|
|
import { useState, useEffect } from 'react';
|
|
|
|
// Default to New York, user can change this
|
|
const LAT = 40.7128;
|
|
const LNG = -74.0060;
|
|
|
|
export function WeatherCard() {
|
|
const [weather, setWeather] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchWeather() {
|
|
try {
|
|
const res = await fetch(
|
|
`https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LNG}¤t=temperature_2m,relative_humidity_2m,weather_code&daily=temperature_2m_max,temperature_2m_min&temperature_unit=celsius&timezone=auto`
|
|
);
|
|
const data = await res.json();
|
|
setWeather(data);
|
|
} catch (e) {
|
|
console.error(e);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
fetchWeather();
|
|
const interval = setInterval(fetchWeather, 600000); // 10 mins
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 relative overflow-hidden animate-pulse">
|
|
<div className="h-4 w-24 bg-neutral-800 rounded mb-4" />
|
|
<div className="h-10 w-16 bg-neutral-800 rounded" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!weather?.current) {
|
|
return (
|
|
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 flex items-center justify-center text-red-500">
|
|
Failed to load weather
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const current = weather.current;
|
|
const today = weather.daily;
|
|
|
|
return (
|
|
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 relative overflow-hidden group hover:border-neutral-700 transition-colors">
|
|
<div className="flex justify-between items-start mb-2">
|
|
<div className="flex items-center gap-2 text-neutral-400">
|
|
<Cloud size={18} />
|
|
<span className="text-sm font-medium">Local Weather</span>
|
|
</div>
|
|
<span className="text-xs text-neutral-500 font-mono">New York, US</span>
|
|
</div>
|
|
|
|
<div className="flex items-end gap-4 mt-2">
|
|
<div className="text-4xl font-bold text-white tracking-tighter">{Math.round(current.temperature_2m)}°C</div>
|
|
<div className="pb-1 text-sm text-neutral-400 font-medium">{getWeatherDescription(current.weather_code)}</div>
|
|
</div>
|
|
|
|
<div className="mt-4 flex gap-4 text-xs font-mono text-neutral-500">
|
|
<div>H: {Math.round(today.temperature_2m_max[0])}° L: {Math.round(today.temperature_2m_min[0])}°</div>
|
|
<div>Humidity: {current.relative_humidity_2m}%</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function getWeatherDescription(code: number) {
|
|
if (code === 0) return 'Clear';
|
|
if (code <= 3) return 'Partly Cloudy';
|
|
if (code <= 48) return 'Foggy';
|
|
if (code <= 67) return 'Rainy';
|
|
if (code <= 77) return 'Snowy';
|
|
return 'Cloudy';
|
|
}
|