code refactor, added time indicator from last message

This commit is contained in:
slawk0
2025-01-02 00:25:31 +01:00
parent 867e465a23
commit 81aeb5a926
17 changed files with 207 additions and 142 deletions

View File

@@ -20,6 +20,7 @@
"axios": "^1.7.7", "axios": "^1.7.7",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"nanoid": "^5.0.8", "nanoid": "^5.0.8",
@@ -3281,6 +3282,16 @@
"url": "https://opencollective.com/daisyui" "url": "https://opencollective.com/daisyui"
} }
}, },
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",

View File

@@ -22,6 +22,7 @@
"axios": "^1.7.7", "axios": "^1.7.7",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"nanoid": "^5.0.8", "nanoid": "^5.0.8",

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Trash2 } from 'lucide-react'; import { Trash2 } from 'lucide-react';
import AttachmentPreview from './AttachmentPreview'; import AttachmentPreview from './AttachmentPreview.tsx';
import { ChatMessagesProps } from '@/pages/Chat.tsx'; import { ChatMessagesProps } from '@/pages/Chat.tsx';
type AnimatedMessageProps = { type AnimatedMessageProps = {
@@ -27,9 +27,8 @@ const AnimatedMessage = ({
return ( return (
<li <li
className={`whitespace-pre-wrap ml-2 rounded p-1 group transition-all duration-300 ${ className={` whitespace-pre-wrap ml-2 rounded-lg p-1 group hover:bg-zinc-900
message.pending ? 'text-gray-400' : 'hover:bg-gray-900' ${isRemoving ? 'transition-all duration-300 opacity-0 -translate-x-full' : 'opacity-100'}`}
} ${isRemoving ? 'opacity-0 -translate-x-full' : 'opacity-100'}`}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import LoadingWheel from './LoadingWheel'; import LoadingWheel from '../LoadingWheel.tsx';
import FileBox from './FileBox'; import FileBox from './FileBox.tsx';
// Cache to keep track of loaded media // Cache to keep track of loaded media
const loadedMedia = new Set(); const loadedMedia = new Set();

View File

@@ -1,9 +1,9 @@
import { useRef, useCallback, useEffect, useState } from 'react'; import { useRef, useCallback, useEffect, useState } from 'react';
import { useForm, SubmitHandler } from 'react-hook-form'; import { useForm, SubmitHandler } from 'react-hook-form';
import type { KeyboardEventHandler } from 'react'; import type { KeyboardEventHandler } from 'react';
import { socket } from '../../socket/socket.tsx'; import { socket } from '../../../socket/socket.tsx';
import { ChatMessagesProps, ContactsProps } from '../../pages/Chat.tsx'; import { ChatMessagesProps, ContactsProps } from '../../../pages/Chat.tsx';
import { axiosClient } from '../../App.tsx'; import { axiosClient } from '../../../App.tsx';
import { File, Paperclip, Send, X } from 'lucide-react'; import { File, Paperclip, Send, X } from 'lucide-react';
import LoadingWheel from '@/components/chat/LoadingWheel.tsx'; import LoadingWheel from '@/components/chat/LoadingWheel.tsx';
type Input = { type Input = {
@@ -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-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 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:border-1 focus:ring-0 focus:border-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}

View File

@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { socket } from '../../socket/socket.tsx'; import { socket } from '@/socket/socket.tsx';
import { useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import { sendContact } from '../../api/contactsApi.tsx'; import { sendContact } from '@/api/contactsApi.tsx';
import LoadingWheel from './LoadingWheel.tsx'; import LoadingWheel from '../LoadingWheel.tsx';
import { ChatMessagesProps, MeProps } from '../../pages/Chat.tsx'; import { ChatMessagesProps, MeProps } from '@/pages/Chat.tsx';
import { ContactsProps } from '../../pages/Chat.tsx'; import { ContactsProps } from '@/pages/Chat.tsx';
import { UsernameType } from '@/utils/ProtectedRoutes.tsx'; import { UsernameType } from '@/utils/ProtectedRoutes.tsx';
import AnimatedMessage from '@/components/chat/AnimatedMessage.tsx'; import AnimatedMessage from '@/components/chat/chatArea/AnimatedMessage.tsx';
type MessagesAreaProps = { type MessagesAreaProps = {
messages: ChatMessagesProps[]; messages: ChatMessagesProps[];

View File

@@ -1,10 +1,11 @@
import LoadingWheel from './LoadingWheel.tsx'; import LoadingWheel from '../LoadingWheel.tsx';
import { useEffect, useRef, useState } from 'react'; import { useEffect, 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 { ContactsProps } from '../../pages/Chat.tsx'; import { ContactsProps } from '../../../pages/Chat.tsx';
import { socket } from '../../socket/socket.tsx'; import { socket } from '../../../socket/socket.tsx';
import { UserRoundPlus } from 'lucide-react'; import { UserRoundPlus } from 'lucide-react';
import { Button } from '@/components/ui/button.tsx';
type Inputs = { type Inputs = {
username: string; username: string;
@@ -130,10 +131,10 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
<UserRoundPlus /> <UserRoundPlus />
</button> </button>
<dialog id="addMemberModal" className="modal" ref={modalRef}> <dialog id="addMemberModal" className="modal" ref={modalRef}>
<div className="modal-box bg-gray-800 text-center relative p-1"> <div className="modal-box bg-zinc-950 text-center relative p-1 border">
<div className="absolute right-2 top-2"> <div className="absolute right-2 top-2">
<form method="dialog"> <form method="dialog">
<button className="btn btn-sm btn-circle btn-ghost"></button> <button className=" btn btn-sm btn-circle btn-ghost"></button>
</form> </form>
</div> </div>
@@ -191,13 +192,13 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
</div> </div>
<div className="mt-4 flex justify-center"> <div className="mt-4 flex justify-center">
<button <Button
type="submit" type="submit"
onClick={handleSubmit(onSubmit)} onClick={handleSubmit(onSubmit)}
className="btn btn-sm bg-green-500 text-black hover:bg-green-600" className="w-24 btn btn-sm bg-emerald-600 text-white hover:bg-green-500 border-black"
> >
{isLoading ? <LoadingWheel /> : 'Add'} {isLoading ? <LoadingWheel /> : 'Add'}
</button> </Button>
</div> </div>
</div> </div>
</dialog> </dialog>

View File

@@ -1,7 +1,7 @@
import profile from '../../../assets/profile.svg'; import profile from '../../../../assets/profile.svg';
import CreateGroupButton from './CreateGroupButton.tsx'; import CreateGroupButton from './CreateGroupButton.tsx';
import AddGroupMember from './AddGroupMember.tsx'; import AddGroupMember from './AddGroupMember.tsx';
import { ContactsProps } from '../../pages/Chat.tsx'; import { ContactsProps } from '@/pages/Chat.tsx';
import { UsersRound } from 'lucide-react'; import { UsersRound } from 'lucide-react';
type ContactProfileProps = { type ContactProfileProps = {

View File

@@ -1,7 +1,7 @@
import LoadingWheel from './LoadingWheel.tsx'; 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'; import { Plus } from 'lucide-react';
type Inputs = { type Inputs = {

View File

@@ -1,9 +1,9 @@
import { useForm, SubmitHandler } from 'react-hook-form'; import { useForm, SubmitHandler } from 'react-hook-form';
import { ContactsProps } from '../../pages/Chat.tsx'; import { ContactsProps } from '../../../pages/Chat.tsx';
import { axiosClient } from '../../App.tsx'; 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'; import { Search } from 'lucide-react';
type Input = { type Input = {
@@ -93,26 +93,28 @@ function ContactForm({ InitializeContact, setContactsList }: ContactFormProps) {
}; };
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (suggestions.length > 0) { switch (e.key) {
switch (e.key) { case 'ArrowDown':
case 'ArrowDown': e.preventDefault();
setSelectedIndex((prev) => (prev + 1) % suggestions.length);
break;
case 'ArrowUp':
e.preventDefault();
setSelectedIndex(
(prev) => (prev - 1 + suggestions.length) % suggestions.length,
);
break;
case 'Enter':
if (suggestions.length > 0) {
e.preventDefault(); e.preventDefault();
setSelectedIndex((prev) => (prev + 1) % suggestions.length); reset({ contact: suggestions[selectedIndex] });
break; handleSubmit(submitContact)();
case 'ArrowUp': }
e.preventDefault(); break;
setSelectedIndex( case 'Escape':
(prev) => (prev - 1 + suggestions.length) % suggestions.length, e.preventDefault();
); reset({ contact: '' });
break; break;
case 'Enter':
if (suggestions.length > 0) {
e.preventDefault();
reset({ contact: suggestions[selectedIndex] });
handleSubmit(submitContact)();
}
break;
}
} }
}; };
@@ -128,8 +130,8 @@ function ContactForm({ InitializeContact, setContactsList }: ContactFormProps) {
<div className="relative"> <div className="relative">
<input <input
type="text" type="text"
placeholder="Search chats..." placeholder="Search for user"
className="w-full bg-zinc-900 rounded-lg py-2 pl-10 outline-0 focus:ring-emerald-800" className="w-full bg-zinc-900 rounded-lg py-2 pl-10 focus:border-1 focus:ring-0 focus:border-emerald-800"
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
{...register('contact', { {...register('contact', {
minLength: 4, minLength: 4,

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
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';
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@@ -12,8 +12,10 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from '@/components/ui/alert-dialog'; } from '@/components/ui/alert-dialog.tsx';
import { axiosClient } from '@/App.tsx'; import { axiosClient } from '@/App.tsx';
import { Dot } from 'lucide-react';
import LastActiveTime from '@/components/chat/leftSidebar/LastActiveTime.tsx';
type ContactsListProps = { type ContactsListProps = {
initializeContact: (contact: ContactsProps) => void; initializeContact: (contact: ContactsProps) => void;
@@ -109,7 +111,7 @@ function ContactsList({
updateContactStatus(contact, true); updateContactStatus(contact, true);
}} }}
> >
<div className="flex flex-grow"> <div className="flex">
<div className=" flex flex-col"> <div className=" flex flex-col">
<div className="flex items-center"> <div className="flex items-center">
<span className="text-lg">{contact.username}</span> <span className="text-lg">{contact.username}</span>
@@ -121,9 +123,13 @@ function ContactsList({
/> />
)} )}
</div> </div>
<span className="text-sm text-gray-500 text-left"> <div className="flex">
{contact.last_message} <span className="text-sm text-gray-500 text-left">
</span> {contact.last_message}
</span>
<Dot className="text-gray-200" />
<LastActiveTime contact={contact} />
</div>
</div> </div>
</div> </div>
<div className="w-4 h-4 flex items-center justify-center ml-2"> <div className="w-4 h-4 flex items-center justify-center ml-2">

View File

@@ -0,0 +1,34 @@
import { useState, useEffect } from 'react';
import { formatDistanceToNow, differenceInSeconds } from 'date-fns';
import { ContactsProps } from '@/pages/Chat.tsx';
type LastActiveTimeProps = {
contact: ContactsProps;
};
const LastActiveTime = ({ contact }: LastActiveTimeProps) => {
const [timeAgo, setTimeAgo] = useState('');
useEffect(() => {
const updateTime = () => {
const lastActiveDate = new Date(contact.last_message_time);
const secondsDiff = differenceInSeconds(new Date(), lastActiveDate);
if (secondsDiff < 60) {
setTimeAgo('now');
return;
}
setTimeAgo(formatDistanceToNow(lastActiveDate));
};
updateTime();
const intervalId = setInterval(updateTime, 60000);
return () => clearInterval(intervalId);
}, [contact?.last_message]);
return <span className="text-sm text-gray-500">{timeAgo}</span>;
};
export default LastActiveTime;

View File

@@ -1,8 +1,8 @@
import zdjecie from '../../../assets/turtleProfileImg3.webp'; import zdjecie from '../../../../assets/turtleProfileImg3.webp';
import logoutIcon from '../../../assets/logout.svg'; import logoutIcon from '../../../../assets/logout.svg';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import { UsernameType } from '../../utils/ProtectedRoutes.tsx'; import { UsernameType } from '../../../utils/ProtectedRoutes.tsx';
function UserProfile() { function UserProfile() {
const user: UsernameType = useOutletContext(); const user: UsernameType = useOutletContext();

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'; import { useContext, useEffect, useMemo, useState } from 'react';
import { axiosClient } from '@/App.tsx'; import { axiosClient } from '@/App.tsx';
import { ContactsProps, MeProps } from '@/pages/Chat.tsx'; import { ContactsProps, MeProps } from '@/pages/Chat.tsx';
import { socket } from '@/socket/socket.tsx'; import { socket } from '@/socket/socket.tsx';
@@ -8,10 +8,10 @@ import {
ContextMenuContent, ContextMenuContent,
ContextMenuItem, ContextMenuItem,
ContextMenuTrigger, ContextMenuTrigger,
} from '@/components/ui/context-menu'; } from '@/components/ui/context-menu.tsx';
import { UsernameType } from '@/utils/ProtectedRoutes.tsx'; import { UsernameType } from '@/utils/ProtectedRoutes.tsx';
import { useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import zdjecie from '../../../assets/turtleProfileImg3.webp'; import zdjecie from '../../../../assets/turtleProfileImg3.webp';
type ParticipantsProps = { type ParticipantsProps = {
user_id: string; user_id: string;
username: string; username: string;
@@ -23,18 +23,20 @@ type ParticipantsBarProps = {
initializeContact: (contact: ContactsProps) => void; initializeContact: (contact: ContactsProps) => void;
currentContact: ContactsProps | null; currentContact: ContactsProps | null;
setMe: React.Dispatch<React.SetStateAction<MeProps>>; setMe: React.Dispatch<React.SetStateAction<MeProps>>;
me: MeProps; setGroupOwner: React.Dispatch<React.SetStateAction<string | undefined>>;
groupOwner: string | undefined;
}; };
function ParticipantsBar({ function ParticipantsBar({
initializeContact, initializeContact,
currentContact, currentContact,
setMe, setMe,
me, setGroupOwner,
groupOwner,
}: ParticipantsBarProps) { }: ParticipantsBarProps) {
const [participants, setParticipants] = useState<ParticipantsProps[]>([]); const [participants, setParticipants] = useState<ParticipantsProps[]>([]);
const user: UsernameType = useOutletContext(); const user: UsernameType = useOutletContext();
const me = useContext(MeContext);
const getParticipants = async () => { const getParticipants = async () => {
try { try {
const response = await axiosClient.get( const response = await axiosClient.get(
@@ -116,7 +118,10 @@ function ParticipantsBar({
(participant) => (participant) =>
participant.user_id === user.user_id && participant.isowner, participant.user_id === user.user_id && participant.isowner,
); );
const whoIsOwner = participants.find(
(participant) => participant.isowner,
);
setGroupOwner(whoIsOwner?.user_id);
setMe({ isGroupAdmin: userIsAdmin, isGroupOwner: userIsOwner }); setMe({ isGroupAdmin: userIsAdmin, isGroupOwner: userIsOwner });
} }
}, [participants, user?.user_id]); }, [participants, user?.user_id]);

View File

@@ -1,15 +1,15 @@
import MessageForm from '../components/chat/MessageForm.tsx'; import MessageForm from '../components/chat/chatArea/MessageForm.tsx';
import ContactProfile from '../components/chat/ContactProfile.tsx'; import ContactProfile from '../components/chat/chatHeader/ContactProfile.tsx';
import UserProfile from '../components/chat/UserProfile.tsx'; import UserProfile from '../components/chat/leftSidebar/UserProfile.tsx';
import ContactForm from '../components/chat/ContactForm.tsx'; import ContactForm from '../components/chat/leftSidebar/ContactForm.tsx';
import MessagesArea from '../components/chat/MessagesArea.tsx'; import MessagesArea from '../components/chat/chatArea/MessagesArea.tsx';
import { useEffect, useState } from 'react'; import { createContext, useEffect, useState } from 'react';
import ContactsList from '../components/chat/ContactsList.tsx'; import ContactsList from '../components/chat/leftSidebar/ContactsList.tsx';
import { initializeSocket, joinRoom } from '../socket/socket.tsx'; import { initializeSocket, joinRoom } from '../socket/socket.tsx';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { getMessages, setContactStatus } from '../api/contactsApi.tsx'; import { getMessages, setContactStatus } from '../api/contactsApi.tsx';
import axios from 'axios'; import axios from 'axios';
import ParticipantsBar from '@/components/chat/ParticipantsBar.tsx'; import ParticipantsBar from '@/components/chat/rightSidebar/ParticipantsBar.tsx';
export type MeProps = { export type MeProps = {
isGroupAdmin: boolean; isGroupAdmin: boolean;
@@ -21,7 +21,6 @@ export type ChatMessagesProps = {
message: string; message: string;
recipient: string; // conversation_id recipient: string; // conversation_id
message_id: number; message_id: number;
pending: boolean;
attachment_urls: string[] | null; attachment_urls: string[] | null;
sender_id: string; sender_id: string;
conversation_id: string; conversation_id: string;
@@ -42,6 +41,10 @@ export type ContactsProps = {
}; };
function Chat() { function Chat() {
const meDefaultValue = {
isGroupAdmin: false,
isGroupOwner: false,
};
const [contactsList, setContactsList] = useState<ContactsProps[]>([]); const [contactsList, setContactsList] = useState<ContactsProps[]>([]);
const [currentContact, setCurrentContact] = useState<ContactsProps | null>( const [currentContact, setCurrentContact] = useState<ContactsProps | null>(
null, null,
@@ -50,10 +53,10 @@ function Chat() {
const [messages, setMessages] = useState<ChatMessagesProps[]>([]); const [messages, setMessages] = useState<ChatMessagesProps[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null); const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [hasMoreMessages, setHasMoreMessages] = useState<boolean>(true); const [hasMoreMessages, setHasMoreMessages] = useState<boolean>(true);
const [me, setMe] = useState<MeProps>({ const [me, setMe] = useState<MeProps>(meDefaultValue);
isGroupAdmin: false, const MeContext = createContext(meDefaultValue);
isGroupOwner: false,
}); const [groupOwner, setGroupOwner] = useState<string | undefined>();
useEffect(() => { useEffect(() => {
const token = Cookies.get('token'); const token = Cookies.get('token');
@@ -206,69 +209,71 @@ function Chat() {
} }
return ( return (
<div className="text-white flex h-screen"> <MeContext.Provider value={me}>
{/* Left Sidebar */} <div className="text-white flex h-screen">
<div className="w-64 h-screen flex-shrink-0 flex flex-col bg-zinc-950 text-center border-r border-zinc-800"> {/* Left Sidebar */}
<ContactForm <div className="w-64 h-screen flex-shrink-0 flex flex-col bg-zinc-950 text-center border-r border-zinc-800">
setContactsList={setContactsList} <ContactForm
InitializeContact={initializeContact} setContactsList={setContactsList}
/> InitializeContact={initializeContact}
<ContactsList />
initializeContact={initializeContact} <ContactsList
contactsList={contactsList} initializeContact={initializeContact}
setContactsList={setContactsList} contactsList={contactsList}
setCurrentContact={setCurrentContact} setContactsList={setContactsList}
updateContactStatus={updateContactStatus} setCurrentContact={setCurrentContact}
setMessages={setMessages} updateContactStatus={updateContactStatus}
currentContact={currentContact} setMessages={setMessages}
setErrorMessage={setErrorMessage} currentContact={currentContact}
/> setErrorMessage={setErrorMessage}
<UserProfile /> />
</div> <UserProfile />
</div>
{/*Chat area */} {/*Chat area */}
<div className="flex-grow flex flex-col h-screen bg-[#0a0a0a]"> <div className="flex-grow flex flex-col h-screen bg-[#0a0a0a]">
<div className="flex flex-grow overflow-hidden"> <div className="flex flex-grow overflow-hidden">
{/* Messages Container and Participants Container */} {/* Messages Container and Participants Container */}
<div className="flex-grow flex flex-col overflow-hidden"> <div className="flex-grow flex flex-col overflow-hidden">
<div className="flex-shrink-0 border-b border-zinc-800 "> <div className="flex-shrink-0 border-b border-zinc-800 ">
<ContactProfile contact={currentContact} /> <ContactProfile contact={currentContact} />
</div> </div>
<div className="flex-grow overflow-y-auto"> <div className="flex-grow overflow-y-auto">
<MessagesArea <MessagesArea
messages={messages} messages={messages}
setMessages={setMessages} setMessages={setMessages}
currentContact={currentContact} currentContact={currentContact}
updateContactStatus={updateContactStatus} updateContactStatus={updateContactStatus}
messageHandler={messageHandler} messageHandler={messageHandler}
setContactsList={setContactsList} setContactsList={setContactsList}
fetchPreviousMessages={fetchPreviousMessages} fetchPreviousMessages={fetchPreviousMessages}
errorMessage={errorMessage} errorMessage={errorMessage}
contactsList={contactsList} contactsList={contactsList}
me={me} />
/> </div>
</div> <div className="flex-shrink-0 p-3 border-t border-zinc-800">
<div className="flex-shrink-0 p-3 border-t border-zinc-800"> {currentContact && currentContact.username?.length >= 4 ? (
{currentContact && currentContact.username?.length >= 4 ? ( <MessageForm contact={currentContact} messages={messages} />
<MessageForm contact={currentContact} messages={messages} /> ) : null}
) : null} </div>
</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>
{/* Right Sidebar - Participants */}
{currentContact?.type === 'group' && (
<div className="w-64 flex-shrink-0 border-l border-zinc-800 bg-zinc-950">
<ParticipantsBar
setMe={setMe}
me={me}
initializeContact={initializeContact}
currentContact={currentContact}
/>
</div>
)}
</div> </div>
</div> </div>
</div> </MeContext.Provider>
); );
} }

View File

@@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
important: true,
darkMode: ['class'], darkMode: ['class'],
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: { theme: {