import React from 'react'
export default function ConversationList({ conversations, loading, activeId, currentUserId, typingByConversation = {}, onSelect }) {
return (
Conversations
Recent threads and group rooms
{conversations.length}
{loading ? (
- Loading conversations…
) : null}
{!loading && conversations.length === 0 ? (
- No conversations yet.
) : null}
{conversations.map((conversation) => (
onSelect(conversation.id)}
/>
))}
)
}
function ConversationRow({ conv, isActive, currentUserId, typingUsers, onClick }) {
const label = convLabel(conv, currentUserId)
const lastMsg = Array.isArray(conv.latest_message) ? conv.latest_message[0] : conv.latest_message
const preview = typingUsers.length > 0
? buildTypingPreview(typingUsers)
: lastMsg?.body ? truncate(lastMsg.body, 88) : 'No messages yet'
const unread = conv.unread_count ?? 0
const myParticipant = conv.all_participants?.find((participant) => participant.user_id === currentUserId)
const isArchived = myParticipant?.is_archived ?? false
const isPinned = myParticipant?.is_pinned ?? false
const activeMembers = conv.all_participants?.filter((participant) => !participant.left_at).length ?? 0
const typeLabel = conv.type === 'group' ? `${activeMembers} members` : 'Direct message'
const senderLabel = lastMsg?.sender?.username ? `@${lastMsg.sender.username}` : null
const initials = label
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((part) => part.charAt(0).toUpperCase())
.join('') || 'M'
return (
)
}
function convLabel(conv, currentUserId) {
if (conv.type === 'group') return conv.title ?? 'Group'
const other = conv.all_participants?.find((participant) => participant.user_id !== currentUserId)
return other?.user?.username ?? 'Direct message'
}
function truncate(str, max) {
if (!str) return ''
return str.length > max ? `${str.slice(0, max)}…` : str
}
function buildTypingPreview(users) {
const names = users.map((user) => `@${user.username}`)
if (names.length === 1) return `${names[0]} is typing...`
if (names.length === 2) return `${names[0]} and ${names[1]} are typing...`
return `${names[0]}, ${names[1]} and ${names.length - 2} others are typing...`
}
function SidebarTypingIcon() {
return (
)
}
function relativeTime(iso) {
if (!iso) return 'No activity'
const diff = (Date.now() - new Date(iso).getTime()) / 1000
if (diff < 60) return 'Now'
if (diff < 3600) return `${Math.floor(diff / 60)}m`
if (diff < 86400) return `${Math.floor(diff / 3600)}h`
return `${Math.floor(diff / 86400)}d`
}