From 28e760d616909c131700407a35db1578ec9cf629 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 23 Dec 2025 15:39:58 -0500 Subject: [PATCH] Major UX improvements: Smart search, navigation, fixed markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Smart Search Integration (MapView.jsx, SmartSearchBar.jsx): - Replace simple search bar with SmartSearchBar component - Suggestions appear on focus (popular cities/regions) - Suggestions update as user types (debounced 150ms) - Click suggestion → fly to location with appropriate zoom - Search triggers even with empty query for popular results 2. Navigation to Cities (MapView.jsx): - New handlePickSuggestion() function - Extracts coords from suggestion (supports coords array or lat/lon) - Uses map.flyTo() with smart zoom levels per type: * Cities: zoom 11 * Regions: zoom 7 * Posts: zoom 16 * Profiles: zoom 14 - Clears search after navigation 3. Fixed Marker Anchoring (markerManager.js): - Changed anchor: "center" → "bottom" - Markers now stay fixed at geographic position - Pin design: circle image + triangular point (52px height) - Point positioned at exact lat/lon location - No more drifting when map moves! 4. Improved Pin Design: - Circle (32px) with category color background - White border + shadow for visibility - Triangular point (16px) below circle - Image inside circle if available - Hover card positioned above pin (bottom: 58px) - Smooth transitions on hover/click Mobile-friendly: Works on cellphone without console, visual feedback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/components/Map/MapView.jsx | 67 ++++++++++++----------- src/components/Map/markerManager.js | 65 ++++++++++++++-------- src/components/Search/SmartSearchBar.jsx | 69 +++++++++++++----------- 3 files changed, 118 insertions(+), 83 deletions(-) diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index 2b0d323..a7eabfb 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -17,6 +17,7 @@ import { usePostsEngine } from "./usePostsEngine"; import { useAuth } from "../../auth/AuthContext"; import FilterButtons from "../Filters/FilterButtons"; import TimeFilterButtons from "../Filters/TimeFilterButtons"; +import SmartSearchBar from "../Search/SmartSearchBar"; export default function MapView({ theme = "dark", @@ -208,6 +209,35 @@ export default function MapView({ map.flyTo({ center: userPosition, zoom: 9, speed: 1.2 }); }; + const handlePickSuggestion = (item, { zoom }) => { + const map = mapRef.current; + if (!map) return; + + // Extract coords from item + let coords = null; + if (Array.isArray(item.coords) && item.coords.length === 2) { + coords = item.coords; // [lon, lat] + } else if (typeof item.lon === 'number' && typeof item.lat === 'number') { + coords = [item.lon, item.lat]; + } + + if (!coords) { + console.warn('[MapView] No coords for suggestion:', item); + return; + } + + console.log('[MapView] Flying to:', item.title, coords, 'zoom:', zoom); + map.flyTo({ + center: coords, + zoom: zoom || 12, + speed: 1.4, + essential: true + }); + + // Clear search query after navigation + setSearchQuery(""); + }; + const handleOpenCreate = () => { if (!authenticated) { alert("You must be logged in to place a wire."); @@ -413,38 +443,15 @@ export default function MapView({
{status &&
{status}
} - {/* TOP: Search à gauche, 2 icônes à droite, sur une seule ligne */} + {/* TOP: Search avec suggestions intelligentes */}
-
- - setSearchQuery(e.target.value)} - autoCapitalize="none" - autoCorrect="off" - spellCheck={false} - /> - {searchQuery ? ( - - ) : null} - +
${escapeHtml(post?.title || "Untitled")} @@ -576,7 +597,7 @@ export function createSimpleMarkerForPost(post, mapRef, markersRef, expandedElRe const marker = new maplibregl.Marker({ element: root, - anchor: "center", + anchor: "bottom", // La pointe du pin est à la position exacte }) .setLngLat(lngLat) .addTo(map); diff --git a/src/components/Search/SmartSearchBar.jsx b/src/components/Search/SmartSearchBar.jsx index 55b07f7..6b47423 100644 --- a/src/components/Search/SmartSearchBar.jsx +++ b/src/components/Search/SmartSearchBar.jsx @@ -67,6 +67,7 @@ export default function SmartSearchBar({ const [items, setItems] = useState([]); const [active, setActive] = useState(-1); const [err, setErr] = useState(""); + const [hasSearched, setHasSearched] = useState(false); const abortRef = useRef(null); const boxRef = useRef(null); @@ -80,45 +81,45 @@ export default function SmartSearchBar({ return () => document.removeEventListener("mousedown", onDocDown); }, []); - useEffect(() => { - const query = q.trim(); - setErr(""); - + // Fonction pour déclencher une recherche + const triggerSearch = async (searchQuery) => { if (abortRef.current) abortRef.current.abort(); - // Allow empty query to get popular suggestions - // if (!query) { - // setItems([]); - // setLoading(false); - // setActive(-1); - // return; - // } - const ctrl = new AbortController(); abortRef.current = ctrl; setLoading(true); + setErr(""); - const t = setTimeout(async () => { - try { - console.log('[SmartSearchBar] Searching for:', query); - const res = await recoSearch(query, { signal: ctrl.signal, limit: 12 }); - console.log('[SmartSearchBar] Got results:', res?.length || 0, res); - setItems(res); - setOpen(true); - setActive(res.length ? 0 : -1); - } catch (e) { - if (ctrl.signal.aborted) return; - console.error('[SmartSearchBar] Search error:', e); - setErr((e && e.message) ? e.message : "Search error"); - setItems([]); - } finally { - if (!ctrl.signal.aborted) setLoading(false); - } - }, 150); // Réduit de 220ms à 150ms pour réactivité accrue + try { + console.log('[SmartSearchBar] Searching for:', searchQuery); + const res = await recoSearch(searchQuery, { signal: ctrl.signal, limit: 12 }); + console.log('[SmartSearchBar] Got results:', res?.length || 0, res); + setItems(res); + setOpen(true); + setActive(res.length ? 0 : -1); + setHasSearched(true); + } catch (e) { + if (ctrl.signal.aborted) return; + console.error('[SmartSearchBar] Search error:', e); + setErr((e && e.message) ? e.message : "Search error"); + setItems([]); + } finally { + if (!ctrl.signal.aborted) setLoading(false); + } + }; + + useEffect(() => { + const query = q.trim(); + + if (abortRef.current) abortRef.current.abort(); + + const t = setTimeout(() => { + triggerSearch(query); + }, 150); // Debounce 150ms return () => { clearTimeout(t); - ctrl.abort(); + if (abortRef.current) abortRef.current.abort(); }; }, [q]); @@ -164,7 +165,13 @@ export default function SmartSearchBar({ className="sw-search__input" value={q} onChange={(e) => setQ(e.target.value)} - onFocus={() => setOpen(true)} + onFocus={() => { + setOpen(true); + // Si pas encore cherché et query vide, charger suggestions populaires + if (!hasSearched && !q.trim()) { + triggerSearch(""); + } + }} onKeyDown={onKeyDown} placeholder={placeholder} aria-label="Search"