import { useCallback, useEffect, useRef, useState } from "react"; import type { Channel } from "@/types/Channel"; import type { Message } from "@/types/Message"; import { getChannels, getMessages, getCurrentUsername } from "@/api"; import { ChannelSidebar } from "@/components/ChannelSidebar"; import { MessageItem } from "@/components/MessageItem"; import { ComposeBox } from "@/components/ComposeBox"; export default function App() { const [channels, setChannels] = useState([]); const [activeChannelId, setActiveChannelId] = useState(null); const [messages, setMessages] = useState([]); const [replyTo, setReplyTo] = useState(null); const scrollRef = useRef(null); const prevMsgCountRef = useRef(0); const activeChannelRef = useRef(activeChannelId); // Keep ref in sync activeChannelRef.current = activeChannelId; const loadChannels = useCallback(async () => { const chs = await getChannels(); setChannels(chs); // Auto-select first channel only if none selected setActiveChannelId((prev) => (prev ? prev : chs[0]?.id ?? null)); }, []); const loadMessages = useCallback(async () => { const channelId = activeChannelRef.current; if (!channelId) return; try { const msgs = await getMessages(channelId); // Only update if we're still on the same channel (prevent race) if (activeChannelRef.current === channelId) { setMessages(msgs); } } catch { // Silently ignore fetch errors during polling } }, []); // Initial channel load useEffect(() => { loadChannels(); }, [loadChannels]); // Load messages on channel switch useEffect(() => { setMessages([]); // Clear immediately on switch setReplyTo(null); prevMsgCountRef.current = 0; loadMessages(); }, [activeChannelId, loadMessages]); // Auto-scroll only when NEW messages arrive (not on every poll) useEffect(() => { if (messages.length > prevMsgCountRef.current && scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } prevMsgCountRef.current = messages.length; }, [messages]); // Poll until WebSocket (S5) useEffect(() => { const interval = setInterval(loadMessages, 3000); return () => clearInterval(interval); }, [loadMessages]); const messagesById = new Map(messages.map((m) => [m.id, m])); const activeChannel = channels.find((c) => c.id === activeChannelId); const _currentUser = getCurrentUsername(); return (
{/* Channel header */}
{activeChannel ? ( <> # {activeChannel.name} {activeChannel.description && ( {activeChannel.description} )} {messages.length} msg ) : ( select a channel )}
{/* Messages */}
{messages.length === 0 && activeChannelId && (
no messages yet
)} {messages.map((msg) => ( ))}
{/* Compose — key forces reset on channel switch */} {activeChannelId && ( setReplyTo(null)} onMessageSent={loadMessages} /> )}
); }