import React from 'react' import { Trash2, Reply, ChevronUp, MessageCircle } from 'lucide-react' import { URL_REGEX } from './CollabChat.constants' import { formatTime, formatDateSeparator, shouldShowDateSeparator } from './CollabChat.helpers' import { MessageText } from './CollabChatMessageText' import { LinkPreview } from './CollabChatLinkPreview' import { ReactionBadge } from './CollabChatReactionBadge' export function ChatMessages(props: any) { const { currentUser, tripId, t, is12h, can, trip, canEdit, messages, setMessages, loading, setLoading, hasMore, setHasMore, loadingMore, setLoadingMore, text, setText, replyTo, setReplyTo, hoveredId, setHoveredId, sending, setSending, showEmoji, setShowEmoji, reactMenu, setReactMenu, deletingIds, setDeletingIds, deleteTimersRef, containerRef, messagesRef, scrollRef, textareaRef, emojiBtnRef, isAtBottom, scrollToBottom, checkAtBottom, handleLoadMore, handleTextChange, handleSend, handleKeyDown, handleDelete, handleReact, handleEmojiSelect, isOwn, isEmojiOnly } = props return ( <> {/* Messages */} {messages.length === 0 ? (
{t('collab.chat.empty')} {t('collab.chat.emptyDesc') || ''}
) : (
{hasMore && (
)} {messages.map((msg, idx) => { const own = isOwn(msg) const prevMsg = messages[idx - 1] const nextMsg = messages[idx + 1] const isNewGroup = idx === 0 || String(prevMsg?.user_id) !== String(msg.user_id) const isLastInGroup = !nextMsg || String(nextMsg?.user_id) !== String(msg.user_id) const showDate = shouldShowDateSeparator(msg, prevMsg) const showAvatar = !own && isLastInGroup const bigEmoji = isEmojiOnly(msg.text) const hasReply = msg.reply_text || msg.reply_to // Deleted message placeholder if (msg._deleted) { return ( {showDate && (
{formatDateSeparator(msg.created_at, t)}
)}
{msg.username} {t('collab.chat.deletedMessage') || 'deleted a message'} ยท {formatTime(msg.created_at, is12h)}
) } // Bubble border radius โ€” iMessage style tails const br = own ? `18px 18px ${isLastInGroup ? '4px' : '18px'} 18px` : `18px 18px 18px ${isLastInGroup ? '4px' : '18px'}` return ( {/* Date separator */} {showDate && (
{formatDateSeparator(msg.created_at, t)}
)}
{/* Avatar slot for others */} {!own && (
{showAvatar && ( msg.user_avatar ? ( ) : (
{(msg.username || '?')[0].toUpperCase()}
) )}
)}
{/* Username for others at group start */} {!own && isNewGroup && ( {msg.username} )} {/* Bubble */}
setHoveredId(msg.id)} onMouseLeave={() => setHoveredId(null)} onContextMenu={e => { e.preventDefault(); if (canEdit) setReactMenu({ msgId: msg.id, x: e.clientX, y: e.clientY }) }} onTouchEnd={e => { const now = Date.now() const lastTap = Number(e.currentTarget.dataset.lastTap) || 0 if (now - lastTap < 300 && canEdit) { e.preventDefault() const touch = e.changedTouches?.[0] if (touch) setReactMenu({ msgId: msg.id, x: touch.clientX, y: touch.clientY }) } e.currentTarget.dataset.lastTap = String(now) }} > {bigEmoji ? (
{msg.text}
) : (
{/* Inline reply quote */} {hasReply && (
{msg.reply_username || ''}
{(msg.reply_text || '').slice(0, 80)}
)} {hasReply ? (
) : } {(msg.text.match(URL_REGEX) || []).slice(0, 1).map(url => ( { if (isAtBottom.current) setTimeout(() => scrollToBottom('smooth'), 50) }} /> ))}
)} {/* Hover actions */}
{own && canEdit && ( )}
{/* Reactions โ€” iMessage style floating badge */} {msg.reactions?.length > 0 && (
{msg.reactions.map(r => { const myReaction = r.users.some(u => String(u.user_id) === String(currentUser.id)) return ( { if (canEdit) handleReact(msg.id, r.emoji) }} /> ) })}
)} {/* Timestamp โ€” only on last message of group */} {isLastInGroup && ( {formatTime(msg.created_at, is12h)} )}
) })}
)} ) }