Files
relay/client/src/pages/Chat.tsx

253 lines
8.4 KiB
TypeScript

import MessageForm from '../components/chat/chatArea/MessageForm.tsx';
import ContactProfile from '../components/chat/chatHeader/ContactProfile.tsx';
import UserProfile from '../components/chat/leftSidebar/UserProfile.tsx';
import ContactForm from '../components/chat/leftSidebar/ContactForm.tsx';
import MessagesArea from '../components/chat/chatArea/MessagesArea.tsx';
import { createContext, useEffect, useState } from 'react';
import ContactsList from '../components/chat/leftSidebar/ContactsList.tsx';
import { initializeSocket, joinRoom } from '../socket/socket.tsx';
import Cookies from 'js-cookie';
import { getMessages, setContactStatus } from '../api/contactsApi.tsx';
import axios from 'axios';
import ParticipantsBar from '@/components/chat/rightSidebar/ParticipantsBar.tsx';
import { ChatMessagesProps, ContactsProps, MeProps } from '@/types/types.ts';
function Chat() {
const meDefaultValue = {
isGroupAdmin: false,
isGroupOwner: false,
};
const [contactsList, setContactsList] = useState<ContactsProps[]>([]);
const [currentContact, setCurrentContact] = useState<ContactsProps | null>(
null,
);
const [cursor, setCursor] = useState<number>(0);
const [messages, setMessages] = useState<ChatMessagesProps[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [hasMoreMessages, setHasMoreMessages] = useState<boolean>(true);
const [me, setMe] = useState<MeProps>(meDefaultValue);
const MeContext = createContext(meDefaultValue);
const [groupOwner, setGroupOwner] = useState<string | undefined>();
useEffect(() => {
const token = Cookies.get('token');
if (token) {
initializeSocket(token);
}
const storedContact = localStorage.getItem('contact');
if (storedContact) {
try {
const parsedContact = JSON.parse(storedContact);
if (parsedContact) {
initializeContact(parsedContact);
}
} catch (error) {
console.error('Failed to parse stored contact:', error);
localStorage.removeItem('contact');
}
}
}, []);
async function initializeContact(newContact: ContactsProps) {
setMessages([]); // Clear messages from previous contact
localStorage.setItem('contact', JSON.stringify(newContact)); // Set current contact in localstorage
setCurrentContact(newContact);
console.log('Initialized contact: ', newContact);
try {
const joinResult = await joinRoom(newContact.conversation_id);
if (!joinResult.success) {
setErrorMessage(joinResult.message);
return false;
}
try {
await fetchMessages(newContact.conversation_id);
} catch (e) {
console.error('Failed to fetch messages: ', e);
return false;
}
setContactsList((prevContacts) =>
prevContacts.map((c) =>
c.username === newContact.username ? { ...c, read: true } : c,
),
);
console.log('Current contact is now: ', newContact);
return true;
} catch (e) {
console.error('Failed to initialize contact:', e);
return false;
}
}
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) {
setHasMoreMessages(false);
setErrorMessage('No more messages found');
}
setCursor(() => {
return Math.min(
...messages.messages.map((message) => message.message_id),
);
});
messages.messages.forEach(messageHandler);
} catch (e) {
if (axios.isAxiosError(e)) {
setErrorMessage(e.message);
}
}
};
const fetchPreviousMessages = async (contact: string | null) => {
if (!hasMoreMessages) {
return;
}
console.log(
'Fetching messages for: ',
contact,
'with cursor: ',
cursor,
'hasmoremessages: ',
hasMoreMessages,
);
try {
const messages = await getMessages(contact, cursor, 50);
if (messages.messages.length < 50) {
setHasMoreMessages(false);
setErrorMessage('No more messages found');
}
messages.messages.forEach((msg) => {
setMessages((prevMessages) => {
if (!prevMessages.some((m) => m.message_id === msg.message_id)) {
const messageWithDate = {
...msg,
sent_at: new Date(msg.sent_at),
};
return [messageWithDate, ...prevMessages];
}
return prevMessages;
});
});
setCursor(() => {
return Math.min(
...messages.messages.map((message) => message.message_id),
);
});
} catch (e) {
if (axios.isAxiosError(e)) {
setErrorMessage(e.message);
}
}
};
function messageHandler(msg: ChatMessagesProps) {
setMessages((prevMessages) => {
// Check if the message already exists in the state
if (!prevMessages.some((m) => m.message_id === msg.message_id)) {
// Convert sent_at to Date object before adding to state
const messageWithDate = {
...msg,
sent_at: new Date(msg.sent_at),
};
return [...prevMessages, messageWithDate];
}
return prevMessages;
});
}
function updateContactStatus(contactObj: ContactsProps, read: boolean) {
console.log('Update contact status: ', contactObj);
setContactsList((prevContacts) =>
prevContacts.map((contact) => {
if (contact.conversation_id === contactObj.conversation_id) {
if (!contactObj.read) {
setContactStatus(contactObj.conversation_id, read);
}
return { ...contact, read: read };
} else {
return contact;
}
}),
);
}
return (
<MeContext.Provider value={me}>
<div className="text-white flex h-screen">
{/* Left Sidebar */}
<div className="w-64 h-screen flex-shrink-0 flex flex-col bg-zinc-950 text-center border-r border-zinc-800">
<ContactForm
setContactsList={setContactsList}
InitializeContact={initializeContact}
/>
<ContactsList
initializeContact={initializeContact}
contactsList={contactsList}
setContactsList={setContactsList}
setCurrentContact={setCurrentContact}
updateContactStatus={updateContactStatus}
setMessages={setMessages}
currentContact={currentContact}
setErrorMessage={setErrorMessage}
/>
<UserProfile />
</div>
{/*Chat area */}
<div className="flex-grow flex flex-col h-screen bg-[#0a0a0a]">
<div className="flex flex-grow overflow-hidden">
{/* Messages Container and Participants Container */}
<div className="flex-grow flex flex-col overflow-hidden">
<div className="flex-shrink-0 border-b border-zinc-800 ">
<ContactProfile contact={currentContact} />
</div>
<div className="flex-grow overflow-y-auto">
<MessagesArea
messages={messages}
setMessages={setMessages}
currentContact={currentContact}
updateContactStatus={updateContactStatus}
messageHandler={messageHandler}
setContactsList={setContactsList}
fetchPreviousMessages={fetchPreviousMessages}
errorMessage={errorMessage}
contactsList={contactsList}
/>
</div>
<div className="flex-shrink-0 p-3 border-t border-zinc-800">
{currentContact && currentContact.username?.length >= 4 ? (
<MessageForm contact={currentContact} messages={messages} />
) : null}
</div>
</div>
{/* Right Sidebar - Participants */}
{currentContact?.type === 'group' && (
<div className="w-64 flex-shrink-0 border-l border-zinc-800 bg-zinc-950">
<ParticipantsBar
setMe={setMe}
initializeContact={initializeContact}
currentContact={currentContact}
setGroupOwner={setGroupOwner}
groupOwner={groupOwner}
/>
</div>
)}
</div>
</div>
</div>
</MeContext.Provider>
);
}
export default Chat;