diff --git a/src/App.jsx b/src/App.jsx index ab708fa..2e993b2 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -32,10 +32,12 @@ export default function App() { setTheme(saved); document.body.setAttribute("data-theme", saved); } else { - document.body.setAttribute("data-theme", "dark"); + setTheme("blue"); + document.body.setAttribute("data-theme", "blue"); } } catch (e) { - document.body.setAttribute("data-theme", "dark"); + setTheme("blue"); + document.body.setAttribute("data-theme", "blue"); } }, []); diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index e77bd38..6816770 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -31,15 +31,29 @@ export default function MapView({ useMapCore(theme); const [mainFilter, setMainFilter] = useState("All"); - const [timeFilter, setTimeFilter] = useState("RECENT"); + const TIME_FILTER_KEY = "sociowire:timeFilter"; + const [timeFilter, setTimeFilter] = useState(() => { + try { + const v = localStorage.getItem(TIME_FILTER_KEY); + const ok = ["NOW","TODAY","RECENT","PAST"].includes(v); + return ok ? v : "TODAY"; + } catch { + return "TODAY"; + } + }); const [subFilter, setSubFilter] = useState("All"); + useEffect(() => { + try { localStorage.setItem(TIME_FILTER_KEY, timeFilter); } catch {} + }, [timeFilter]); + const stageRef = useRef(null); const [showSubcats, setShowSubcats] = useState(true); const userPosition = useUserPosition(mapRef, hasLastView); const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({ + theme, mapRef, viewParams, mainFilter, diff --git a/src/components/Map/MapView.jsx.bak b/src/components/Map/MapView.jsx.bak new file mode 100644 index 0000000..da4bebe --- /dev/null +++ b/src/components/Map/MapView.jsx.bak @@ -0,0 +1,423 @@ +import React, { useEffect, useRef, useState } from "react"; +import "maplibre-gl/dist/maplibre-gl.css"; + +import "../../styles/mapMarkers.css"; +import "../../styles/overlays.css"; +import "../../styles/posts.css"; +import "../../styles/filters.css"; + +import { createPost } from "../../api/client"; +import { FILTER_MAIN_CATEGORIES, FILTER_CATEGORY_MAP, CREATE_CATEGORY_MAP } from "./mapConfig"; +import { useMapCore } from "./useMapCore"; +import { useUserPosition } from "./useUserPosition"; +import { usePostsEngine } from "./usePostsEngine"; +import { useAuth } from "../../auth/AuthContext"; +import FilterButtons from "../Filters/FilterButtons"; +import TimeFilterButtons from "../Filters/TimeFilterButtons"; + +export default function MapView({ + theme = "dark", + + // lift posts to App (so Sociowall is NOT inside the map) + onPostsState, + + // selection controlled by App + selectedPost, + onSelectPost, +}) { + const { authenticated, username, token } = useAuth(); + + const { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView } = + useMapCore(theme); + + const [mainFilter, setMainFilter] = useState("All"); + const TIME_FILTER_KEY = "sociowire:timeFilter"; + const [timeFilter, setTimeFilter] = useState(() => { + try { + const v = localStorage.getItem(TIME_FILTER_KEY); + const ok = ["NOW","TODAY","RECENT","PAST"].includes(v); + return ok ? v : "TODAY"; + } catch { + return "TODAY"; + } + }); + const [subFilter, setSubFilter] = useState("All"); + + useEffect(() => { + try { localStorage.setItem(TIME_FILTER_KEY, timeFilter); } catch {} + }, [timeFilter]); + + const stageRef = useRef(null); + const [showSubcats, setShowSubcats] = useState(true); + + const userPosition = useUserPosition(mapRef, hasLastView); + + const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({ + mapRef, + viewParams, + mainFilter, + subFilter, + timeFilter, + markersRef, + expandedElRef, + }); + + const [isCreating, setIsCreating] = useState(false); + const [draftTitle, setDraftTitle] = useState(""); + const [draftBody, setDraftBody] = useState(""); + const [draftCategory, setDraftCategory] = useState("News"); + const [draftSubCategory, setDraftSubCategory] = useState(CREATE_CATEGORY_MAP.News[0]); + const [draftCoords, setDraftCoords] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [saveError, setSaveError] = useState(""); + + const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"]; + + /* SW_SUBCAT_ICONS:BEGIN */ + const getSubcatIcon = (main, sub) => { + const M = { + All: { + All: "fa-solid fa-layer-group", + }, + + News: { + All: "fa-solid fa-layer-group", + World: "fa-solid fa-globe", + Politics: "fa-solid fa-landmark-flag", + Tech: "fa-solid fa-microchip", + Finance: "fa-solid fa-chart-line", + Local: "fa-solid fa-location-dot", + }, + + Friends: { + All: "fa-solid fa-layer-group", + Nearby: "fa-solid fa-location-crosshairs", + Chats: "fa-solid fa-comments", + Groups: "fa-solid fa-user-group", + Stories: "fa-solid fa-book-open", + Favs: "fa-solid fa-heart", + }, + + Events: { + All: "fa-solid fa-layer-group", + Today: "fa-solid fa-calendar-day", + Weekend: "fa-solid fa-calendar-week", + Music: "fa-solid fa-music", + Sports: "fa-solid fa-football", + Local: "fa-solid fa-location-dot", + }, + + Market: { + All: "fa-solid fa-layer-group", + Actu: "fa-solid fa-newspaper", + Tech: "fa-solid fa-microchip", + Finan: "fa-solid fa-money-bill-trend-up", + Sport: "fa-solid fa-basketball", + Deals: "fa-solid fa-tags", + }, + }; + + return (M[main] && M[main][sub]) || ""; + }; + /* SW_SUBCAT_ICONS:END */ + + // keep subFilter valid + useEffect(() => { + if (!bottomCategories.length) return; + if (!subFilter || !bottomCategories.includes(subFilter)) setSubFilter("All"); + }, [mainFilter, bottomCategories, subFilter]); + + // push posts state up to App (for Sociowall) + useEffect(() => { + if (!onPostsState) return; + onPostsState({ + status, + posts: visiblePosts, + loading: loadingPosts, + error: loadError, + }); + }, [status, visiblePosts, loadingPosts, loadError, onPostsState]); + + // if App selects a post (from Sociowall), fly to it on map + useEffect(() => { + if (!selectedPost) return; + const map = mapRef.current; + if (!map) return; + + const lng = selectedPost.lon ?? selectedPost.lng; + const lat = selectedPost.lat ?? selectedPost.latitude; + + if (typeof lng === "number" && typeof lat === "number") { + map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 }); + } + }, [selectedPost, mapRef]); + + // Websocket for new posts + useEffect(() => { + const el = stageRef.current; + if (!el || typeof IntersectionObserver === "undefined") return; + + const obs = new IntersectionObserver( + (entries) => { + const e = entries && entries[0]; + const ok = !!(e && e.isIntersecting && e.intersectionRatio > 0.15); + setShowSubcats(ok); + }, + { threshold: [0, 0.15, 0.3, 0.6] } + ); + + obs.observe(el); + return () => obs.disconnect(); + }, []); + + useEffect(() => { + const proto = window.location.protocol === "https:" ? "wss" : "ws"; + const host = + window.location.port === "5173" + ? `${window.location.hostname}:8081` + : window.location.host; + const wsUrl = `${proto}://${host}/ws`; + + let ws; + try { + ws = new WebSocket(wsUrl); + } catch { + return; + } + + ws.onmessage = (ev) => { + try { + const msg = JSON.parse(ev.data); + if (msg.type === "new_post" && msg.post) handleIncomingPost(msg.post); + } catch {} + }; + + return () => ws && ws.close(); + }, [handleIncomingPost]); + + const handleFlyToMe = () => { + const map = mapRef.current; + if (!map || !userPosition) return; + map.flyTo({ center: userPosition, zoom: 9, speed: 1.2 }); + }; + + const handleOpenCreate = () => { + if (!authenticated) { + alert("You must be logged in to place a wire."); + return; + } + setIsCreating(true); + setDraftTitle(""); + setDraftBody(""); + const main = mainFilter === "All" ? "News" : mainFilter; + setDraftCategory(main); + const list = CREATE_CATEGORY_MAP[main] || CREATE_CATEGORY_MAP.News; + setDraftSubCategory(list[0]); + setDraftCoords(null); + }; + + const handleSearchClick = () => { + alert("🔍 Search feature coming soon!"); + }; + + const handleSubmitPost = async () => { + if (isSaving) return; + setSaveError(""); + + const map = mapRef.current; + if (!map) return; + + if (!authenticated) { + setSaveError("You must be logged in to post."); + return; + } + + const authorName = (username || "").trim() || "anon"; + + let baseLng; + let baseLat; + + if (draftCoords && draftCoords.length === 2) { + baseLng = draftCoords[0]; + baseLat = draftCoords[1]; + } else { + const center = map.getCenter(); + baseLng = center.lng; + baseLat = center.lat; + } + + // keep marker visually above pointer + const pixelOffsetY = -20; + const screenPoint = map.project([baseLng, baseLat]); + const correctedPoint = { x: screenPoint.x, y: screenPoint.y + pixelOffsetY }; + const correctedLngLat = map.unproject([correctedPoint.x, correctedPoint.y]); + const lng = correctedLngLat.lng; + const lat = correctedLngLat.lat; + + if (!draftTitle.trim()) { + setSaveError("Title required."); + return; + } + + try { + setIsSaving(true); + + const newPost = await createPost( + { + title: draftTitle.trim(), + snippet: draftBody.trim() || draftTitle.trim(), + category: draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase(), + sub_category: draftSubCategory || "ALL", + lat, + lon: lng, + author: authorName, + }, + token + ); + + if (newPost && newPost.id) handleIncomingPost(newPost); + + setIsSaving(false); + setIsCreating(false); + } catch (e) { + console.error(e); + setIsSaving(false); + setSaveError("Error creating post."); + } + }; + + const handleMainFilterSelect = (code) => { + if (!FILTER_MAIN_CATEGORIES.includes(code)) return; + setMainFilter(code); + }; + + // allow selecting from map later (optional hook) + const handleSelectPost = (post) => { + if (onSelectPost) onSelectPost(post); + const map = mapRef.current; + if (!map) return; + const lng = post.lon ?? post.lng; + const lat = post.lat ?? post.latitude; + if (typeof lng === "number" && typeof lat === "number") { + map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 }); + } + }; + + return ( +
+
+
+ {status &&
{status}
} + +
+ + +
+ +
+ setTimeFilter(code)} /> +
+ +
+ +
+ +
+
+ {bottomCategories.map((c) => ( + + ))} +
+
+ +
+ +
+ + {isCreating && ( +
+
+
+ )} + + {isCreating && ( +
+
+ Create wire + +
+ + + Move the map, aim with the crosshair — your wire will be pinned there. + + +
+ + + +
+ + setDraftTitle(e.target.value)} + /> + +