added username suggestion to AddGroupMember.tsx
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import LoadingWheel from './LoadingWheel.tsx';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { axiosClient } from '../../App.tsx';
|
||||
import { ContactsProps } from '../../pages/Chat.tsx';
|
||||
import { socket } from '../../socket/socket.tsx';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
type Inputs = {
|
||||
username: string;
|
||||
user_id: number;
|
||||
};
|
||||
|
||||
interface AddGroupMemberProps {
|
||||
@@ -15,20 +15,61 @@ interface AddGroupMemberProps {
|
||||
}
|
||||
|
||||
function AddGroupMember({ contact }: AddGroupMemberProps) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const { register, handleSubmit, watch, reset } = useForm<Inputs>();
|
||||
const modalRef = useRef<HTMLDialogElement | null>(null);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>();
|
||||
const contactInput = watch('username');
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [notFound, setNotFound] = useState<boolean>(false);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSuggestions = async () => {
|
||||
if (contactInput?.length >= 3) {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response: AxiosResponse<string[]> = await axiosClient.get(
|
||||
`/api/chat/contacts/suggestions/${contactInput}`,
|
||||
);
|
||||
setSuggestions(response.data);
|
||||
setSelectedIndex(0); // Reset selection to first item when suggestions update
|
||||
if (response.data.length < 1) {
|
||||
setIsLoading(false);
|
||||
setNotFound(true);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setNotFound(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.error('Error fetching suggestions:', error);
|
||||
}
|
||||
} else {
|
||||
setNotFound(false);
|
||||
setIsLoading(false);
|
||||
setSuggestions([]);
|
||||
}
|
||||
};
|
||||
|
||||
const delay = setTimeout(() => {
|
||||
fetchSuggestions();
|
||||
}, 300);
|
||||
|
||||
return () => clearTimeout(delay);
|
||||
}, [contactInput]);
|
||||
|
||||
if (!socket) return;
|
||||
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
||||
const contactToSubmit =
|
||||
suggestions.length > 0
|
||||
? suggestions[selectedIndex]
|
||||
: data.username.trim();
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await axiosClient.post(`/api/chat/groups/addMember/`, {
|
||||
group_id: contact?.conversation_id,
|
||||
username: data.username,
|
||||
username: contactToSubmit,
|
||||
});
|
||||
console.log(response.data);
|
||||
setIsLoading(false);
|
||||
@@ -42,6 +83,30 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (suggestions.length > 0) {
|
||||
switch (e.key) {
|
||||
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();
|
||||
reset({ username: suggestions[selectedIndex] });
|
||||
handleSubmit(onSubmit)();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
@@ -65,26 +130,51 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
|
||||
|
||||
<div className="flex flex-col items-center justify-center pb-1 mt-6">
|
||||
<h3 className="text-lg mb-4">Enter username</h3>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full max-w-xs relative"
|
||||
>
|
||||
<input
|
||||
className="input input-bordered bg-green-50 w-full text-black rounded-md text-center"
|
||||
{...register('username', {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
maxLength: 20,
|
||||
})}
|
||||
aria-invalid={errors.username ? 'true' : 'false'}
|
||||
/>
|
||||
{errors.username?.type === 'minLength' && (
|
||||
<p className="text-gray-300">Invalid username</p>
|
||||
)}
|
||||
{errors.username?.type === 'maxLength' && (
|
||||
<p className="text-gray-300">Invalid username</p>
|
||||
)}
|
||||
</form>
|
||||
<div className="text-center">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input
|
||||
className="text-black bg-green-50 pl-2 shadow-lg rounded-md h-8 mb-2 mt-2"
|
||||
type="text"
|
||||
onKeyDown={handleKeyDown}
|
||||
{...register('username', {
|
||||
minLength: 4,
|
||||
maxLength: 20,
|
||||
})}
|
||||
/>
|
||||
</form>
|
||||
<div className="m-1">
|
||||
{suggestions?.length > 0 ? (
|
||||
<ul className="text-left p-2 bg-gray-900 shadow-md rounded-md w-full border">
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li
|
||||
key={suggestion}
|
||||
className={`p-1 cursor-pointer rounded-md mt-1 mb-1 ${
|
||||
index === selectedIndex
|
||||
? 'bg-gray-500'
|
||||
: 'hover:bg-gray-500'
|
||||
}`}
|
||||
onClick={() => {
|
||||
reset({ username: suggestion });
|
||||
handleSubmit(() =>
|
||||
onSubmit({ username: suggestion }),
|
||||
)();
|
||||
setSuggestions([]);
|
||||
}}
|
||||
>
|
||||
{suggestion}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : isLoading ? (
|
||||
<LoadingWheel />
|
||||
) : null}
|
||||
{notFound ? (
|
||||
<p className="p-1 bg-gray-800 shadow-md rounded-md w-full border text-gray-400">
|
||||
user not found
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-center">
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import { getContactsList } from '../../api/contactsApi.tsx';
|
||||
import { ChatMessages, ContactsProps } from '../../pages/Chat.tsx';
|
||||
import { axiosClient } from '../../App.tsx';
|
||||
import { joinRoom, socket } from '../../socket/socket.tsx';
|
||||
import { socket } from '../../socket/socket.tsx';
|
||||
import GroupIcon from '../../../assets/group.svg';
|
||||
|
||||
type ContactsListProps = {
|
||||
@@ -34,7 +34,6 @@ function ContactsList({
|
||||
fetchContacts();
|
||||
});
|
||||
|
||||
// Cleanup socket event listener
|
||||
return () => {
|
||||
socket?.off('added to group');
|
||||
};
|
||||
|
||||
90
client/src/components/chat/ContactsSuggestions.tsx
Normal file
90
client/src/components/chat/ContactsSuggestions.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import LoadingWheel from './LoadingWheel.tsx';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { axiosClient } from '../../App.tsx';
|
||||
import { ContactsProps } from '../../pages/Chat.tsx';
|
||||
import { socket } from '../../socket/socket.tsx';
|
||||
|
||||
type ContactsSuggestionsProps = {
|
||||
contact: ContactsProps;
|
||||
};
|
||||
|
||||
function ContactsSuggestions({ contact }: ContactsSuggestionsProps) {
|
||||
const { register, handleSubmit, reset, watch } = useForm<{
|
||||
contact: string;
|
||||
}>();
|
||||
|
||||
const submitContact: SubmitHandler<{ contact: string }> = async (data) => {
|
||||
const contactToSubmit =
|
||||
suggestions.length > 0 ? suggestions[selectedIndex] : data.contact.trim();
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await axiosClient.post(`/api/chat/groups/addMember/`, {
|
||||
group_id: contact?.conversation_id,
|
||||
username: data.username,
|
||||
});
|
||||
console.log(response.data);
|
||||
setIsLoading(false);
|
||||
socket?.emit('added to group', { group_id: contact?.conversation_id });
|
||||
if (modalRef.current) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to create group: ', e);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-center">
|
||||
<form onSubmit={handleSubmit(submitContact)}>
|
||||
<input
|
||||
className="text-black bg-green-50 pl-2 shadow-lg rounded-md h-8 mb-2 mt-2"
|
||||
type="text"
|
||||
placeholder="Enter contact"
|
||||
onKeyDown={handleKeyDown}
|
||||
{...register('contact', {
|
||||
minLength: 4,
|
||||
maxLength: 20,
|
||||
})}
|
||||
/>
|
||||
</form>
|
||||
<div className="m-1">
|
||||
{suggestions?.length > 0 ? (
|
||||
<ul className="text-left p-2 bg-gray-900 shadow-md rounded-md w-full border">
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li
|
||||
key={suggestion}
|
||||
className={`p-1 cursor-pointer rounded-md mt-1 mb-1 ${
|
||||
index === selectedIndex
|
||||
? 'bg-gray-500'
|
||||
: 'hover:bg-gray-500'
|
||||
}`}
|
||||
onClick={() => {
|
||||
reset({ contact: suggestion });
|
||||
handleSubmit(() =>
|
||||
submitContact({ contact: suggestion }),
|
||||
)();
|
||||
setSuggestions([]);
|
||||
}}
|
||||
>
|
||||
{suggestion}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : isLoading ? (
|
||||
<LoadingWheel />
|
||||
) : null}
|
||||
{notFound ? (
|
||||
<p className="p-1 bg-gray-800 shadow-md rounded-md w-full border text-gray-400">
|
||||
user not found
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ type MessagesAreaProps = {
|
||||
setContactsList: React.Dispatch<React.SetStateAction<ContactsProps[]>>;
|
||||
fetchPreviousMessages: (contact: number | null) => Promise<void>;
|
||||
errorMessage: string | null;
|
||||
setContactStatus: (contact: string, read: boolean) => Promise<void>;
|
||||
};
|
||||
|
||||
function MessagesArea({
|
||||
@@ -53,7 +54,7 @@ function MessagesArea({
|
||||
const previousScrollHeight = container.scrollHeight;
|
||||
|
||||
try {
|
||||
await fetchPreviousMessages(currentContact?.conversation_id);
|
||||
await fetchPreviousMessages(currentContact!.conversation_id);
|
||||
|
||||
container.scrollTop = container.scrollHeight - previousScrollHeight;
|
||||
} catch (e) {
|
||||
@@ -79,7 +80,9 @@ function MessagesArea({
|
||||
prevMessages.filter((message) => message.message_id !== message_id),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Failed to delete message');
|
||||
if (e instanceof Error) {
|
||||
console.error('Failed to delete message');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,6 +113,7 @@ function MessagesArea({
|
||||
id: msg.message_id,
|
||||
user_id: msg.sender_id,
|
||||
conversation_id: msg.conversation_id,
|
||||
type: 'direct',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import ContactsList from '../components/chat/ContactsList.tsx';
|
||||
import { initializeSocket, joinRoom } from '../socket/socket.tsx';
|
||||
import Cookies from 'js-cookie';
|
||||
import { getMessages, setContactStatus } from '../api/contactsApi.tsx';
|
||||
import axios from 'axios';
|
||||
|
||||
export type ChatMessages = {
|
||||
sender: string;
|
||||
@@ -94,7 +95,9 @@ function Chat() {
|
||||
|
||||
messages.messages.forEach(messageHandler);
|
||||
} catch (e) {
|
||||
setErrorMessage(e.response.data.message);
|
||||
if (axios.isAxiosError(e)) {
|
||||
setErrorMessage(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -133,7 +136,9 @@ function Chat() {
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
setErrorMessage(e.response.data.message);
|
||||
if (axios.isAxiosError(e)) {
|
||||
setErrorMessage(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
function messageHandler(msg: ChatMessages) {
|
||||
@@ -199,7 +204,6 @@ function Chat() {
|
||||
setContactsList={setContactsList}
|
||||
fetchPreviousMessages={fetchPreviousMessages}
|
||||
errorMessage={errorMessage}
|
||||
hasMoreMessages={hasMoreMessages}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0 mb-2 mt-0">
|
||||
|
||||
Reference in New Issue
Block a user