Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect, useCallback } from 'react'; | |
| import { useAuth } from '../context/AuthContext'; | |
| import { useSocket } from '../context/SocketContext'; | |
| import ServerList from './ServerList'; | |
| import ChannelSidebar from './ChannelSidebar'; | |
| // Lazy-loaded modals — these will be built in a separate batch | |
| const CreateServerModal = React.lazy(() => import('./modals/CreateServerModal')); | |
| const ServerSettings = React.lazy(() => import('./modals/ServerSettings')); | |
| const UserSettings = React.lazy(() => import('./modals/UserSettings')); | |
| const InviteModal = React.lazy(() => import('./modals/InviteModal')); | |
| // Chat area — will be built separately | |
| const ChatArea = React.lazy(() => import('./ChatArea')); | |
| const MemberList = React.lazy(() => import('./MemberList')); | |
| export default function Layout() { | |
| const { user, token } = useAuth(); | |
| const { socket } = useSocket(); | |
| const [servers, setServers] = useState([]); | |
| const [activeServer, setActiveServer] = useState(null); | |
| const [activeChannel, setActiveChannel] = useState(null); | |
| const [serverDetail, setServerDetail] = useState(null); | |
| const [showServerSettings, setShowServerSettings] = useState(false); | |
| const [showUserSettings, setShowUserSettings] = useState(false); | |
| const [showCreateServer, setShowCreateServer] = useState(false); | |
| const [showInviteModal, setShowInviteModal] = useState(false); | |
| const [showMemberList, setShowMemberList] = useState(true); | |
| // Fetch user's servers | |
| const fetchServers = useCallback(async () => { | |
| try { | |
| const res = await fetch('/api/servers', { | |
| headers: { Authorization: `Bearer ${token}` }, | |
| }); | |
| if (!res.ok) return; | |
| const data = await res.json(); | |
| const list = data.servers || data; | |
| setServers(list); | |
| // Auto-select first server if none active | |
| if (!activeServer && list.length > 0) { | |
| setActiveServer(list[0]); | |
| } | |
| } catch (err) { | |
| console.error('Failed to fetch servers:', err); | |
| } | |
| }, [token, activeServer]); | |
| useEffect(() => { | |
| fetchServers(); | |
| }, [fetchServers]); | |
| // Fetch full server detail when active server changes | |
| useEffect(() => { | |
| if (!activeServer) { | |
| setServerDetail(null); | |
| setActiveChannel(null); | |
| return; | |
| } | |
| const fetchDetail = async () => { | |
| try { | |
| const res = await fetch(`/api/servers/${activeServer.id}`, { | |
| headers: { Authorization: `Bearer ${token}` }, | |
| }); | |
| if (!res.ok) return; | |
| const data = await res.json(); | |
| const detail = data.server || data; | |
| setServerDetail(detail); | |
| // Auto-select first channel | |
| if (detail.channels && detail.channels.length > 0) { | |
| setActiveChannel(detail.channels[0]); | |
| } else if (detail.categories) { | |
| for (const cat of detail.categories) { | |
| if (cat.channels && cat.channels.length > 0) { | |
| setActiveChannel(cat.channels[0]); | |
| break; | |
| } | |
| } | |
| } | |
| } catch (err) { | |
| console.error('Failed to fetch server detail:', err); | |
| } | |
| }; | |
| fetchDetail(); | |
| // Join server room via socket | |
| if (socket) { | |
| socket.emit('server:join', activeServer.id); | |
| } | |
| return () => { | |
| if (socket && activeServer) { | |
| socket.emit('server:leave', activeServer.id); | |
| } | |
| }; | |
| }, [activeServer?.id, token, socket]); | |
| // Socket listeners for server updates | |
| useEffect(() => { | |
| if (!socket) return; | |
| const handleServerUpdate = (data) => { | |
| setServers((prev) => | |
| prev.map((s) => (s.id === data.id ? { ...s, ...data } : s)) | |
| ); | |
| if (activeServer?.id === data.id) { | |
| setActiveServer((prev) => (prev ? { ...prev, ...data } : prev)); | |
| } | |
| }; | |
| socket.on('server:updated', handleServerUpdate); | |
| return () => { | |
| socket.off('server:updated', handleServerUpdate); | |
| }; | |
| }, [socket, activeServer?.id]); | |
| const handleSelectServer = (server) => { | |
| setActiveServer(server); | |
| }; | |
| const handleOpenDMs = () => { | |
| setActiveServer(null); | |
| setServerDetail(null); | |
| setActiveChannel(null); | |
| }; | |
| const handleSelectChannel = (channel) => { | |
| setActiveChannel(channel); | |
| }; | |
| const handleServerCreated = (newServer) => { | |
| setServers((prev) => [...prev, newServer]); | |
| setActiveServer(newServer); | |
| setShowCreateServer(false); | |
| }; | |
| const dmMode = activeServer === null; | |
| return ( | |
| <div className="flex h-screen bg-[#0e0e10] overflow-hidden"> | |
| {/* Server List */} | |
| <ServerList | |
| servers={servers} | |
| activeServer={activeServer} | |
| onSelectServer={handleSelectServer} | |
| onOpenDMs={handleOpenDMs} | |
| onCreateServer={() => setShowCreateServer(true)} | |
| onOpenUserSettings={() => setShowUserSettings(true)} | |
| user={user} | |
| /> | |
| {/* Channel Sidebar */} | |
| <ChannelSidebar | |
| server={serverDetail} | |
| activeChannel={activeChannel} | |
| onSelectChannel={handleSelectChannel} | |
| onOpenServerSettings={() => setShowServerSettings(true)} | |
| onOpenInvite={() => setShowInviteModal(true)} | |
| dmMode={dmMode} | |
| user={user} | |
| token={token} | |
| /> | |
| {/* Chat Area */} | |
| <div className="flex-1 flex flex-col min-w-0"> | |
| <React.Suspense | |
| fallback={ | |
| <div className="flex-1 flex items-center justify-center bg-[#1e1e22]"> | |
| <div className="w-8 h-8 border-3 border-[#FFD700] border-t-transparent rounded-full animate-spin" /> | |
| </div> | |
| } | |
| > | |
| <ChatArea | |
| server={activeServer} | |
| channel={activeChannel} | |
| token={token} | |
| user={user} | |
| socket={socket} | |
| onToggleMemberList={() => setShowMemberList((v) => !v)} | |
| showMemberList={showMemberList} | |
| /> | |
| </React.Suspense> | |
| </div> | |
| {/* Member List */} | |
| {showMemberList && activeServer && ( | |
| <React.Suspense fallback={null}> | |
| <MemberList server={activeServer} token={token} /> | |
| </React.Suspense> | |
| )} | |
| {/* Modals */} | |
| <React.Suspense fallback={null}> | |
| {showCreateServer && ( | |
| <CreateServerModal | |
| onClose={() => setShowCreateServer(false)} | |
| onCreated={handleServerCreated} | |
| token={token} | |
| /> | |
| )} | |
| {showServerSettings && serverDetail && ( | |
| <ServerSettings | |
| server={serverDetail} | |
| onClose={() => setShowServerSettings(false)} | |
| token={token} | |
| onUpdate={(updated) => { | |
| setServerDetail((prev) => ({ ...prev, ...updated })); | |
| setServers((prev) => | |
| prev.map((s) => (s.id === updated.id ? { ...s, ...updated } : s)) | |
| ); | |
| }} | |
| /> | |
| )} | |
| {showUserSettings && ( | |
| <UserSettings onClose={() => setShowUserSettings(false)} /> | |
| )} | |
| {showInviteModal && activeServer && ( | |
| <InviteModal | |
| server={activeServer} | |
| onClose={() => setShowInviteModal(false)} | |
| token={token} | |
| /> | |
| )} | |
| </React.Suspense> | |
| </div> | |
| ); | |
| } | |