diff --git a/client/src/App.tsx b/client/src/App.tsx index e0f63f8..69f6be8 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -14,9 +14,11 @@ import ProtectedRoutes from './utils/ProtectedRoutes.tsx'; import PublicRoute from '@/utils/PublicRoute.tsx'; import axios from 'axios'; import Cookies from 'js-cookie'; + export const axiosClient = axios.create({ baseURL: '/', }); + const router = createBrowserRouter([ { path: '/', @@ -55,17 +57,15 @@ const router = createBrowserRouter([ ]); function App() { + // Check for token immediately and set initial states accordingly + const hasToken = Boolean(Cookies.get('token')); const [authorized, setAuthorized] = useState(false); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(hasToken); // Only start loading if there's a token useEffect(() => { async function validateToken() { - const token = Cookies.get('token'); - if (!token) { - setAuthorized(false); - setIsLoading(false); - return; - } + // If there's no token, we're already in the correct state + if (!hasToken) return; try { await axiosClient.get('/api/auth/validate', { @@ -81,7 +81,7 @@ function App() { } validateToken(); - }, []); + }, [hasToken]); return ( @@ -89,4 +89,5 @@ function App() { ); } + export default App; diff --git a/client/src/components/chat/AddGroupMember.tsx b/client/src/components/chat/AddGroupMember.tsx index 72fd994..6954a4a 100644 --- a/client/src/components/chat/AddGroupMember.tsx +++ b/client/src/components/chat/AddGroupMember.tsx @@ -120,7 +120,7 @@ function AddGroupMember({ contact }: AddGroupMemberProps) { <>
- + Leave Group? @@ -169,8 +180,8 @@ function ContactsList({ ); return ( -
-
    +
    +
      {sortedContacts.map((contact: ContactsProps) => ( ))} diff --git a/client/src/components/chat/CreateGroupButton.tsx b/client/src/components/chat/CreateGroupButton.tsx index e5ef5a1..a497aa9 100644 --- a/client/src/components/chat/CreateGroupButton.tsx +++ b/client/src/components/chat/CreateGroupButton.tsx @@ -2,6 +2,7 @@ import LoadingWheel from './LoadingWheel.tsx'; import { useRef, useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { axiosClient } from '../../App.tsx'; +import { Plus } from 'lucide-react'; type Inputs = { groupName: string; @@ -40,14 +41,14 @@ function CreateGroupButton() { <>
      @@ -58,7 +59,7 @@ function CreateGroupButton() {
      -

      Enter room name

      +

      Enter group name

      {errors.groupName?.type === 'minLength' && ( -

      room name is too short

      +

      group name is too short

      )} {errors.groupName?.type === 'maxLength' && ( -

      room name is too long

      +

      group name is too long

      )}
      diff --git a/client/src/components/chat/MessageForm.tsx b/client/src/components/chat/MessageForm.tsx index c0034b6..9f39c8d 100644 --- a/client/src/components/chat/MessageForm.tsx +++ b/client/src/components/chat/MessageForm.tsx @@ -228,7 +228,7 @@ const MessageForm = ({ contact }: MessageFormProps) => { /> ) : (
      - +

      {file.file.name}

      )} @@ -259,7 +259,7 @@ const MessageForm = ({ contact }: MessageFormProps) => { ref(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`} autoFocus={!!contact} disabled={!contact} @@ -281,12 +281,12 @@ const MessageForm = ({ contact }: MessageFormProps) => {
    {errorMessage ?

    {errorMessage}

    : null} -
    +
    { Uploading... )}
    diff --git a/client/src/components/chat/MessagesArea.tsx b/client/src/components/chat/MessagesArea.tsx index 37913dd..c0a5ea5 100644 --- a/client/src/components/chat/MessagesArea.tsx +++ b/client/src/components/chat/MessagesArea.tsx @@ -30,7 +30,6 @@ function MessagesArea({ fetchPreviousMessages, errorMessage, setMessages, - contactsList, me, }: MessagesAreaProps) { const containerRef = useRef(null); @@ -108,7 +107,7 @@ function MessagesArea({ msg.sender !== user.username ) { setContactsList((prevContacts) => { - // Find if contact already exists + // Find if contact already exists1 const existingContact = prevContacts.find( (c) => c.conversation_id === msg.conversation_id, ); @@ -125,42 +124,32 @@ function MessagesArea({ conversation_id: msg.conversation_id, type: 'direct', 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) => 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, ); } - - return prevContacts; }); - // Find the existing contact from contactsList - 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, - ); + // Update contact status... } else { if (msg.conversation_id == currentContact?.conversation_id) { messageHandler(msg); setContactsList((prevContacts) => { - // Find if contact already exists const existingContact = prevContacts.find( (c) => c.conversation_id === msg.conversation_id, ); @@ -177,16 +166,24 @@ function MessagesArea({ conversation_id: msg.conversation_id, type: 'direct', 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) => 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, ); } - return prevContacts; }); } } diff --git a/client/src/components/chat/ParticipantsBar.tsx b/client/src/components/chat/ParticipantsBar.tsx index b2b880d..4d2a9c7 100644 --- a/client/src/components/chat/ParticipantsBar.tsx +++ b/client/src/components/chat/ParticipantsBar.tsx @@ -164,6 +164,9 @@ function ParticipantsBar({ type: 'group', conversation_id: msg.group_id, last_active: new Date().toString(), + last_message: '', + last_message_sender: '', + last_message_time: '', }); } @@ -230,8 +233,8 @@ function ParticipantsBar({ socket.on('left group', handleLeftGroup); return () => { - socket?.off('added to group', handleAddedToGroup); - socket?.off('left group', handleLeftGroup); + socket?.off('added to group'); + socket?.off('left group'); }; }, [socket, currentContact, currentContact, user?.user_id]); @@ -239,7 +242,7 @@ function ParticipantsBar({ (participant: ParticipantsProps) => ( -
  • +
  • -
      {ParticipantsList}
    +
    +

    + Members +

    + +
      {ParticipantsList}
    ); } diff --git a/client/src/components/chat/UserProfile.tsx b/client/src/components/chat/UserProfile.tsx index 9f77e31..eb14b32 100644 --- a/client/src/components/chat/UserProfile.tsx +++ b/client/src/components/chat/UserProfile.tsx @@ -13,7 +13,7 @@ function UserProfile() { window.location.reload(); } return ( -
    +
    -
    - {/* Sidebar */} -
    - - -
    - -
    +
    + {/* Left Sidebar */} +
    + + + +
    - {/*Chat area */} -
    -
    - -
    -
    - {/* Messages and Participants Container */} -
    - {/* Messages Container */} -
    + {/*Chat area */} +
    +
    + {/* Messages Container and Participants Container */} +
    +
    + +
    +
    - - {/* Participants Bar */} - {currentContact?.type == 'group' ? ( -
    - -
    - ) : null} +
    + {currentContact && currentContact.username?.length >= 4 ? ( + + ) : null} +
    -
    - {currentContact && currentContact.username?.length >= 4 ? ( - - ) : null} -
    + {/* Right Sidebar - Participants */} + {currentContact?.type === 'group' && ( +
    + +
    + )}
    - +
    ); } diff --git a/server/db/db.js b/server/db/db.js index 4757060..d97821a 100644 --- a/server/db/db.js +++ b/server/db/db.js @@ -586,6 +586,16 @@ async function insertContact(userUsername, receiverUsername, read) { async function getContacts(user_id) { 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) c.contact_id AS id, CASE @@ -599,11 +609,15 @@ async function getContacts(user_id) { conv.last_active, c.conversation_id, conv.conversation_type AS type, - c.read + c.read, + lm.last_message, + lm.last_message_time, + lm.last_message_sender FROM Contacts c JOIN Conversations conv ON c.conversation_id = conv.conversation_id JOIN Memberships m ON m.conversation_id = c.conversation_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 AND ( conv.conversation_type = 'direct' @@ -614,7 +628,10 @@ async function getContacts(user_id) { `; try { + // Execute the query with the user_id parameter 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) => ({ id: row.id, user_id: row.user_id, @@ -623,10 +640,14 @@ async function getContacts(user_id) { conversation_id: row.conversation_id, type: row.type, read: row.read, + last_message: row.last_message, + last_message_time: row.last_message_time, + last_message_sender: row.last_message_sender, })); + return contacts; - } catch (e) { - console.error("Failed to get contacts:", e); + } catch (error) { + console.error("Failed to get contacts:", error); return []; } } diff --git a/server/server.js b/server/server.js index d627f4d..f25ba24 100644 --- a/server/server.js +++ b/server/server.js @@ -449,6 +449,12 @@ app.get( }, ); +app.get( + "/api/chat/messages/lastMessage", + authorizeUser, + async (req, res) => {}, +); + initializeSocket(io); server.listen(PORT, () => {