feat: Enhance messaging page scrolling and typing indicator UI, and add socket acknowledgement timeouts for messaging actions.

This commit is contained in:
pradeepkumar
2026-02-11 07:36:40 +05:30
parent 50e593d7ae
commit a3880bf9a7
2 changed files with 46 additions and 7 deletions

View File

@@ -118,6 +118,7 @@ export function MessagingPage() {
const [conversationAvatarUrls, setConversationAvatarUrls] = useState<Record<string, string>>({});
const messagesContainerRef = useRef<HTMLDivElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const justSwitchedConversationRef = useRef(false);
const currentUserId = session?.user?.id;
const userRole = session?.user?.role;
@@ -296,14 +297,21 @@ export function MessagingPage() {
}
};
// Scroll to bottom when new message arrives or conversation changes
// Mark that we switched conversations so we scroll after messages load
useEffect(() => {
scrollToBottom();
justSwitchedConversationRef.current = true;
}, [currentConversation?.id]);
// Scroll to bottom when new messages are added (not when loading more)
// Scroll to bottom when messages load after opening a chat, or when a new message arrives
useEffect(() => {
if (messages.length > 0) {
// After opening/switching a conversation — scroll once messages are rendered
if (justSwitchedConversationRef.current) {
justSwitchedConversationRef.current = false;
setTimeout(scrollToBottom, 50);
return;
}
// New incoming message — scroll if it's very recent
const lastMessage = messages[messages.length - 1];
const isVeryRecent = new Date().getTime() - new Date(lastMessage.createdAt).getTime() < 2000;
if (isVeryRecent) {
@@ -312,6 +320,13 @@ export function MessagingPage() {
}
}, [messages]);
// Scroll to bottom when typing indicator appears
useEffect(() => {
if (typingUsers.size > 0) {
scrollToBottom();
}
}, [typingUsers]);
// Filter conversations by search
const filteredConversations = conversations.filter((conv) =>
conv.otherParty?.name?.toLowerCase().includes(searchQuery.toLowerCase())
@@ -616,11 +631,16 @@ export function MessagingPage() {
{typingUserNames.length > 0 && (
<div className="flex justify-start">
<div className="bg-[#00293d]/10 text-[#00293D] rounded-[15px] px-4 py-3">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
<span className="w-2 h-2 bg-[#00293D]/50 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="w-2 h-2 bg-[#00293D]/50 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<span className="w-2 h-2 bg-[#00293D]/50 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
<span className="font-serif font-normal text-[12px] leading-[16px] text-[#00293D]/50">
{formatMessageTime(new Date().toISOString())}
</span>
</div>
</div>
</div>
)}

View File

@@ -161,7 +161,13 @@ class SocketService {
resolve({ success: false, error: 'Not connected' });
return;
}
const timeout = setTimeout(() => {
resolve({ success: false, error: 'Socket timeout' });
}, 10000);
this.socket.emit('join_conversation', { conversationId }, (response: { success: boolean; error?: string }) => {
clearTimeout(timeout);
resolve(response);
});
});
@@ -183,10 +189,17 @@ class SocketService {
resolve({ success: false, error: 'Not connected' });
return;
}
// Timeout fallback in case acknowledgement never arrives
const timeout = setTimeout(() => {
resolve({ success: false, error: 'Socket timeout' });
}, 10000);
this.socket.emit(
'send_message',
{ conversationId, message },
(response: { success: boolean; message?: Message; error?: string }) => {
clearTimeout(timeout);
resolve(response);
}
);
@@ -213,7 +226,13 @@ class SocketService {
resolve({ success: false, error: 'Not connected' });
return;
}
const timeout = setTimeout(() => {
resolve({ success: false, error: 'Socket timeout' });
}, 10000);
this.socket.emit('mark_read', { conversationId }, (response: { success: boolean; error?: string }) => {
clearTimeout(timeout);
resolve(response);
});
});