import { useMemo } from 'react'; import { useMessages } from '../hooks/use-messages'; import { useMembers } from '../hooks/use-members'; import { Composer } from './composer'; import { MessageItem } from './message-item'; /** * The main conversation view: top-level messages + composer. Replies (messages with a * parentInteractionId) are hidden here and live in the ThreadPane — a message with replies shows a * "N replies" link that opens it. @mentions, reactions, and attachments all work. */ export function Thread({ threadId, activeRootId, onOpenThread, }: { threadId: string | null; activeRootId?: string | null; onOpenThread?: (rootId: string) => void; }) { const { messages, loading, error, send, react, upload, typingUserIds, seenIds, sendTyping, canReact, canUpload } = useMessages(threadId); const members = useMembers(threadId); const memberNames = useMemo(() => members.map((m) => m.name), [members]); const topLevel = useMemo(() => messages.filter((m) => !m.parentInteractionId), [messages]); const replyCount = useMemo(() => { const counts = new Map(); for (const m of messages) if (m.parentInteractionId) counts.set(m.parentInteractionId, (counts.get(m.parentInteractionId) ?? 0) + 1); return counts; }, [messages]); if (!threadId) { return
Select a conversation.
; } return (
{loading && messages.length === 0 ?
Loading…
: null} {error ?
{error}
: null} {topLevel.map((m) => ( void react(id, emoji)} seen={seenIds.has(m.id)} replyCount={replyCount.get(m.id) ?? 0} {...(onOpenThread ? { onOpenThread } : {})} /> ))} {typingUserIds.length > 0 ? (
{typingUserIds.length === 1 ? 'typing…' : 'several people are typing…'}
) : null}
); }