mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
feat: fethincg user in messenger & improved perfomance
This commit is contained in:
parent
b60b330fe9
commit
5602c29b15
@ -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;
|
||||||
|
|||||||
@ -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 };
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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(() => {
|
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
|
// TODO load message history
|
||||||
|
/**
|
||||||
|
* Loading messages history
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
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,34 +126,40 @@ const Window: React.FC<GridItemProps> = props => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<GridItem pt={'7rem'} pb='5' px='12' {...props}>
|
<Wrapper>
|
||||||
{chatId ? (
|
<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'}>
|
<Flex flexDir={'column'}>
|
||||||
<Box my='3'>
|
<Box my='3'>
|
||||||
<Heading as='h2' fontWeight={'semibold'} fontSize='3xl'>
|
<Heading as='h2' fontWeight={'semibold'} fontSize='3xl'>
|
||||||
{chatId}
|
{selectedUser.fullname}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider my='2' />
|
<Divider my='2' />
|
||||||
|
<MessageList messages={messages} />
|
||||||
<MessageList messages={messages}>
|
|
||||||
{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>
|
|
||||||
<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)}>
|
<form onSubmit={e => onSubmit(e)}>
|
||||||
<Flex mt='auto' alignItems={'flex-end'}>
|
<Flex mt='auto' alignItems={'flex-end'}>
|
||||||
<Input
|
<Input
|
||||||
@ -127,15 +185,7 @@ const Window: React.FC<GridItemProps> = props => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
</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>
|
|
||||||
)}
|
|
||||||
</GridItem>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user