import React, { Suspense, useEffect, useState, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { version as appVersion } from "../package.json"; import TopBar from "./components/Layout/TopBarNew"; import PostList from "./components/Posts/PostList"; import ContentFilters from "./components/Filters/ContentFilters"; import IslandViewer from "./components/Island/IslandViewer"; import ChatContainer from "./components/Chat/ChatContainer"; import PostDetailModal from "./components/Posts/PostDetailModal"; import PostComposer, { CreatePostFAB } from "./components/Posts/PostComposer"; import { AppStore } from "./components/Apps"; import { AnimatePresence, motion } from "framer-motion"; import { fetchIslandByUsername } from "./api/client"; const MapView = React.lazy(() => import("./components/Map/MapView")); const IslandsWorld = React.lazy(() => import("./components/World/IslandsWorld")); const PlanetsWorld = React.lazy(() => import("./components/World/PlanetsWorld")); const THEME_KEY = "sociowire:theme"; const CONTENT_FILTER_KEY = "sociowire:contentFilter"; const VIEW_MODE_KEY = "sociowire:viewMode"; // "map" | "islands" | "planets" export default function App() { const { t } = useTranslation(); const [theme, setTheme] = useState("blue"); // Current world view: "map" | "islands" | "planets" // Always start on map for now const [currentView, setCurrentView] = useState("map"); // Clean up Facebook OAuth hash fragment (#_=_) useEffect(() => { if (window.location.hash === "#_=_") { if (window.history.replaceState) { window.history.replaceState(null, "", window.location.pathname + window.location.search); } else { window.location.hash = ""; } } }, []); // Wall state (lifted from MapView) const [wallPosts, setWallPosts] = useState([]); const [wallLoading, setWallLoading] = useState(false); const [wallError, setWallError] = useState(""); const [selectedPost, setSelectedPost] = useState(null); // Content filter (shared between map and sociowall) - persisted // "all" | "links" | "video" | "image" | "text" | "live" | "cluster" const [contentFilter, setContentFilter] = useState(() => { try { const saved = localStorage.getItem(CONTENT_FILTER_KEY); if (saved && ["all", "links", "video", "image", "text", "live", "cluster"].includes(saved)) { return saved; } } catch {} return "all"; }); // Save content filter to localStorage useEffect(() => { try { localStorage.setItem(CONTENT_FILTER_KEY, contentFilter); } catch {} }, [contentFilter]); // Island Viewer state (for viewing individual islands) const [selectedIsland, setSelectedIsland] = useState(null); // Post Composer state (for creating posts with island context) const [showPostComposer, setShowPostComposer] = useState(false); const [postComposerIsland, setPostComposerIsland] = useState(null); const [postComposerPosition, setPostComposerPosition] = useState(null); // { x, y } // Post detail modal state (triggered from map markers) const [detailPost, setDetailPost] = useState(null); // App Store state const [showAppStore, setShowAppStore] = useState(false); const handlePostsState = useCallback((next) => { if (!next) return; if (Array.isArray(next.posts)) setWallPosts(next.posts); setWallLoading(!!next.loading); setWallError(next.error || ""); }, []); const handlePostDeleted = useCallback((postId) => { setWallPosts((prev) => prev.filter((p) => (p.id ?? p._id) !== postId)); if (selectedPost && (selectedPost.id ?? selectedPost._id) === postId) { setSelectedPost(null); } }, [selectedPost]); const handlePostUpdated = useCallback((updatedPost) => { if (!updatedPost) return; setWallPosts((prev) => prev.map((p) => (p.id ?? p._id) === (updatedPost.id ?? updatedPost._id) ? { ...p, ...updatedPost } : p ) ); }, []); const handlePostCreated = useCallback((newPost) => { if (!newPost) return; // Add to beginning of wall posts setWallPosts((prev) => [newPost, ...prev]); }, []); // charge le thème au démarrage useEffect(() => { if (typeof window === "undefined") return; try { const saved = localStorage.getItem(THEME_KEY); if (saved === "dark" || saved === "blue" || saved === "light") { setTheme(saved); document.body.setAttribute("data-theme", saved); } else { setTheme("blue"); document.body.setAttribute("data-theme", "blue"); } } catch (e) { setTheme("blue"); document.body.setAttribute("data-theme", "blue"); } }, []); // applique / sauvegarde le thème useEffect(() => { if (typeof window === "undefined") return; try { document.body.setAttribute("data-theme", theme); localStorage.setItem(THEME_KEY, theme); } catch (e) { // ignore } }, [theme]); useEffect(() => { const onScroll = () => { const threshold = window.innerHeight * 0.55; const expanded = window.scrollY > threshold; document.body.classList.toggle("sw-wall-expanded", expanded); }; window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => window.removeEventListener("scroll", onScroll); }, []); // Listen for post detail modal open event from map markers useEffect(() => { const handleOpenPostDetail = (e) => { const post = e.detail?.post; if (post) { setDetailPost(post); } }; window.addEventListener("sw:openPostDetail", handleOpenPostDetail); return () => window.removeEventListener("sw:openPostDetail", handleOpenPostDetail); }, []); // Listen for post creator events useEffect(() => { // Handle opening post composer with island context and coordinates const handleOpenPostCreator = (e) => { const { island_username, island_x, island_y } = e.detail || {}; setPostComposerIsland(island_username || null); if (island_x !== undefined && island_y !== undefined) { setPostComposerPosition({ x: island_x, y: island_y }); } else { setPostComposerPosition(null); } setShowPostComposer(true); }; // Legacy island post event const handleIslandPost = (e) => { const { island } = e.detail || {}; if (island) { setPostComposerIsland(island); setPostComposerPosition(null); setShowPostComposer(true); } }; window.addEventListener('sw:openPostCreator', handleOpenPostCreator); window.addEventListener('sociowire:create-post', handleIslandPost); return () => { window.removeEventListener('sw:openPostCreator', handleOpenPostCreator); window.removeEventListener('sociowire:create-post', handleIslandPost); }; }, []); // World navigation handlers const handleOpenIslands = () => { setCurrentView("islands"); }; const handleOpenPlanets = () => { setCurrentView("planets"); }; const handleOpenMap = () => { setCurrentView("map"); }; const handleIslandClick = (island) => { setSelectedIsland(island); // Open island viewer }; const handleOpenUserIsland = async (uname) => { const u = (uname || "").trim(); if (!u) return; try { const island = await fetchIslandByUsername(u); if (island) { setSelectedIsland(island); } else { // Island doesn't exist - switch to Islands view to create it setCurrentView('islands'); } } catch (err) { console.error("Error fetching island:", err); // On error, switch to Islands view setCurrentView('islands'); } }; // World loading placeholder const WorldPlaceholder = ({ title, subtitle }) => (
{title}
{subtitle}
); return (
setShowAppStore(true)} />
{/* MAP WORLD - Reality */} {currentView === "map" && (
}>
{/* BELOW THE MAP (page scroll like Facebook) */}

SOCIOWALL

)} {/* ISLANDS WORLD - Personal Metaverse */} {currentView === "islands" && ( }> )} {/* PLANETS WORLD - Group Metaverse */} {currentView === "planets" && ( }> )}
{/* Chat System - available in all worlds */}
{/* Footer only shows in map view */} {currentView === "map" && ( )} {/* Island Viewer Modal */} {selectedIsland && ( setSelectedIsland(null)} /> )} {/* Post Detail Modal (from map markers) */} {detailPost && ( setDetailPost(null)} onPostUpdated={(updated) => { handlePostUpdated(updated); setDetailPost((prev) => (prev ? { ...prev, ...updated } : null)); }} onPostDeleted={(postId) => { handlePostDeleted(postId); setDetailPost(null); }} /> )} {/* Post Composer Modal (for island posts) */} {showPostComposer && ( { setShowPostComposer(false); setPostComposerIsland(null); setPostComposerPosition(null); }} onPostCreated={(newPost) => { // Refresh wall posts if on map view if (currentView === 'map') { setWallPosts((prev) => [newPost, ...prev]); } }} /> )} {/* Floating Action Button for creating posts */} {/* App Store Modal */} {showAppStore && ( setShowAppStore(false)} /> )}
); }