feat: fethincg user in messenger & improved perfomance

This commit is contained in:
Sergey Yarkov 2022-02-11 11:55:56 +03:00
parent b60b330fe9
commit 5602c29b15
5 changed files with 135 additions and 72 deletions

View File

@ -20,8 +20,6 @@ const Sidebar: React.FC<GridItemProps> & ISidebarComposition = props => {
{props.children} {props.children}
</GridItem> </GridItem>
); );
return null;
}; };
Sidebar.Heading = Heading; Sidebar.Heading = Heading;

View File

@ -1,16 +1,16 @@
import React from 'react'; import React, { memo } from 'react';
import { WrapItem, Avatar as ChakraAvatar } from '@chakra-ui/react'; import { WrapItem, Avatar as ChakraAvatar } from '@chakra-ui/react';
export interface IAvatarProps { export interface IAvatarProps {
fullname: string; fullname: string;
} }
const Avatar: React.FC<IAvatarProps> = ({ fullname: name }) => { const Avatar: React.FC<IAvatarProps> = memo(({ fullname: name }) => {
return ( return (
<WrapItem> <WrapItem>
<ChakraAvatar name={name} size={'sm'} /> <ChakraAvatar name={name} size={'sm'} />
</WrapItem> </WrapItem>
); );
}; });
export { Avatar }; export { Avatar };

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef } from 'react'; import React, { memo, useEffect, useRef } from 'react';
import { Flex, Stack } from '@chakra-ui/react'; import { Flex, Box, Stack, Text as ChakraText } from '@chakra-ui/react';
import { Avatar, IAvatarProps } from './Avatar'; import { Avatar, IAvatarProps } from './Avatar';
import { Text, ITextProps } from './Text'; import { Text, ITextProps } from './Text';
import { MessageContext } from './context'; import { MessageContext } from './context';
@ -14,7 +14,7 @@ interface IMyMessageProps {
isMyMessage: boolean; isMyMessage: boolean;
} }
const MessageList: React.FC<{ messages: MessageType[] }> = ({ messages, children }) => { const MessageList: React.FC<{ messages: MessageType[] }> = memo(({ messages }) => {
const listRef = useRef<HTMLDivElement | null>(null); const listRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
@ -25,10 +25,23 @@ const MessageList: React.FC<{ messages: MessageType[] }> = ({ messages, children
return ( return (
<Stack ref={listRef} h='65vh' overflowY={'scroll'} mb='12' mt='6' spacing={'3'} pr='8'> <Stack ref={listRef} h='65vh' overflowY={'scroll'} mb='12' mt='6' spacing={'3'} pr='8'>
{children} {messages.length !== 0 ? (
messages.map((message, i) => (
<Message key={i} isMyMessage={message.isMyMessage}>
<Message.Avatar fullname={message.userName} />
<Message.Text content={message.content} time={message.time} />
</Message>
))
) : (
<Box>
<ChakraText mt='5' textAlign={'center'} color='gray.500' userSelect={'none'}>
Your message history will be displayed here.
</ChakraText>
</Box>
)}
</Stack> </Stack>
); );
}; });
const Message: React.FC<IMyMessageProps> & IMessageComposition = ({ children, isMyMessage }) => { const Message: React.FC<IMyMessageProps> & IMessageComposition = ({ children, isMyMessage }) => {
return ( return (

View File

@ -1,21 +1,42 @@
import React, { ChangeEvent, useContext, useEffect, useState } from 'react'; 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 { GridItem, GridItemProps, Flex, Box, Icon, Text, Input, Heading, IconButton, Divider } from '@chakra-ui/react';
import { MdOutlineChat, MdSend } from 'react-icons/md'; import { MdOutlineChat, MdSend } from 'react-icons/md';
import { Message, MessageList } from './Message'; import { MessageList } from './Message';
import { useRootStore } from '@educt/hooks/useRootStore';
import { SocketContext } from '@educt/contexts'; import { SocketContext } from '@educt/contexts';
import { useSocketEvent } from '@educt/hooks/useSocketEvent';
import { MessageType } from '@educt/types'; import { MessageType } from '@educt/types';
import { UserServiceInstance } from '@educt/services';
import { IUser } from '@educt/interfaces';
import LoadingList from '@educt/components/LoadingList';
const Window: React.FC<GridItemProps> = props => { /**
* 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 { const {
userStore: { me }, userStore: { me },
onlineStore, onlineStore,
} = useRootStore(); } = useRootStore();
const chatId = new URLSearchParams(location.search).get('chat_id'); 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 [inputValue, setInputValue] = useState<string>('');
const [messages, setMessages] = useState<MessageType[]>([]); const [messages, setMessages] = useState<MessageType[]>([]);
const { socket } = useContext(SocketContext); const { socket } = useContext(SocketContext);
const history = useHistory();
const toast = useToast();
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => { const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -44,16 +65,47 @@ const Window: React.FC<GridItemProps> = props => {
const onChangeInputValue = (e: ChangeEvent<HTMLInputElement>) => setInputValue(e.target.value); 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(() => { useEffect(() => {
// TODO load message history
console.log('chat id changed!'); console.log('chat id changed!');
setMessages([]); setMessages([]);
}, [chatId]); }, [chatId]);
// TODO add types for this event // TODO add types for this event
/**
* Event on accept new messages
*/
useSocketEvent('message:private', data => { useSocketEvent('message:private', data => {
const user = onlineStore.getUser(data.from); const user = onlineStore.getUser(data.from);
console.log('message!');
if (user) { if (user) {
if (chatId === data.from) { if (chatId === data.from) {
@ -74,68 +126,66 @@ const Window: React.FC<GridItemProps> = props => {
} }
}); });
return ( if (isLoading) {
<GridItem pt={'7rem'} pb='5' px='12' {...props}> return (
{chatId ? ( <Wrapper>
<Flex flexDir={'column'}> <LoadingList />
<Box my='3'> </Wrapper>
<Heading as='h2' fontWeight={'semibold'} fontSize='3xl'> );
{chatId} }
</Heading>
</Box>
<Divider my='2' /> /**
* Unselected user
<MessageList messages={messages}> */
{messages.length !== 0 ? ( if (!chatId || selectedUser === null) {
messages.map((message, i) => ( return (
<Message key={i} isMyMessage={message.isMyMessage}> <Wrapper>
<Message.Avatar fullname={message.userName} />
<Message.Text content={message.content} time={message.time} />
</Message>
))
) : (
<Box>
<Text mt='5' textAlign={'center'} color='gray.500' userSelect={'none'}>
Your message history will be displayed here.
</Text>
</Box>
)}
</MessageList>
<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>
) : (
<Box textAlign={'center'} mt={{ base: '0', lg: '15rem' }}> <Box textAlign={'center'} mt={{ base: '0', lg: '15rem' }}>
<Icon as={MdOutlineChat} color='gray.600' w='16' h='16' /> <Icon as={MdOutlineChat} color='gray.600' w='16' h='16' />
<Text color='gray.500' userSelect={'none'}> <Text color='gray.500' userSelect={'none'}>
Select a chat to start messaging Select a chat to start messaging
</Text> </Text>
</Box> </Box>
)} </Wrapper>
</GridItem> );
}
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>
); );
}; };

View File

@ -25,6 +25,7 @@ import { useDetachStudents } from './course/useDetachStudents';
/** /**
* User * User
*/ */
import { useFetchUser } from './user/useFetchUser';
import { useCreateUser } from './user/useCreateUser'; import { useCreateUser } from './user/useCreateUser';
import { useDeleteUser } from './user/useDeleteUser'; import { useDeleteUser } from './user/useDeleteUser';
import { useUpdateUser } from './user/useUpdateUser'; import { useUpdateUser } from './user/useUpdateUser';
@ -57,6 +58,7 @@ export {
useCreateLesson, useCreateLesson,
useDeleteLesson, useDeleteLesson,
useUpdateLesson, useUpdateLesson,
useFetchUser,
useCreateUser, useCreateUser,
useDeleteUser, useDeleteUser,
useUpdateUser, useUpdateUser,