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 BACKEND_URL = 'http://localhost:3333';
|
||||||
|
export const WS_URL = 'ws://localhost:3333';
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
import * as constants from '../constants';
|
||||||
|
|
||||||
const apiConfig: AxiosRequestConfig = {
|
const apiConfig: AxiosRequestConfig = {
|
||||||
baseURL: '/api/',
|
baseURL: `${constants.BACKEND_URL}/api/`,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Avatar as ChakraAvatar, AvatarBadge, WrapItem } from '@chakra-ui/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 {
|
export interface IAvatarProps {
|
||||||
fullname: string;
|
fullname: string;
|
||||||
@ -7,12 +9,21 @@ export interface IAvatarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Avatar: React.FC<IAvatarProps> = ({ fullname: name, isOnline }) => {
|
const Avatar: React.FC<IAvatarProps> = ({ fullname: name, isOnline }) => {
|
||||||
|
const {
|
||||||
|
userStore: { me },
|
||||||
|
} = useRootStore();
|
||||||
|
const isMe = me?.fullname === name;
|
||||||
const onlineBg = isOnline ? 'green.500' : 'red.500';
|
const onlineBg = isOnline ? 'green.500' : 'red.500';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapItem>
|
<WrapItem>
|
||||||
<ChakraAvatar name={name} size='md'>
|
<ChakraAvatar
|
||||||
<AvatarBadge boxSize='0.90em' bg={onlineBg} />
|
name={isMe ? undefined : name}
|
||||||
|
bg={isMe ? 'blue.400' : undefined}
|
||||||
|
icon={<MdOutlineBookmark color='white' />}
|
||||||
|
size='md'
|
||||||
|
>
|
||||||
|
{!isMe && <AvatarBadge boxSize='0.90em' bg={onlineBg} />}
|
||||||
</ChakraAvatar>
|
</ChakraAvatar>
|
||||||
</WrapItem>
|
</WrapItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Text } from '@chakra-ui/react';
|
import { Box, Text } from '@chakra-ui/react';
|
||||||
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
|
||||||
export interface IInfoProps {
|
export interface IInfoProps {
|
||||||
fullname: string;
|
fullname: string;
|
||||||
@ -7,10 +8,15 @@ export interface IInfoProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Info: React.FC<IInfoProps> = ({ fullname, lastMessage }) => {
|
const Info: React.FC<IInfoProps> = ({ fullname, lastMessage }) => {
|
||||||
|
const {
|
||||||
|
userStore: { me },
|
||||||
|
} = useRootStore();
|
||||||
|
const isMe = me?.fullname === fullname;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ml='4'>
|
<Box ml='4'>
|
||||||
<Text fontWeight={'semibold'}>
|
<Text fontWeight={'semibold'}>
|
||||||
{fullname} <br />
|
{isMe ? 'Favorites' : fullname} <br />
|
||||||
<Text as={'span'} fontWeight='normal' fontSize={'sm'} color='gray.500'>
|
<Text as={'span'} fontWeight='normal' fontSize={'sm'} color='gray.500'>
|
||||||
{lastMessage}
|
{lastMessage}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@ -38,6 +38,8 @@ const Window: React.FC = props => {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
const isMe = me?.id === selectedUser?.id;
|
||||||
|
|
||||||
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -54,7 +56,7 @@ const Window: React.FC = props => {
|
|||||||
* Emit event to server and update messages state
|
* Emit event to server and update messages state
|
||||||
*/
|
*/
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
socket?.emit('message:private', {
|
socket?.emit('chat:message', {
|
||||||
content: message.content,
|
content: message.content,
|
||||||
to: chatId,
|
to: chatId,
|
||||||
});
|
});
|
||||||
@ -74,7 +76,22 @@ const Window: React.FC = props => {
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const { data: user } = await UserServiceInstance.fetchUserById(chatId);
|
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);
|
setSelectedUser(user);
|
||||||
|
setMessages(messages);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
if (error.response?.status === 404) {
|
if (error.response?.status === 404) {
|
||||||
@ -91,20 +108,11 @@ const Window: React.FC = props => {
|
|||||||
}
|
}
|
||||||
}, [chatId]);
|
}, [chatId]);
|
||||||
|
|
||||||
// TODO load message history
|
|
||||||
/**
|
|
||||||
* Loading messages history
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('chat id changed!');
|
|
||||||
setMessages([]);
|
|
||||||
}, [chatId]);
|
|
||||||
|
|
||||||
// TODO add types for this event
|
// TODO add types for this event
|
||||||
/**
|
/**
|
||||||
* Event on accept new messages
|
* Event on accept new messages
|
||||||
*/
|
*/
|
||||||
useSocketEvent('message:private', data => {
|
useSocketEvent('chat:message', data => {
|
||||||
const user = onlineStore.getUser(data.from);
|
const user = onlineStore.getUser(data.from);
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
@ -118,7 +126,7 @@ const Window: React.FC = props => {
|
|||||||
content: data.content,
|
content: data.content,
|
||||||
userName: user.userName,
|
userName: user.userName,
|
||||||
isMyMessage: false,
|
isMyMessage: false,
|
||||||
time: new Date().toLocaleTimeString(),
|
time: new Date(data.time).toLocaleTimeString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
setMessages(prev => [...prev, message]);
|
setMessages(prev => [...prev, message]);
|
||||||
@ -155,7 +163,7 @@ const Window: React.FC = 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'>
|
||||||
{selectedUser.fullname}
|
{isMe && me ? 'Favorites' : selectedUser.fullname}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider my='2' />
|
<Divider my='2' />
|
||||||
|
|||||||
@ -47,11 +47,12 @@ const SettingsContextProvider: React.FC = ({ children }) => {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (me !== null) {
|
if (me !== null) {
|
||||||
me.about = about === undefined ? me.about : about;
|
me.about = about === undefined ? me.about : about;
|
||||||
if (me.contacts) {
|
me.contacts = {
|
||||||
me.contacts.phone_number = phone_number === undefined ? me.contacts.phone_number : phone_number;
|
phone_number: phone_number === undefined ? me.contacts?.phone_number || null : phone_number,
|
||||||
me.contacts.twitter_id = twitter_id === undefined ? me.contacts.twitter_id : twitter_id;
|
twitter_id: twitter_id === undefined ? me.contacts?.twitter_id || null : twitter_id,
|
||||||
me.contacts.telegram_id = telegram_id === undefined ? me.contacts.telegram_id : telegram_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>
|
<Stat>
|
||||||
<StatLabel>Online</StatLabel>
|
<StatLabel>Online</StatLabel>
|
||||||
<StatContent>
|
<StatContent>
|
||||||
<StatNumber>{onlineStore.count}</StatNumber>
|
<StatNumber>{onlineStore.count || 0}</StatNumber>
|
||||||
<StatIcon icon={MdOutlineGroup} />
|
<StatIcon icon={MdOutlineGroup} />
|
||||||
</StatContent>
|
</StatContent>
|
||||||
</Stat>
|
</Stat>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
@ -10,6 +9,7 @@ import { IPageProps } from '@educt/interfaces';
|
|||||||
/**
|
/**
|
||||||
* Components
|
* Components
|
||||||
*/
|
*/
|
||||||
|
import { Page } from '@educt/components/PageElements';
|
||||||
import LoadingPage from '@educt/components/LoadingPage';
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
import UpdateEmailForm from './components/UpdateEmailForm';
|
import UpdateEmailForm from './components/UpdateEmailForm';
|
||||||
|
|
||||||
@ -32,7 +32,6 @@ import { ChangeEmailPageContext } from '@educt/contexts';
|
|||||||
* Providers
|
* Providers
|
||||||
*/
|
*/
|
||||||
import { ChangeEmailPageContextProvider } from '@educt/providers';
|
import { ChangeEmailPageContextProvider } from '@educt/providers';
|
||||||
import PrevPageButton from '@educt/components/PrevPageButton';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change email page
|
* Change email page
|
||||||
@ -48,22 +47,18 @@ const ChangeEmailPage: React.FC<IPageProps> = () => {
|
|||||||
<ChangeEmailPageContextProvider>
|
<ChangeEmailPageContextProvider>
|
||||||
<ChangeEmailPageContext.Consumer>
|
<ChangeEmailPageContext.Consumer>
|
||||||
{({ isCodeSent, confirmEmailData }) => (
|
{({ isCodeSent, confirmEmailData }) => (
|
||||||
<>
|
<Page>
|
||||||
{!isCodeSent ? (
|
{!isCodeSent ? (
|
||||||
<Box maxW='700px'>
|
<>
|
||||||
<Flex alignItems='center'>
|
<Page.Heading heading='Edit email address' description='Type your new email address.' />
|
||||||
<PrevPageButton prevPage='/profile' />
|
<Page.Content maxW='650px'>
|
||||||
<Heading as='h1'>Edit email address</Heading>
|
<UpdateEmailForm currentEmail={me.email} />
|
||||||
</Flex>
|
</Page.Content>
|
||||||
<Box mt='3'>
|
</>
|
||||||
<Text>Type your new email address.</Text>
|
|
||||||
</Box>
|
|
||||||
<UpdateEmailForm currentEmail={me.email} />
|
|
||||||
</Box>
|
|
||||||
) : (
|
) : (
|
||||||
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
|
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
|
||||||
)}
|
)}
|
||||||
</>
|
</Page>
|
||||||
)}
|
)}
|
||||||
</ChangeEmailPageContext.Consumer>
|
</ChangeEmailPageContext.Consumer>
|
||||||
</ChangeEmailPageContextProvider>
|
</ChangeEmailPageContextProvider>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from '@chakra-ui/button';
|
|
||||||
import { FormControl, FormHelperText, FormLabel } from '@chakra-ui/form-control';
|
import { FormControl, FormHelperText, FormLabel } from '@chakra-ui/form-control';
|
||||||
import { Input } from '@chakra-ui/input';
|
import { Input } from '@chakra-ui/input';
|
||||||
import { Box, Stack } from '@chakra-ui/layout';
|
import { Box, Stack } from '@chakra-ui/layout';
|
||||||
@ -26,6 +25,7 @@ import { ChangeEmailPageContext } from '@educt/contexts';
|
|||||||
* Schema
|
* Schema
|
||||||
*/
|
*/
|
||||||
import UpdateEmailSchema from './UpdateEmailForm.validator';
|
import UpdateEmailSchema from './UpdateEmailForm.validator';
|
||||||
|
import { SaveButton } from '@educt/components/Buttons';
|
||||||
|
|
||||||
type UpdateEmailInputType = {
|
type UpdateEmailInputType = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -87,17 +87,7 @@ const UpdateEmailForm: React.FC<UpdateEmailFormPropsType> = ({ currentEmail }) =
|
|||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button
|
<SaveButton mt='4' type='submit' isLoading={isLoading} />
|
||||||
colorScheme='blue'
|
|
||||||
mt='4'
|
|
||||||
type='submit'
|
|
||||||
size='md'
|
|
||||||
variant='outline'
|
|
||||||
isLoading={isLoading}
|
|
||||||
loadingText='Sending...'
|
|
||||||
>
|
|
||||||
Send code
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Box, Heading, Flex, Text } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
@ -11,13 +10,13 @@ import { IPageProps } from '@educt/interfaces';
|
|||||||
* Components
|
* Components
|
||||||
*/
|
*/
|
||||||
import UpdatePasswordForm from './components/UpdatePasswordForm';
|
import UpdatePasswordForm from './components/UpdatePasswordForm';
|
||||||
import PrevPageButton from '@educt/components/PrevPageButton';
|
|
||||||
import LoadingPage from '@educt/components/LoadingPage';
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
import { Page } from '@educt/components/PageElements';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change password page
|
* Change password page
|
||||||
@ -30,16 +29,12 @@ const ChangePasswordPage: React.FC<IPageProps> = () => {
|
|||||||
if (me === null) return <LoadingPage />;
|
if (me === null) return <LoadingPage />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box maxW='700px'>
|
<Page>
|
||||||
<Flex alignItems='center'>
|
<Page.Heading heading='Update password' description='Your new password must be different from the current one.' />
|
||||||
<PrevPageButton prevPage='/profile' />
|
<Page.Content>
|
||||||
<Heading as='h1'>Update password</Heading>
|
<UpdatePasswordForm />
|
||||||
</Flex>
|
</Page.Content>
|
||||||
<Box mt='3'>
|
</Page>
|
||||||
<Text>Your new password must be different from the current one.</Text>
|
|
||||||
</Box>
|
|
||||||
<UpdatePasswordForm />
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Stack } from '@chakra-ui/layout';
|
import { Box, Stack } from '@chakra-ui/layout';
|
||||||
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
||||||
import { Button } from '@chakra-ui/button';
|
|
||||||
import { Input } from '@chakra-ui/input';
|
import { Input } from '@chakra-ui/input';
|
||||||
|
import { SaveButton } from '@educt/components/Buttons';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,17 +129,7 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button
|
<SaveButton mt='4' type='submit' isLoading={isLoading} />
|
||||||
colorScheme='blue'
|
|
||||||
mt='4'
|
|
||||||
type='submit'
|
|
||||||
size='md'
|
|
||||||
variant='outline'
|
|
||||||
isLoading={isLoading}
|
|
||||||
loadingText='Saving...'
|
|
||||||
>
|
|
||||||
Change
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { useState, ReactNode } from 'react';
|
import React, { useState, ReactNode } from 'react';
|
||||||
|
import * as constants from '../../constants';
|
||||||
import {
|
import {
|
||||||
ChangeEmailPageContext,
|
ChangeEmailPageContext,
|
||||||
CoursesPageContext,
|
CoursesPageContext,
|
||||||
@ -25,7 +26,7 @@ export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
* Socket context provider
|
* Socket context provider
|
||||||
*/
|
*/
|
||||||
export const SocketContextProvider: React.FC = ({ children }) => {
|
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>;
|
return <SocketContext.Provider value={{ socket }}>{children}</SocketContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -163,6 +163,17 @@ class UserService {
|
|||||||
});
|
});
|
||||||
return result.data;
|
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);
|
export const UserServiceInstance = new UserService(ApiServiceInstance.api);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user