feat: online user status

This commit is contained in:
Sergey Yarkov 2022-02-06 22:34:21 +03:00
parent 6f855af3db
commit f92ef01353
9 changed files with 49 additions and 25 deletions

View File

@ -1,10 +1,11 @@
import React, { useContext, useEffect } from 'react'; import React from 'react';
import { Router, Switch } from 'react-router-dom'; import { Router, Switch } from 'react-router-dom';
import { routes } from './routes.config'; import { routes } from './routes.config';
/** /**
* Hooks * Hooks
*/ */
import { useContext } from 'react';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
/** /**
@ -18,6 +19,7 @@ import { SocketContext } from './contexts';
const App = () => { const App = () => {
const { const {
uiStore: { history }, uiStore: { history },
onlineStore,
} = useRootStore(); } = useRootStore();
const { socket } = useContext(SocketContext); const { socket } = useContext(SocketContext);
@ -33,6 +35,7 @@ const App = () => {
socket.userId = userId; socket.userId = userId;
} }
}); });
useSocketEvent('user:online', online => onlineStore.loadOnline(online));
return ( return (
<Router history={history}> <Router history={history}>

View File

@ -1,7 +1,6 @@
import React, { Suspense, useContext } from 'react'; import React, { Suspense } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Redirect } from 'react-router'; import { Redirect } from 'react-router';
import { useErrorHandler } from 'react-error-boundary';
import { userContainRoles } from '@educt/helpers'; import { userContainRoles } from '@educt/helpers';
import { Flex, Box } from '@chakra-ui/react'; import { Flex, Box } from '@chakra-ui/react';
@ -20,7 +19,8 @@ import NavDesktop from '@educt/components/Nav/Desktop';
/** /**
* Hooks * Hooks
*/ */
import { useEffect } from 'react'; import { useEffect, useContext } from 'react';
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
/** /**

View File

@ -10,7 +10,7 @@ const Avatar: React.FC<IAvatarProps> = props => {
return ( return (
<Flex flexDir={'column'} alignItems={'center'}> <Flex flexDir={'column'} alignItems={'center'}>
<ChakraAvatar border={`4px solid ${borderColor}`} borderColor={borderColor} name={name} size='2xl'> <ChakraAvatar border={`4px solid ${borderColor}`} borderColor={borderColor} name={name} size='2xl'>
{isOnline && <ChakraAvatarBadge boxSize='0.75em' bg={onlineColor} />} {isOnline !== undefined && <ChakraAvatarBadge boxSize='0.75em' bg={onlineColor} />}
</ChakraAvatar> </ChakraAvatar>
{props.children} {props.children}
</Flex> </Flex>

View File

@ -5,7 +5,12 @@ export const useSocket = (url: string) => {
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
useEffect(() => { useEffect(() => {
const socketClient = io(url, { autoConnect: false, withCredentials: true }); const socketClient = io(url, {
autoConnect: false,
withCredentials: true,
transports: ['websocket'],
upgrade: false,
});
setSocket(socketClient); setSocket(socketClient);

View File

@ -25,9 +25,7 @@ import { observer } from 'mobx-react';
/** /**
* Hooks * Hooks
*/ */
import { useEffect, useState } from 'react';
import { useFetchCourses, useFetchStat } from '@educt/hooks/queries'; import { useFetchCourses, useFetchStat } from '@educt/hooks/queries';
import { useSocketEvent } from '@educt/hooks/useSocketEvent';
/** /**
* Main page * Main page
@ -35,23 +33,10 @@ import { useSocketEvent } from '@educt/hooks/useSocketEvent';
const MainPage: React.FC<IPageProps> = () => { const MainPage: React.FC<IPageProps> = () => {
const { const {
userStore: { me }, userStore: { me },
onlineStore,
} = useRootStore(); } = useRootStore();
const { data: courses } = useFetchCourses({ limit: 3 }); const { data: courses } = useFetchCourses({ limit: 3 });
const { data: stat } = useFetchStat(); const { data: stat } = useFetchStat();
const [online, setOnline] = useState<number>(0);
/**
* Update online state on socket event
*/
useSocketEvent('user:online', online => setOnline(online));
// useSocketEvent('user:connected', user => console.table(user));
useEffect(() => {
if (stat) {
setOnline(stat.online);
}
}, [stat]);
if (me === null || courses === null || stat === null) return <LoadingPage />; if (me === null || courses === null || stat === null) return <LoadingPage />;
@ -63,7 +48,7 @@ const MainPage: React.FC<IPageProps> = () => {
<Stat> <Stat>
<StatLabel>Online</StatLabel> <StatLabel>Online</StatLabel>
<StatContent> <StatContent>
<StatNumber>{online}</StatNumber> <StatNumber>{onlineStore.count}</StatNumber>
<StatIcon icon={MdOutlineGroup} /> <StatIcon icon={MdOutlineGroup} />
</StatContent> </StatContent>
</Stat> </Stat>

View File

@ -17,6 +17,7 @@ const UserPage: React.FC<IPageProps> = () => {
const params = useParams<{ id: string }>(); const params = useParams<{ id: string }>();
const { const {
userStore: { me }, userStore: { me },
onlineStore,
} = useRootStore(); } = useRootStore();
const { data: user, error, isLoading } = useFetchUser(params.id); const { data: user, error, isLoading } = useFetchUser(params.id);
@ -34,7 +35,7 @@ const UserPage: React.FC<IPageProps> = () => {
<Page.Content maxW={'900px'}> <Page.Content maxW={'900px'}>
<UserProfile> <UserProfile>
<UserProfile.Base> <UserProfile.Base>
<UserProfile.Avatar name={user.fullname} isOnline> <UserProfile.Avatar name={user.fullname} isOnline={onlineStore.isOnline(user.id)}>
<UserProfile.ChatButton userid={user.id} /> <UserProfile.ChatButton userid={user.id} />
</UserProfile.Avatar> </UserProfile.Avatar>
<UserProfile.Info> <UserProfile.Info>

27
src/stores/OnlineStore.ts Normal file
View File

@ -0,0 +1,27 @@
import { makeAutoObservable } from 'mobx';
import RootStore from './RootStore';
export default class OnlineStore {
public root: RootStore;
public online: Set<string>;
constructor(root: RootStore) {
this.root = root;
this.online = new Set();
makeAutoObservable(this);
}
public loadOnline(online: string[]) {
this.online = new Set(online);
}
public isOnline(id: string) {
return this.online.has(id);
}
public get count() {
return this.online.size;
}
}

View File

@ -9,6 +9,7 @@ import UIStore from './UIStore';
import CategoryStore from './CategoryStore'; import CategoryStore from './CategoryStore';
import CourseStore from './CourseStore'; import CourseStore from './CourseStore';
import PageStore from './PageStore'; import PageStore from './PageStore';
import OnlineStore from './OnlineStore';
export default class RootStore { export default class RootStore {
public pageStore: PageStore; public pageStore: PageStore;
@ -23,6 +24,8 @@ export default class RootStore {
public userStore: UserStore; public userStore: UserStore;
public onlineStore: OnlineStore;
constructor() { constructor() {
/** /**
* Stores * Stores
@ -33,6 +36,7 @@ export default class RootStore {
this.pageStore = new PageStore(this); this.pageStore = new PageStore(this);
this.uiStore = new UIStore(this); this.uiStore = new UIStore(this);
this.userStore = new UserStore(this); this.userStore = new UserStore(this);
this.onlineStore = new OnlineStore(this);
makeAutoObservable(this); makeAutoObservable(this);
} }

View File

@ -8,7 +8,6 @@ import {
CreateUserParamsType, CreateUserParamsType,
FetchUsersParamsType, FetchUsersParamsType,
UpdateUserContactsParamsType, UpdateUserContactsParamsType,
UpdateUserInfoParamsType,
UpdateUserParamsType, UpdateUserParamsType,
} from '@educt/types'; } from '@educt/types';
import { IUser, IMe, IPaginationMeta } from '@educt/interfaces'; import { IUser, IMe, IPaginationMeta } from '@educt/interfaces';