feat: add Reverb realtime messaging
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function ConversationList({ conversations, loading, activeId, currentUserId, onSelect }) {
|
||||
export default function ConversationList({ conversations, loading, activeId, currentUserId, typingByConversation = {}, onSelect }) {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex items-center justify-between border-b border-white/[0.06] px-4 py-3">
|
||||
@@ -13,7 +13,7 @@ export default function ConversationList({ conversations, loading, activeId, cur
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul className="flex-1 space-y-2 overflow-y-auto p-3">
|
||||
<ul className="nova-scrollbar-message flex-1 space-y-2 overflow-y-auto p-3 pr-2">
|
||||
{loading ? (
|
||||
<li className="rounded-2xl border border-white/[0.06] bg-white/[0.025] px-4 py-8 text-center text-sm text-white/40">Loading conversations…</li>
|
||||
) : null}
|
||||
@@ -28,6 +28,7 @@ export default function ConversationList({ conversations, loading, activeId, cur
|
||||
conv={conversation}
|
||||
isActive={conversation.id === activeId}
|
||||
currentUserId={currentUserId}
|
||||
typingUsers={typingByConversation[conversation.id] ?? []}
|
||||
onClick={() => onSelect(conversation.id)}
|
||||
/>
|
||||
))}
|
||||
@@ -36,10 +37,12 @@ export default function ConversationList({ conversations, loading, activeId, cur
|
||||
)
|
||||
}
|
||||
|
||||
function ConversationRow({ conv, isActive, currentUserId, onClick }) {
|
||||
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 = lastMsg?.body ? truncate(lastMsg.body, 88) : 'No messages yet'
|
||||
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
|
||||
@@ -89,8 +92,11 @@ function ConversationRow({ conv, isActive, currentUserId, onClick }) {
|
||||
</div>
|
||||
|
||||
<div className="mt-3 rounded-2xl border border-white/[0.04] bg-black/10 px-3 py-2.5">
|
||||
{senderLabel ? <p className="text-[11px] text-white/35">{senderLabel}</p> : null}
|
||||
<p className="mt-1 truncate text-sm text-white/62">{preview}</p>
|
||||
{typingUsers.length === 0 && senderLabel ? <p className="text-[11px] text-white/35">{senderLabel}</p> : null}
|
||||
<p className={`mt-1 flex items-center gap-2 truncate text-sm ${typingUsers.length > 0 ? 'text-emerald-200' : 'text-white/62'}`}>
|
||||
{typingUsers.length > 0 ? <SidebarTypingIcon /> : null}
|
||||
<span className="truncate">{preview}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,6 +116,23 @@ function truncate(str, max) {
|
||||
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 (
|
||||
<span className="inline-flex shrink-0 items-center gap-1">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-300 animate-[pulse_1s_ease-in-out_infinite]" />
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-300/80 animate-[pulse_1s_ease-in-out_150ms_infinite]" />
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-300/60 animate-[pulse_1s_ease-in-out_300ms_infinite]" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function relativeTime(iso) {
|
||||
if (!iso) return 'No activity'
|
||||
const diff = (Date.now() - new Date(iso).getTime()) / 1000
|
||||
|
||||
Reference in New Issue
Block a user