feat: Implement real-time messaging functionality with dedicated services, types, a custom hook, and UI integration.
This commit is contained in:
@@ -1,17 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
|
||||
interface MessageInputProps {
|
||||
onSend?: (message: string) => void;
|
||||
onTypingStart?: () => void;
|
||||
onTypingStop?: () => void;
|
||||
}
|
||||
|
||||
export function MessageInput({ onSend }: MessageInputProps) {
|
||||
export function MessageInput({ onSend, onTypingStart, onTypingStop }: MessageInputProps) {
|
||||
const [message, setMessage] = useState('');
|
||||
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isTypingRef = useRef(false);
|
||||
|
||||
// Handle typing indicator
|
||||
const handleTyping = useCallback(() => {
|
||||
// Start typing indicator
|
||||
if (!isTypingRef.current && onTypingStart) {
|
||||
isTypingRef.current = true;
|
||||
onTypingStart();
|
||||
}
|
||||
|
||||
// Clear existing timeout
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Set timeout to stop typing indicator after 2 seconds of inactivity
|
||||
typingTimeoutRef.current = setTimeout(() => {
|
||||
if (isTypingRef.current && onTypingStop) {
|
||||
isTypingRef.current = false;
|
||||
onTypingStop();
|
||||
}
|
||||
}, 2000);
|
||||
}, [onTypingStart, onTypingStop]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current);
|
||||
}
|
||||
if (isTypingRef.current && onTypingStop) {
|
||||
onTypingStop();
|
||||
}
|
||||
};
|
||||
}, [onTypingStop]);
|
||||
|
||||
const handleSend = () => {
|
||||
if (message.trim() && onSend) {
|
||||
// Stop typing indicator before sending
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current);
|
||||
}
|
||||
if (isTypingRef.current && onTypingStop) {
|
||||
isTypingRef.current = false;
|
||||
onTypingStop();
|
||||
}
|
||||
|
||||
onSend(message.trim());
|
||||
setMessage('');
|
||||
}
|
||||
@@ -24,13 +71,18 @@ export function MessageInput({ onSend }: MessageInputProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
handleTyping();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border border-[#00293d]/10 rounded-[20px] bg-white p-4">
|
||||
{/* Text input area */}
|
||||
<div className="border border-[#00293d]/10 rounded-[20px] p-4 mb-3">
|
||||
<textarea
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Write a Message........"
|
||||
className="w-full min-h-[120px] resize-none font-serif font-normal text-[14px] leading-[19px] text-[#00293D] placeholder-[#00293D]/50 focus:outline-none"
|
||||
@@ -79,7 +131,12 @@ export function MessageInput({ onSend }: MessageInputProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleSend}
|
||||
className="px-4 py-1.5 bg-[#e58625] text-[#00293d] font-serif font-normal text-[14px] leading-[19px] rounded-[15px] hover:bg-[#d47720] transition-colors cursor-pointer"
|
||||
disabled={!message.trim()}
|
||||
className={`px-4 py-1.5 font-serif font-normal text-[14px] leading-[19px] rounded-[15px] transition-colors cursor-pointer ${
|
||||
message.trim()
|
||||
? 'bg-[#e58625] text-[#00293d] hover:bg-[#d47720]'
|
||||
: 'bg-[#e58625]/50 text-[#00293d]/50 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user