feat: load chat history

This commit is contained in:
Sergey Yarkov 2022-02-12 22:25:02 +03:00
parent 5602c29b15
commit ce5411bd50
13 changed files with 84 additions and 74 deletions

View File

@ -1 +1,2 @@
export const BACKEND_URL = 'http://localhost:3333';
export const WS_URL = 'ws://localhost:3333';

View File

@ -1,7 +1,8 @@
import { AxiosRequestConfig } from 'axios';
import * as constants from '../constants';
const apiConfig: AxiosRequestConfig = {
baseURL: '/api/',
baseURL: `${constants.BACKEND_URL}/api/`,
responseType: 'json',
withCredentials: true,
};

View File

@ -1,5 +1,7 @@
import React from 'react';
import { Avatar as ChakraAvatar, AvatarBadge, WrapItem } from '@chakra-ui/react';
import { useRootStore } from '@educt/hooks/useRootStore';
import { MdOutlineBookmark } from 'react-icons/md';
export interface IAvatarProps {
fullname: string;
@ -7,12 +9,21 @@ export interface IAvatarProps {
}
const Avatar: React.FC<IAvatarProps> = ({ fullname: name, isOnline }) => {
const {
userStore: { me },
} = useRootStore();
const isMe = me?.fullname === name;
const onlineBg = isOnline ? 'green.500' : 'red.500';
return (
<WrapItem>
<ChakraAvatar name={name} size='md'>
<AvatarBadge boxSize='0.90em' bg={onlineBg} />
<ChakraAvatar
name={isMe ? undefined : name}
bg={isMe ? 'blue.400' : undefined}
icon={<MdOutlineBookmark color='white' />}
size='md'
>
{!isMe && <AvatarBadge boxSize='0.90em' bg={onlineBg} />}
</ChakraAvatar>
</WrapItem>
);

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Box, Text } from '@chakra-ui/react';
import { useRootStore } from '@educt/hooks/useRootStore';
export interface IInfoProps {
fullname: string;
@ -7,10 +8,15 @@ export interface IInfoProps {
}
const Info: React.FC<IInfoProps> = ({ fullname, lastMessage }) => {
const {
userStore: { me },
} = useRootStore();
const isMe = me?.fullname === fullname;
return (
<Box ml='4'>
<Text fontWeight={'semibold'}>
{fullname} <br />
{isMe ? 'Favorites' : fullname} <br />
<Text as={'span'} fontWeight='normal' fontSize={'sm'} color='gray.500'>
{lastMessage}
</Text>

View File

@ -38,6 +38,8 @@ const Window: React.FC = props => {
const history = useHistory();
const toast = useToast();
const isMe = me?.id === selectedUser?.id;
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@ -54,7 +56,7 @@ const Window: React.FC = props => {
* Emit event to server and update messages state
*/
if (chatId) {
socket?.emit('message:private', {
socket?.emit('chat:message', {
content: message.content,
to: chatId,
});
@ -74,7 +76,22 @@ const Window: React.FC = props => {
try {
setIsLoading(true);
const { data: user } = await UserServiceInstance.fetchUserById(chatId);
const {
data: { history },
} = await UserServiceInstance.fetchChatHistory(chatId);
// TODO add type for message
const messages = history.map(message => {
return {
isMyMessage: message.from === me.id,
content: message.content,
time: new Date(message.time).toLocaleTimeString(),
userName: message.from === me.id ? me.fullname : user.fullname,
};
});
setSelectedUser(user);
setMessages(messages);
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 404) {
@ -91,20 +108,11 @@ const Window: React.FC = props => {
}
}, [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 => {
useSocketEvent('chat:message', data => {
const user = onlineStore.getUser(data.from);
if (user) {
@ -118,7 +126,7 @@ const Window: React.FC = props => {
content: data.content,
userName: user.userName,
isMyMessage: false,
time: new Date().toLocaleTimeString(),
time: new Date(data.time).toLocaleTimeString(),
};
setMessages(prev => [...prev, message]);
@ -155,7 +163,7 @@ const Window: React.FC = props => {
<Flex flexDir={'column'}>
<Box my='3'>
<Heading as='h2' fontWeight={'semibold'} fontSize='3xl'>
{selectedUser.fullname}
{isMe && me ? 'Favorites' : selectedUser.fullname}
</Heading>
</Box>
<Divider my='2' />

View File

@ -47,11 +47,12 @@ const SettingsContextProvider: React.FC = ({ children }) => {
runInAction(() => {
if (me !== null) {
me.about = about === undefined ? me.about : about;
if (me.contacts) {
me.contacts.phone_number = phone_number === undefined ? me.contacts.phone_number : phone_number;
me.contacts.twitter_id = twitter_id === undefined ? me.contacts.twitter_id : twitter_id;
me.contacts.telegram_id = telegram_id === undefined ? me.contacts.telegram_id : telegram_id;
}
me.contacts = {
phone_number: phone_number === undefined ? me.contacts?.phone_number || null : phone_number,
twitter_id: twitter_id === undefined ? me.contacts?.twitter_id || null : twitter_id,
telegram_id: telegram_id === undefined ? me.contacts?.telegram_id || null : telegram_id,
vk_id: null,
};
}
});

View File

@ -48,7 +48,7 @@ const MainPage: React.FC<IPageProps> = () => {
<Stat>
<StatLabel>Online</StatLabel>
<StatContent>
<StatNumber>{onlineStore.count}</StatNumber>
<StatNumber>{onlineStore.count || 0}</StatNumber>
<StatIcon icon={MdOutlineGroup} />
</StatContent>
</Stat>

View File

@ -1,6 +1,5 @@
import React from 'react';
import { observer } from 'mobx-react';
import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
/**
* Types
@ -10,6 +9,7 @@ import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import { Page } from '@educt/components/PageElements';
import LoadingPage from '@educt/components/LoadingPage';
import UpdateEmailForm from './components/UpdateEmailForm';
@ -32,7 +32,6 @@ import { ChangeEmailPageContext } from '@educt/contexts';
* Providers
*/
import { ChangeEmailPageContextProvider } from '@educt/providers';
import PrevPageButton from '@educt/components/PrevPageButton';
/**
* Change email page
@ -48,22 +47,18 @@ const ChangeEmailPage: React.FC<IPageProps> = () => {
<ChangeEmailPageContextProvider>
<ChangeEmailPageContext.Consumer>
{({ isCodeSent, confirmEmailData }) => (
<>
<Page>
{!isCodeSent ? (
<Box maxW='700px'>
<Flex alignItems='center'>
<PrevPageButton prevPage='/profile' />
<Heading as='h1'>Edit email address</Heading>
</Flex>
<Box mt='3'>
<Text>Type your new email address.</Text>
</Box>
<>
<Page.Heading heading='Edit email address' description='Type your new email address.' />
<Page.Content maxW='650px'>
<UpdateEmailForm currentEmail={me.email} />
</Box>
</Page.Content>
</>
) : (
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
)}
</>
</Page>
)}
</ChangeEmailPageContext.Consumer>
</ChangeEmailPageContextProvider>

View File

@ -1,5 +1,4 @@
import React from 'react';
import { Button } from '@chakra-ui/button';
import { FormControl, FormHelperText, FormLabel } from '@chakra-ui/form-control';
import { Input } from '@chakra-ui/input';
import { Box, Stack } from '@chakra-ui/layout';
@ -26,6 +25,7 @@ import { ChangeEmailPageContext } from '@educt/contexts';
* Schema
*/
import UpdateEmailSchema from './UpdateEmailForm.validator';
import { SaveButton } from '@educt/components/Buttons';
type UpdateEmailInputType = {
email: string;
@ -87,17 +87,7 @@ const UpdateEmailForm: React.FC<UpdateEmailFormPropsType> = ({ currentEmail }) =
</FormHelperText>
</FormControl>
</Stack>
<Button
colorScheme='blue'
mt='4'
type='submit'
size='md'
variant='outline'
isLoading={isLoading}
loadingText='Sending...'
>
Send code
</Button>
<SaveButton mt='4' type='submit' isLoading={isLoading} />
</form>
</Box>
);

View File

@ -1,6 +1,5 @@
import React from 'react';
import { observer } from 'mobx-react';
import { Box, Heading, Flex, Text } from '@chakra-ui/react';
/**
* Types
@ -11,13 +10,13 @@ import { IPageProps } from '@educt/interfaces';
* Components
*/
import UpdatePasswordForm from './components/UpdatePasswordForm';
import PrevPageButton from '@educt/components/PrevPageButton';
import LoadingPage from '@educt/components/LoadingPage';
/**
* Hooks
*/
import { useRootStore } from '@educt/hooks/useRootStore';
import { Page } from '@educt/components/PageElements';
/**
* Change password page
@ -30,16 +29,12 @@ const ChangePasswordPage: React.FC<IPageProps> = () => {
if (me === null) return <LoadingPage />;
return (
<Box maxW='700px'>
<Flex alignItems='center'>
<PrevPageButton prevPage='/profile' />
<Heading as='h1'>Update password</Heading>
</Flex>
<Box mt='3'>
<Text>Your new password must be different from the current one.</Text>
</Box>
<Page>
<Page.Heading heading='Update password' description='Your new password must be different from the current one.' />
<Page.Content>
<UpdatePasswordForm />
</Box>
</Page.Content>
</Page>
);
};

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Box, Stack } from '@chakra-ui/layout';
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
import { Button } from '@chakra-ui/button';
import { Input } from '@chakra-ui/input';
import { SaveButton } from '@educt/components/Buttons';
import { yupResolver } from '@hookform/resolvers/yup';
/**
@ -129,17 +129,7 @@ const UpdatePasswordForm: React.FC = () => {
</FormHelperText>
</FormControl>
</Stack>
<Button
colorScheme='blue'
mt='4'
type='submit'
size='md'
variant='outline'
isLoading={isLoading}
loadingText='Saving...'
>
Change
</Button>
<SaveButton mt='4' type='submit' isLoading={isLoading} />
</form>
</Box>
);

View File

@ -1,4 +1,5 @@
import React, { useState, ReactNode } from 'react';
import * as constants from '../../constants';
import {
ChangeEmailPageContext,
CoursesPageContext,
@ -25,7 +26,7 @@ export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
* Socket context provider
*/
export const SocketContextProvider: React.FC = ({ children }) => {
const socket = useSocket('ws://localhost:3333');
const socket = useSocket(constants.WS_URL);
return <SocketContext.Provider value={{ socket }}>{children}</SocketContext.Provider>;
};

View File

@ -163,6 +163,17 @@ class UserService {
});
return result.data;
}
/**
* Get history of chat
*
* @param chatId Chat id
* @returns Array of messages
*/
public async fetchChatHistory(chatId: string): Promise<IApiRespose<{ history: unknown[] }>> {
const result = await this.api.get(`v1/me/messages/${chatId}`);
return result.data;
}
}
export const UserServiceInstance = new UserService(ApiServiceInstance.api);