diff --git a/src/components/Layout/TopBar.jsx b/src/components/Layout/TopBar.jsx index 1ae1f69..b773e7f 100644 --- a/src/components/Layout/TopBar.jsx +++ b/src/components/Layout/TopBar.jsx @@ -347,6 +347,7 @@ export default function TopBar({ theme = "dark", onChangeTheme, onIslandsClick, const verified = get("verified") || + get("validated") || get("email_verified") || get("emailVerified") || get("kc_verified"); @@ -393,8 +394,8 @@ export default function TopBar({ theme = "dark", onChangeTheme, onIslandsClick, } const touched = - qs.has("verified") || qs.has("email_verified") || qs.has("emailVerified") || qs.has("signup") || qs.has("notice") || - hs.has("verified") || hs.has("email_verified") || hs.has("emailVerified") || hs.has("signup") || hs.has("notice"); + qs.has("verified") || qs.has("validated") || qs.has("email_verified") || qs.has("emailVerified") || qs.has("signup") || qs.has("notice") || + hs.has("verified") || hs.has("validated") || hs.has("email_verified") || hs.has("emailVerified") || hs.has("signup") || hs.has("notice"); if (touched) { const cleanHash = hash.includes("?") ? hash.split("?")[0] : hash; diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index b9638e2..f847dde 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -5,7 +5,7 @@ import "../../styles/mapMarkers.css"; import "../../styles/posts.css"; import "../../styles/filters.css"; -import { createPost, fetchPostPreview, resolveAssetRef } from "../../api/client"; +import { createPost, fetchPostPreview, resolveAssetRef, fetchWeatherPosts } from "../../api/client"; import { LiveKitBroadcast, LiveKitViewerModal } from "../Live"; import { WeatherModal } from "../Weather"; import { @@ -144,10 +144,10 @@ export default function MapView({ }); const [subFilter, setSubFilter] = useState("All"); const [mainNewsOnly, setMainNewsOnly] = useState(false); - const DAY_NIGHT_KEY = "sociowire:dayNight"; - const [dayNightEnabled, setDayNightEnabled] = useState(() => { + const WEATHER_VIEW_KEY = "sociowire:weatherView"; + const [weatherViewEnabled, setWeatherViewEnabled] = useState(() => { try { - return localStorage.getItem(DAY_NIGHT_KEY) === "true"; + return localStorage.getItem(WEATHER_VIEW_KEY) === "true"; } catch { return false; } @@ -170,9 +170,9 @@ export default function MapView({ useEffect(() => { try { - localStorage.setItem(DAY_NIGHT_KEY, dayNightEnabled ? "true" : "false"); + localStorage.setItem(WEATHER_VIEW_KEY, weatherViewEnabled ? "true" : "false"); } catch {} - }, [dayNightEnabled]); + }, [weatherViewEnabled]); useEffect(() => { if (clusterFocus) setClusterFocus(null); @@ -198,6 +198,9 @@ export default function MapView({ const [liveCoords, setLiveCoords] = useState(null); // Weather modal state const [activeWeatherPost, setActiveWeatherPost] = useState(null); + // Weather cities widget (top left under search) + const [weatherCities, setWeatherCities] = useState([]); + const [nearestWeather, setNearestWeather] = useState(null); const openedPostRef = useRef(null); const queryPostRef = useRef(false); const overlayTimerRef = useRef(null); @@ -447,6 +450,7 @@ export default function MapView({ onSelectPost, username, debugEnabled, + weatherEnabled: weatherViewEnabled, }); const [isCreating, setIsCreating] = useState(false); @@ -1206,6 +1210,82 @@ export default function MapView({ return () => map.off("movestart", handleMove); }, [mapRef, searchQuery]); + // Fetch weather cities on mount + useEffect(() => { + const loadWeatherCities = async () => { + try { + const posts = await fetchWeatherPosts(); + if (Array.isArray(posts) && posts.length > 0) { + // Parse weather data from posts + const cities = posts.map(p => { + const title = p.title || ''; + const tempMatch = title.match(/:\s*(-?\d+)°/); + const cityMatch = title.match(/^[^\w]*([A-Za-zÀ-ÿ\s]+):/); + const temp = tempMatch ? parseInt(tempMatch[1], 10) : null; + const city = cityMatch ? cityMatch[1].trim() : ''; + + // Detect weather icon from emoji in title + let icon = '🌡️'; + if (title.includes('☀️')) icon = '☀️'; + else if (title.includes('🌙')) icon = '🌙'; + else if (title.includes('⛅')) icon = '⛅'; + else if (title.includes('☁️')) icon = '☁️'; + else if (title.includes('🌫️')) icon = '🌫️'; + else if (title.includes('🌧️')) icon = '🌧️'; + else if (title.includes('❄️')) icon = '❄️'; + else if (title.includes('🌦️')) icon = '🌦️'; + else if (title.includes('🌨️')) icon = '🌨️'; + else if (title.includes('⛈️')) icon = '⛈️'; + + return { ...p, city, temp, icon }; + }).filter(c => c.city && c.temp !== null); + + setWeatherCities(cities); + } + } catch (err) { + console.warn('[Weather] Failed to load cities:', err); + } + }; + + loadWeatherCities(); + // Refresh every 10 minutes + const interval = setInterval(loadWeatherCities, 10 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + // Find nearest weather city when map moves + useEffect(() => { + const map = mapRef.current; + if (!map || weatherCities.length === 0) return; + + const updateNearest = () => { + try { + const center = map.getCenter(); + if (!center) return; + + let nearest = null; + let minDist = Infinity; + + for (const w of weatherCities) { + if (w.lat == null || w.lon == null) continue; + const dist = Math.sqrt( + Math.pow(center.lat - w.lat, 2) + Math.pow(center.lng - w.lon, 2) + ); + if (dist < minDist) { + minDist = dist; + nearest = w; + } + } + + if (nearest) setNearestWeather(nearest); + } catch {} + }; + + updateNearest(); + map.on("idle", updateNearest); + return () => map.off("idle", updateNearest); + }, [mapRef, weatherCities]); + const handleProfileVisitIsland = (island) => { console.log('[MapView] Visiting island from profile:', island.username); setSelectedProfile(null); // Close profile modal @@ -1721,7 +1801,7 @@ export default function MapView({