added displaying last message under contact, UI improvement
This commit is contained in:
@@ -14,9 +14,11 @@ import ProtectedRoutes from './utils/ProtectedRoutes.tsx';
|
|||||||
import PublicRoute from '@/utils/PublicRoute.tsx';
|
import PublicRoute from '@/utils/PublicRoute.tsx';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
export const axiosClient = axios.create({
|
export const axiosClient = axios.create({
|
||||||
baseURL: '/',
|
baseURL: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -55,17 +57,15 @@ const router = createBrowserRouter([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
// Check for token immediately and set initial states accordingly
|
||||||
|
const hasToken = Boolean(Cookies.get('token'));
|
||||||
const [authorized, setAuthorized] = useState(false);
|
const [authorized, setAuthorized] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(hasToken); // Only start loading if there's a token
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function validateToken() {
|
async function validateToken() {
|
||||||
const token = Cookies.get('token');
|
// If there's no token, we're already in the correct state
|
||||||
if (!token) {
|
if (!hasToken) return;
|
||||||
setAuthorized(false);
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axiosClient.get('/api/auth/validate', {
|
await axiosClient.get('/api/auth/validate', {
|
||||||
@@ -81,7 +81,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validateToken();
|
validateToken();
|
||||||
}, []);
|
}, [hasToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ authorized, isLoading, setAuthorized }}>
|
<AuthContext.Provider value={{ authorized, isLoading, setAuthorized }}>
|
||||||
@@ -89,4 +89,5 @@ function App() {
|
|||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
|
|||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
className="m-2 p-1 rounded-xl hover:bg-gray-800 border"
|
className="p-2 hover:bg-zinc-800 rounded-lg"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
(
|
(
|
||||||
document.getElementById('addMemberModal') as HTMLDialogElement
|
document.getElementById('addMemberModal') as HTMLDialogElement
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { axiosClient } from '../../App.tsx';
|
|||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import LoadingWheel from './LoadingWheel.tsx';
|
import LoadingWheel from './LoadingWheel.tsx';
|
||||||
|
import { Search } from 'lucide-react';
|
||||||
|
|
||||||
type Input = {
|
type Input = {
|
||||||
contact: string;
|
contact: string;
|
||||||
@@ -117,18 +118,30 @@ function ContactForm({ InitializeContact, setContactsList }: ContactFormProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="text-center">
|
<div>
|
||||||
|
<h1 className="text-xl font-semibold text-gray-200 text-left p-4">
|
||||||
|
Chats
|
||||||
|
</h1>
|
||||||
|
<hr className="border-b border-zinc-800" />
|
||||||
<form onSubmit={handleSubmit(submitContact)}>
|
<form onSubmit={handleSubmit(submitContact)}>
|
||||||
<input
|
<div className="p-4">
|
||||||
className="text-white bg-gray-800 pl-2 shadow-lg rounded-xl h-8 mb-2 mt-2 placeholder:text-gray-400 outline-none border-gray-600 focus:outline-none focus:ring-0"
|
<div className="relative">
|
||||||
type="text"
|
<input
|
||||||
placeholder="Search for user"
|
type="text"
|
||||||
onKeyDown={handleKeyDown}
|
placeholder="Search chats..."
|
||||||
{...register('contact', {
|
className="w-full bg-zinc-900 rounded-lg py-2 pl-10 outline-0 focus:ring-emerald-800"
|
||||||
minLength: 4,
|
onKeyDown={handleKeyDown}
|
||||||
maxLength: 20,
|
{...register('contact', {
|
||||||
})}
|
minLength: 4,
|
||||||
/>
|
maxLength: 20,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Search
|
||||||
|
className="absolute left-3 top-2.5 text-gray-400"
|
||||||
|
size={18}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="m-1">
|
<div className="m-1">
|
||||||
{suggestions?.length > 0 ? (
|
{suggestions?.length > 0 ? (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type ContactProfileProps = {
|
|||||||
|
|
||||||
function ContactProfile({ contact }: ContactProfileProps) {
|
function ContactProfile({ contact }: ContactProfileProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center p-2">
|
||||||
<div className="flex items-center p-2">
|
<div className="flex items-center p-2">
|
||||||
{contact?.type === 'group' ? (
|
{contact?.type === 'group' ? (
|
||||||
<UsersRound className="w-5 mr-2" />
|
<UsersRound className="w-5 mr-2" />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { getContactsList } from '../../api/contactsApi.tsx';
|
|
||||||
import { ChatMessagesProps, ContactsProps } from '../../pages/Chat.tsx';
|
import { ChatMessagesProps, ContactsProps } from '../../pages/Chat.tsx';
|
||||||
import { socket } from '../../socket/socket.tsx';
|
import { socket } from '../../socket/socket.tsx';
|
||||||
import GroupIcon from '../../../assets/group.svg';
|
import GroupIcon from '../../../assets/group.svg';
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from '@/components/ui/alert-dialog';
|
} from '@/components/ui/alert-dialog';
|
||||||
|
import { axiosClient } from '@/App.tsx';
|
||||||
|
|
||||||
type ContactsListProps = {
|
type ContactsListProps = {
|
||||||
initializeContact: (contact: ContactsProps) => void;
|
initializeContact: (contact: ContactsProps) => void;
|
||||||
@@ -56,10 +56,11 @@ function ContactsList({
|
|||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
const fetchContacts = async () => {
|
const fetchContacts = async () => {
|
||||||
const contacts: ContactsProps[] = await getContactsList();
|
console.log('Fetching contacts list');
|
||||||
console.log('Fetching contacts list', contacts);
|
const response = await axiosClient.get(`/api/chat/contacts`);
|
||||||
|
console.log('Get contacts list response: ', response.data);
|
||||||
|
|
||||||
setContactsList(contacts);
|
setContactsList(response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function removeContact(contactId: number, conversation_id: string) {
|
async function removeContact(contactId: number, conversation_id: string) {
|
||||||
@@ -102,20 +103,30 @@ function ContactsList({
|
|||||||
|
|
||||||
const ContactItem = ({ contact }: { contact: ContactsProps }) => (
|
const ContactItem = ({ contact }: { contact: ContactsProps }) => (
|
||||||
<li
|
<li
|
||||||
className=" flex hover:bg-slate-700 p-2 rounded-2xl cursor-pointer justify-between items-center min-h-[40px]"
|
className="m-1 flex p-2 hover:bg-zinc-900 cursor-pointer transition-colors rounded-lg justify-between items-center min-h-[40px]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
initializeContact(contact);
|
initializeContact(contact);
|
||||||
updateContactStatus(contact, true);
|
updateContactStatus(contact, true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex-shrink-0">
|
<div className="flex flex-grow">
|
||||||
{/*usrId:{contact.user_id} cnvId:{contact.conversation_id}*/}
|
<div className=" flex flex-col">
|
||||||
{contact.username}
|
<div className="flex items-center">
|
||||||
</span>
|
<span className="text-lg">{contact.username}</span>
|
||||||
{contact.type === 'group' ? (
|
{contact.type === 'group' && (
|
||||||
<img src={GroupIcon} alt="Group icon" className="ml-2 invert w-5" />
|
<img
|
||||||
) : null}
|
src={GroupIcon}
|
||||||
<div className="w-4 h-4 mx-2 flex items-center justify-center mr-auto">
|
alt="Group icon"
|
||||||
|
className="ml-2 invert w-5"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-gray-500 text-left">
|
||||||
|
{contact.last_message}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-4 h-4 flex items-center justify-center ml-2">
|
||||||
<p className="text-center text-2xl text-red-200 leading-none">
|
<p className="text-center text-2xl text-red-200 leading-none">
|
||||||
{contact.read ? '' : '•'}
|
{contact.read ? '' : '•'}
|
||||||
</p>
|
</p>
|
||||||
@@ -131,7 +142,7 @@ function ContactsList({
|
|||||||
x
|
x
|
||||||
</button>
|
</button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent className="bg-[09090B]">
|
<AlertDialogContent className="bg-zinc-950">
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle className="text-white">
|
<AlertDialogTitle className="text-white">
|
||||||
Leave Group?
|
Leave Group?
|
||||||
@@ -169,8 +180,8 @@ function ContactsList({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-grow overflow-y-auto w-64">
|
<div className=" flex-grow overflow-y-auto w-full p-1">
|
||||||
<ul className="items-center text-center ml-1 mt-2 flex-grow-1">
|
<ul className="items-center text-center flex-grow-1">
|
||||||
{sortedContacts.map((contact: ContactsProps) => (
|
{sortedContacts.map((contact: ContactsProps) => (
|
||||||
<ContactItem key={contact.conversation_id} contact={contact} />
|
<ContactItem key={contact.conversation_id} contact={contact} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import LoadingWheel from './LoadingWheel.tsx';
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import { axiosClient } from '../../App.tsx';
|
import { axiosClient } from '../../App.tsx';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
groupName: string;
|
groupName: string;
|
||||||
@@ -40,14 +41,14 @@ function CreateGroupButton() {
|
|||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
className="m-2 border p-1 rounded-xl"
|
className="p-2 hover:bg-zinc-800 rounded-lg"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
(
|
(
|
||||||
document.getElementById('createGroupModal') as HTMLDialogElement
|
document.getElementById('createGroupModal') as HTMLDialogElement
|
||||||
).showModal()
|
).showModal()
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Create group
|
<Plus />
|
||||||
</button>
|
</button>
|
||||||
<dialog id="createGroupModal" className="modal" ref={modalRef}>
|
<dialog id="createGroupModal" className="modal" ref={modalRef}>
|
||||||
<div className="modal-box bg-gray-800 text-center relative p-1">
|
<div className="modal-box bg-gray-800 text-center relative p-1">
|
||||||
@@ -58,7 +59,7 @@ function CreateGroupButton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center justify-center pb-1 mt-6">
|
<div className="flex flex-col items-center justify-center pb-1 mt-6">
|
||||||
<h3 className="text-lg mb-4">Enter room name</h3>
|
<h3 className="text-lg mb-4">Enter group name</h3>
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
className="w-full max-w-xs relative"
|
className="w-full max-w-xs relative"
|
||||||
@@ -73,10 +74,10 @@ function CreateGroupButton() {
|
|||||||
aria-invalid={errors.groupName ? 'true' : 'false'}
|
aria-invalid={errors.groupName ? 'true' : 'false'}
|
||||||
/>
|
/>
|
||||||
{errors.groupName?.type === 'minLength' && (
|
{errors.groupName?.type === 'minLength' && (
|
||||||
<p className="text-gray-300">room name is too short</p>
|
<p className="text-gray-300">group name is too short</p>
|
||||||
)}
|
)}
|
||||||
{errors.groupName?.type === 'maxLength' && (
|
{errors.groupName?.type === 'maxLength' && (
|
||||||
<p className="text-gray-300">room name is too long</p>
|
<p className="text-gray-300">group name is too long</p>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center text-center p-2">
|
<div className="flex items-center justify-center text-center p-2">
|
||||||
<File className="mr-2 text-gray-300" />
|
<File className=" text-gray-300" />
|
||||||
<p className="text-sm">{file.file.name}</p>
|
<p className="text-sm">{file.file.name}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -259,7 +259,7 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
ref(e);
|
ref(e);
|
||||||
textareaRef.current = e;
|
textareaRef.current = e;
|
||||||
}}
|
}}
|
||||||
className={`ml-2 w-full overflow-y-hidden resize-none bg-gray-800 border-none text-white rounded-2xl p-2 min-h-[40px] max-h-96 placeholder:text-gray-400 focus:outline-none focus:ring-0
|
className={` ml-2 w-full overflow-y-hidden resize-none bg-zinc-900 rounded-lg text-white min-h-[40px] max-h-96 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-emerald-800
|
||||||
${isOverLimit ? 'border-2 border-red-500' : isNearLimit ? 'border-2 border-yellow-500' : ''} mx-auto`}
|
${isOverLimit ? 'border-2 border-red-500' : isNearLimit ? 'border-2 border-yellow-500' : ''} mx-auto`}
|
||||||
autoFocus={!!contact}
|
autoFocus={!!contact}
|
||||||
disabled={!contact}
|
disabled={!contact}
|
||||||
@@ -281,12 +281,12 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{errorMessage ? <p className="text-red-200">{errorMessage}</p> : null}
|
{errorMessage ? <p className="text-red-200">{errorMessage}</p> : null}
|
||||||
<div className="flex mr-2">
|
<div className="flex">
|
||||||
<label
|
<label
|
||||||
htmlFor="attachments"
|
htmlFor="attachments"
|
||||||
className="flex items-center justify-center hover:cursor-pointer p-1 rounded-full hover:bg-gray-800"
|
className="flex items-center justify-center hover:cursor-pointer p-2 rounded-full hover:bg-zinc-800"
|
||||||
>
|
>
|
||||||
<Paperclip />
|
<Paperclip className="text-gray-200" />
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="attachments"
|
name="attachments"
|
||||||
@@ -303,11 +303,15 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
<span className="text-gray-500 text-sm">Uploading...</span>
|
<span className="text-gray-500 text-sm">Uploading...</span>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="text-white hover:bg-gray-800 p-1 rounded-full flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed h-10 w-10"
|
className="text-white hover:bg-zinc-800 rounded-full flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed h-10 w-10"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isOverLimit || isSending || isUploading}
|
disabled={isOverLimit || isSending || isUploading}
|
||||||
>
|
>
|
||||||
{isSending ? <LoadingWheel /> : <Send />}
|
{isSending ? (
|
||||||
|
<LoadingWheel />
|
||||||
|
) : (
|
||||||
|
<Send className="text-gray-200" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ function MessagesArea({
|
|||||||
fetchPreviousMessages,
|
fetchPreviousMessages,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
setMessages,
|
setMessages,
|
||||||
contactsList,
|
|
||||||
me,
|
me,
|
||||||
}: MessagesAreaProps) {
|
}: MessagesAreaProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -108,7 +107,7 @@ function MessagesArea({
|
|||||||
msg.sender !== user.username
|
msg.sender !== user.username
|
||||||
) {
|
) {
|
||||||
setContactsList((prevContacts) => {
|
setContactsList((prevContacts) => {
|
||||||
// Find if contact already exists
|
// Find if contact already exists1
|
||||||
const existingContact = prevContacts.find(
|
const existingContact = prevContacts.find(
|
||||||
(c) => c.conversation_id === msg.conversation_id,
|
(c) => c.conversation_id === msg.conversation_id,
|
||||||
);
|
);
|
||||||
@@ -125,42 +124,32 @@ function MessagesArea({
|
|||||||
conversation_id: msg.conversation_id,
|
conversation_id: msg.conversation_id,
|
||||||
type: 'direct',
|
type: 'direct',
|
||||||
last_active: new Date().toString(),
|
last_active: new Date().toString(),
|
||||||
|
last_message: msg.message,
|
||||||
|
last_message_sender: msg.sender,
|
||||||
|
last_message_time: new Date().toString(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else if (existingContact) {
|
} else {
|
||||||
return prevContacts.map((contact) =>
|
return prevContacts.map((contact) =>
|
||||||
contact.conversation_id === msg.conversation_id
|
contact.conversation_id === msg.conversation_id
|
||||||
? { ...contact, last_active: new Date().toString() }
|
? {
|
||||||
|
...contact,
|
||||||
|
last_active: new Date().toString(),
|
||||||
|
last_message: msg.message,
|
||||||
|
last_message_sender: msg.sender,
|
||||||
|
last_message_time: new Date().toString(),
|
||||||
|
}
|
||||||
: contact,
|
: contact,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevContacts;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find the existing contact from contactsList
|
// Update contact status...
|
||||||
const existingContact = contactsList.find(
|
|
||||||
(c) => c.conversation_id === msg.conversation_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
updateContactStatus(
|
|
||||||
{
|
|
||||||
username: msg.sender,
|
|
||||||
read: false,
|
|
||||||
id: existingContact ? existingContact.id : msg.message_id, // Use existing contact's ID if found
|
|
||||||
user_id: msg.sender_id,
|
|
||||||
type: 'direct',
|
|
||||||
conversation_id: msg.conversation_id,
|
|
||||||
last_active: new Date().toString(),
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
if (msg.conversation_id == currentContact?.conversation_id) {
|
if (msg.conversation_id == currentContact?.conversation_id) {
|
||||||
messageHandler(msg);
|
messageHandler(msg);
|
||||||
|
|
||||||
setContactsList((prevContacts) => {
|
setContactsList((prevContacts) => {
|
||||||
// Find if contact already exists
|
|
||||||
const existingContact = prevContacts.find(
|
const existingContact = prevContacts.find(
|
||||||
(c) => c.conversation_id === msg.conversation_id,
|
(c) => c.conversation_id === msg.conversation_id,
|
||||||
);
|
);
|
||||||
@@ -177,16 +166,24 @@ function MessagesArea({
|
|||||||
conversation_id: msg.conversation_id,
|
conversation_id: msg.conversation_id,
|
||||||
type: 'direct',
|
type: 'direct',
|
||||||
last_active: new Date().toString(),
|
last_active: new Date().toString(),
|
||||||
|
last_message: msg.message,
|
||||||
|
last_message_sender: msg.sender,
|
||||||
|
last_message_time: new Date().toString(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else if (existingContact) {
|
} else {
|
||||||
return prevContacts.map((contact) =>
|
return prevContacts.map((contact) =>
|
||||||
contact.conversation_id === msg.conversation_id
|
contact.conversation_id === msg.conversation_id
|
||||||
? { ...contact, last_active: new Date().toString() }
|
? {
|
||||||
|
...contact,
|
||||||
|
last_active: new Date().toString(),
|
||||||
|
last_message: msg.message,
|
||||||
|
last_message_sender: msg.sender,
|
||||||
|
last_message_time: new Date().toString(),
|
||||||
|
}
|
||||||
: contact,
|
: contact,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return prevContacts;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,9 @@ function ParticipantsBar({
|
|||||||
type: 'group',
|
type: 'group',
|
||||||
conversation_id: msg.group_id,
|
conversation_id: msg.group_id,
|
||||||
last_active: new Date().toString(),
|
last_active: new Date().toString(),
|
||||||
|
last_message: '',
|
||||||
|
last_message_sender: '',
|
||||||
|
last_message_time: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +233,8 @@ function ParticipantsBar({
|
|||||||
socket.on('left group', handleLeftGroup);
|
socket.on('left group', handleLeftGroup);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket?.off('added to group', handleAddedToGroup);
|
socket?.off('added to group');
|
||||||
socket?.off('left group', handleLeftGroup);
|
socket?.off('left group');
|
||||||
};
|
};
|
||||||
}, [socket, currentContact, currentContact, user?.user_id]);
|
}, [socket, currentContact, currentContact, user?.user_id]);
|
||||||
|
|
||||||
@@ -239,7 +242,7 @@ function ParticipantsBar({
|
|||||||
(participant: ParticipantsProps) => (
|
(participant: ParticipantsProps) => (
|
||||||
<ContextMenu key={participant.user_id}>
|
<ContextMenu key={participant.user_id}>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<li className="p-2 hover:bg-slate-600 rounded-2xl flex items-center justify-between group bg-slate-700 m-2">
|
<li className="p-2 hover:bg-zinc-800 rounded-2xl flex items-center justify-between group m-2">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<div className="flex items-center justify-center w-8 h-8 overflow-hidden rounded-full bg-gray-100">
|
<div className="flex items-center justify-center w-8 h-8 overflow-hidden rounded-full bg-gray-100">
|
||||||
<img
|
<img
|
||||||
@@ -305,8 +308,12 @@ function ParticipantsBar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-zinc-900 h-full w-full flex flex-col ml-auto">
|
<div className="border-l border-zinc-800 bg-zinc-950 h-full w-full flex flex-col ml-auto">
|
||||||
<ul className="m-2 rounded">{ParticipantsList}</ul>
|
<h1 className="text-xl font-semibold text-gray-200 text-left p-4 border-b border-zinc-800">
|
||||||
|
Members
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<ul className=" rounded">{ParticipantsList}</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function UserProfile() {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="dropdown dropdown-top">
|
<div className="dropdown dropdown-top border-t border-zinc-800">
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="flex items-center cursor-pointer"
|
className="flex items-center cursor-pointer"
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export type ContactsProps = {
|
|||||||
type: 'direct' | 'group';
|
type: 'direct' | 'group';
|
||||||
conversation_id: string;
|
conversation_id: string;
|
||||||
last_active: string;
|
last_active: string;
|
||||||
|
last_message: string;
|
||||||
|
last_message_time: string;
|
||||||
|
last_message_sender: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Chat() {
|
function Chat() {
|
||||||
@@ -203,38 +206,35 @@ function Chat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="text-white flex h-screen">
|
||||||
<div className="text-white flex h-screen">
|
{/* Left Sidebar */}
|
||||||
{/* Sidebar */}
|
<div className="w-64 h-screen flex-shrink-0 flex flex-col bg-zinc-950 text-center border-r border-zinc-800">
|
||||||
<div className="h-screen bg-[#1E1E1E] flex flex-col">
|
<ContactForm
|
||||||
<ContactForm
|
setContactsList={setContactsList}
|
||||||
setContactsList={setContactsList}
|
InitializeContact={initializeContact}
|
||||||
InitializeContact={initializeContact}
|
/>
|
||||||
/>
|
<ContactsList
|
||||||
<ContactsList
|
initializeContact={initializeContact}
|
||||||
initializeContact={initializeContact}
|
contactsList={contactsList}
|
||||||
contactsList={contactsList}
|
setContactsList={setContactsList}
|
||||||
setContactsList={setContactsList}
|
setCurrentContact={setCurrentContact}
|
||||||
setCurrentContact={setCurrentContact}
|
updateContactStatus={updateContactStatus}
|
||||||
updateContactStatus={updateContactStatus}
|
setMessages={setMessages}
|
||||||
setMessages={setMessages}
|
currentContact={currentContact}
|
||||||
currentContact={currentContact}
|
setErrorMessage={setErrorMessage}
|
||||||
setErrorMessage={setErrorMessage}
|
/>
|
||||||
/>
|
<UserProfile />
|
||||||
<hr />
|
</div>
|
||||||
<UserProfile />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/*Chat area */}
|
{/*Chat area */}
|
||||||
<div className="text-white bg-[#121212] flex flex-col h-screen flex-grow">
|
<div className="flex-grow flex flex-col h-screen bg-[#0a0a0a]">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex flex-grow overflow-hidden">
|
||||||
<ContactProfile contact={currentContact} />
|
{/* Messages Container and Participants Container */}
|
||||||
</div>
|
<div className="flex-grow flex flex-col overflow-hidden">
|
||||||
<hr />
|
<div className="flex-shrink-0 border-b border-zinc-800 ">
|
||||||
{/* Messages and Participants Container */}
|
<ContactProfile contact={currentContact} />
|
||||||
<div className="flex flex-grow overflow-hidden">
|
</div>
|
||||||
{/* Messages Container */}
|
<div className="flex-grow overflow-y-auto">
|
||||||
<div className="flex-grow overflow-x-hidden overflow-y-auto">
|
|
||||||
<MessagesArea
|
<MessagesArea
|
||||||
messages={messages}
|
messages={messages}
|
||||||
setMessages={setMessages}
|
setMessages={setMessages}
|
||||||
@@ -248,28 +248,27 @@ function Chat() {
|
|||||||
me={me}
|
me={me}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-shrink-0 p-3 border-t border-zinc-800">
|
||||||
{/* Participants Bar */}
|
{currentContact && currentContact.username?.length >= 4 ? (
|
||||||
{currentContact?.type == 'group' ? (
|
<MessageForm contact={currentContact} messages={messages} />
|
||||||
<div className="w-80 bg-[#1E1E1E] flex-shrink-0">
|
) : null}
|
||||||
<ParticipantsBar
|
</div>
|
||||||
setMe={setMe}
|
|
||||||
me={me}
|
|
||||||
initializeContact={initializeContact}
|
|
||||||
currentContact={currentContact}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-shrink-0 mb-2 mt-0">
|
{/* Right Sidebar - Participants */}
|
||||||
{currentContact && currentContact.username?.length >= 4 ? (
|
{currentContact?.type === 'group' && (
|
||||||
<MessageForm contact={currentContact} messages={messages} />
|
<div className="w-64 flex-shrink-0 border-l border-zinc-800 bg-zinc-950">
|
||||||
) : null}
|
<ParticipantsBar
|
||||||
</div>
|
setMe={setMe}
|
||||||
|
me={me}
|
||||||
|
initializeContact={initializeContact}
|
||||||
|
currentContact={currentContact}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -586,6 +586,16 @@ async function insertContact(userUsername, receiverUsername, read) {
|
|||||||
|
|
||||||
async function getContacts(user_id) {
|
async function getContacts(user_id) {
|
||||||
const contactsQuery = `
|
const contactsQuery = `
|
||||||
|
WITH LastMessages AS (
|
||||||
|
SELECT DISTINCT ON (m.conversation_id)
|
||||||
|
m.conversation_id,
|
||||||
|
m.content as last_message,
|
||||||
|
m.sent_at as last_message_time,
|
||||||
|
a.username as last_message_sender
|
||||||
|
FROM Messages m
|
||||||
|
JOIN Accounts a ON m.user_id = a.user_id
|
||||||
|
ORDER BY m.conversation_id, m.sent_at DESC
|
||||||
|
)
|
||||||
SELECT DISTINCT ON (c.conversation_id)
|
SELECT DISTINCT ON (c.conversation_id)
|
||||||
c.contact_id AS id,
|
c.contact_id AS id,
|
||||||
CASE
|
CASE
|
||||||
@@ -599,11 +609,15 @@ async function getContacts(user_id) {
|
|||||||
conv.last_active,
|
conv.last_active,
|
||||||
c.conversation_id,
|
c.conversation_id,
|
||||||
conv.conversation_type AS type,
|
conv.conversation_type AS type,
|
||||||
c.read
|
c.read,
|
||||||
|
lm.last_message,
|
||||||
|
lm.last_message_time,
|
||||||
|
lm.last_message_sender
|
||||||
FROM Contacts c
|
FROM Contacts c
|
||||||
JOIN Conversations conv ON c.conversation_id = conv.conversation_id
|
JOIN Conversations conv ON c.conversation_id = conv.conversation_id
|
||||||
JOIN Memberships m ON m.conversation_id = c.conversation_id
|
JOIN Memberships m ON m.conversation_id = c.conversation_id
|
||||||
JOIN Accounts a2 ON a2.user_id = m.user_id
|
JOIN Accounts a2 ON a2.user_id = m.user_id
|
||||||
|
LEFT JOIN LastMessages lm ON c.conversation_id = lm.conversation_id
|
||||||
WHERE c.user_id = $1
|
WHERE c.user_id = $1
|
||||||
AND (
|
AND (
|
||||||
conv.conversation_type = 'direct'
|
conv.conversation_type = 'direct'
|
||||||
@@ -614,7 +628,10 @@ async function getContacts(user_id) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Execute the query with the user_id parameter
|
||||||
const contactsResult = await client.query(contactsQuery, [user_id]);
|
const contactsResult = await client.query(contactsQuery, [user_id]);
|
||||||
|
console.error(contactsResult.rows);
|
||||||
|
// Map the results to a more friendly format
|
||||||
const contacts = contactsResult.rows.map((row) => ({
|
const contacts = contactsResult.rows.map((row) => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
user_id: row.user_id,
|
user_id: row.user_id,
|
||||||
@@ -623,10 +640,14 @@ async function getContacts(user_id) {
|
|||||||
conversation_id: row.conversation_id,
|
conversation_id: row.conversation_id,
|
||||||
type: row.type,
|
type: row.type,
|
||||||
read: row.read,
|
read: row.read,
|
||||||
|
last_message: row.last_message,
|
||||||
|
last_message_time: row.last_message_time,
|
||||||
|
last_message_sender: row.last_message_sender,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return contacts;
|
return contacts;
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error("Failed to get contacts:", e);
|
console.error("Failed to get contacts:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -449,6 +449,12 @@ app.get(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
"/api/chat/messages/lastMessage",
|
||||||
|
authorizeUser,
|
||||||
|
async (req, res) => {},
|
||||||
|
);
|
||||||
|
|
||||||
initializeSocket(io);
|
initializeSocket(io);
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user