139 lines
4.7 KiB
JavaScript
139 lines
4.7 KiB
JavaScript
/**
|
|
* MessageList Component
|
|
* Displays messages in a conversation
|
|
*/
|
|
|
|
import React, { useEffect, useRef } from 'react';
|
|
import './MessageList.css';
|
|
|
|
export default function MessageList({ messages, typingUsers }) {
|
|
const messagesEndRef = useRef(null);
|
|
|
|
// Auto-scroll to bottom when new messages arrive
|
|
useEffect(() => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
}, [messages]);
|
|
|
|
const formatTime = (timestamp) => {
|
|
const date = new Date(timestamp);
|
|
return date.toLocaleTimeString('en-US', {
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
hour12: true
|
|
});
|
|
};
|
|
|
|
const getCurrentUserId = () => {
|
|
// In a real app, get this from auth context
|
|
return localStorage.getItem('userId');
|
|
};
|
|
|
|
const isOwnMessage = (message) => {
|
|
return message.senderId === getCurrentUserId();
|
|
};
|
|
|
|
if (messages.length === 0 && typingUsers.length === 0) {
|
|
return (
|
|
<div className="message-list empty">
|
|
<div className="no-messages">
|
|
<p>No messages yet</p>
|
|
<p className="hint">Send a message to start the conversation</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="message-list">
|
|
{messages.map((message, index) => {
|
|
const showAvatar = index === messages.length - 1 ||
|
|
messages[index + 1]?.senderId !== message.senderId;
|
|
|
|
const showTimestamp = index === 0 ||
|
|
new Date(message.createdAt) - new Date(messages[index - 1].createdAt) > 300000; // 5 mins
|
|
|
|
return (
|
|
<div key={message.id}>
|
|
{showTimestamp && (
|
|
<div className="message-timestamp-divider">
|
|
{new Date(message.createdAt).toLocaleDateString()}
|
|
</div>
|
|
)}
|
|
|
|
<div className={`message ${isOwnMessage(message) ? 'own' : 'other'}`}>
|
|
{!isOwnMessage(message) && showAvatar && (
|
|
<div className="message-avatar">
|
|
{message.senderAvatar ? (
|
|
<img src={message.senderAvatar} alt={message.senderUsername} />
|
|
) : (
|
|
<div className="avatar-placeholder">
|
|
{message.senderUsername?.[0]?.toUpperCase()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="message-content-wrapper">
|
|
{!isOwnMessage(message) && (
|
|
<div className="message-sender">
|
|
{message.senderDomain || message.senderUsername}
|
|
{message.senderDomain && <span className="verified-badge">✓</span>}
|
|
</div>
|
|
)}
|
|
|
|
<div className="message-bubble">
|
|
{message.replyToId && (
|
|
<div className="message-reply-reference">
|
|
Replying to a message
|
|
</div>
|
|
)}
|
|
|
|
<div className="message-text">{message.content}</div>
|
|
|
|
{message.metadata?.attachments && message.metadata.attachments.length > 0 && (
|
|
<div className="message-attachments">
|
|
{message.metadata.attachments.map((attachment, i) => (
|
|
<div key={i} className="attachment">
|
|
📎 {attachment.filename}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="message-footer">
|
|
<span className="message-time">{formatTime(message.createdAt)}</span>
|
|
{message.editedAt && <span className="edited-indicator">edited</span>}
|
|
{message._sending && <span className="sending-indicator">sending...</span>}
|
|
</div>
|
|
|
|
{message.reactions && message.reactions.length > 0 && (
|
|
<div className="message-reactions">
|
|
{message.reactions.map((reaction, i) => (
|
|
<span key={i} className="reaction" title={`${reaction.users.length} reaction(s)`}>
|
|
{reaction.emoji} {reaction.users.length > 1 && reaction.users.length}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{typingUsers.length > 0 && (
|
|
<div className="typing-indicator">
|
|
<div className="typing-dots">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
<span className="typing-text">Someone is typing...</span>
|
|
</div>
|
|
)}
|
|
|
|
<div ref={messagesEndRef} />
|
|
</div>
|
|
);
|
|
}
|