sw-fe/src/App.jsx

217 lines
6.5 KiB
JavaScript

import React, { Suspense, useEffect, useState, useCallback } from "react";
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 { fetchIslandByUsername } from "./api/client";
const MapView = React.lazy(() => import("./components/Map/MapView"));
const THEME_KEY = "sociowire:theme";
const ACTIVE_CHATS_KEY = "sociowire:activeChats";
export default function App() {
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 || "");
}, []);
// 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]);
// Chats actifs (s'ajoutent quand on clique sur un contact)
const [activeChats, setActiveChats] = useState([]);
const [showContactsList, setShowContactsList] = useState(false);
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);
}, []);
useEffect(() => {
try {
const saved = localStorage.getItem(ACTIVE_CHATS_KEY);
if (saved) setActiveChats(JSON.parse(saved));
} catch {}
}, []);
useEffect(() => {
try {
localStorage.setItem(ACTIVE_CHATS_KEY, JSON.stringify(activeChats));
} catch {}
}, [activeChats]);
// Ouvrir un chat
const openChat = (name) => {
if (!activeChats.includes(name)) {
setActiveChats(prev => [...prev, name]);
}
setShowContactsList(false);
};
const contactsList = ["Amely", "Nancy", "Stéphanie"];
// 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">
<Suspense fallback={<div style={{ padding: 16 }}>Loading map</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">
<div className="sociowall-header">SOCIOWALL</div>
<PostList
posts={wallPosts}
loading={wallLoading}
error={wallError}
selectedPost={selectedPost}
onSelectPost={setSelectedPost}
/>
</div>
{/* CHAT PANEL SUPPRIMÉ */}
</div>
</div>
<div style={{ height: 18 }} />
{/* Bouton Contacts flottant en bas à droite */}
<button className="contacts-float-btn" onClick={() => setShowContactsList(!showContactsList)}>
<span className="contact-icon">👥</span>
Contacts
</button>
{/* Liste des contacts (dropdown) */}
{showContactsList && (
<div className="contacts-dropdown">
{contactsList.map((name, idx) => (
<div key={idx} className="contact-item" onClick={() => openChat(name)}>
<div className="contact-avatar">👤</div>
<span>{name}</span>
</div>
))}
</div>
)}
{/* Bulles de chats actifs flottantes à côté du bouton */}
<div className="active-chats-bubbles">
{activeChats.map((name, idx) => (
<div key={idx} className="chat-bubble">
<div className="chat-avatar">👤</div>
<span className="chat-name">{name}</span>
<button className="bubble-remove" onClick={() => setActiveChats(prev => prev.filter(n => n !== name))}></button>
</div>
))}
</div>
</div>
{/* Island Universe Modal */}
{showUniverse && (
<IslandUniverse
onClose={handleCloseUniverse}
onIslandClick={handleIslandClick}
/>
)}
{/* Island Viewer Modal */}
{selectedIsland && (
<IslandViewer
island={selectedIsland}
onClose={() => setSelectedIsland(null)}
/>
)}
</div>
);
}