mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: load chat history
This commit is contained in:
parent
5602c29b15
commit
ce5411bd50
@ -1 +1,2 @@
|
||||
export const BACKEND_URL = 'http://localhost:3333';
|
||||
export const WS_URL = 'ws://localhost:3333';
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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' />
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
<UpdateEmailForm currentEmail={me.email} />
|
||||
</Box>
|
||||
<>
|
||||
<Page.Heading heading='Edit email address' description='Type your new email address.' />
|
||||
<Page.Content maxW='650px'>
|
||||
<UpdateEmailForm currentEmail={me.email} />
|
||||
</Page.Content>
|
||||
</>
|
||||
) : (
|
||||
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
|
||||
)}
|
||||
</>
|
||||
</Page>
|
||||
)}
|
||||
</ChangeEmailPageContext.Consumer>
|
||||
</ChangeEmailPageContextProvider>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
<UpdatePasswordForm />
|
||||
</Box>
|
||||
<Page>
|
||||
<Page.Heading heading='Update password' description='Your new password must be different from the current one.' />
|
||||
<Page.Content>
|
||||
<UpdatePasswordForm />
|
||||
</Page.Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>;
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user