code refactor, created ChatProvider.tsx, use context instead of tons of props
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
RouterProvider,
|
|
||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
Navigate,
|
Navigate,
|
||||||
|
RouterProvider,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import Chat from './pages/Chat.tsx';
|
import Chat from './pages/Chat.tsx';
|
||||||
@@ -12,12 +12,9 @@ import Lost from './pages/404.tsx';
|
|||||||
import { AuthContext } from './utils/AuthProvider.tsx';
|
import { AuthContext } from './utils/AuthProvider.tsx';
|
||||||
import ProtectedRoutes from './utils/ProtectedRoutes.tsx';
|
import ProtectedRoutes from './utils/ProtectedRoutes.tsx';
|
||||||
import PublicRoute from '@/utils/PublicRoute.tsx';
|
import PublicRoute from '@/utils/PublicRoute.tsx';
|
||||||
import axios from 'axios';
|
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import { ChatProvider } from '@/context/chat/ChatProvider.tsx';
|
||||||
export const axiosClient = axios.create({
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
baseURL: '/',
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -29,7 +26,11 @@ const router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/chat',
|
path: '/chat',
|
||||||
element: <Chat />,
|
element: (
|
||||||
|
<ChatProvider>
|
||||||
|
<Chat />
|
||||||
|
</ChatProvider>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
@@ -57,14 +58,12 @@ const router = createBrowserRouter([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// Check for token immediately and set initial states accordingly
|
|
||||||
const hasToken = Boolean(Cookies.get('token'));
|
const hasToken = Boolean(Cookies.get('token'));
|
||||||
const [authorized, setAuthorized] = useState(false);
|
const [authorized, setAuthorized] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(hasToken); // Only start loading if there's a token
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function validateToken() {
|
async function validateToken() {
|
||||||
// If there's no token, we're already in the correct state
|
|
||||||
if (!hasToken) return;
|
if (!hasToken) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { axiosClient } from '../App.tsx';
|
|
||||||
|
|
||||||
import { ChatMessagesProps, ContactsProps } from '@/types/types.ts';
|
import { ChatMessagesProps, ContactsProps } from '@/types/types.ts';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
export async function getContactsList(): Promise<ContactsProps[]> {
|
export async function getContactsList(): Promise<ContactsProps[]> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import type { KeyboardEventHandler } from 'react';
|
import type { KeyboardEventHandler } from 'react';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import { socket } from '../../../socket/socket.tsx';
|
import { socket } from '@/socket/socket.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';
|
||||||
import {
|
import { FileWithPreviewProps } from '@/types/types.ts';
|
||||||
FileWithPreviewProps,
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
InputProps,
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
MessageFormProps,
|
|
||||||
} from '@/types/types.ts';
|
|
||||||
|
|
||||||
const MessageForm = ({ contact }: MessageFormProps) => {
|
export type InputProps = {
|
||||||
|
message: string;
|
||||||
|
attachments: FileList | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageForm = () => {
|
||||||
|
const { currentContact } = useChat();
|
||||||
const [files, setFiles] = useState<FileWithPreviewProps[]>([]);
|
const [files, setFiles] = useState<FileWithPreviewProps[]>([]);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
@@ -130,9 +133,9 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
'chat message',
|
'chat message',
|
||||||
{
|
{
|
||||||
message: data.message.trim(),
|
message: data.message.trim(),
|
||||||
recipient: contact.conversation_id,
|
recipient: currentContact?.conversation_id,
|
||||||
attachment_urls: attachmentUrls,
|
attachment_urls: attachmentUrls,
|
||||||
recipient_id: contact.user_id,
|
recipient_id: currentContact?.user_id,
|
||||||
},
|
},
|
||||||
(response: { status: string; message: string }) => {
|
(response: { status: string; message: string }) => {
|
||||||
if (response.status === 'ok') {
|
if (response.status === 'ok') {
|
||||||
@@ -148,9 +151,9 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
);
|
);
|
||||||
console.log('sent: ', {
|
console.log('sent: ', {
|
||||||
message: data.message.trim(),
|
message: data.message.trim(),
|
||||||
recipient: contact.conversation_id,
|
recipient: currentContact?.conversation_id,
|
||||||
attachment_urls: attachmentUrls,
|
attachment_urls: attachmentUrls,
|
||||||
recipient_id: contact.user_id,
|
recipient_id: currentContact?.user_id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,8 +255,8 @@ const MessageForm = ({ contact }: MessageFormProps) => {
|
|||||||
}}
|
}}
|
||||||
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
|
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={!!currentContact}
|
||||||
disabled={!contact}
|
disabled={!currentContact}
|
||||||
placeholder="Enter message"
|
placeholder="Enter message"
|
||||||
onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
rows={1}
|
rows={1}
|
||||||
|
|||||||
@@ -4,37 +4,21 @@ 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 AnimatedMessage from '@/components/chat/chatArea/AnimatedMessage.tsx';
|
import AnimatedMessage from '@/components/chat/chatArea/AnimatedMessage.tsx';
|
||||||
import {
|
import { ChatMessagesProps, UsernameType } from '@/types/types.ts';
|
||||||
ChatMessagesProps,
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
ContactsProps,
|
|
||||||
MeProps,
|
|
||||||
UsernameType,
|
|
||||||
} from '@/types/types.ts';
|
|
||||||
|
|
||||||
type MessagesAreaProps = {
|
function MessagesArea() {
|
||||||
messages: ChatMessagesProps[];
|
const {
|
||||||
setMessages: React.Dispatch<React.SetStateAction<ChatMessagesProps[]>>;
|
messages,
|
||||||
currentContact: ContactsProps | null;
|
setMessages,
|
||||||
updateContactStatus: (contact: ContactsProps, read: boolean) => void;
|
currentContact,
|
||||||
messageHandler: (msg: ChatMessagesProps) => void;
|
updateContactStatus,
|
||||||
setContactsList: React.Dispatch<React.SetStateAction<ContactsProps[]>>;
|
messageHandler,
|
||||||
fetchPreviousMessages: (contact: string | null) => Promise<void>;
|
setContactsList,
|
||||||
errorMessage: string | null;
|
errorMessage,
|
||||||
contactsList: ContactsProps[];
|
fetchPreviousMessages,
|
||||||
me: MeProps;
|
me,
|
||||||
};
|
} = useChat();
|
||||||
|
|
||||||
function MessagesArea({
|
|
||||||
messages,
|
|
||||||
currentContact,
|
|
||||||
updateContactStatus,
|
|
||||||
setContactsList,
|
|
||||||
messageHandler,
|
|
||||||
fetchPreviousMessages,
|
|
||||||
errorMessage,
|
|
||||||
setMessages,
|
|
||||||
me,
|
|
||||||
}: MessagesAreaProps) {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const user: UsernameType = useOutletContext();
|
const user: UsernameType = useOutletContext();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
@@ -231,7 +215,7 @@ function MessagesArea({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="flex flex-col h-full overflow-y-auto">
|
<div ref={containerRef} className="flex flex-col h-full overflow-y-auto">
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
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 { 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';
|
import { Button } from '@/components/ui/button.tsx';
|
||||||
import { ContactsProps } from '@/types/types.ts';
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AddGroupMemberProps {
|
function AddGroupMember() {
|
||||||
contact?: ContactsProps;
|
const { currentContact } = useChat();
|
||||||
}
|
|
||||||
|
|
||||||
function AddGroupMember({ contact }: AddGroupMemberProps) {
|
|
||||||
const { register, handleSubmit, watch, reset } = useForm<Inputs>();
|
const { register, handleSubmit, watch, reset } = useForm<Inputs>();
|
||||||
const modalRef = useRef<HTMLDialogElement | null>(null);
|
const modalRef = useRef<HTMLDialogElement | null>(null);
|
||||||
const contactInput = watch('username');
|
const contactInput = watch('username');
|
||||||
@@ -43,12 +41,16 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setNotFound(false);
|
setNotFound(false);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
setIsLoading(false);
|
if (axios.isAxiosError(e)) {
|
||||||
console.error('Error fetching suggestions:', e);
|
setIsLoading(false);
|
||||||
setErrorMessage(
|
console.error('Error fetching suggestions:', e);
|
||||||
e.response?.data?.message || 'Failed to fetch suggestions',
|
setErrorMessage(
|
||||||
);
|
e.response?.data?.message || 'Failed to fetch suggestions',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected error occurred');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setNotFound(false);
|
setNotFound(false);
|
||||||
@@ -76,20 +78,26 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
const response = await axiosClient.post(`/api/chat/groups/addMember/`, {
|
const response = await axiosClient.post(`/api/chat/groups/addMember/`, {
|
||||||
group_id: contact?.conversation_id,
|
group_id: currentContact?.conversation_id,
|
||||||
username: contactToSubmit,
|
username: contactToSubmit,
|
||||||
});
|
});
|
||||||
console.log('Add member to group', response);
|
console.log('Add member to group', response);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
socket?.emit('added to group', { group_id: contact?.conversation_id });
|
socket?.emit('added to group', {
|
||||||
|
group_id: currentContact?.conversation_id,
|
||||||
|
});
|
||||||
if (modalRef.current) {
|
if (modalRef.current) {
|
||||||
modalRef.current.close();
|
modalRef.current.close();
|
||||||
}
|
}
|
||||||
reset();
|
reset();
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
console.error('Failed to add group member: ', e);
|
if (axios.isAxiosError(e)) {
|
||||||
setIsLoading(false);
|
console.error('Failed to add group member: ', e);
|
||||||
setErrorMessage(e.response?.data?.message || 'Failed to add member');
|
setIsLoading(false);
|
||||||
|
setErrorMessage(e.response?.data?.message || 'Failed to add member');
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected error occurred');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,34 +2,27 @@ 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 { UsersRound } from 'lucide-react';
|
import { UsersRound } from 'lucide-react';
|
||||||
import { ContactsProps } from '@/types/types.ts';
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
|
|
||||||
type ContactProfileProps = {
|
function ContactProfile() {
|
||||||
contact: ContactsProps | null;
|
const { currentContact } = useChat();
|
||||||
};
|
|
||||||
|
|
||||||
function ContactProfile({ contact }: ContactProfileProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center p-2">
|
<div className="flex items-center p-2">
|
||||||
<div className="flex items-center p-2">
|
<div className="flex items-center p-2">
|
||||||
{contact?.type === 'group' ? (
|
{currentContact?.type === 'group' ? (
|
||||||
<UsersRound className="w-5 mr-2" />
|
<UsersRound className="w-5 mr-2" />
|
||||||
) : (
|
) : (
|
||||||
<img className="w-4 mr-2 invert" src={profile} alt="profile img" />
|
<img className="w-4 mr-2 invert" src={profile} alt="profile img" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p>{contact ? contact.username : null}</p>
|
<p>{currentContact ? currentContact.username : null}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow"></div>
|
<div className="flex-grow"></div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<CreateGroupButton />
|
<CreateGroupButton />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>{currentContact?.type === 'group' ? <AddGroupMember /> : null}</div>
|
||||||
{contact?.type === 'group' ? (
|
|
||||||
<AddGroupMember contact={contact} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
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 { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
groupName: string;
|
groupName: string;
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
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';
|
||||||
import { ContactsProps } from '@/types/types.ts';
|
import { ContactsProps } from '@/types/types.ts';
|
||||||
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
type Input = {
|
type Input = {
|
||||||
contact: string;
|
contact: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ContactFormProps = {
|
function ContactForm() {
|
||||||
InitializeContact: (contact: ContactsProps) => void;
|
const { setContactsList, initializeContact } = useChat();
|
||||||
setContactsList: React.Dispatch<React.SetStateAction<ContactsProps[]>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ContactForm({ InitializeContact, setContactsList }: ContactFormProps) {
|
|
||||||
const { register, handleSubmit, reset, watch } = useForm<Input>();
|
const { register, handleSubmit, reset, watch } = useForm<Input>();
|
||||||
const contactInput = watch('contact');
|
const contactInput = watch('contact');
|
||||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||||
@@ -76,7 +73,7 @@ function ContactForm({ InitializeContact, setContactsList }: ContactFormProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
console.log('contact post response: ', response.data);
|
console.log('contact post response: ', response.data);
|
||||||
InitializeContact(response.data);
|
initializeContact(response.data);
|
||||||
|
|
||||||
setContactsList((prevContacts) => {
|
setContactsList((prevContacts) => {
|
||||||
if (!prevContacts.some((c) => c.username === contactToSubmit)) {
|
if (!prevContacts.some((c) => c.username === contactToSubmit)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
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 {
|
||||||
@@ -12,36 +12,27 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from '@/components/ui/alert-dialog.tsx';
|
} from '@/components/ui/alert-dialog.tsx';
|
||||||
import { axiosClient } from '@/App.tsx';
|
|
||||||
import { Dot } from 'lucide-react';
|
import { Dot } from 'lucide-react';
|
||||||
import LastActiveTime from '@/components/chat/leftSidebar/LastActiveTime.tsx';
|
import LastActiveTime from '@/components/chat/leftSidebar/LastActiveTime.tsx';
|
||||||
import { ChatMessagesProps, ContactsProps } from '@/types/types.ts';
|
import { ContactsProps } from '@/types/types.ts';
|
||||||
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
type ContactsListProps = {
|
function ContactsList() {
|
||||||
initializeContact: (contact: ContactsProps) => void;
|
const {
|
||||||
setContactsList: React.Dispatch<React.SetStateAction<ContactsProps[]>>;
|
initializeContact,
|
||||||
contactsList: ContactsProps[];
|
contactsList,
|
||||||
setCurrentContact: React.Dispatch<React.SetStateAction<ContactsProps | null>>;
|
setContactsList,
|
||||||
updateContactStatus: (contactObj: ContactsProps, read: boolean) => void;
|
setCurrentContact,
|
||||||
setMessages: React.Dispatch<React.SetStateAction<ChatMessagesProps[]>>;
|
updateContactStatus,
|
||||||
currentContact: ContactsProps | null;
|
setMessages,
|
||||||
setErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
|
setErrorMessage,
|
||||||
};
|
} = useChat();
|
||||||
|
|
||||||
function ContactsList({
|
|
||||||
initializeContact,
|
|
||||||
contactsList,
|
|
||||||
setContactsList,
|
|
||||||
setCurrentContact,
|
|
||||||
updateContactStatus,
|
|
||||||
setMessages,
|
|
||||||
setErrorMessage,
|
|
||||||
}: ContactsListProps) {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchContacts().catch((e) =>
|
fetchContacts().catch((e) =>
|
||||||
console.error('Failed to fetch contacts: ', e),
|
console.error('Failed to fetch contacts: ', e),
|
||||||
);
|
);
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { axiosClient } from '@/App.tsx';
|
|
||||||
import { socket } from '@/socket/socket.tsx';
|
import { socket } from '@/socket/socket.tsx';
|
||||||
import { Crown, Sword } from 'lucide-react';
|
import { Crown, Sword } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -10,32 +9,15 @@ import {
|
|||||||
} from '@/components/ui/context-menu.tsx';
|
} from '@/components/ui/context-menu.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';
|
||||||
import { ContactsProps, MeProps, UsernameType } from '@/types/types.ts';
|
import { ParticipantsProps, UsernameType } from '@/types/types.ts';
|
||||||
type ParticipantsProps = {
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
user_id: string;
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
username: string;
|
|
||||||
isadmin: boolean;
|
|
||||||
isowner: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ParticipantsBarProps = {
|
function ParticipantsBar() {
|
||||||
initializeContact: (contact: ContactsProps) => void;
|
const { setMe, initializeContact, setGroupOwner, currentContact, me } =
|
||||||
currentContact: ContactsProps | null;
|
useChat();
|
||||||
setMe: React.Dispatch<React.SetStateAction<MeProps>>;
|
|
||||||
setGroupOwner: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
||||||
groupOwner: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ParticipantsBar({
|
|
||||||
initializeContact,
|
|
||||||
currentContact,
|
|
||||||
setMe,
|
|
||||||
setGroupOwner,
|
|
||||||
groupOwner,
|
|
||||||
}: 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(
|
||||||
|
|||||||
6
client/src/context/chat/ChatContext.tsx
Normal file
6
client/src/context/chat/ChatContext.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { ChatContextType } from '@/types/types.ts';
|
||||||
|
|
||||||
|
export const ChatContext = createContext<ChatContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
181
client/src/context/chat/ChatProvider.tsx
Normal file
181
client/src/context/chat/ChatProvider.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { ChatContext } from '@/context/chat/ChatContext.tsx';
|
||||||
|
import { ReactNode, useState } from 'react';
|
||||||
|
import { ChatMessagesProps, ContactsProps, MeProps } from '@/types/types.ts';
|
||||||
|
import { joinRoom } from '@/socket/socket.tsx';
|
||||||
|
import { getMessages, setContactStatus } from '@/api/contactsApi.tsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const ChatProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [contactsList, setContactsList] = useState<ContactsProps[]>([]);
|
||||||
|
const [currentContact, setCurrentContact] = useState<ContactsProps | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const [cursor, setCursor] = useState<number>(0);
|
||||||
|
const [messages, setMessages] = useState<ChatMessagesProps[]>([]);
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
const [hasMoreMessages, setHasMoreMessages] = useState<boolean>(true);
|
||||||
|
const [me, setMe] = useState<MeProps>({
|
||||||
|
isGroupAdmin: false,
|
||||||
|
isGroupOwner: false,
|
||||||
|
});
|
||||||
|
const [groupOwner, setGroupOwner] = useState<string | undefined>();
|
||||||
|
|
||||||
|
async function initializeContact(newContact: ContactsProps) {
|
||||||
|
setMessages([]); // Clear messages from previous contact
|
||||||
|
localStorage.setItem('contact', JSON.stringify(newContact)); // Set current contact in localstorage
|
||||||
|
setCurrentContact(newContact);
|
||||||
|
console.log('Initialized contact: ', newContact);
|
||||||
|
try {
|
||||||
|
const joinResult = await joinRoom(newContact.conversation_id);
|
||||||
|
if (!joinResult.success) {
|
||||||
|
setErrorMessage(joinResult.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fetchMessages(newContact.conversation_id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch messages: ', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setContactsList((prevContacts) =>
|
||||||
|
prevContacts.map((c) =>
|
||||||
|
c.username === newContact.username ? { ...c, read: true } : c,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log('Current contact is now: ', newContact);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to initialize contact:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMessages = async (conversation_id: string) => {
|
||||||
|
console.log('Fetching messages for: ', conversation_id);
|
||||||
|
try {
|
||||||
|
const messages = await getMessages(conversation_id);
|
||||||
|
if (messages.messages.length < 50) {
|
||||||
|
setHasMoreMessages(false);
|
||||||
|
setErrorMessage('No more messages found');
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursor(() => {
|
||||||
|
return Math.min(
|
||||||
|
...messages.messages.map((message) => message.message_id),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
messages.messages.forEach(messageHandler);
|
||||||
|
} catch (e) {
|
||||||
|
if (axios.isAxiosError(e)) {
|
||||||
|
setErrorMessage(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function messageHandler(msg: ChatMessagesProps) {
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
// Check if the message already exists in the state
|
||||||
|
if (!prevMessages.some((m) => m.message_id === msg.message_id)) {
|
||||||
|
// Convert sent_at to Date object before adding to state
|
||||||
|
const messageWithDate = {
|
||||||
|
...msg,
|
||||||
|
sent_at: new Date(msg.sent_at),
|
||||||
|
};
|
||||||
|
return [...prevMessages, messageWithDate];
|
||||||
|
}
|
||||||
|
return prevMessages;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContactStatus(contactObj: ContactsProps, read: boolean) {
|
||||||
|
console.log('Update contact status: ', contactObj);
|
||||||
|
|
||||||
|
setContactsList((prevContacts) =>
|
||||||
|
prevContacts.map((contact) => {
|
||||||
|
if (contact.conversation_id === contactObj.conversation_id) {
|
||||||
|
if (!contactObj.read) {
|
||||||
|
setContactStatus(contactObj.conversation_id, read);
|
||||||
|
}
|
||||||
|
return { ...contact, read: read };
|
||||||
|
} else {
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPreviousMessages = async (contact: string | null) => {
|
||||||
|
if (!hasMoreMessages) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'Fetching messages for: ',
|
||||||
|
contact,
|
||||||
|
'with cursor: ',
|
||||||
|
cursor,
|
||||||
|
'hasmoremessages: ',
|
||||||
|
hasMoreMessages,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const messages = await getMessages(contact, cursor, 50);
|
||||||
|
if (messages.messages.length < 50) {
|
||||||
|
setHasMoreMessages(false);
|
||||||
|
setErrorMessage('No more messages found');
|
||||||
|
}
|
||||||
|
messages.messages.forEach((msg) => {
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
if (!prevMessages.some((m) => m.message_id === msg.message_id)) {
|
||||||
|
const messageWithDate = {
|
||||||
|
...msg,
|
||||||
|
sent_at: new Date(msg.sent_at),
|
||||||
|
};
|
||||||
|
|
||||||
|
return [messageWithDate, ...prevMessages];
|
||||||
|
}
|
||||||
|
return prevMessages;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setCursor(() => {
|
||||||
|
return Math.min(
|
||||||
|
...messages.messages.map((message) => message.message_id),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (axios.isAxiosError(e)) {
|
||||||
|
setErrorMessage(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
contactsList,
|
||||||
|
currentContact,
|
||||||
|
cursor,
|
||||||
|
messages,
|
||||||
|
errorMessage,
|
||||||
|
hasMoreMessages,
|
||||||
|
me,
|
||||||
|
groupOwner,
|
||||||
|
|
||||||
|
setContactsList,
|
||||||
|
setCurrentContact,
|
||||||
|
setCursor,
|
||||||
|
setMessages,
|
||||||
|
setErrorMessage,
|
||||||
|
setHasMoreMessages,
|
||||||
|
setMe,
|
||||||
|
setGroupOwner,
|
||||||
|
|
||||||
|
initializeContact,
|
||||||
|
fetchMessages,
|
||||||
|
messageHandler,
|
||||||
|
updateContactStatus,
|
||||||
|
fetchPreviousMessages,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||||
|
};
|
||||||
11
client/src/context/chat/useChat.ts
Normal file
11
client/src/context/chat/useChat.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { ChatContext } from '@/context/chat/ChatContext.tsx';
|
||||||
|
import { ChatContextType } from '@/types/types.ts';
|
||||||
|
|
||||||
|
export const useChat = (): ChatContextType => {
|
||||||
|
const context = useContext(ChatContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useChat must be used within ChatProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -3,33 +3,15 @@ import ContactProfile from '../components/chat/chatHeader/ContactProfile.tsx';
|
|||||||
import UserProfile from '../components/chat/leftSidebar/UserProfile.tsx';
|
import UserProfile from '../components/chat/leftSidebar/UserProfile.tsx';
|
||||||
import ContactForm from '../components/chat/leftSidebar/ContactForm.tsx';
|
import ContactForm from '../components/chat/leftSidebar/ContactForm.tsx';
|
||||||
import MessagesArea from '../components/chat/chatArea/MessagesArea.tsx';
|
import MessagesArea from '../components/chat/chatArea/MessagesArea.tsx';
|
||||||
import { createContext, useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
import ContactsList from '../components/chat/leftSidebar/ContactsList.tsx';
|
import ContactsList from '../components/chat/leftSidebar/ContactsList.tsx';
|
||||||
import { initializeSocket, joinRoom } from '../socket/socket.tsx';
|
import { initializeSocket } from '../socket/socket.tsx';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import { getMessages, setContactStatus } from '../api/contactsApi.tsx';
|
|
||||||
import axios from 'axios';
|
|
||||||
import ParticipantsBar from '@/components/chat/rightSidebar/ParticipantsBar.tsx';
|
import ParticipantsBar from '@/components/chat/rightSidebar/ParticipantsBar.tsx';
|
||||||
import { ChatMessagesProps, ContactsProps, MeProps } from '@/types/types.ts';
|
import { useChat } from '@/context/chat/useChat.ts';
|
||||||
|
|
||||||
function Chat() {
|
function Chat() {
|
||||||
const meDefaultValue = {
|
const { initializeContact, currentContact } = useChat();
|
||||||
isGroupAdmin: false,
|
|
||||||
isGroupOwner: false,
|
|
||||||
};
|
|
||||||
const [contactsList, setContactsList] = useState<ContactsProps[]>([]);
|
|
||||||
const [currentContact, setCurrentContact] = useState<ContactsProps | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
const [cursor, setCursor] = useState<number>(0);
|
|
||||||
const [messages, setMessages] = useState<ChatMessagesProps[]>([]);
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
||||||
const [hasMoreMessages, setHasMoreMessages] = useState<boolean>(true);
|
|
||||||
const [me, setMe] = useState<MeProps>(meDefaultValue);
|
|
||||||
const MeContext = createContext(meDefaultValue);
|
|
||||||
|
|
||||||
const [groupOwner, setGroupOwner] = useState<string | undefined>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = Cookies.get('token');
|
const token = Cookies.get('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
@@ -47,205 +29,44 @@ function Chat() {
|
|||||||
localStorage.removeItem('contact');
|
localStorage.removeItem('contact');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
async function initializeContact(newContact: ContactsProps) {
|
|
||||||
setMessages([]); // Clear messages from previous contact
|
|
||||||
localStorage.setItem('contact', JSON.stringify(newContact)); // Set current contact in localstorage
|
|
||||||
setCurrentContact(newContact);
|
|
||||||
console.log('Initialized contact: ', newContact);
|
|
||||||
try {
|
|
||||||
const joinResult = await joinRoom(newContact.conversation_id);
|
|
||||||
if (!joinResult.success) {
|
|
||||||
setErrorMessage(joinResult.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await fetchMessages(newContact.conversation_id);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to fetch messages: ', e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setContactsList((prevContacts) =>
|
|
||||||
prevContacts.map((c) =>
|
|
||||||
c.username === newContact.username ? { ...c, read: true } : c,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log('Current contact is now: ', newContact);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to initialize contact:', e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchMessages = async (conversation_id: string) => {
|
|
||||||
console.log('Fetching messages for: ', conversation_id);
|
|
||||||
try {
|
|
||||||
const messages = await getMessages(conversation_id);
|
|
||||||
if (messages.messages.length < 50) {
|
|
||||||
setHasMoreMessages(false);
|
|
||||||
setErrorMessage('No more messages found');
|
|
||||||
}
|
|
||||||
|
|
||||||
setCursor(() => {
|
|
||||||
return Math.min(
|
|
||||||
...messages.messages.map((message) => message.message_id),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
messages.messages.forEach(messageHandler);
|
|
||||||
} catch (e) {
|
|
||||||
if (axios.isAxiosError(e)) {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchPreviousMessages = async (contact: string | null) => {
|
|
||||||
if (!hasMoreMessages) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
'Fetching messages for: ',
|
|
||||||
contact,
|
|
||||||
'with cursor: ',
|
|
||||||
cursor,
|
|
||||||
'hasmoremessages: ',
|
|
||||||
hasMoreMessages,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const messages = await getMessages(contact, cursor, 50);
|
|
||||||
if (messages.messages.length < 50) {
|
|
||||||
setHasMoreMessages(false);
|
|
||||||
setErrorMessage('No more messages found');
|
|
||||||
}
|
|
||||||
messages.messages.forEach((msg) => {
|
|
||||||
setMessages((prevMessages) => {
|
|
||||||
if (!prevMessages.some((m) => m.message_id === msg.message_id)) {
|
|
||||||
const messageWithDate = {
|
|
||||||
...msg,
|
|
||||||
sent_at: new Date(msg.sent_at),
|
|
||||||
};
|
|
||||||
|
|
||||||
return [messageWithDate, ...prevMessages];
|
|
||||||
}
|
|
||||||
return prevMessages;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
setCursor(() => {
|
|
||||||
return Math.min(
|
|
||||||
...messages.messages.map((message) => message.message_id),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (axios.isAxiosError(e)) {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function messageHandler(msg: ChatMessagesProps) {
|
|
||||||
setMessages((prevMessages) => {
|
|
||||||
// Check if the message already exists in the state
|
|
||||||
if (!prevMessages.some((m) => m.message_id === msg.message_id)) {
|
|
||||||
// Convert sent_at to Date object before adding to state
|
|
||||||
const messageWithDate = {
|
|
||||||
...msg,
|
|
||||||
sent_at: new Date(msg.sent_at),
|
|
||||||
};
|
|
||||||
return [...prevMessages, messageWithDate];
|
|
||||||
}
|
|
||||||
return prevMessages;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateContactStatus(contactObj: ContactsProps, read: boolean) {
|
|
||||||
console.log('Update contact status: ', contactObj);
|
|
||||||
|
|
||||||
setContactsList((prevContacts) =>
|
|
||||||
prevContacts.map((contact) => {
|
|
||||||
if (contact.conversation_id === contactObj.conversation_id) {
|
|
||||||
if (!contactObj.read) {
|
|
||||||
setContactStatus(contactObj.conversation_id, read);
|
|
||||||
}
|
|
||||||
return { ...contact, read: read };
|
|
||||||
} else {
|
|
||||||
return contact;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MeContext.Provider value={me}>
|
<div className="text-white flex h-screen">
|
||||||
<div className="text-white flex h-screen">
|
{/* Left Sidebar */}
|
||||||
{/* Left 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="w-64 h-screen flex-shrink-0 flex flex-col bg-zinc-950 text-center border-r border-zinc-800">
|
<ContactForm />
|
||||||
<ContactForm
|
<ContactsList />
|
||||||
setContactsList={setContactsList}
|
<UserProfile />
|
||||||
InitializeContact={initializeContact}
|
</div>
|
||||||
/>
|
|
||||||
<ContactsList
|
|
||||||
initializeContact={initializeContact}
|
|
||||||
contactsList={contactsList}
|
|
||||||
setContactsList={setContactsList}
|
|
||||||
setCurrentContact={setCurrentContact}
|
|
||||||
updateContactStatus={updateContactStatus}
|
|
||||||
setMessages={setMessages}
|
|
||||||
currentContact={currentContact}
|
|
||||||
setErrorMessage={setErrorMessage}
|
|
||||||
/>
|
|
||||||
<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 />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow overflow-y-auto">
|
<div className="flex-grow overflow-y-auto">
|
||||||
<MessagesArea
|
<MessagesArea />
|
||||||
messages={messages}
|
</div>
|
||||||
setMessages={setMessages}
|
<div className="flex-shrink-0 p-3 border-t border-zinc-800">
|
||||||
currentContact={currentContact}
|
{currentContact && currentContact.username?.length >= 4 ? (
|
||||||
updateContactStatus={updateContactStatus}
|
<MessageForm />
|
||||||
messageHandler={messageHandler}
|
) : null}
|
||||||
setContactsList={setContactsList}
|
|
||||||
fetchPreviousMessages={fetchPreviousMessages}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
contactsList={contactsList}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-shrink-0 p-3 border-t border-zinc-800">
|
|
||||||
{currentContact && currentContact.username?.length >= 4 ? (
|
|
||||||
<MessageForm contact={currentContact} messages={messages} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</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 />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MeContext.Provider>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { Link, useNavigate } from 'react-router-dom';
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { AuthContext } from '../utils/AuthProvider.tsx';
|
import { AuthContext } from '../utils/AuthProvider.tsx';
|
||||||
import LoadingWheel from '../components/chat/LoadingWheel.tsx';
|
import LoadingWheel from '../components/chat/LoadingWheel.tsx';
|
||||||
import { axiosClient } from '../App.tsx';
|
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
export type Inputs = {
|
export type Inputs = {
|
||||||
username: string;
|
username: string;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { useForm, SubmitHandler } from 'react-hook-form';
|
|||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { AuthContext } from '../utils/AuthProvider.tsx';
|
import { AuthContext } from '../utils/AuthProvider.tsx';
|
||||||
import { axiosClient } from '../App.tsx';
|
|
||||||
import LoadingWheel from '../components/chat/LoadingWheel.tsx';
|
import LoadingWheel from '../components/chat/LoadingWheel.tsx';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
username: string;
|
username: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { File } from 'lucide-react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
|
||||||
export type MeProps = {
|
export type MeProps = {
|
||||||
isGroupAdmin: boolean;
|
isGroupAdmin: boolean;
|
||||||
@@ -26,19 +26,46 @@ export type ContactsProps = {
|
|||||||
last_message_time: string;
|
last_message_time: string;
|
||||||
last_message_sender: string;
|
last_message_sender: string;
|
||||||
};
|
};
|
||||||
export type InputProps = {
|
|
||||||
message: string;
|
export type ParticipantsProps = {
|
||||||
attachments: FileList | null;
|
user_id: string;
|
||||||
};
|
username: string;
|
||||||
export type MessageFormProps = {
|
isadmin: boolean;
|
||||||
contact: ContactsProps;
|
isowner: boolean;
|
||||||
messages: ChatMessagesProps[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FileWithPreviewProps = {
|
export type FileWithPreviewProps = {
|
||||||
file: File;
|
file: File;
|
||||||
preview: string | null;
|
preview: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UsernameType = {
|
export type UsernameType = {
|
||||||
username: string | null;
|
username: string | null;
|
||||||
user_id: string | null;
|
user_id: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ChatContextType = {
|
||||||
|
contactsList: ContactsProps[];
|
||||||
|
currentContact: ContactsProps | null;
|
||||||
|
cursor: number;
|
||||||
|
messages: ChatMessagesProps[];
|
||||||
|
errorMessage: string | null;
|
||||||
|
hasMoreMessages: boolean;
|
||||||
|
me: MeProps;
|
||||||
|
groupOwner: string | undefined;
|
||||||
|
|
||||||
|
setContactsList: Dispatch<SetStateAction<ContactsProps[]>>;
|
||||||
|
setCurrentContact: Dispatch<SetStateAction<ContactsProps | null>>;
|
||||||
|
setCursor: Dispatch<SetStateAction<number>>;
|
||||||
|
setMessages: Dispatch<SetStateAction<ChatMessagesProps[]>>;
|
||||||
|
setErrorMessage: Dispatch<SetStateAction<string | null>>;
|
||||||
|
setHasMoreMessages: Dispatch<SetStateAction<boolean>>;
|
||||||
|
setMe: Dispatch<SetStateAction<MeProps>>;
|
||||||
|
setGroupOwner: Dispatch<SetStateAction<string | undefined>>;
|
||||||
|
|
||||||
|
initializeContact: (newContact: ContactsProps) => Promise<boolean>;
|
||||||
|
fetchMessages: (conversation_id: string) => Promise<void>;
|
||||||
|
messageHandler: (msg: ChatMessagesProps) => void;
|
||||||
|
updateContactStatus: (contactObj: ContactsProps, read: boolean) => void;
|
||||||
|
fetchPreviousMessages: (contact: string | null) => Promise<void>;
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { Navigate, Outlet } from 'react-router-dom';
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { AuthContext } from './AuthProvider.tsx';
|
import { AuthContext } from './AuthProvider.tsx';
|
||||||
import LoadingScreen from '../components/LoadingScreen.tsx';
|
import LoadingScreen from '../components/LoadingScreen.tsx';
|
||||||
import { axiosClient } from '../App.tsx';
|
|
||||||
import { UsernameType } from '@/types/types.ts';
|
import { UsernameType } from '@/types/types.ts';
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
function ProtectedRoutes() {
|
function ProtectedRoutes() {
|
||||||
const { authorized, isLoading } = useContext(AuthContext);
|
const { authorized, isLoading } = useContext(AuthContext);
|
||||||
|
|||||||
5
client/src/utils/axiosClient.ts
Normal file
5
client/src/utils/axiosClient.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const axiosClient = axios.create({
|
||||||
|
baseURL: '/',
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import { axiosClient } from '../App.tsx';
|
|
||||||
|
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||||
|
|
||||||
function useAuth() {
|
function useAuth() {
|
||||||
const [authorized, setAuthorized] = useState<boolean>(false);
|
const [authorized, setAuthorized] = useState<boolean>(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user