feat: logout hook & ui store window dimensions

This commit is contained in:
Sergey Yarkov 2021-08-27 17:26:49 +03:00
parent f6356a33df
commit 91fed1a002
12 changed files with 116 additions and 86 deletions

View File

@ -3,12 +3,14 @@ import { Flex, Box } from '@chakra-ui/react';
import AppName from './AppName';
import UserInfo from './User/UserInfo';
import UserNotifications from './User/UserNotifications';
import useWindowDimensions from 'hooks/useWindowDimensions';
import NavMobile from '../Nav/Mobile/Nav';
import { observer } from 'mobx-react';
import { useRootStore } from 'hooks/useRootStore';
const Header: React.FC = () => {
const { isDesktop } = useWindowDimensions();
const {
uiStore: { isDesktop },
} = useRootStore();
return (
<Box

View File

@ -11,29 +11,28 @@ import {
Box,
Avatar,
Text,
useToast,
Skeleton,
} from '@chakra-ui/react';
import { MdSettings, MdExitToApp } from 'react-icons/md';
import { observer } from 'mobx-react';
import { useRootStore } from 'hooks/useRootStore';
import { useHistory, Link } from 'react-router-dom';
import { Link } from 'react-router-dom';
import UserBadge from 'components/User/UserBadge';
import useLogoutQuery from 'hooks/useLogoutQuery';
const UserInfo: React.FC = () => {
const { authStore, userStore } = useRootStore();
const history = useHistory();
const toast = useToast();
const { userStore } = useRootStore();
const { logout } = useLogoutQuery();
const onLogoutHandler = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
try {
await authStore.logout();
history.push('/auth');
toast({ title: 'You are logged out.', isClosable: true, status: 'info' });
} catch (error) {
console.error(error);
}
};
// const onLogoutHandler = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
// try {
// await authStore.logout();
// history.push('/auth');
// toast({ title: 'You are logged out.', isClosable: true, status: 'info' });
// } catch (error) {
// console.error(error);
// }
// };
if (userStore.me === null) {
return <Skeleton width='260px' height='40px' borderRadius='md' />;
@ -60,7 +59,7 @@ const UserInfo: React.FC = () => {
My profile
</MenuItem>
</Link>
<MenuItem onClick={onLogoutHandler}>
<MenuItem onClick={logout}>
<Box as={MdExitToApp} mr={2} />
Logout
</MenuItem>

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Flex, Box } from '@chakra-ui/react';
import useWindowDimensions from 'hooks/useWindowDimensions';
import NavList from '../NavList';
import { useRootStore } from 'hooks/useRootStore';
import { observer } from 'mobx-react';
/**
*
@ -9,7 +10,9 @@ import NavList from '../NavList';
* Component for navigating the application.
*/
const Nav: React.FC = () => {
const { isDesktop } = useWindowDimensions();
const {
uiStore: { isDesktop },
} = useRootStore();
if (isDesktop) {
return (
@ -32,4 +35,4 @@ const Nav: React.FC = () => {
return null;
};
export default Nav;
export default observer(Nav);

View File

@ -1,5 +1,6 @@
import React from 'react';
import { observer } from 'mobx-react';
import { MdExitToApp } from 'react-icons/md';
import { Link } from 'react-router-dom';
import { useRootStore } from 'hooks/useRootStore';
@ -47,6 +48,7 @@ const NavMobile: React.FC = () => {
View profile
</Link>
</Flex>
<IconButton aria-label='Logout' backgroundColor='gray.200' marginLeft='auto' icon={<MdExitToApp />} />
</Flex>
</Box>
) : (

View File

@ -4,7 +4,6 @@ import Layout from '../Layout/Layout';
import { Route, Redirect } from 'react-router-dom';
import { IPrivateRouteProps } from 'interfaces';
import { useRootStore } from 'hooks/useRootStore';
import { observer } from 'mobx-react';
import { ErrorBoundary } from 'react-error-boundary';
import ErrorFallback from '../ErrorFallback';
import config from 'config';
@ -39,4 +38,4 @@ const PrivateRoute: React.FC<IPrivateRouteProps> = ({ component: Component, titl
);
};
export default observer(PrivateRoute);
export default PrivateRoute;

View File

@ -2,14 +2,13 @@ import { Badge } from '@chakra-ui/react';
import React from 'react';
import { UserRoleEnum } from 'enums';
import { IUserRole } from 'interfaces';
import { userHasRoles } from 'helpers';
/**
* Returns the badge for user based on roles
*/
const UserBadge: React.FC<{ roles: IUserRole[] }> = ({ roles }) => {
const slugs = roles.map(role => role.slug);
if (slugs.includes(UserRoleEnum.ADMIN)) {
if (userHasRoles(roles, [UserRoleEnum.ADMIN])) {
return (
<Badge colorScheme='purple' variant='subtle'>
Administrator
@ -17,7 +16,7 @@ const UserBadge: React.FC<{ roles: IUserRole[] }> = ({ roles }) => {
);
}
if (slugs.includes(UserRoleEnum.TEACHER)) {
if (userHasRoles(roles, [UserRoleEnum.TEACHER])) {
return (
<Badge colorScheme='orange' variant='subtle'>
Teacher
@ -25,7 +24,7 @@ const UserBadge: React.FC<{ roles: IUserRole[] }> = ({ roles }) => {
);
}
if (slugs.includes(UserRoleEnum.STUDENT)) {
if (userHasRoles(roles, [UserRoleEnum.STUDENT])) {
return <Badge variant='green'>Student</Badge>;
}

View File

@ -0,0 +1,49 @@
import React from 'react';
import { useToast } from '@chakra-ui/react';
import { useHistory } from 'react-router-dom';
import { useRootStore } from './useRootStore';
import { IDataResult } from 'interfaces';
import { useErrorHandler } from 'react-error-boundary';
type StateType = {
result: IDataResult | null;
error: any;
loading: boolean;
fetched: boolean;
};
const useLogoutQuery = () => {
const { authStore } = useRootStore();
const [state, setState] = React.useState<StateType>({ result: null, error: null, loading: false, fetched: false });
const isMounted = React.useRef(true);
const history = useHistory();
const toast = useToast();
const handleError = useErrorHandler();
const request = React.useCallback(async () => {
setState(s => ({ ...s, loading: true }));
try {
const data = await authStore.logout();
history.push('/auth');
toast({ title: 'You are logged out.', isClosable: true, status: 'info' });
if (isMounted.current) setState(s => ({ ...s, result: data }));
} catch (error) {
handleError(error);
setState(s => ({ ...s, error }));
} finally {
if (isMounted.current) setState(s => ({ ...s, loading: false, fetched: true }));
}
}, [authStore, history, toast, handleError]);
const logout = () => request();
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return { ...state, logout };
};
export default useLogoutQuery;

View File

@ -1,32 +0,0 @@
import React from 'react';
const useWindowDimensions = () => {
const [windowDimensions, setWindowDimensions] = React.useState({
width: window.innerWidth,
height: window.innerHeight,
});
React.useEffect(() => {
/**
* Update state on resize
*/
const handleResize = () => {
setWindowDimensions({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return {
windowDimensions,
isDesktop: windowDimensions.width > 768,
isTablet: windowDimensions.width < 768,
isMobile: windowDimensions.width < 568,
};
};
export default useWindowDimensions;

View File

@ -71,3 +71,8 @@ export interface IPublicRouteProps extends RouteProps {
component: React.FC<IPageProps>;
title?: string;
}
export interface IWindowDimensions {
width: number;
height: number;
}

View File

@ -1,23 +1,12 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
import { IPageProps } from 'interfaces';
import { useRootStore } from 'hooks/useRootStore';
import { useHistory } from 'react-router-dom';
/**
* Courses page
*/
const CoursesPage: React.FC<IPageProps> = ({ title }) => {
const { authStore } = useRootStore();
const history = useHistory();
useEffect(() => {
if (!authStore.isLoggedIn) {
history.push('/auth');
}
});
return (
<>
<Breadcrumb fontWeight='medium' fontSize='sm'>

View File

@ -1,23 +1,12 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
import { IPageProps } from 'interfaces';
import { useRootStore } from 'hooks/useRootStore';
import { useHistory } from 'react-router-dom';
/**
* Messages page
*/
const MessagesPage: React.FC<IPageProps> = ({ title }) => {
const { authStore } = useRootStore();
const history = useHistory();
useEffect(() => {
if (!authStore.isLoggedIn) {
history.push('/auth');
}
});
return (
<>
<Breadcrumb fontWeight='medium' fontSize='sm'>

View File

@ -1,6 +1,7 @@
import { makeAutoObservable } from 'mobx';
import { makeAutoObservable, observable } from 'mobx';
import { createBrowserHistory } from 'history';
import RootStore from './RootStore';
import { IWindowDimensions } from 'interfaces';
export default class UIStore {
public root: RootStore;
@ -9,18 +10,43 @@ export default class UIStore {
public history: ReturnType<typeof createBrowserHistory>;
public windowDimensions: IWindowDimensions = { width: window.innerWidth, height: window.innerHeight };
constructor(root: RootStore) {
this.root = root;
this.history = createBrowserHistory();
makeAutoObservable(this);
makeAutoObservable(this, {
windowDimensions: observable.struct,
});
this.history.listen(({ pathname }) => {
console.log(`[history]: pathname changed to "${pathname}"`);
this.location = pathname;
});
window.onresize = () => {
this.windowDimensions = this.getWindowDimensions();
};
}
public setLocation(location: string) {
public setLocation(location: string): void {
this.location = location;
}
private getWindowDimensions(): IWindowDimensions {
return { width: window.innerWidth, height: window.innerHeight };
}
get isDesktop() {
return this.windowDimensions.width > 768;
}
get isTablet() {
return this.windowDimensions.width < 768;
}
get isMobile() {
return this.windowDimensions.width < 568;
}
}