mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
193 lines
5.1 KiB
TypeScript
193 lines
5.1 KiB
TypeScript
import React, { ChangeEvent } from 'react';
|
|
import axios from 'axios';
|
|
import { GridItem, GridItemProps, Flex, Box, Icon, Text, Input, Heading, IconButton, Divider } from '@chakra-ui/react';
|
|
import { MdOutlineChat, MdSend } from 'react-icons/md';
|
|
import { MessageList } from './Message';
|
|
import { SocketContext } from '@educt/contexts';
|
|
import { MessageType } from '@educt/types';
|
|
import { UserServiceInstance } from '@educt/services';
|
|
import { IUser } from '@educt/interfaces';
|
|
import LoadingList from '@educt/components/LoadingList';
|
|
|
|
/**
|
|
* Hooks
|
|
*/
|
|
import { useContext, useEffect, useState } from 'react';
|
|
import { useToast } from '@chakra-ui/react';
|
|
import { useSocketEvent } from '@educt/hooks/useSocketEvent';
|
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
import { useHistory } from 'react-router-dom';
|
|
|
|
const Wrapper: React.FC<GridItemProps> = props => (
|
|
<GridItem pt={'7rem'} pb='5' px='12' {...props}>
|
|
{props.children}
|
|
</GridItem>
|
|
);
|
|
|
|
const Window: React.FC = props => {
|
|
const {
|
|
userStore: { me },
|
|
onlineStore,
|
|
} = useRootStore();
|
|
const chatId = new URLSearchParams(location.search).get('chat_id');
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
const [selectedUser, setSelectedUser] = useState<IUser | null>(null);
|
|
const [inputValue, setInputValue] = useState<string>('');
|
|
const [messages, setMessages] = useState<MessageType[]>([]);
|
|
const { socket } = useContext(SocketContext);
|
|
const history = useHistory();
|
|
const toast = useToast();
|
|
|
|
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
|
|
if (inputValue === '' || me === null) return;
|
|
|
|
const message: MessageType = {
|
|
userName: me.fullname,
|
|
content: inputValue,
|
|
time: new Date().toLocaleTimeString(),
|
|
isMyMessage: true,
|
|
};
|
|
|
|
/**
|
|
* Emit event to server and update messages state
|
|
*/
|
|
if (chatId) {
|
|
socket?.emit('message:private', {
|
|
content: message.content,
|
|
to: chatId,
|
|
});
|
|
setMessages(prev => [...prev, message]);
|
|
setInputValue('');
|
|
}
|
|
};
|
|
|
|
const onChangeInputValue = (e: ChangeEvent<HTMLInputElement>) => setInputValue(e.target.value);
|
|
|
|
/**
|
|
* Load user data by `chat_id`
|
|
*/
|
|
useEffect(() => {
|
|
if (chatId) {
|
|
(async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
const { data: user } = await UserServiceInstance.fetchUserById(chatId);
|
|
setSelectedUser(user);
|
|
} catch (error) {
|
|
if (axios.isAxiosError(error)) {
|
|
if (error.response?.status === 404) {
|
|
toast({ title: 'Chat not found.', status: 'warning' });
|
|
history.replace({ search: '' });
|
|
}
|
|
}
|
|
|
|
console.error(error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
})();
|
|
}
|
|
}, [chatId]);
|
|
|
|
// TODO load message history
|
|
/**
|
|
* Loading messages history
|
|
*/
|
|
useEffect(() => {
|
|
console.log('chat id changed!');
|
|
setMessages([]);
|
|
}, [chatId]);
|
|
|
|
// TODO add types for this event
|
|
/**
|
|
* Event on accept new messages
|
|
*/
|
|
useSocketEvent('message:private', data => {
|
|
const user = onlineStore.getUser(data.from);
|
|
|
|
if (user) {
|
|
if (chatId === data.from) {
|
|
/**
|
|
* Do not add duplicate of message
|
|
*/
|
|
if (data.to === data.from) return;
|
|
|
|
const message: MessageType = {
|
|
content: data.content,
|
|
userName: user.userName,
|
|
isMyMessage: false,
|
|
time: new Date().toLocaleTimeString(),
|
|
};
|
|
|
|
setMessages(prev => [...prev, message]);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Wrapper>
|
|
<LoadingList />
|
|
</Wrapper>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Unselected user
|
|
*/
|
|
if (!chatId || selectedUser === null) {
|
|
return (
|
|
<Wrapper>
|
|
<Box textAlign={'center'} mt={{ base: '0', lg: '15rem' }}>
|
|
<Icon as={MdOutlineChat} color='gray.600' w='16' h='16' />
|
|
<Text color='gray.500' userSelect={'none'}>
|
|
Select a chat to start messaging
|
|
</Text>
|
|
</Box>
|
|
</Wrapper>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Wrapper {...props}>
|
|
<Flex flexDir={'column'}>
|
|
<Box my='3'>
|
|
<Heading as='h2' fontWeight={'semibold'} fontSize='3xl'>
|
|
{selectedUser.fullname}
|
|
</Heading>
|
|
</Box>
|
|
<Divider my='2' />
|
|
<MessageList messages={messages} />
|
|
<form onSubmit={e => onSubmit(e)}>
|
|
<Flex mt='auto' alignItems={'flex-end'}>
|
|
<Input
|
|
onChange={onChangeInputValue}
|
|
value={inputValue}
|
|
resize={'block'}
|
|
type='text'
|
|
placeholder='Write a message...'
|
|
maxW='900px'
|
|
variant={'outline'}
|
|
rounded='xl'
|
|
mr='2'
|
|
/>
|
|
<IconButton
|
|
type='submit'
|
|
icon={<MdSend />}
|
|
aria-label='Send message'
|
|
variant={'ghost'}
|
|
_hover={{ bg: 'inherit' }}
|
|
_active={{ bg: 'inherit' }}
|
|
_focus={{ outline: 'none' }}
|
|
/>
|
|
</Flex>
|
|
</form>
|
|
</Flex>
|
|
</Wrapper>
|
|
);
|
|
};
|
|
|
|
export { Window };
|