AeThex-Connect/src/frontend/components/Chat/ConversationList.jsx
MrPiglr cad2e81fc4
Phase 2: Complete Messaging System Implementation
- Added real-time messaging with Socket.io
- Created comprehensive database schema (8 tables, functions, triggers)
- Implemented messaging service with full CRUD operations
- Built Socket.io service for real-time communication
- Created React messaging components (Chat, ConversationList, MessageList, MessageInput)
- Added end-to-end encryption utilities (RSA + AES-256-GCM)
- Implemented 16 RESTful API endpoints
- Added typing indicators, presence tracking, reactions
- Created modern, responsive UI with animations
- Updated server with Socket.io integration
- Fixed auth middleware imports
- Added comprehensive documentation

Features:
- Direct and group conversations
- Real-time message delivery
- Message editing and deletion
- Emoji reactions
- Typing indicators
- Online/offline presence
- Read receipts
- User search
- File attachment support (endpoint ready)
- Client-side encryption utilities

Dependencies:
- socket.io ^4.7.5
- socket.io-client ^4.7.5
2026-01-10 04:45:07 +00:00

110 lines
3.6 KiB
JavaScript

/**
* ConversationList Component
* Displays list of conversations in sidebar
*/
import React from 'react';
import './ConversationList.css';
export default function ConversationList({ conversations, activeConversation, onSelectConversation }) {
const formatTime = (timestamp) => {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
};
const getConversationTitle = (conv) => {
if (conv.title) return conv.title;
// For direct conversations, show other participant's domain
if (conv.otherParticipants && conv.otherParticipants.length > 0) {
return conv.otherParticipants[0].verified_domain || conv.otherParticipants[0].username;
}
return 'Unknown';
};
const getConversationAvatar = (conv) => {
if (conv.avatarUrl) return conv.avatarUrl;
// For direct conversations, show other participant's avatar
if (conv.otherParticipants && conv.otherParticipants.length > 0) {
return conv.otherParticipants[0].avatar_url;
}
return null;
};
return (
<div className="conversation-list">
<div className="conversation-list-header">
<h2>Messages</h2>
<button className="btn-new-conversation" title="New Conversation">
+
</button>
</div>
<div className="conversation-list-items">
{conversations.length === 0 ? (
<div className="no-conversations">
<p>No conversations yet</p>
<p className="hint">Start a new conversation to get started</p>
</div>
) : (
conversations.map(conv => (
<div
key={conv.id}
className={`conversation-item ${activeConversation?.id === conv.id ? 'active' : ''}`}
onClick={() => onSelectConversation(conv)}
>
<div className="conversation-avatar-container">
{getConversationAvatar(conv) ? (
<img
src={getConversationAvatar(conv)}
alt="Avatar"
className="conversation-avatar-img"
/>
) : (
<div className="conversation-avatar-placeholder">
{getConversationTitle(conv)[0]?.toUpperCase()}
</div>
)}
{conv.otherParticipants?.[0]?.status === 'online' && (
<span className="online-indicator"></span>
)}
</div>
<div className="conversation-details">
<div className="conversation-header-row">
<h3 className="conversation-title">{getConversationTitle(conv)}</h3>
<span className="conversation-time">
{formatTime(conv.updatedAt)}
</span>
</div>
<div className="conversation-last-message">
<p className="last-message-text">
{conv.lastMessage?.content || 'No messages yet'}
</p>
{conv.unreadCount > 0 && (
<span className="unread-badge">{conv.unreadCount}</span>
)}
</div>
</div>
</div>
))
)}
</div>
</div>
);
}