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({
- + {status &&
{status}
} {debugEnabled && ( <> @@ -1807,15 +1887,58 @@ export default function MapView({
+ {/* Weather widget - single nearest city - BELOW search bar */} + {nearestWeather && (() => { + const w = nearestWeather; + const isSunny = w.icon === '☀️' || w.icon === '⛅'; + const isNight = w.icon === '🌙'; + const isRainy = w.icon === '🌧️' || w.icon === '🌦️' || w.icon === '⛈️'; + const isSnow = w.icon === '❄️' || w.icon === '🌨️'; + const bg = isNight + ? 'linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.9))' + : isSunny + ? 'linear-gradient(135deg, rgba(56, 189, 248, 0.85), rgba(14, 165, 233, 0.85))' + : isRainy + ? 'linear-gradient(135deg, rgba(71, 85, 105, 0.9), rgba(51, 65, 85, 0.9))' + : isSnow + ? 'linear-gradient(135deg, rgba(148, 163, 184, 0.9), rgba(203, 213, 225, 0.9))' + : 'linear-gradient(135deg, rgba(100, 116, 139, 0.85), rgba(71, 85, 105, 0.85))'; + + return ( +
setActiveWeatherPost(w)} + style={{ + display: 'inline-flex', + alignItems: 'center', + gap: '5px', + marginTop: '8px', + padding: '5px 12px', + borderRadius: '16px', + background: bg, + border: '1px solid rgba(255,255,255,0.25)', + color: isSnow ? '#1e293b' : '#fff', + fontSize: '13px', + fontWeight: '600', + cursor: 'pointer', + backdropFilter: 'blur(8px)', + boxShadow: '0 2px 8px rgba(0,0,0,0.2)', + }} + > + {w.icon} + {w.city}: + {w.temp}° +
+ ); + })()}
diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 2a902a6..5b00728 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -2228,8 +2228,9 @@ export function createWeatherMarkerForPost(post, mapRef, markersRef, expandedElR const cityDisplay = city || post?.city || ""; const root = document.createElement("div"); - root.className = "weather-marker sw-appear"; - root.style.zIndex = "2"; + root.className = "sw-weather-pill sw-appear"; + root.style.zIndex = "5"; + root.style.cursor = "pointer"; root.__post = post; requestAnimationFrame(() => { @@ -2237,11 +2238,19 @@ export function createWeatherMarkerForPost(post, mapRef, markersRef, expandedElR }); root.innerHTML = ` -
- ${emoji} - ${tempDisplay} +
+ ${emoji} + ${tempDisplay}
-
${cityDisplay}
`; // Click to open weather modal @@ -2252,17 +2261,15 @@ export function createWeatherMarkerForPost(post, mapRef, markersRef, expandedElR } catch {} }); - // Hover effect + // Hover effect - subtle root.addEventListener("mouseenter", () => { try { - root.style.transform = "scale(1.1)"; - root.style.zIndex = "100"; + root.style.opacity = "1"; } catch {} }); root.addEventListener("mouseleave", () => { try { - root.style.transform = ""; - root.style.zIndex = "2"; + root.style.opacity = ""; } catch {} }); @@ -2387,21 +2394,27 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe } } + // Weather city name for label + const weatherCity = isWeather ? (activePost?.city || (activePost?.title || '').split(':')[0].replace(/^[^\w\s]+/, '').trim()) : ''; + // Marqueur pin avec image en haut et pointe en bas root.innerHTML = `
- ${isWeather ? `${weatherEmoji}${weatherTemp}` : isLive ? `` : isCamera ? `` : img ? `` : ` + ${isWeather ? ` + ${weatherEmoji} + ${weatherTemp} + ` : isLive ? `` : isCamera ? `` : img ? `` : `
` : `
${escapeHtml(activePost?.city || '')}
`} + ">
` : ''}
{