243 lines
8.0 KiB
JavaScript
243 lines
8.0 KiB
JavaScript
import React, { Suspense, useEffect, useState, useCallback } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import TopBar from "./components/Layout/TopBar";
|
|
import PostList from "./components/Posts/PostList";
|
|
import IslandUniverse from "./components/Island/IslandUniverse";
|
|
import IslandViewer from "./components/Island/IslandViewer";
|
|
import ChatContainer from "./components/Chat/ChatContainer";
|
|
import { fetchIslandByUsername } from "./api/client";
|
|
|
|
const MapView = React.lazy(() => import("./components/Map/MapView"));
|
|
|
|
const THEME_KEY = "sociowire:theme";
|
|
|
|
export default function App() {
|
|
const { t } = useTranslation();
|
|
const [theme, setTheme] = useState("blue");
|
|
|
|
// Wall state (lifted from MapView)
|
|
const [wallPosts, setWallPosts] = useState([]);
|
|
const [wallLoading, setWallLoading] = useState(false);
|
|
const [wallError, setWallError] = useState("");
|
|
const [selectedPost, setSelectedPost] = useState(null);
|
|
|
|
// Island Universe & Island Viewer state
|
|
const [showUniverse, setShowUniverse] = useState(false);
|
|
const [selectedIsland, setSelectedIsland] = useState(null);
|
|
|
|
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
|
|
)
|
|
);
|
|
}, []);
|
|
|
|
// 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);
|
|
}, []);
|
|
|
|
// Island Universe handlers
|
|
const handleOpenUniverse = () => {
|
|
setShowUniverse(true);
|
|
};
|
|
|
|
const handleCloseUniverse = () => {
|
|
setShowUniverse(false);
|
|
};
|
|
|
|
const handleIslandClick = (island) => {
|
|
setShowUniverse(false); // Close universe
|
|
setSelectedIsland(island); // Open island viewer
|
|
};
|
|
|
|
const handleOpenUserIsland = async (uname) => {
|
|
const u = (uname || "").trim();
|
|
if (!u) return;
|
|
try {
|
|
const island = await fetchIslandByUsername(u);
|
|
if (island) {
|
|
setShowUniverse(false);
|
|
setSelectedIsland(island);
|
|
}
|
|
} catch {}
|
|
};
|
|
|
|
return (
|
|
<div className="app-root">
|
|
<TopBar
|
|
theme={theme}
|
|
onChangeTheme={setTheme}
|
|
onIslandsClick={handleOpenUniverse}
|
|
onUserIsland={handleOpenUserIsland}
|
|
/>
|
|
|
|
<div className="main-shell">
|
|
<div className="map-shell" id="map">
|
|
<Suspense
|
|
fallback={
|
|
<div className="map-page">
|
|
<div className="map-stage map-stage--placeholder">
|
|
<div className="map-placeholder-content">
|
|
<div className="map-placeholder-title">{t('map.loadingMap')}</div>
|
|
<div className="map-placeholder-subtitle">{t('map.warmingUp')}</div>
|
|
</div>
|
|
<div className="map-placeholder-overlay map-placeholder-top">
|
|
<div className="map-placeholder-pill map-placeholder-pill--wide" />
|
|
</div>
|
|
<div className="map-placeholder-overlay map-placeholder-left">
|
|
<div className="map-placeholder-pill" />
|
|
<div className="map-placeholder-pill" />
|
|
<div className="map-placeholder-pill" />
|
|
</div>
|
|
<div className="map-placeholder-overlay map-placeholder-right">
|
|
<div className="map-placeholder-pill" />
|
|
<div className="map-placeholder-pill" />
|
|
</div>
|
|
<div className="map-placeholder-overlay map-placeholder-bottom">
|
|
<div className="map-placeholder-chip" />
|
|
<div className="map-placeholder-chip" />
|
|
<div className="map-placeholder-chip" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
>
|
|
<MapView
|
|
theme={theme}
|
|
onPostsState={handlePostsState}
|
|
selectedPost={selectedPost}
|
|
onSelectPost={setSelectedPost}
|
|
selectedIsland={selectedIsland}
|
|
onSelectIsland={setSelectedIsland}
|
|
onOpenIslands={handleOpenUniverse}
|
|
/>
|
|
</Suspense>
|
|
</div>
|
|
|
|
{/* BELOW THE MAP (page scroll like Facebook) */}
|
|
<div className="below-stage">
|
|
<div className="below-row">
|
|
<div className="sociowall-panel" id="wall">
|
|
<div className="sociowall-header">SOCIOWALL</div>
|
|
<PostList
|
|
posts={wallPosts}
|
|
loading={wallLoading}
|
|
error={wallError}
|
|
selectedPost={selectedPost}
|
|
onSelectPost={setSelectedPost}
|
|
onPostDeleted={handlePostDeleted}
|
|
onPostUpdated={handlePostUpdated}
|
|
/>
|
|
</div>
|
|
{/* CHAT PANEL SUPPRIMÉ */}
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ height: 18 }} />
|
|
|
|
<section className="seo-section" id="islands">
|
|
<div className="seo-card">
|
|
<div className="seo-kicker">{t('islands.communities')}</div>
|
|
<h2>{t('islands.exploreIslands')}</h2>
|
|
<p>
|
|
{t('islands.islandsDescription')}
|
|
</p>
|
|
<button className="seo-cta" type="button" onClick={handleOpenUniverse}>
|
|
{t('islands.openIslands')}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Chat System */}
|
|
<ChatContainer />
|
|
</div>
|
|
|
|
<footer className="sw-footer">
|
|
<div className="sw-footer-inner">
|
|
<div className="sw-footer-brand">SOCIOWIRE</div>
|
|
<div className="sw-footer-brand">© Sociowire v0.0008</div>
|
|
<nav className="sw-footer-nav" aria-label="Site">
|
|
<a href="/">{t('nav.home')}</a>
|
|
<a href="/#map">{t('nav.liveMap')}</a>
|
|
<a href="/#wall">{t('nav.sociowall')}</a>
|
|
<a href="/#islands">{t('nav.islands')}</a>
|
|
<a href="/about.html">{t('nav.about')}</a>
|
|
<a href="/contact.html">{t('nav.contact')}</a>
|
|
<a href="/privacy.html">{t('nav.privacy')}</a>
|
|
</nav>
|
|
</div>
|
|
</footer>
|
|
|
|
{/* Island Universe Modal */}
|
|
{showUniverse && (
|
|
<IslandUniverse
|
|
onClose={handleCloseUniverse}
|
|
onIslandClick={handleIslandClick}
|
|
/>
|
|
)}
|
|
|
|
{/* Island Viewer Modal */}
|
|
{selectedIsland && (
|
|
<IslandViewer
|
|
island={selectedIsland}
|
|
onClose={() => setSelectedIsland(null)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|