diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index 34a2aaa..10b69ff 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -35,7 +35,7 @@ export default function MapView({ const [timeFilter, setTimeFilter] = useState(() => { try { const v = localStorage.getItem(TIME_FILTER_KEY); - const ok = ["NOW","TODAY","RECENT","PAST"].includes(v); + const ok = ["NOW", "TODAY", "RECENT", "PAST"].includes(v); return ok ? v : "TODAY"; } catch { return "TODAY"; @@ -44,7 +44,9 @@ export default function MapView({ const [subFilter, setSubFilter] = useState("All"); useEffect(() => { - try { localStorage.setItem(TIME_FILTER_KEY, timeFilter); } catch {} + try { + localStorage.setItem(TIME_FILTER_KEY, timeFilter); + } catch {} }, [timeFilter]); const stageRef = useRef(null); @@ -52,6 +54,8 @@ export default function MapView({ const userPosition = useUserPosition(mapRef, hasLastView); + const [searchQuery, setSearchQuery] = useState(""); + const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({ theme, mapRef, @@ -59,6 +63,7 @@ export default function MapView({ mainFilter, subFilter, timeFilter, + searchQuery, markersRef, expandedElRef, }); @@ -72,8 +77,6 @@ export default function MapView({ const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(""); - const [searchQuery, setSearchQuery] = useState(""); - const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"]; /* SW_SUBCAT_ICONS:BEGIN */ @@ -155,7 +158,7 @@ export default function MapView({ } }, [selectedPost, mapRef]); - // Websocket for new posts + // Hide subcats when map isn't visible enough (keeps UI clean) useEffect(() => { const el = stageRef.current; if (!el || typeof IntersectionObserver === "undefined") return; @@ -173,6 +176,7 @@ export default function MapView({ return () => obs.disconnect(); }, []); + // Websocket for new posts useEffect(() => { const proto = window.location.protocol === "https:" ? "wss" : "ws"; const host = @@ -209,12 +213,15 @@ export default function MapView({ alert("You must be logged in to place a wire."); return; } - + if (needsEmailVerification) { - alert("You must verify your email before posting. Use 'Resend email' in the top bar, then login again."); + alert( + "You must verify your email before posting. Use 'Resend email' in the top bar, then login again." + ); return; } -setIsCreating(true); + + setIsCreating(true); setDraftTitle(""); setDraftBody(""); const main = mainFilter === "All" ? "News" : mainFilter; @@ -224,11 +231,9 @@ setIsCreating(true); setDraftCoords(null); }; + // Keep submit to allow Enter key, but no popup: const handleSearchSubmit = (e) => { e.preventDefault(); - const q = (searchQuery || "").trim(); - if (!q) return; - alert(`🔍 Search: ${q} (coming soon!)`); }; const handleClearSearch = () => { @@ -247,12 +252,12 @@ setIsCreating(true); return; } - if (needsEmailVerification) { setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again."); return; } -const authorName = (username || "").trim() || "anon"; + + const authorName = (username || "").trim() || "anon"; let baseLng; let baseLat; @@ -305,7 +310,9 @@ const authorName = (username || "").trim() || "anon"; const code = e && (e.code || ""); const msg = String((e && e.message) || "").toLowerCase(); - const isNotVerified = code === "email_not_verified" || (e && e.status === 403 && (msg.includes("not verified") || msg.includes("verify"))); + const isNotVerified = + code === "email_not_verified" || + (e && e.status === 403 && (msg.includes("not verified") || msg.includes("verify"))); if (isNotVerified) { setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again."); @@ -339,7 +346,12 @@ const authorName = (username || "").trim() || "anon"; {status &&
{status}
}
-
+ t.trim()).filter(Boolean); + for (const t of terms) { + if (t.startsWith("@")) { + const want = t.slice(1).trim(); + if (!want) continue; + if (!author.includes(want)) return false; + continue; + } + if (!hay.includes(t)) return false; + } + return true; +} + export function usePostsEngine({ mapRef, viewParams, mainFilter, subFilter, timeFilter, + searchQuery, markersRef, expandedElRef, theme = "blue", @@ -103,10 +138,11 @@ export function usePostsEngine({ } if (!matchesSubFilter(p, subFilter)) return false; if (!matchesTimeFilter(p.created_at || p.CreatedAt || p.createdAt, tf)) return false; + if (!matchesSearch(p, searchQuery)) return false; return true; }); }, - [mainFilter, subFilter] + [mainFilter, subFilter, searchQuery] ); const refreshVisibleAndMarkers = useCallback( @@ -116,7 +152,7 @@ export function usePostsEngine({ // reduce wall blink: don't update if same ids setVisiblePosts((prev) => (sameIdList(prev, v) ? prev : v)); - // no annoying "No posts found" bubble + // keep status silent (no annoying bubbles) setStatus(""); syncMarkers(v); @@ -124,10 +160,10 @@ export function usePostsEngine({ [computeVisible, syncMarkers] ); - // On filter changes: recompute + sync (NO clear-all rebuild) + // On filter/search changes: recompute + sync (NO clear-all rebuild) useEffect(() => { refreshVisibleAndMarkers(timeFilter); - }, [timeFilter, mainFilter, subFilter, refreshVisibleAndMarkers]); + }, [timeFilter, mainFilter, subFilter, searchQuery, refreshVisibleAndMarkers]); // Fetch on view change (moveend) useEffect(() => { @@ -245,6 +281,7 @@ export function usePostsEngine({ } if (!matchesSubFilter(p, subFilter)) return; if (!matchesTimeFilter(p.created_at || p.CreatedAt || p.createdAt, timeFilter)) return; + if (!matchesSearch(p, searchQuery)) return; // add marker if missing const id = getId(p); @@ -253,12 +290,9 @@ export function usePostsEngine({ createMarkerForPost(p, mapRef, markersRef, expandedElRef, theme); } - setVisiblePosts((current) => { - const next = [...current, p]; - return next; - }); + setVisiblePosts((current) => [...current, p]); }, - [mainFilter, subFilter, timeFilter, mapRef, markersRef, expandedElRef, theme] + [mainFilter, subFilter, timeFilter, searchQuery, mapRef, markersRef, expandedElRef, theme] ); return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost };