AeThex-Connect/astro-site/src/react-app/components/Chat/MessageList.jsx
MrPiglr de54903c15
new file: astro-site/src/components/auth/SupabaseLogin.jsx
new file:   astro-site/src/components/auth/SupabaseLogin.jsx
2026-02-03 09:09:36 +00:00

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>
);
}