- 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
134 lines
3 KiB
JavaScript
134 lines
3 KiB
JavaScript
/**
|
|
* MessageInput Component
|
|
* Input field for sending messages
|
|
*/
|
|
|
|
import React, { useState, useRef } from 'react';
|
|
import './MessageInput.css';
|
|
|
|
export default function MessageInput({ onSend, onTyping, onStopTyping }) {
|
|
const [message, setMessage] = useState('');
|
|
const [uploading, setUploading] = useState(false);
|
|
const fileInputRef = useRef(null);
|
|
const typingTimeoutRef = useRef(null);
|
|
|
|
const handleChange = (e) => {
|
|
setMessage(e.target.value);
|
|
|
|
// Trigger typing indicator
|
|
if (onTyping) onTyping();
|
|
|
|
// Reset stop-typing timeout
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current);
|
|
}
|
|
|
|
typingTimeoutRef.current = setTimeout(() => {
|
|
if (onStopTyping) onStopTyping();
|
|
}, 1000);
|
|
};
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
if (!message.trim()) return;
|
|
|
|
onSend(message);
|
|
setMessage('');
|
|
|
|
if (onStopTyping) onStopTyping();
|
|
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current);
|
|
}
|
|
};
|
|
|
|
const handleKeyPress = (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSubmit(e);
|
|
}
|
|
};
|
|
|
|
const handleFileUpload = async (e) => {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
setUploading(true);
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
const response = await fetch(
|
|
`${import.meta.env.VITE_API_URL || 'http://localhost:3000'}/api/files/upload`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
},
|
|
body: formData
|
|
}
|
|
);
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// Send message with file attachment
|
|
onSend(`📎 ${file.name}`, [data.file]);
|
|
}
|
|
} catch (error) {
|
|
console.error('File upload failed:', error);
|
|
alert('Failed to upload file');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form className="message-input" onSubmit={handleSubmit}>
|
|
<button
|
|
type="button"
|
|
className="btn-attach"
|
|
onClick={() => fileInputRef.current?.click()}
|
|
disabled={uploading}
|
|
title="Attach file"
|
|
>
|
|
{uploading ? '⏳' : '📎'}
|
|
</button>
|
|
|
|
<input
|
|
type="file"
|
|
ref={fileInputRef}
|
|
onChange={handleFileUpload}
|
|
style={{ display: 'none' }}
|
|
/>
|
|
|
|
<textarea
|
|
value={message}
|
|
onChange={handleChange}
|
|
onKeyDown={handleKeyPress}
|
|
placeholder="Type a message..."
|
|
rows={1}
|
|
disabled={uploading}
|
|
className="message-textarea"
|
|
/>
|
|
|
|
<button
|
|
type="button"
|
|
className="btn-emoji"
|
|
title="Add emoji"
|
|
>
|
|
😊
|
|
</button>
|
|
|
|
<button
|
|
type="submit"
|
|
className="btn-send"
|
|
disabled={!message.trim() || uploading}
|
|
>
|
|
Send
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|