Fix email verification redirect handling
Add 'validated' to the list of accepted URL params for email verification, since Keycloak sends validated=1 instead of verified=1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8c2a4cda1
commit
177f0034d6
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
<div className="map-page">
|
||||
<div className="map-stage" ref={stageRef}>
|
||||
<div ref={containerRef} className="map-container" />
|
||||
<DayNightTerminator map={mapRef.current} enabled={dayNightEnabled} opacity={0.3} />
|
||||
<DayNightTerminator map={mapRef.current} enabled={weatherViewEnabled} opacity={0.3} />
|
||||
{status && <div className="map-status">{status}</div>}
|
||||
{debugEnabled && (
|
||||
<>
|
||||
|
|
@ -1807,15 +1887,58 @@ export default function MapView({
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={"sw-search__iconBtn" + (dayNightEnabled ? " is-active" : "")}
|
||||
title="Day/Night View"
|
||||
onClick={() => setDayNightEnabled((prev) => !prev)}
|
||||
className={"sw-search__iconBtn" + (weatherViewEnabled ? " is-active" : "")}
|
||||
title="Météo & Ensoleillement"
|
||||
onClick={() => setWeatherViewEnabled((prev) => !prev)}
|
||||
>
|
||||
<i className={dayNightEnabled ? "fa-solid fa-moon" : "fa-solid fa-sun"}></i>
|
||||
<i className="fa-solid fa-cloud-sun"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 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 (
|
||||
<div
|
||||
onClick={() => 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)',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '16px' }}>{w.icon}</span>
|
||||
<span>{w.city}:</span>
|
||||
<span style={{ fontWeight: '700' }}>{w.temp}°</span>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div className="map-overlay map-overlay-left">
|
||||
|
|
|
|||
|
|
@ -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 = `
|
||||
<div class="weather-marker-content">
|
||||
<span class="weather-marker-icon">${emoji}</span>
|
||||
<span class="weather-marker-temp">${tempDisplay}</span>
|
||||
<div style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(14, 165, 233, 0.75);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255,255,255,0.4);
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
||||
">
|
||||
<span style="font-size:12px;line-height:1;">${emoji}</span>
|
||||
<span style="font-size:11px;font-weight:600;color:#fff;">${tempDisplay}</span>
|
||||
</div>
|
||||
<div class="weather-marker-city">${cityDisplay}</div>
|
||||
`;
|
||||
|
||||
// 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 = `
|
||||
<div class="sw-simple-marker ${isCamera ? 'sw-camera-marker' : ''} ${isLive ? 'sw-live-marker' : ''} ${isWeather ? 'sw-weather-marker' : ''}" style="
|
||||
${isWeather ? 'width: auto; height: auto; padding: 6px 10px; border-radius: 16px;' : 'width: 32px; height: 32px; border-radius: 50%;'}
|
||||
background: ${color};
|
||||
border: 2px solid ${isCamera ? 'rgba(255,255,255,0.85)' : isLive ? 'rgba(255,255,255,0.9)' : isWeather ? 'rgba(255,255,255,0.4)' : 'white'};
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3)${isCamera ? ', 0 0 10px rgba(56, 189, 248, 0.5)' : isLive ? ', 0 0 12px rgba(239,68,68,0.6)' : isWeather ? ', 0 3px 10px rgba(0,0,0,0.4)' : ''};
|
||||
${isWeather ? 'width: auto; height: auto; padding: 4px 8px; border-radius: 12px;' : 'width: 32px; height: 32px; border-radius: 50%;'}
|
||||
background: ${isWeather ? 'rgba(14, 165, 233, 0.75)' : color};
|
||||
border: ${isWeather ? '1px solid rgba(255,255,255,0.4)' : isCamera ? '2px solid rgba(255,255,255,0.85)' : isLive ? '2px solid rgba(255,255,255,0.9)' : '2px solid white'};
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
${isWeather ? 'gap: 4px;' : ''}
|
||||
${isWeather ? 'gap: 3px;' : ''}
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
">
|
||||
${isWeather ? `<span style="font-size:16px;line-height:1;">${weatherEmoji}</span><span style="font-size:13px;font-weight:700;color:#fff;">${weatherTemp}</span>` : isLive ? `<i class="fa-solid fa-broadcast-tower" style="font-size:14px;color:#fff;"></i>` : isCamera ? `<i class="fa-solid fa-video" style="font-size:14px;color:#fff;"></i>` : img ? `<img src="${img}" alt="" style="width:100%;height:100%;object-fit:cover;" />` : `
|
||||
${isWeather ? `
|
||||
<span style="font-size:12px;line-height:1;">${weatherEmoji}</span>
|
||||
<span style="font-size:11px;font-weight:600;color:#fff;">${weatherTemp}</span>
|
||||
` : isLive ? `<i class="fa-solid fa-broadcast-tower" style="font-size:14px;color:#fff;"></i>` : isCamera ? `<i class="fa-solid fa-video" style="font-size:14px;color:#fff;"></i>` : img ? `<img src="${img}" alt="" style="width:100%;height:100%;object-fit:cover;" />` : `
|
||||
<div style="
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
|
@ -2420,7 +2433,7 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe
|
|||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
||||
margin-top: -2px;
|
||||
z-index: 1;
|
||||
"></div>` : `<div style="position:absolute;bottom:-24px;left:50%;transform:translateX(-50%);font-size:13px;font-weight:700;color:#fff;background:rgba(0,0,0,0.85);padding:4px 10px;border-radius:6px;white-space:nowrap;">${escapeHtml(activePost?.city || '')}</div>`}
|
||||
"></div>` : ''}
|
||||
<div class="sw-simple-hover-card" style="
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
|
|
|
|||
|
|
@ -387,6 +387,7 @@ export function usePostsEngine({
|
|||
username,
|
||||
debugEnabled = false,
|
||||
theme = "blue",
|
||||
weatherEnabled = false,
|
||||
}) {
|
||||
const allPostsRef = useRef([]);
|
||||
const lastFetchRef = useRef({ center: null, radiusKm: null, filterKey: "", timeHours: 0, bounds: null });
|
||||
|
|
@ -756,11 +757,10 @@ export function usePostsEngine({
|
|||
for (const post of visible) {
|
||||
const id = getId(post);
|
||||
if (id != null) {
|
||||
// Weather posts always simple (blue bubble), never full card
|
||||
// Weather posts: skip completely (shown in top bar widget instead)
|
||||
const ct = (post?.content_type || "").toLowerCase();
|
||||
if (ct === "weather") {
|
||||
wanted.set(id, { post, type: 'simple' });
|
||||
continue;
|
||||
continue; // Skip weather markers - they appear in the search bar widget
|
||||
}
|
||||
// Tag avec le type de marqueur souhaité
|
||||
const isFullCard =
|
||||
|
|
@ -831,7 +831,7 @@ export function usePostsEngine({
|
|||
}
|
||||
}
|
||||
},
|
||||
[mapRef, markersRef, expandedElRef, theme, searchQuery, forceFullPostId]
|
||||
[mapRef, markersRef, expandedElRef, theme, searchQuery, forceFullPostId, weatherEnabled]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue