From 1fecc3903588f12ca0a76955605bea107643236c Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Mon, 2 Mar 2026 17:02:16 +0000 Subject: [PATCH] Add automatic IP-based weather detection to a new sidebar widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a `SidebarWeatherWidget` component that displays localized weather information using IP address for location detection, removing the need for explicit user permission. The widget is placed in the homepage sidebar below the "Zuletzt hinzugefügt" section. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fbb31221-fc0e-43bb-bb7c-88ad38d97042 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/nFw7xof Replit-Helium-Checkpoint-Created: true --- client/src/components/weather-widget.tsx | 130 ++++++++++++++++++++--- client/src/pages/home.tsx | 5 +- 2 files changed, 121 insertions(+), 14 deletions(-) diff --git a/client/src/components/weather-widget.tsx b/client/src/components/weather-widget.tsx index b4b2965..b8101f9 100644 --- a/client/src/components/weather-widget.tsx +++ b/client/src/components/weather-widget.tsx @@ -76,20 +76,22 @@ export function WeatherWidget() { } } - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - async (pos) => { - const city = await reverseGeocode(pos.coords.latitude, pos.coords.longitude); - fetchWeather(pos.coords.latitude, pos.coords.longitude, city); - }, - () => { + async function getLocationByIP() { + try { + const res = await fetch("https://ipapi.co/json/"); + const data = await res.json(); + if (data.latitude && data.longitude) { + const city = data.city || await reverseGeocode(data.latitude, data.longitude); + fetchWeather(data.latitude, data.longitude, city); + } else { fetchWeather(defaultLat, defaultLon, defaultCity); - }, - { timeout: 5000 } - ); - } else { - fetchWeather(defaultLat, defaultLon, defaultCity); + } + } catch { + fetchWeather(defaultLat, defaultLon, defaultCity); + } } + + getLocationByIP(); }, []); if (loading) { @@ -125,3 +127,107 @@ export function WeatherWidget() { ); } + +export function SidebarWeatherWidget() { + const [weather, setWeather] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const defaultLat = 46.55; + const defaultLon = 14.55; + const defaultCity = "Klagenfurt"; + + async function fetchWeather(lat: number, lon: number, city: string) { + try { + const res = await fetch( + `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,weather_code,wind_speed_10m,relative_humidity_2m&daily=temperature_2m_max,temperature_2m_min,weather_code&timezone=auto&forecast_days=3` + ); + const data = await res.json(); + setWeather({ + temperature: Math.round(data.current.temperature_2m), + weatherCode: data.current.weather_code, + windSpeed: Math.round(data.current.wind_speed_10m), + humidity: data.current.relative_humidity_2m, + city, + }); + setForecast(data.daily ? data.daily.time.slice(1, 3).map((d: string, i: number) => ({ + day: new Date(d).toLocaleDateString("de-DE", { weekday: "short" }), + high: Math.round(data.daily.temperature_2m_max[i + 1]), + low: Math.round(data.daily.temperature_2m_min[i + 1]), + code: data.daily.weather_code[i + 1], + })) : []); + } catch { + } finally { + setLoading(false); + } + } + + async function getLocationByIP() { + try { + const res = await fetch("https://ipapi.co/json/"); + const ipData = await res.json(); + if (ipData.latitude && ipData.longitude) { + fetchWeather(ipData.latitude, ipData.longitude, ipData.city || "Unbekannt"); + } else { + fetchWeather(defaultLat, defaultLon, defaultCity); + } + } catch { + fetchWeather(defaultLat, defaultLon, defaultCity); + } + } + + getLocationByIP(); + }, []); + + const [forecast, setForecast] = useState<{ day: string; high: number; low: number; code: number }[]>([]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (!weather) return null; + + return ( +
+
+
+ + {weather.city} +
+ + {new Date().toLocaleDateString("de-DE", { weekday: "short", day: "numeric", month: "short" })} + +
+ +
+
{getWeatherIcon(weather.weatherCode)}
+
+
{weather.temperature}°
+
{getWeatherText(weather.weatherCode)}
+
+
+ +
+ {weather.windSpeed} km/h + {weather.humidity}% +
+ + {forecast.length > 0 && ( +
+ {forecast.map((f) => ( +
+
{f.day}
+
{getWeatherIcon(f.code)}
+
{f.high}°
+
{f.low}°
+
+ ))} +
+ )} +
+ ); +} diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index 0658fa3..1e239d1 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -12,7 +12,7 @@ import { PhotoGalleryWidget } from "@/components/photo-gallery"; import { HoroscopeWidget } from "@/components/horoscope-widget"; import { RecipeWidget } from "@/components/recipe-widget"; import { NewsWidget } from "@/components/news-widget"; -import { WeatherWidget } from "@/components/weather-widget"; +import { WeatherWidget, SidebarWeatherWidget } from "@/components/weather-widget"; import { BreakingNewsWidget } from "@/components/breaking-news-widget"; import { useState, useEffect, useCallback, useRef, useMemo } from "react"; @@ -587,8 +587,9 @@ export default function Home() {
)} -
+
{articles && articles.length > 0 && new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()).slice(0, 5)} />} +