code refactor, added time indicator from last message
This commit is contained in:
11
client/package-lock.json
generated
11
client/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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();
|
||||||
@@ -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}
|
||||||
@@ -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[];
|
||||||
@@ -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>
|
||||||
@@ -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 = {
|
||||||
@@ -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 = {
|
||||||
@@ -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,
|
||||||
@@ -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">
|
||||||
34
client/src/components/chat/leftSidebar/LastActiveTime.tsx
Normal file
34
client/src/components/chat/leftSidebar/LastActiveTime.tsx
Normal 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;
|
||||||
@@ -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();
|
||||||
@@ -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]);
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user