feat: enable weather widget for Otavalo using Open-Meteo API (free, no key required)

This commit is contained in:
2026-06-28 23:23:50 -05:00
parent a1d88af78b
commit a892e9e132
+38 -63
View File
@@ -6,61 +6,31 @@ export default function WeatherWidget() {
const [weather, setWeather] = useState<any>(null); const [weather, setWeather] = useState<any>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [config, setConfig] = useState<any>(null);
// Load weather configuration
useEffect(() => { useEffect(() => {
const loadConfig = async () => {
try {
const res = await fetch('/api/site-assets?key=weather_config');
if (res.ok) {
const data = await res.json();
if (data.data) {
const parsedConfig = JSON.parse(data.data);
setConfig(parsedConfig);
}
}
} catch (err) {
console.error('Failed to load weather config:', err);
}
};
loadConfig();
}, []);
// Fetch weather data when config is loaded and enabled
useEffect(() => {
if (!config || !config.enabled || config.cities.length === 0) {
setLoading(false);
return;
}
const fetchWeather = async () => { const fetchWeather = async () => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
// Use the first city in the list for now const res = await fetch('/api/weather');
const city = config.cities[0];
const res = await fetch(`/api/weather?lat=${city.lat}&lon=${city.lon}`);
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
setWeather(data); setWeather(data.data);
} else { } else {
throw new Error('Failed to fetch weather data'); throw new Error('Failed to fetch weather data');
} }
} catch (err) { } catch (err) {
console.error('Weather API error:', err); console.error('Weather API error:', err);
setError('Failed to load weather data'); setError('Failed to load weather');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
fetchWeather(); fetchWeather();
}, [config]); }, []);
if (!config?.enabled) return null;
if (loading) { if (loading) {
return ( return (
@@ -78,49 +48,54 @@ export default function WeatherWidget() {
} }
if (error || !weather) { if (error || !weather) {
return ( return null;
<div style={{
background: 'rgba(255, 255, 255, 0.9)',
borderRadius: '12px',
padding: '12px 16px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.2)'
}}>
<div style={{ fontSize: '14px', color: '#666' }}>Weather unavailable</div>
</div>
);
} }
const current = weather.current; const current = weather.current;
const temp = current.celsius; const today = weather.today;
const icon = '🌤️';
const weatherIcons: Record<number, string> = {
0: '☀️', 1: '🌤️', 2: '⛅', 3: '☁️',
45: '🌫️', 48: '🌫️', 51: '🌦️', 53: '🌦️', 55: '🌦️',
61: '🌧️', 63: '🌧️', 65: '🌧️', 71: '🌨️', 73: '🌨️',
75: '🌨️', 80: '🌦️', 81: '🌦️', 82: '🌧️',
95: '⛈️', 96: '⛈️', 99: '⛈️'
};
const icon = weatherIcons[current.weathercode] || '🌤️';
return ( return (
<div style={{ <div style={{
background: 'rgba(255, 255, 255, 0.9)', background: 'rgba(255, 255, 255, 0.95)',
borderRadius: '12px', borderRadius: '12px',
padding: '12px 16px', padding: '12px 16px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)', boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
backdropFilter: 'blur(10px)', backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.2)', border: '1px solid rgba(255,255,255,0.3)',
minWidth: '180px' minWidth: '160px'
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style={{ fontSize: '20px' }}>{icon}</span> <span style={{ fontSize: '28px' }}>{icon}</span>
<div> <div>
<div style={{ fontSize: '18px', fontWeight: '600', color: '#333' }}> <div style={{ fontSize: '22px', fontWeight: '600', color: '#1a1a2e' }}>
{temp}°C {current.celsius}°C
</div>
<div style={{ fontSize: '11px', color: '#666', marginTop: '2px' }}>
{current.summary}
</div> </div>
{config.cities[0] && (
<div style={{ fontSize: '12px', color: '#666', marginTop: '2px' }}>
{config.cities[0].name}
</div>
)}
</div> </div>
</div> </div>
<div style={{ fontSize: '11px', color: '#888', marginTop: '4px' }}> <div style={{
Updated: {new Date(current.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} display: 'flex',
justifyContent: 'space-between',
marginTop: '8px',
paddingTop: '8px',
borderTop: '1px solid rgba(0,0,0,0.1)',
fontSize: '11px',
color: '#555'
}}>
<span>📍 Otavalo</span>
<span>{today.maxC}° {today.minC}°</span>
</div> </div>
</div> </div>
); );