unread messages handling works

This commit is contained in:
slawk0
2025-01-12 15:37:38 +01:00
parent e9763084f4
commit d75208f88b
9 changed files with 130 additions and 115 deletions

View File

@@ -7,7 +7,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/turtle.svg"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Turtel chat 🐢</title>
<title>Turtel chat</title>
<style>
body{

View File

@@ -12,21 +12,6 @@ export async function getContactsList(): Promise<ContactsProps[]> {
}
}
export async function setContactStatus(conversation_id: string, read: boolean) {
try {
const response = await axiosClient.put(
`/api/chat/contacts/${conversation_id}`,
{ status: read },
{ withCredentials: true },
);
console.log(response.data);
return response.data;
} catch (e) {
console.error('Failed to set contact status: ', e);
throw e;
}
}
export async function sendContact(contact: string) {
try {
const response = await axiosClient.post(`/api/chat/contact/${contact}`);

View File

@@ -1,6 +1,6 @@
import { useContext, useEffect, useRef, useState } from 'react';
import { socket } from '@/socket/socket.ts';
import { sendContact, setContactStatus } from '@/api/contactsApi.tsx';
import { sendContact } from '@/api/contactsApi.tsx';
import LoadingWheel from '../LoadingWheel.tsx';
import AnimatedMessage from '@/components/chat/chatArea/AnimatedMessage.tsx';
import { ChatMessagesProps } from '@/types/types.ts';
@@ -13,11 +13,11 @@ function MessagesArea() {
messages,
setMessages,
currentContact,
updateContactStatus,
messageHandler,
setContactsList,
errorMessage,
fetchPreviousMessages,
contactsList,
setCurrentContact,
} = useChat();
const containerRef = useRef<HTMLDivElement>(null);
@@ -98,9 +98,23 @@ function MessagesArea() {
if (isFromCurrentConversation) {
messageHandler(msg);
if (!isFocused) {
updateContactStatus(msg.conversation_id, false);
setCurrentContact({ ...currentContact, read: false });
setContactsList((prevContacts) => {
return prevContacts.map((contact) =>
contact.conversation_id === currentContact?.conversation_id
? {
...contact,
read: false,
}
: contact,
);
});
document.title = 'New message ❗';
} else {
socket?.emit('read message', {
conversation_id: msg.conversation_id,
message_id: msg.conversation_id,
});
}
}
@@ -108,8 +122,6 @@ function MessagesArea() {
setContactsList((prevContacts) => {
// If message is from another conversation
if (!isFromCurrentConversation && !isFromCurrentUser) {
setContactStatus(msg.conversation_id, false);
// Check if contact exists
const existingContact = prevContacts.find(
(c) => c.conversation_id === msg.conversation_id,
@@ -120,8 +132,8 @@ function MessagesArea() {
return [
...prevContacts,
{
username: msg.sender,
read: false,
username: msg.sender,
id: msg.message_id,
user_id: msg.sender_id,
conversation_id: msg.conversation_id,
@@ -189,23 +201,43 @@ function MessagesArea() {
socket.off('chat message');
socket.off('delete message');
};
}, [
currentContact,
user?.username,
setContactsList,
updateContactStatus,
isFocused,
]);
}, [currentContact, user?.username, setContactsList, isFocused]);
useEffect(() => {
if (isFocused && currentContact?.read == true) {
if (!currentContact) return;
const currentContactOnList = contactsList.find((contact) => {
return contact.conversation_id == currentContact?.conversation_id;
});
if (currentContactOnList && messages.length > 1) {
socket?.emit('message read', {
conversation_id: currentContact?.conversation_id,
message_id: messages[messages.length - 1].message_id,
});
setCurrentContact({ ...currentContact, read: currentContactOnList.read });
}
return () => {
if (!socket) return;
socket.off('message read');
};
}, [contactsList, currentContact?.conversation_id]);
useEffect(() => {
if (currentContact?.read == true) {
return;
}
document.title = previousTitle.current;
if (messages.length > 1) {
console.error(
'read !== true, emiting message read socket for: ',
currentContact?.username,
messages[messages.length - 1].message_id,
);
if (currentContact?.conversation_id && currentContact?.read !== true) {
updateContactStatus(currentContact.conversation_id, true);
setCurrentContact({ ...currentContact, read: true });
socket?.emit('message read', {
conversation_id: currentContact?.conversation_id,
message_id: messages[messages.length - 1].message_id,
});
}
const timeout = setTimeout(() => {
@@ -219,24 +251,11 @@ function MessagesArea() {
: contact,
);
});
}, 1000);
}, 800);
return () => clearTimeout(timeout);
}, [isFocused]);
useEffect(() => {
if (!socket) return;
if (currentContact?.read == true) return;
socket.emit('message read', {
conversation_id: currentContact?.conversation_id,
message_id: messages[messages?.length - 1].message_id,
});
console.info('MESSAGE READ: ', {
conversation_id: currentContact?.conversation_id,
message_id: messages[messages?.length - 1].message_id,
});
}, [isFocused]);
useEffect(() => {
const hasNewMessages = messages.length > previousMessagesLength.current;
previousMessagesLength.current = messages.length;
@@ -254,8 +273,6 @@ function MessagesArea() {
return (
<div ref={containerRef} className="flex flex-col h-full overflow-y-auto">
<p>READ STATUS: {currentContact?.read ? <p>true</p> : <p>false</p>}</p>
<p>ISFOCUSED: {isFocused ? <p>tak</p> : <p>nie</p>}</p>
<p className="text-center text-gray-400">{errorMessage}</p>
<ul className="flex-grow list-none">
{isLoading ? <LoadingWheel /> : null}
@@ -267,6 +284,8 @@ function MessagesArea() {
/>
))}
</ul>
<p>READ STATUS: {currentContact?.read ? 'tak' : 'nie'}</p>
<p>ISFOCUSED: {isFocused ? 'tak' : 'nie'}</p>
</div>
);
}

View File

@@ -24,7 +24,6 @@ function ContactsList() {
contactsList,
setContactsList,
setCurrentContact,
updateContactStatus,
setMessages,
setErrorMessage,
} = useChat();
@@ -48,28 +47,25 @@ function ContactsList() {
};
}, [socket]);
useEffect(() => {
contactsList.forEach((contact) => {
if (contact.last_message_id && contact.last_read_message_id) {
if (contact.last_message_id > contact.last_read_message_id) {
setContactsList((prevContacts) =>
prevContacts.map((prevContact) =>
prevContact.id === contact.id
? { ...prevContact, read: false }
: prevContact,
),
);
}
}
});
}, [contactsList]);
const fetchContacts = async () => {
console.log('Fetching contacts list');
const response = await axiosClient.get(`/api/chat/contacts`);
console.log('Get contacts list response: ', response.data);
const fetchedContacts: ContactsProps[] = response.data;
setContactsList(response.data);
const updatedContacts = fetchedContacts.map((contact) => {
if (
contact.last_message_id &&
contact.last_read_message_id &&
contact.last_message_id !== contact.last_read_message_id
) {
return { ...contact, read: false };
} else {
return { ...contact, read: true };
}
});
setContactsList(updatedContacts);
};
async function removeContact(contactId: number, conversation_id: string) {
@@ -100,13 +96,22 @@ function ContactsList() {
}
const sortedContacts = [...contactsList].sort((a, b) => {
// First, sort by read status
// First, sort by read status (unread first)
if (a.read !== b.read) {
return a.read ? 1 : -1;
}
if (a.read && b.read) {
return -a.last_active?.localeCompare(b.last_active);
// If both are read or both are unread, sort by last_message_time (if available)
if (a.last_message_time !== null && b.last_message_time !== null) {
return -a.last_message_time.localeCompare(b.last_message_time);
}
// If one or both last_message_time are null, sort by last_active_time
if (a.last_message_time === null || b.last_message_time === null) {
return -a.last_active.localeCompare(b.last_active);
}
// Default case (should not be reached)
return 0;
});
@@ -115,7 +120,6 @@ function ContactsList() {
className="m-1 flex p-2 hover:bg-zinc-900 cursor-pointer transition-colors rounded-lg justify-between items-start min-h-[40px]"
onClick={() => {
initializeContact(contact);
updateContactStatus(contact.conversation_id, true);
}}
>
<div className="flex flex-col w-full">

View File

@@ -30,7 +30,7 @@ const LastActiveTime = ({ contact }: LastActiveTimeProps) => {
const intervalId = setInterval(updateTime, 60000);
return () => clearInterval(intervalId);
}, [contact?.last_message]);
}, [contact?.last_message_time]);
return <span className="text-xs font-bold text-gray-500">{timeAgo}</span>;
};

View File

@@ -139,6 +139,7 @@ function ParticipantsBar() {
last_message: string | null;
last_message_time: string | null;
last_message_sender: string | null;
last_read_message_id: number | null;
}) => {
const { group_id } = msg;
if (
@@ -157,6 +158,7 @@ function ParticipantsBar() {
last_message_id: msg.last_message_id,
last_message_sender: msg.last_message_sender,
last_message_time: msg.last_message_time,
last_read_message_id: msg.last_read_message_id,
});
}

View File

@@ -2,7 +2,7 @@ import { ChatContext } from '@/context/chat/ChatContext.tsx';
import { ReactNode, useState } from 'react';
import { ChatMessagesProps, ContactsProps, MeProps } from '@/types/types.ts';
import { joinRoom } from '@/socket/socket.ts';
import { getMessages, setContactStatus } from '@/api/contactsApi.tsx';
import { getMessages } from '@/api/contactsApi.tsx';
import axios from 'axios';
export const ChatProvider = ({ children }: { children: ReactNode }) => {
@@ -23,7 +23,7 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
async function initializeContact(newContact: ContactsProps) {
setMessages([]); // Clear messages from previous contact
localStorage.setItem('contact', JSON.stringify(newContact)); // Set current contact in localstorage
setCurrentContact({ ...newContact, read: true });
setCurrentContact({ ...newContact });
console.log('Initialized contact: ', newContact);
try {
const joinResult = await joinRoom(newContact.conversation_id);
@@ -53,19 +53,19 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
const fetchMessages = async (conversation_id: string) => {
console.log('Fetching messages for: ', conversation_id);
try {
const messages = await getMessages(conversation_id);
if (messages.messages.length < 50) {
const fetchedMessages = await getMessages(conversation_id);
if (fetchedMessages.messages.length < 50) {
setHasMoreMessages(false);
setErrorMessage('No more messages found');
}
setCursor(() => {
return Math.min(
...messages.messages.map((message) => message.message_id),
...fetchedMessages.messages.map((message) => message.message_id),
);
});
messages.messages.forEach(messageHandler);
fetchedMessages.messages.forEach(messageHandler);
} catch (e) {
if (axios.isAxiosError(e)) {
setErrorMessage(e.message);
@@ -94,8 +94,6 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
setContactsList((prevContacts) =>
prevContacts.map((contact) => {
if (contact.conversation_id === conversation_id) {
setContactStatus(conversation_id, read);
return { ...contact, read: read };
} else {
return contact;

View File

@@ -113,7 +113,7 @@ export default function Login() {
<div className="text-red-400 text-sm">{message}</div>
</form>
<p className="text-gray-300 mt-10 text-center text-sm">
Don't have account?{' '}
or{' '}
<Link
to="/signup"
className="text-green-400 leading-6 hover:text-green-600"